diff --git a/.eslintrc.js b/.eslintrc.js index 32b1815eaa03..395506eae20d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -567,7 +567,6 @@ module.exports = { $Shape: 'readonly', CallSite: 'readonly', ConsoleTask: 'readonly', // TOOD: Figure out what the official name of this will be. - Readonly: 'readonly', ReturnType: 'readonly', AggregateError: 'readonly', AnimationFrameID: 'readonly', diff --git a/.github/workflows/compiler_discord_notify.yml b/.github/workflows/compiler_discord_notify.yml index 8d0a698c24f0..5a57cf6a32c1 100644 --- a/.github/workflows/compiler_discord_notify.yml +++ b/.github/workflows/compiler_discord_notify.yml @@ -25,7 +25,7 @@ jobs: check_maintainer: if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }} needs: [check_access] - uses: react/react/.github/workflows/shared_check_maintainer.yml@main + uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main permissions: # Used by check_maintainer contents: read diff --git a/.github/workflows/compiler_prereleases_manual.yml b/.github/workflows/compiler_prereleases_manual.yml index 5d138d82ed71..c4a7a16aca3b 100644 --- a/.github/workflows/compiler_prereleases_manual.yml +++ b/.github/workflows/compiler_prereleases_manual.yml @@ -29,7 +29,7 @@ env: jobs: publish_prerelease_experimental: name: Publish to Experimental channel - uses: react/react/.github/workflows/compiler_prereleases.yml@main + uses: facebook/react/.github/workflows/compiler_prereleases.yml@main with: commit_sha: ${{ inputs.prerelease_commit_sha || github.sha }} release_channel: ${{ inputs.release_channel }} diff --git a/.github/workflows/compiler_prereleases_nightly.yml b/.github/workflows/compiler_prereleases_nightly.yml index 813cfa0c7cd7..ca2b5589def2 100644 --- a/.github/workflows/compiler_prereleases_nightly.yml +++ b/.github/workflows/compiler_prereleases_nightly.yml @@ -13,7 +13,7 @@ env: jobs: publish_prerelease_experimental: name: Publish to Experimental channel - uses: react/react/.github/workflows/compiler_prereleases.yml@main + uses: facebook/react/.github/workflows/compiler_prereleases.yml@main with: commit_sha: ${{ github.sha }} release_channel: experimental diff --git a/.github/workflows/compiler_rust.yml b/.github/workflows/compiler_rust.yml deleted file mode 100644 index 0fd373c08c0b..000000000000 --- a/.github/workflows/compiler_rust.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: React Compiler (Rust) - -on: - push: - branches: [main] - pull_request: - paths: - - compiler/** - - .github/workflows/compiler_rust.yml - -permissions: {} - -concurrency: - group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: true - -env: - TZ: /usr/share/zoneinfo/America/Los_Angeles - SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 - -defaults: - run: - working-directory: compiler - -jobs: - rust: - name: Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - # --- Rust toolchain --- - - uses: dtolnay/rust-toolchain@stable - - - name: Cache cargo registry and build - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - compiler/target - key: cargo-${{ runner.os }}-${{ hashFiles('compiler/Cargo.lock') }} - restore-keys: cargo-${{ runner.os }}- - - # --- Node (needed for test scripts) --- - - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: yarn - cache-dependency-path: compiler/yarn.lock - - - name: Restore cached node_modules - uses: actions/cache@v4 - id: node_modules - with: - path: '**/node_modules' - key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }} - - - run: yarn install --frozen-lockfile - if: steps.node_modules.outputs.cache-hit != 'true' - - # --- Build & test --- - - run: cargo check - - run: cargo build - - run: bash scripts/test-babel-ast.sh - - run: bash scripts/test-rust-port.sh - - run: yarn workspace snap run build - - run: yarn snap --rust diff --git a/.github/workflows/devtools_discord_notify.yml b/.github/workflows/devtools_discord_notify.yml index a4b711131c9a..bb498f003710 100644 --- a/.github/workflows/devtools_discord_notify.yml +++ b/.github/workflows/devtools_discord_notify.yml @@ -25,7 +25,7 @@ jobs: check_maintainer: if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }} needs: [check_access] - uses: react/react/.github/workflows/shared_check_maintainer.yml@main + uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main permissions: # Used by check_maintainer contents: read diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml index db5cde73833b..aa9338321ef9 100644 --- a/.github/workflows/runtime_build_and_test.yml +++ b/.github/workflows/runtime_build_and_test.yml @@ -2,10 +2,11 @@ name: (Runtime) Build and Test on: push: - branches: - # release branches (keep in sync with branches that receive artifact attestations) - - main - - releases/** + branches: [main] + tags: + # To get CI for backport releases. + # This will duplicate CI for releases from main which is acceptable + - "v*" pull_request: paths-ignore: - compiler/** @@ -488,13 +489,13 @@ jobs: ./build2.tgz if-no-files-found: error - uses: actions/attest-build-provenance@v2 - # We don't verify builds generated from pull requests not originating from react/react. - # However, if the PR lands, the run on release branches will generate the attestation which can then + # We don't verify builds generated from pull requests not originating from facebook/react. + # However, if the PR lands, the run on `main` will generate the attestation which can then # be used to download a build via scripts/release/download-experimental-build.js. # # Note that this means that scripts/release/download-experimental-build.js must be run with # --no-verify when downloading a build from a fork. - if: github.event_name == 'push' && (github.ref_name == 'main' || startsWith(github.ref_name, 'releases/')) || github.event.pull_request.head.repo.full_name == github.repository + if: github.event_name == 'push' && github.ref_name == 'main' || github.event.pull_request.head.repo.full_name == github.repository with: subject-name: artifacts_combined.zip subject-digest: sha256:${{ steps.upload_artifacts_combined.outputs.artifact-digest }} diff --git a/.github/workflows/runtime_commit_artifacts.yml b/.github/workflows/runtime_commit_artifacts.yml index 96a15ba7fb12..d39d03ba023f 100644 --- a/.github/workflows/runtime_commit_artifacts.yml +++ b/.github/workflows/runtime_commit_artifacts.yml @@ -292,7 +292,7 @@ jobs: git config --global user.name "${{ github.triggering_actor }}" git fetch origin --quiet - git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/react/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit" + git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit" - name: Push changes to branch if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true') run: git push @@ -466,7 +466,7 @@ jobs: git config --global user.name "${{ github.triggering_actor }}" git fetch origin --quiet - git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/react/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit" + git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit" - name: Push changes to branch if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true') run: git push diff --git a/.github/workflows/runtime_discord_notify.yml b/.github/workflows/runtime_discord_notify.yml index 22a4b3385dfe..ae9930adf114 100644 --- a/.github/workflows/runtime_discord_notify.yml +++ b/.github/workflows/runtime_discord_notify.yml @@ -27,7 +27,7 @@ jobs: check_maintainer: if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }} needs: [check_access] - uses: react/react/.github/workflows/shared_check_maintainer.yml@main + uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main permissions: # Used by check_maintainer contents: read diff --git a/.github/workflows/shared_label_core_team_prs.yml b/.github/workflows/shared_label_core_team_prs.yml index b98a5ca9b20a..cc10e87dcc2c 100644 --- a/.github/workflows/shared_label_core_team_prs.yml +++ b/.github/workflows/shared_label_core_team_prs.yml @@ -26,7 +26,7 @@ jobs: check_maintainer: if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }} needs: [check_access] - uses: react/react/.github/workflows/shared_check_maintainer.yml@main + uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main permissions: # Used by check_maintainer contents: read diff --git a/.gitignore b/.gitignore index 3913cc69fd5d..16982164c421 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,6 @@ chrome-user-data /tmp /.worktrees .claude/*.local.* -.claude/worktrees packages/react-devtools-core/dist packages/react-devtools-extensions/chrome/build diff --git a/.prettierignore b/.prettierignore index eaed5e651000..7e09af76a3af 100644 --- a/.prettierignore +++ b/.prettierignore @@ -22,12 +22,6 @@ compiler/**/.next # contains invalid graphql`...` which results in a promise rejection error from `yarn prettier-all`. compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.js -# contains invalid const reassignment; the fixture asserts the compiler diagnostic. -compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.js -# preserves the exact sequence-expression source shape used by the fixture. -compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/memoize-value-block-value-sequence.js -# preserves minimized production input; formatting changes the reduced repro shape. -compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_promote_used_temps.js compiler/crates compiler/target diff --git a/.prettierrc.js b/.prettierrc.js index d47168603e32..aa54cbae1f9f 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -8,8 +8,7 @@ module.exports = { bracketSameLine: true, trailingComma: 'es5', printWidth: 80, - plugins: ['prettier-plugin-hermes-parser'], - parser: 'hermes', + parser: 'flow', arrowParens: 'avoid', overrides: [ { @@ -31,16 +30,5 @@ module.exports = { parser: 'typescript', }, }, - { - // Flow `match` syntax fixtures: prettier's built-in Flow parser cannot - // parse the experimental syntax, hermes-parser can. - files: [ - 'compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-*', - ], - options: { - parser: 'hermes', - plugins: ['prettier-plugin-hermes-parser'], - }, - }, ], }; diff --git a/CLAUDE.md b/CLAUDE.md index 863aaab66c52..81f9b4217235 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,8 +6,3 @@ React is a JavaScript library for building user interfaces. - **React**: All files outside `/compiler/` - **React Compiler**: `/compiler/` directory (has its own instructions) - -## Current Active Work - -- **Rust Compiler Port**: Plans in `compiler/docs/rust-port/`, implementation in `compiler/crates/` -- Branch: `rust-research` diff --git a/compiler/.claude/agents/analyze-pass-impact.md b/compiler/.claude/agents/analyze-pass-impact.md deleted file mode 100644 index f2cf849f42b0..000000000000 --- a/compiler/.claude/agents/analyze-pass-impact.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -name: analyze-pass-impact -description: Analyzes how a specific topic affects a group of compiler passes. Used by the /plan-update skill to parallelize research across all compiler phases. Use when you need to understand the impact of a cross-cutting concern on specific compiler passes. -model: opus -color: blue ---- - -You are a React Compiler pass analysis specialist. Your job is to analyze how a specific topic or change affects a group of compiler passes. - -## Your Process - -1. **Read the pass documentation** for each pass in your assigned group from `compiler/packages/babel-plugin-react-compiler/docs/passes/` - -2. **Read the pass implementation source** in `compiler/packages/babel-plugin-react-compiler/src/`. Check these directories: - - `src/HIR/` — IR definitions, utilities, lowering - - `src/Inference/` — Effect inference (aliasing, mutation, types) - - `src/Validation/` — Validation passes - - `src/Optimization/` — Optimization passes - - `src/ReactiveScopes/` — Reactive scope analysis - - `src/Entrypoint/Pipeline.ts` — Pass ordering and invocation - -3. **Read the port conventions** from `compiler/docs/rust-port/rust-port-architecture.md` - -4. **For each pass**, analyze the topic's impact and produce a structured report - -## Output Format - -For each pass in your group, report: - -``` -### () -**Purpose**: <1-line description> -**Impact**: none | minor | moderate | significant -**Details**: -**Key locations**: -``` - -At the end, provide a brief summary: -``` -### Phase Summary -- Passes with no impact: -- Passes with minor impact: -- Passes with moderate impact: -- Passes with significant impact: -- Key insight: <1-2 sentences about the most important finding> -``` - -## Guidelines - -- Be concrete, not speculative. Reference specific code patterns you found. -- "Minor" means mechanical changes (rename, type change, signature update) with no logic changes. -- "Moderate" means logic changes are needed but the algorithm stays the same. -- "Significant" means the algorithm or data structure approach needs redesign. -- Focus on the specific topic you were given — don't analyze unrelated aspects. diff --git a/compiler/.claude/agents/compiler-review.md b/compiler/.claude/agents/compiler-review.md deleted file mode 100644 index ac3f04a9ffd8..000000000000 --- a/compiler/.claude/agents/compiler-review.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -name: compiler-review -description: Reviews Rust port code for port fidelity, convention compliance, and error handling. Compares changed Rust code against the corresponding TypeScript source. Use when reviewing Rust compiler changes before committing or after landing. -model: opus -color: green ---- - -You are a React Compiler Rust port reviewer. Your job is to review Rust code in `compiler/crates/` for port fidelity, convention compliance, and correct error handling by comparing it against the original TypeScript source. - -## Input - -You will receive a diff of changed Rust files. For each changed file, you must: - -1. **Read the architecture guide**: `compiler/docs/rust-port/rust-port-architecture.md` -2. **Identify the corresponding TypeScript file** using the mapping below -3. **Read the full corresponding TypeScript file** -4. **Review the changed Rust code** against the TS source and architecture guide - -## Rust Crate -> TypeScript Path Mapping - -| Rust Crate | TypeScript Path | -|---|---| -| `react_compiler_hir` | `src/HIR/` (excluding `BuildHIR.ts`, `HIRBuilder.ts`) | -| `react_compiler_lowering` | `src/HIR/BuildHIR.ts`, `src/HIR/HIRBuilder.ts` | -| `react_compiler` | `src/Babel/`, `src/Entrypoint/` | -| `react_compiler_diagnostics` | `src/CompilerError.ts` | -| `react_compiler_` | `src//` (1:1, e.g., `react_compiler_optimization` -> `src/Optimization/`) | - -Within a crate, Rust filenames use `snake_case.rs` corresponding to `PascalCase.ts` or `camelCase.ts` in the TS source. When multiple TS files exist in the mapped folder, match by comparing exported types/functions to the Rust file's contents. - -The TypeScript source root is `compiler/packages/babel-plugin-react-compiler/src/`. - -## Review Checklist - -### Port Fidelity -- Same high-level data flow as the TypeScript (only deviate where strictly necessary for arenas/borrow checker) -- Same grouping of logic: types, functions, struct methods should correspond to the TS file's exports -- Algorithms and control flow match the TS logic structurally -- No unnecessary additions, removals, or reorderings vs the TS - -### Convention Compliance -- Arena patterns: `IdentifierId`, `ScopeId`, `FunctionId`, `TypeId` used correctly (not inline data) -- `Place` is cloned, not shared by reference -- `EvaluationOrder` (not `InstructionId`) for evaluation ordering -- `InstructionId` for indexing into `HirFunction.instructions` -- `IndexMap`/`IndexSet` where iteration order matters -- `env: &mut Environment` passed separately from `func: &mut HirFunction` -- Environment fields accessed directly (not via sub-structs) for sliced borrows -- Side maps use ID-keyed `HashMap`/`HashSet` (not reference-identity maps) -- Naming: `snake_case` for functions/variables, `PascalCase` for types (matching Rust conventions) - -### Error Handling -- Non-null assertions (`!` in TS) -> `.unwrap()` or similar panic -- `CompilerError.invariant()`, `CompilerError.throwTodo()`, `throw` -> `Result<_, CompilerDiagnostic>` with `Err(...)` -- `pushDiagnostic()` with invariant errors -> `return Err(...)` -- `env.recordError()` or non-invariant `pushDiagnostic()` -> accumulate on `Environment` (keep as-is) - -## Output Format - -Produce a numbered list of issues. For each issue: - -``` -N. [CATEGORY] file_path:line_number — Description of the issue - Expected: what should be there (with TS reference if applicable) - Found: what is actually there -``` - -Categories: `FIDELITY`, `CONVENTION`, `ERROR_HANDLING` - -If no issues are found, report "No issues found." - -## Guidelines - -- Focus only on the changed lines and their immediate context — don't review unchanged code -- Be concrete: reference specific lines in both the Rust and TS source -- Don't flag intentional deviations that are necessary for Rust's ownership model (arenas, two-phase collect/apply, `std::mem::replace`, etc.) -- Don't flag style preferences that aren't covered by the architecture guide -- Don't suggest adding comments, docs, or type annotations beyond what the TS has diff --git a/compiler/.claude/agents/port-pass.md b/compiler/.claude/agents/port-pass.md deleted file mode 100644 index 3321f17fee59..000000000000 --- a/compiler/.claude/agents/port-pass.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -name: port-pass -description: Ports a single compiler pass from TypeScript to Rust, including crate setup, implementation, pipeline wiring, and test-fix loop until all fixtures pass. -model: opus -color: orange ---- - -You are a Rust compiler port specialist. Your job is to port a single React Compiler pass from TypeScript to Rust, then iterate on test failures until all fixtures pass. - -## Input - -You will receive: -- **Pass name**: The exact name from Pipeline.ts log entries -- **TypeScript source**: The full content of the TS file(s) to port -- **Target crate**: Name and path of the Rust crate to add code to -- **Implementation plan**: What files to create, types needed, pipeline wiring -- **Architecture guide**: Key patterns and conventions -- **Current pipeline.rs**: How existing passes are wired -- **Existing crate structure**: Files already in the target crate (if any) - -## Phases - -### Phase 1: Setup -- Understand the TypeScript source thoroughly -- Identify all types, functions, and their dependencies -- Note which types already exist in Rust (from HIR crate, etc.) - -### Phase 2: New Types -- Add any new types needed by this pass -- Place them in the appropriate crate (usually the target crate or `react_compiler_hir`) -- IMPORTANT: Follow the data modeling guidelines in docs/rust-port/rust-port-architecture.md for arena types (non-exhaustive types to pay extra attention to: `Identifier`, `HirFunction`, `ReactiveScope`, `Environment` etc) - -### Phase 3: Crate Setup (if new crate needed) -- Create `Cargo.toml` with appropriate dependencies -- Create `src/lib.rs` with module declarations -- Add the crate to the workspace `Cargo.toml` -- Add the crate as a dependency of `react_compiler` - -### Phase 4: Port the Pass -- Create the Rust file(s) corresponding to the TypeScript source -- Follow the translation guidelines from docs/rust-port/rust-port-architecture.md - -Key conventions: -- **Place is Clone**: `Place` stores `IdentifierId`, making it cheap to clone -- **env separate from func**: Pass `env: &mut Environment` separately from `func: &mut HirFunction` -- **Reactive passes**: Reactive passes take `&mut ReactiveFunction` + `&Environment` or `&mut Environment` (not `&mut HirFunction`) -- **Flat environment fields**: Access env fields directly for sliced borrows -- **Two-phase collect/apply**: When you can't mutate through stored references, collect IDs first, then apply mutations -- **Ordered maps**: Use `IndexMap`/`IndexSet` where TS uses `Map`/`Set` and iteration order matters -- **Error handling**: Non-fatal errors accumulate on `env`; fatal errors return `Err` -- **Structural similarity**: Target ~85-95% correspondence with TypeScript. A developer should be able to view TS and Rust side-by-side - -### Phase 5: Wire Pipeline -- Add the pass call to `compiler/crates/react_compiler/src/entrypoint/pipeline.rs` -- Follow the existing pattern: call the pass function, then log with `debug_print` and `context.log_debug` -- Match the exact ordering from Pipeline.ts -- Add necessary `use` imports - -### Phase 6: Test-Fix Loop - -This is the core of your work. You must achieve 0 test failures. - -**Commands:** -- Full suite: `bash compiler/scripts/test-rust-port.sh ` -- Single fixture: `bash compiler/scripts/test-rust-port.sh ` - -**Process:** -1. Run the full test suite -2. If failures exist, pick ONE specific failing fixture from the output -3. Run that single fixture in isolation to see the full diff -4. Read the diff carefully — it shows TS output vs Rust output line by line -5. Identify the root cause in the Rust code and fix it -6. Re-run the single fixture to confirm the fix -7. Re-run the full suite to check overall progress -8. Repeat from step 2 until 0 failures - -**Discipline:** -- Fix one fixture at a time — don't try to fix multiple issues at once -- Always verify a fix works on the single fixture before running the full suite -- Never stop early — the goal is exactly 0 failures -- If a fix causes regressions, investigate and fix those too - -**Common failure patterns:** -- Missing match arms (Rust requires exhaustive matching) -- Wrong iteration order (need `IndexMap` instead of `HashMap`) -- Range off-by-one errors (mutable range start/end) -- Formatting diffs (debug print format doesn't match TS) -- Event mismatches (CompileError/CompileSkip events differ) -- Missing handling for edge cases the TS handles implicitly -- Identifier/scope lookups that should go through the arena - -## Output - -When done, report: -- Files created/modified with brief descriptions -- Final test results (should be 0 failed) -- Any notable translation decisions made diff --git a/compiler/.claude/rules/commit-convention.md b/compiler/.claude/rules/commit-convention.md deleted file mode 100644 index 2aa2e4cb5dc2..000000000000 --- a/compiler/.claude/rules/commit-convention.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -description: Compiler commit message convention -globs: - - compiler/**/*.js - - compiler/**/*.jsx - - compiler/**/*.ts - - compiler/**/*.tsx - - compiler/**/*.rs - - compiler/**/*.json - - compiler/**/*.md ---- - -When committing changes in the compiler directory, follow this convention: - -- **Rust port work** (files in `compiler/crates/` and/or `compiler/docs/rust-port`): prefix with `[rust-compiler]` -- **TS compiler work** (files in `compiler/packages/`): prefix with `[compiler]` - -Format: -``` -[prefix] Title - -Summary of changes (1-3 sentences). -``` - -Use `/compiler-commit` to automatically verify and commit with the correct convention. diff --git a/compiler/.claude/rules/multi-step-instructions.md b/compiler/.claude/rules/multi-step-instructions.md deleted file mode 100644 index 8c6f9578f101..000000000000 --- a/compiler/.claude/rules/multi-step-instructions.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -description: Ensure all steps in multi-step user instructions are completed -globs: - - compiler/**/* ---- - -When the user gives multi-step instructions (e.g., "implement X, then /review, then /compiler-commit"): -- Track all steps as a checklist -- Complete ALL steps before responding -- Before declaring done, re-read the original prompt to verify nothing was missed -- If interrupted mid-way, note which steps remain diff --git a/compiler/.claude/rules/pass-docs.md b/compiler/.claude/rules/pass-docs.md deleted file mode 100644 index d8f090b2a72a..000000000000 --- a/compiler/.claude/rules/pass-docs.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -description: Read pass documentation before modifying compiler passes -globs: - - compiler/packages/babel-plugin-react-compiler/src/**/*.ts ---- - -Before modifying a compiler pass, read its documentation in `compiler/packages/babel-plugin-react-compiler/docs/passes/`. Pass docs explain the pass's role in the pipeline, its inputs/outputs, and key invariants. - -Pass docs are numbered to match pipeline order (e.g., `08-inferMutationAliasingEffects.md`). Check `Pipeline.ts` if you're unsure which doc corresponds to the code you're modifying. diff --git a/compiler/.claude/rules/plan-docs.md b/compiler/.claude/rules/plan-docs.md deleted file mode 100644 index 4fdc6c27d81b..000000000000 --- a/compiler/.claude/rules/plan-docs.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -description: Guidelines for editing Rust port plan documents -globs: - - compiler/docs/rust-port/*.md ---- - -When editing plan documents in `compiler/docs/rust-port/`: - -- Use `/plan-update ` for deep research across all compiler passes before making significant updates -- Read the architecture guide (`rust-port-architecture.md`) for context -- Reference specific pass docs from `compiler/packages/babel-plugin-react-compiler/docs/passes/` when discussing pass behavior -- Update the "Current status" line at the top of plan docs after changes -- Keep plan docs as the source of truth — if implementation diverges from the plan, update the plan diff --git a/compiler/.claude/rules/rust-port.md b/compiler/.claude/rules/rust-port.md deleted file mode 100644 index e133e21e9b0b..000000000000 --- a/compiler/.claude/rules/rust-port.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -description: Conventions for Rust port code in compiler/crates -globs: - - compiler/crates/**/*.rs - - compiler/crates/**/Cargo.toml ---- - -When working on Rust code in `compiler/crates/`: - -- Follow patterns from `compiler/docs/rust-port/rust-port-architecture.md` -- Use arenas + copyable IDs instead of shared references: `IdentifierId`, `ScopeId`, `FunctionId`, `TypeId` -- Pass `env: &mut Environment` separately from `func: &mut HirFunction` -- Use two-phase collect/apply when you can't mutate through stored references -- Run `bash compiler/scripts/test-babel-ast.sh` to test AST round-tripping -- Use `/port-pass ` when porting a new compiler pass -- Use `/compiler-verify` before committing to run both Rust and TS tests -- Keep Rust code structurally close to the TypeScript (~85-95% correspondence) - -Before declaring work complete on a plan doc: -- Re-read the original user prompt to ensure all requested steps are done -- Check the plan doc for any "Remaining Work" items -- Verify test-babel-ast.sh passes with the expected fixture count -- Update the plan doc's status section diff --git a/compiler/.claude/settings.json b/compiler/.claude/settings.json index 60f03a88de74..6f27a36ce3d4 100644 --- a/compiler/.claude/settings.json +++ b/compiler/.claude/settings.json @@ -3,12 +3,7 @@ "allow": [ "Bash(yarn snap:*)", "Bash(yarn snap:build)", - "Bash(node scripts/enable-feature-flag.js:*)", - "Bash(yarn workspace babel-plugin-react-compiler lint:*)", - "Bash(yarn prettier-all:*)", - "Bash(bash compiler/scripts/test-babel-ast.sh:*)", - "Bash(cargo test:*)", - "Bash(cargo check:*)" + "Bash(node scripts/enable-feature-flag.js:*)" ], "deny": [ "Skill(extract-errors)", diff --git a/compiler/.claude/skills/compiler-commit/SKILL.md b/compiler/.claude/skills/compiler-commit/SKILL.md deleted file mode 100644 index cd450b6974d7..000000000000 --- a/compiler/.claude/skills/compiler-commit/SKILL.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -name: compiler-commit -description: Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits with the [compiler] or [rust-compiler] prefix. ---- - -# Compiler Commit - -Verify and commit compiler changes with the correct convention. - -Arguments: -- $ARGUMENTS: Commit title (required). Optionally a test pattern after `--` (e.g., `Fix aliasing bug -- aliasing`) - -## Instructions - -1. **Run `/compiler-verify`** first (with test pattern if provided after `--`). Stop on any failure. - -2. **Run `/compiler-review`** on the uncommitted changes. Report the findings to the user. If any issues are found, stop and do NOT commit — let the user decide how to proceed. - -3. **Detect commit prefix** from changed files: - - If any files in `compiler/crates/` changed: use `[rust-compiler]` - - Otherwise: use `[compiler]` - -4. **Update orchestrator log**: If `compiler/docs/rust-port/rust-port-orchestrator-log.md` exists and the commit includes Rust changes (`compiler/crates/`): - - Run `test-rust-port` with `--json` to get machine-readable results: - ```bash - bash compiler/scripts/test-rust-port.sh --json 2>/dev/null - ``` - This outputs a JSON object with fields: `pass`, `autoDetected`, `total`, `passed`, `failed`, `frontier`, `perPass`, `failures`. - - Then update the orchestrator log: - - Update the `# Status` section with the results (use the frontier, per-pass counts, and pass/fail totals) - - Add a `## YYYYMMDD-HHMMSS` log entry noting the commit and what changed - -5. **Stage files** — stage only the relevant changed files by name (including the orchestrator log if updated in step 4). Do NOT use `git add -A` or `git add .`. - -6. **Compose commit message**: - ``` - [prefix] - - <summary of what changed and why, 1-3 sentences> - ``` - The title comes from $ARGUMENTS. Write the summary yourself based on the actual changes. - -7. **Commit** using a heredoc for the message: - ```bash - git commit -m "$(cat <<'EOF' - [rust-compiler] Title here - - Summary here. - EOF - )" - ``` - -8. **Do NOT push** unless the user explicitly asks. - -## Examples - -- `/compiler-commit Fix aliasing bug in optional chains` — runs full verify, commits as `[compiler] Fix aliasing bug in optional chains` -- `/compiler-commit Implement scope tree types -- round_trip` — runs verify with `-p round_trip`, commits as `[rust-compiler] Implement scope tree types` diff --git a/compiler/.claude/skills/compiler-orchestrator/SKILL.md b/compiler/.claude/skills/compiler-orchestrator/SKILL.md deleted file mode 100644 index e58d50033ea0..000000000000 --- a/compiler/.claude/skills/compiler-orchestrator/SKILL.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -name: compiler-orchestrator -description: Orchestrate the Rust compiler port end-to-end. Discovers the current frontier, fixes failing passes, ports new passes, reviews, and commits in a loop. ---- - -# Compiler Orchestrator - -Automatically drive the Rust compiler port forward by discovering the current state, fixing failures, porting new passes, reviewing, and committing — in a continuous loop. - -Arguments: -- $ARGUMENTS: Optional. A pass name to start from, or `status` to just report current state without acting. - -## Pass Order Reference - -These are the passes in Pipeline.ts order, with their exact log names: - -| # | Log Name | Kind | Notes | -|---|----------|------|-------| -| 1 | HIR | hir | | -| 2 | PruneMaybeThrows | hir | Validation: validateContextVariableLValues, validateUseMemo after | -| 3 | DropManualMemoization | hir | Conditional | -| 4 | InlineImmediatelyInvokedFunctionExpressions | hir | | -| 5 | MergeConsecutiveBlocks | hir | | -| 6 | SSA | hir | | -| 7 | EliminateRedundantPhi | hir | | -| 8 | ConstantPropagation | hir | | -| 9 | InferTypes | hir | Validation: validateHooksUsage, validateNoCapitalizedCalls after (conditional) | -| 10 | OptimizePropsMethodCalls | hir | | -| 11 | AnalyseFunctions | hir | | -| 12 | InferMutationAliasingEffects | hir | | -| 13 | OptimizeForSSR | hir | Conditional: outputMode === 'ssr' | -| 14 | DeadCodeElimination | hir | | -| 15 | PruneMaybeThrows (2nd) | hir | Reuses existing fn, just needs 2nd call + log in pipeline.rs | -| 16 | InferMutationAliasingRanges | hir | Validation block (8 validators) after (conditional) | -| 17 | InferReactivePlaces | hir | Validation: validateExhaustiveDependencies after (conditional) | -| 18 | RewriteInstructionKindsBasedOnReassignment | hir | Validation: validateStaticComponents after (conditional) | -| 19 | InferReactiveScopeVariables | hir | Conditional: enableMemoization | -| 20 | MemoizeFbtAndMacroOperandsInSameScope | hir | | -| -- | outlineJSX | hir | Between #20 and #21, conditional: enableJsxOutlining, no log entry | -| 21 | NameAnonymousFunctions | hir | Conditional | -| 22 | OutlineFunctions | hir | Conditional | -| 23 | AlignMethodCallScopes | hir | | -| 24 | AlignObjectMethodScopes | hir | | -| 25 | PruneUnusedLabelsHIR | hir | | -| 26 | AlignReactiveScopesToBlockScopesHIR | hir | | -| 27 | MergeOverlappingReactiveScopesHIR | hir | | -| 28 | BuildReactiveScopeTerminalsHIR | hir | | -| 29 | FlattenReactiveLoopsHIR | hir | | -| 30 | FlattenScopesWithHooksOrUseHIR | hir | | -| 31 | PropagateScopeDependenciesHIR | hir | | -| 32 | BuildReactiveFunction | reactive | | -| 33 | AssertWellFormedBreakTargets | debug | Validation | -| 34 | PruneUnusedLabels | reactive | | -| 35 | AssertScopeInstructionsWithinScopes | debug | Validation | -| 36 | PruneNonEscapingScopes | reactive | | -| 37 | PruneNonReactiveDependencies | reactive | | -| 38 | PruneUnusedScopes | reactive | | -| 39 | MergeReactiveScopesThatInvalidateTogether | reactive | | -| 40 | PruneAlwaysInvalidatingScopes | reactive | | -| 41 | PropagateEarlyReturns | reactive | | -| 42 | PruneUnusedLValues | reactive | | -| 43 | PromoteUsedTemporaries | reactive | | -| 44 | ExtractScopeDeclarationsFromDestructuring | reactive | | -| 45 | StabilizeBlockIds | reactive | | -| 46 | RenameVariables | reactive | | -| 47 | PruneHoistedContexts | reactive | | -| 48 | ValidatePreservedManualMemoization | debug | Conditional | -| 49 | Codegen | ast | | - -Validation passes (no log entries, tested via CompileError/CompileSkip events): -- After PruneMaybeThrows (#2): validateContextVariableLValues, validateUseMemo -- After InferTypes (#9): validateHooksUsage, validateNoCapitalizedCalls (conditional) -- After InferMutationAliasingRanges (#16): 8 validators (conditional) -- After InferReactivePlaces (#17): validateExhaustiveDependencies (conditional) -- After RewriteInstructionKindsBasedOnReassignment (#18): validateStaticComponents (conditional) -- After PruneHoistedContexts (#45): validatePreservedManualMemoization (conditional) -- After Codegen (#46): validateSourceLocations (conditional) - -## Orchestrator Log - -Maintain a log file at `compiler/docs/rust-port/rust-port-orchestrator-log.md` that tracks all progress. - -### Log file format - -```markdown -# Status - -HIR: complete (1717/1717) -PruneMaybeThrows: complete (1717/1717) -DropManualMemoization: complete (1717/1717) -... -AnalyseFunctions: partial (1700/1717) -InferMutationAliasingEffects: todo -... - -# Logs - -## 20260318-143022 Port AnalyseFunctions pass - -Ported AnalyseFunctions from TypeScript to Rust. Added new crate react_compiler_analyse_functions. -1700/1717 tests passing, 17 failures in edge cases with nested functions. - -## 20260318-141500 Fix SSA phi node ordering - -Fixed phi node operand ordering in SSA pass that caused 3 test failures. -All 1717 tests now passing through OptimizePropsMethodCalls. -``` - -### Status section - -The `# Status` section lists every pass from #1 to #49 with one of: -- `complete (N/N)` — all tests passing through this pass -- `partial (passed/total)` — some test failures remain -- `todo` — not yet ported - -Update the Status section after every test run to reflect the latest results. - -### Log entries - -Add a new log entry (below the most recent one, so newest entries are at the bottom) whenever: -- A pass is newly ported -- Test failures are fixed -- A commit is made - -Entry format: `## YYYYMMDD-HHMMSS <short-summary>` followed by 1-3 lines describing what changed. - -Use the current timestamp when creating entries. Get it via `date '+%Y%m%d-%H%M%S'`. - -### Initialization - -On first run, if the log file doesn't exist, create it with the Status section populated from the current state (read pipeline.rs and run tests to determine pass statuses). - -## Core Loop - -**Main context role**: The main context is ONLY an orchestration loop. It parses subagent results, updates the orchestrator log, prints status, and launches the next subagent. The main context MUST NOT read source code, investigate failures, debug issues, or make edits directly. ALL implementation work — fixing, porting, reviewing, verifying — happens in subagents. - -Execute these steps in order, looping back to Step 1 after each commit: - -### Step 1: Discover Frontier - -Run `test-rust-port` with `--json` to get machine-readable results: - -```bash -bash compiler/scripts/test-rust-port.sh --json 2>/dev/null -``` - -This outputs a single JSON object with fields: `pass`, `autoDetected`, `total`, `passed`, `failed`, `frontier`, `perPass`, `failures`. - -Parse the JSON to extract: -- `passed`, `failed`, `total` counts -- `frontier` — the earliest pass with failures, or `null` if all clean -- `perPass` — per-pass breakdown of passed/failed counts - -If frontier is `null`, determine the next action: -- The `pass` field shows the last ported pass (auto-detected from pipeline.rs) -- Look up the next pass in the Pass Order Reference table -- Otherwise, the mode is **PORT** for that next pass - -If frontier is a pass name, the mode is **FIX** for that pass. Use `--failures` to get the full list of failing fixture paths: -```bash -bash compiler/scripts/test-rust-port.sh <FrontierPassName> --failures -``` - -Then run specific failing fixtures to get diffs for investigation: -```bash -bash compiler/scripts/test-rust-port.sh <FrontierPassName> <fixture-path> --no-color -``` - -Also check if `compiler/docs/rust-port/rust-port-orchestrator-log.md` exists. If not, create it with the Status section populated from the current state. - -Update the orchestrator log Status section, then proceed to Step 2. - -### Step 2: Report Status - -Print a status report: -``` -## Orchestrator Status -- Ported passes: <count> / 49 -- Test results: <passed> passed, <failed> failed (<total> total) -- Frontier: #<num> <PassName> (<FIX|PORT> mode) — or "none (all clean)" -- Action: <what will happen next> -``` - -If `$ARGUMENTS` is `status`, stop here. - -### Step 3: Act on Frontier - -**Do NOT investigate, read source code, or debug in the main context.** Always delegate to a subagent. - -#### 3a. FIX mode (frontier is a ported pass with failures) - -Launch two subagents **in parallel** to diagnose the failures: - -1. **Review subagent**: Run `/compiler-review` on the failing pass to identify obvious issues — missing features, incorrect porting of logic, divergences from the TypeScript source. - -2. **Analysis subagent**: A `general-purpose` subagent that investigates the actual test failures. Its prompt MUST include: - - **The pass name** and its position number - - **The full test failure output** (copy it verbatim) - - **Instructions**: Run failing fixtures individually with `bash compiler/scripts/test-rust-port.sh <PassName> <fixture-path> --no-color` to get diffs. Analyze the diffs to determine what the Rust port is doing wrong. Read the corresponding TypeScript source to understand expected behavior. Report findings but do NOT make fixes yet. - - **Architecture guide path**: `compiler/docs/rust-port/rust-port-architecture.md` - - **Pipeline path**: `compiler/crates/react_compiler/src/entrypoint/pipeline.rs` - -After both subagents complete, **synthesize their results** to determine a plan of action. The review may surface porting gaps that explain the test failures, and the failure analysis may reveal issues the review missed. Use both inputs to form a complete picture. - -Then launch a single `general-purpose` subagent to fix the failures. The subagent prompt MUST include: - -1. **The pass name** and its position number -2. **The synthesized diagnosis** — both the review findings and the failure analysis -3. **Instructions**: Fix the test failures in the Rust port. Do NOT re-port from scratch. Use the diagnosis to guide fixes. After fixing, run `bash compiler/scripts/test-rust-port.sh <PassName>` to verify. Repeat until 0 failures or you've made 3 fix attempts without progress. -4. **Architecture guide path**: `compiler/docs/rust-port/rust-port-architecture.md` -5. **Pipeline path**: `compiler/crates/react_compiler/src/entrypoint/pipeline.rs` - -After the fix subagent completes: -1. Re-run `bash compiler/scripts/test-rust-port.sh --json 2>/dev/null` to get updated counts and frontier -2. If still failing, repeat the parallel diagnosis + fix cycle (max 3 rounds total) -3. Once clean (or after 3 rounds), update the orchestrator log Status section and add a log entry -4. Go to Step 4 (Review and Commit) - -#### 3b. PORT mode (frontier is the next unported pass) - -Handle special cases first: -- **Second PruneMaybeThrows call (#15)**: Launch a `general-purpose` subagent to add a second call to `prune_maybe_throws` + `log_debug!` in pipeline.rs, then run tests. -- **outlineJSX (between #20 and #21)**: Conditional on `enableJsxOutlining`. Has no log entry. Launch a subagent to handle inline or via the compiler-port pattern. -- **Conditional passes** (#3, #13, #19, #21, #22): Note the condition when delegating. - -For standard passes, launch a single `general-purpose` subagent with these instructions: - -1. **Pass name**: `<PassName>` (position #N in the pipeline) -2. **Instructions**: Port the `<PassName>` pass from TypeScript to Rust. Follow these steps: - a. Read the architecture guide at `compiler/docs/rust-port/rust-port-architecture.md` - b. Read the pass documentation in `compiler/packages/babel-plugin-react-compiler/docs/passes/` - c. Find the TypeScript source by following the import in `compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts` - d. Read the Rust pipeline at `compiler/crates/react_compiler/src/entrypoint/pipeline.rs` and existing crate structure - e. Port the pass, create/update crates as needed, wire into pipeline.rs - f. Run `bash compiler/scripts/test-rust-port.sh <PassName>` and fix failures in a loop until 0 failures (max 5 attempts) - g. Report: files created/modified, final test count, any remaining issues -3. **Special notes** (if any — e.g., conditional gating, reuse of existing functions) - -After the subagent completes: -1. Re-run `bash compiler/scripts/test-rust-port.sh --json 2>/dev/null` to get updated counts and frontier -2. Update the orchestrator log Status section and add a log entry -3. Go to Step 4 (Review and Commit) - -### Step 4: Review and Commit - -Use `/compiler-commit <title>` to review, verify, and commit the changes. This skill: -1. Runs `/compiler-verify` (tests, lint, format) -2. Runs `/compiler-review` on uncommitted changes — stops if issues are found -3. Updates the orchestrator log with test results -4. Commits with the correct `[rust-compiler]` prefix - -Choose a descriptive commit title based on what the subagent did (e.g., "Port AnalyseFunctions pass" or "Fix SSA phi node ordering"). - -After committing: -1. Parse the commit hash from the output -2. Add a log entry noting the commit -3. Work continues — commits are checkpoints, not stopping points - -### Step 5: Loop - -Go back to Step 1. The loop continues until: -- All passes are ported and clean (up to #49) -- An unrecoverable error occurs - -## Key Principles - -1. **Earliest failure wins**: Even a single test failure in pass #2 must be fixed before working on pass #11. Early errors cascade — a bug in lowering can cause false failures in every downstream pass. - -2. **Cumulative testing**: `test-rust-port.sh <PassName>` tests ALL passes up to and including the named pass. A clean result for the last pass implies all earlier passes are clean too. - -3. **Incremental commits**: Commit after each meaningful unit of progress. Don't batch multiple passes into one commit. Each commit should leave the tree in a clean state. - -4. **Delegate everything**: The main context MUST NOT read source code, investigate bugs, or make edits. It only: parses subagent results, updates the orchestrator log, prints status, and launches the next subagent. All code reading, debugging, fixing, porting, reviewing, and committing happens in subagents. diff --git a/compiler/.claude/skills/compiler-port/SKILL.md b/compiler/.claude/skills/compiler-port/SKILL.md deleted file mode 100644 index 7aecf17241c7..000000000000 --- a/compiler/.claude/skills/compiler-port/SKILL.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -name: compiler-port -description: Port a compiler pass from TypeScript to Rust. Gathers context, plans the port, implements in a subagent with test-fix loop, then reviews. ---- - -# Port Compiler Pass - -Port a compiler pass from TypeScript to Rust end-to-end. - -Arguments: -- $ARGUMENTS: Pass name exactly as it appears in Pipeline.ts log entries (e.g., `PruneMaybeThrows`, `SSA`, `ConstantPropagation`) - -## Step 0: Validate pass name - -1. Read `compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts` -2. Search for `name: '$ARGUMENTS'` in log entries -3. If not found, list all available pass names from the `log({...name: '...'})` calls and stop -4. Check the `kind` field of the matching log entry: - - If `kind: 'ast'`, report that test-rust-port only supports `hir` and `reactive` kind passes currently and stop - - If `kind: 'hir'` or `kind: 'reactive'`, proceed - -## Step 1: Determine TS source files and Rust crate - -1. Follow the import in Pipeline.ts to find the actual TypeScript file(s) for the pass -2. Map the TS folder to a Rust crate using this mapping: - -| TypeScript Path | Rust Crate | -|---|---| -| `src/HIR/` (excluding `BuildHIR.ts`, `HIRBuilder.ts`) | `react_compiler_hir` | -| `src/HIR/BuildHIR.ts`, `src/HIR/HIRBuilder.ts` | `react_compiler_lowering` | -| `src/Babel/`, `src/Entrypoint/` | `react_compiler` | -| `src/CompilerError.ts` | `react_compiler_diagnostics` | -| `src/ReactiveScopes/` | `react_compiler_reactive_scopes` | -| `src/<Name>/` | `react_compiler_<name>` (1:1, e.g., `src/Optimization/` -> `react_compiler_optimization`) | - -3. Check if the pass is already ported: - - Check if the corresponding Rust file exists in the target crate - - Check if `compiler/crates/react_compiler/src/entrypoint/pipeline.rs` already calls it - - If both are true, report the pass is already ported and stop - -## Step 2: Gather context - -Read the following files (all reads happen in main context): - -1. **Architecture guide**: `compiler/docs/rust-port/rust-port-architecture.md` -2. **Pass documentation**: Check `compiler/packages/babel-plugin-react-compiler/docs/passes/` for docs about this pass -3. **TypeScript source**: All TypeScript source files for the pass + any helpers imported from the same folder -4. **Rust pipeline**: `compiler/crates/react_compiler/src/entrypoint/pipeline.rs` -5. **Rust HIR types**: Key type files in `compiler/crates/react_compiler_hir/src/` (especially `hir.rs`, `environment.rs`) -6. **Rust reactive types**: For reactive passes, also read `compiler/crates/react_compiler_hir/src/reactive_function.rs` -7. **Target crate**: If the target crate already exists, read its `Cargo.toml`, `src/lib.rs`, and existing files to understand the current structure - -## Step 3: Create implementation plan - -Based on the gathered context, create and present a plan covering: - -1. **New types needed**: Any Rust types that need to be added or modified -2. **Files to create**: List of new Rust files with their TS counterparts -3. **Crate setup**: Whether a new crate is needed or adding to an existing one -4. **Pipeline wiring**: How the pass will be called from `pipeline.rs` -5. **Key translation decisions**: Any non-obvious TS-to-Rust translations - -Present the plan to the user, then proceed to implementation. - -## Step 4: Implementation - -Launch the `port-pass` agent with all gathered context: - -- Pass name: `$ARGUMENTS` -- TypeScript source file content(s) -- Target Rust crate name and path -- Pipeline wiring details -- Implementation plan from Step 3 -- Architecture guide content -- Current pipeline.rs content -- Existing crate structure (if any) - -The agent will: -1. Port the TypeScript code to Rust -2. Create or update the crate as needed -3. Wire the pass into pipeline.rs -4. Run the test-fix loop until 0 failures (see agent prompt for details) - -## Step 5: Review loop - -1. Run `/compiler-review` on the changes -2. If issues are found: - - Launch the `port-pass` agent again with: - - The review findings - - Instruction to fix the issues - - Instruction to re-run `bash compiler/scripts/test-rust-port.sh` (no args, auto-detects last ported pass) to confirm 0 failures still hold - - After the agent completes, run `/compiler-review` again -3. Repeat until review is clean - -## Step 6: Final report - -Report to the user: -- Files created and modified -- Test results (pass count) -- Review status -- Do NOT auto-commit (user should review and commit manually, or use `/compiler-commit`) diff --git a/compiler/.claude/skills/compiler-review/SKILL.md b/compiler/.claude/skills/compiler-review/SKILL.md deleted file mode 100644 index 1aed32476da8..000000000000 --- a/compiler/.claude/skills/compiler-review/SKILL.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: compiler-review -description: Review Rust port code for port fidelity, convention compliance, and error handling. Compares against the original TypeScript source. ---- - -# Compiler Review - -Review Rust compiler port code for correctness and convention compliance. - -Arguments: -- $ARGUMENTS: Optional commit ref or range (e.g., `HEAD~3..HEAD`, `abc123`). If omitted, reviews uncommitted/staged changes. - -## Instructions - -1. **Get the diff** based on arguments: - - No arguments: `git diff HEAD -- compiler/crates/` (uncommitted changes). If empty, also check `git diff --cached -- compiler/crates/` (staged changes). - - Commit ref (e.g., `abc123`): `git diff abc123~1..abc123 -- compiler/crates/` - - Commit range (e.g., `HEAD~3..HEAD`): `git diff HEAD~3..HEAD -- compiler/crates/` - -2. **If no Rust changes found**, report "No Rust changes to review." and stop. - -3. **Identify changed Rust files** from the diff using `git diff --name-only` with the same ref arguments. - -4. **Launch the `compiler-review` agent** via the Agent tool, passing it the full diff content. The agent will: - - Read the architecture guide - - Find and read the corresponding TypeScript files - - Review for port fidelity, convention compliance, and error handling - - Return a numbered issue list - -5. **Report the agent's findings** to the user. diff --git a/compiler/.claude/skills/compiler-verify/SKILL.md b/compiler/.claude/skills/compiler-verify/SKILL.md deleted file mode 100644 index 63199a9163b1..000000000000 --- a/compiler/.claude/skills/compiler-verify/SKILL.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: compiler-verify -description: Use when you need to run all compiler checks (tests, lint, format) before committing. Detects whether TS or Rust code changed and runs the appropriate checks. ---- - -# Compiler Verify - -Run all verification steps for compiler changes. - -Arguments: -- $ARGUMENTS: Optional test pattern for `yarn snap -p <pattern>` - -## Instructions - -1. **Detect what changed** by running `git diff --name-only HEAD` (or vs the base branch). - Categorize changes: - - **TS changes**: files in `compiler/packages/` - - **Rust changes**: files in `compiler/crates/` - - **Both**: run all checks - -2. **If TS changed**, run these sequentially (stop on failure): - - `yarn snap` (or `yarn snap -p <pattern>` if a pattern was provided) — compiler tests - - `yarn test` — test full compiler - - `yarn workspace babel-plugin-react-compiler lint` — lint compiler source - -3. **If Rust changed**, run these sequentially (stop on failure): - - `bash compiler/scripts/test-babel-ast.sh` — Babel AST round-trip tests - - `bash compiler/scripts/test-rust-port.sh` — full Rust port test suite (compares Rust vs TS compiler output across all passes; must have 0 failures — do not regress) - - `yarn snap --rust` — end-to-end snap tests using the Rust compiler (compares compiled output and logger events against `.expect.md` fixtures; use `yarn snap --rust -p <pattern>` for focused checks) - -4. **Always run** (from the repo root): - - `yarn prettier-all` — format all changed files - -5. **If implementing a plan doc**, check: - - Plan doc has no unaddressed "Remaining Work" items - - Plan doc status is updated to reflect current state - -6. Report results: list each step as passed/failed. On failure, stop and show the error with suggested fixes. - -## Common Mistakes - -- **Running `yarn snap` without `-p`** is fine for full verification, but slow. Use `-p` for focused checks. -- **Running prettier from compiler/** — must run from the repo root. -- **Forgetting Rust tests** — if you touched `.rs` files, always run the round-trip test and `yarn snap --rust`. diff --git a/compiler/.claude/skills/plan-update/SKILL.md b/compiler/.claude/skills/plan-update/SKILL.md deleted file mode 100644 index b2ef6103b1bb..000000000000 --- a/compiler/.claude/skills/plan-update/SKILL.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -name: plan-update -description: Use when you need to update a plan document with deep research across all compiler passes. Launches parallel subagents to analyze how a topic affects every compiler phase, then consolidates findings into the plan doc. ---- - -# Plan Update - -Deep-research a topic across all compiler passes and update a plan document. - -Arguments: -- $ARGUMENTS: `<plan-doc-path> <topic/question>` - - Example: `compiler/docs/rust-port/rust-port-0001-babel-ast.md scope resolution strategy` - - Example: `compiler/docs/rust-port/rust-port-architecture.md error handling patterns` - -## Instructions - -### Step 1: Read context - -Read these files to understand the current state: -- The plan doc specified in $ARGUMENTS -- `compiler/docs/rust-port/rust-port-architecture.md` (architecture guide and port conventions) -- `compiler/packages/babel-plugin-react-compiler/docs/passes/README.md` (pass overview) - -### Step 2: Launch parallel analysis agents - -Launch **8 parallel Agent tool calls** using the `analyze-pass-impact` agent. Each agent analyzes one phase group. Pass each agent the topic from $ARGUMENTS and the list of pass doc files for its phase. - -**Phase groups and their pass docs:** - -1. **Lowering & SSA** (passes 01-03): - `01-lower.md`, `02-enterSSA.md`, `03-eliminateRedundantPhi.md` - -2. **Optimization & Types** (passes 04-06): - `04-constantPropagation.md`, `05-deadCodeElimination.md`, `06-inferTypes.md` - -3. **Function & Effect Analysis** (passes 07-09): - `07-analyseFunctions.md`, `08-inferMutationAliasingEffects.md`, `09-inferMutationAliasingRanges.md` - -4. **Reactivity & Scope Variables** (passes 10-14): - `10-inferReactivePlaces.md`, `11-inferReactiveScopeVariables.md`, `12-rewriteInstructionKindsBasedOnReassignment.md`, `13-alignMethodCallScopes.md`, `14-alignObjectMethodScopes.md` - -5. **Scope Alignment & Terminals** (passes 15-20): - `15-alignReactiveScopesToBlockScopesHIR.md`, `16-mergeOverlappingReactiveScopesHIR.md`, `17-buildReactiveScopeTerminalsHIR.md`, `18-flattenReactiveLoopsHIR.md`, `19-flattenScopesWithHooksOrUseHIR.md`, `20-propagateScopeDependenciesHIR.md` - -6. **Reactive Function & Transforms** (passes 21-30): - `21-buildReactiveFunction.md`, `22-pruneUnusedLabels.md`, `23-pruneNonEscapingScopes.md`, `24-pruneNonReactiveDependencies.md`, `25-pruneUnusedScopes.md`, `26-mergeReactiveScopesThatInvalidateTogether.md`, `27-pruneAlwaysInvalidatingScopes.md`, `28-propagateEarlyReturns.md`, `29-promoteUsedTemporaries.md`, `30-renameVariables.md` - -7. **Codegen & Optimization** (passes 31, 34-38): - `31-codegenReactiveFunction.md`, `34-optimizePropsMethodCalls.md`, `35-optimizeForSSR.md`, `36-outlineJSX.md`, `37-outlineFunctions.md`, `38-memoizeFbtAndMacroOperandsInSameScope.md` - -8. **Validation Passes** (passes 39-55): - `39-validateContextVariableLValues.md`, `40-validateUseMemo.md`, `41-validateHooksUsage.md`, `42-validateNoCapitalizedCalls.md`, `43-validateLocalsNotReassignedAfterRender.md`, `44-validateNoSetStateInRender.md`, `45-validateNoDerivedComputationsInEffects.md`, `46-validateNoSetStateInEffects.md`, `47-validateNoJSXInTryStatement.md`, `48-validateNoImpureValuesInRender.md`, `49-validateNoRefAccessInRender.md`, `50-validateNoFreezingKnownMutableFunctions.md`, `51-validateExhaustiveDependencies.md`, `53-validatePreservedManualMemoization.md`, `54-validateStaticComponents.md`, `55-validateSourceLocations.md` - -Each agent prompt should be: -``` -Analyze how the topic "<topic>" affects the following compiler passes. - -Read each pass's documentation in compiler/packages/babel-plugin-react-compiler/docs/passes/ and its implementation source. Also read compiler/docs/rust-port/rust-port-architecture.md for port conventions. - -Pass docs to analyze: <list of pass doc filenames> - -For each pass, report: -- Pass name and purpose (1 line) -- Impact: "none", "minor" (mechanical changes only), "moderate" (logic changes), or "significant" (redesign needed) -- If impact is not "none": specific details of what changes are needed -- Key code locations in the implementation (file:line references) - -Be thorough but concise. Focus on concrete impacts, not speculation. -``` - -### Step 3: Consolidate and update - -After all agents complete: -1. Merge their findings into a coherent analysis -2. Group by impact level (significant > moderate > minor > none) -3. Update the plan document. The final state should reflect the latest findings and understanding: - - Update the plan document in-place to remove outdated content and describe the latest understanding. - - KEEP any existing content that is still relevant - - REMOVE outdated or now-irrelevant content - - Per-pass impact summary table (updated in place, ie update each section based on new findings) - - Detailed notes for passes with moderate+ impact - - Updated "Current status" or "Remaining Work" section if applicable - -### Step 4: Show summary - -Show the user a brief summary of findings: how many passes are affected at each level, and the key insights. diff --git a/compiler/.claude/skills/rust-port-status/SKILL.md b/compiler/.claude/skills/rust-port-status/SKILL.md deleted file mode 100644 index a39155df920a..000000000000 --- a/compiler/.claude/skills/rust-port-status/SKILL.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: rust-port-status -description: Show the status of all Rust port plan documents and recent related commits. Use when you need to understand what's been done vs what remains. ---- - -# Rust Port Status - -Show current status of the Rust compiler port. - -## Instructions - -1. List all files in `compiler/docs/rust-port/` -2. For each numbered plan doc (e.g., `rust-port-0001-*.md`): - - Show the title (first heading) - - Show the status line (if present) - - Note whether it has "Remaining Work" items - - Show recent commits referencing it: `git log --oneline --grep="<key phrase>"` -3. Show a summary table of plan doc statuses -4. Show the 10 most recent `[rust-compiler]` commits: `git log --oneline --grep="rust-compiler" -10` diff --git a/compiler/.gitignore b/compiler/.gitignore index 500dd888f452..70622d250d00 100644 --- a/compiler/.gitignore +++ b/compiler/.gitignore @@ -5,8 +5,6 @@ node_modules .watchmanconfig .watchman-cookie-* dist -target -crates/react_compiler_ast/tests/fixtures .vscode !packages/playground/.vscode testfilter.txt diff --git a/compiler/CLAUDE.md b/compiler/CLAUDE.md index aed679ba3cda..877c74a8a5d4 100644 --- a/compiler/CLAUDE.md +++ b/compiler/CLAUDE.md @@ -236,21 +236,6 @@ Feature flags are configured in `src/HIR/Environment.ts`, for example `enableJsx Would enable the `enableJsxOutlining` feature and disable the `enableNameAnonymousFunctions` feature. -## Rust Port (Active) - -Work is tracked in `compiler/docs/rust-port/` with numbered plan docs. -Rust crates live in `compiler/crates/`. - -### Before implementing from a plan: -- Run `git log --oneline --grep="<plan-name>"` to see what's already done -- Read the plan doc's Remaining Work / Status section -- Only implement what's actually remaining - -### After implementing: -- Update the plan doc's status -- Run `/compiler-verify` -- Ensure `compiler/scripts/test-babel-ast.sh` passes - ## Debugging Tips 1. Run `yarn snap -p <fixture>` to see full HIR output with effects diff --git a/compiler/Cargo.lock b/compiler/Cargo.lock deleted file mode 100644 index 14273c9c43c4..000000000000 --- a/compiler/Cargo.lock +++ /dev/null @@ -1,572 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown", - "serde", - "serde_core", -] - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "libc" -version = "0.2.183" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "napi" -version = "2.16.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" -dependencies = [ - "bitflags", - "ctor", - "napi-derive", - "napi-sys", - "once_cell", -] - -[[package]] -name = "napi-build" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" - -[[package]] -name = "napi-derive" -version = "2.16.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" -dependencies = [ - "cfg-if", - "convert_case", - "napi-derive-backend", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "napi-derive-backend" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" -dependencies = [ - "convert_case", - "once_cell", - "proc-macro2", - "quote", - "regex", - "semver", - "syn", -] - -[[package]] -name = "napi-sys" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" -dependencies = [ - "libloading", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "react_compiler" -version = "0.1.0" -dependencies = [ - "indexmap", - "react_compiler_ast", - "react_compiler_diagnostics", - "react_compiler_hir", - "react_compiler_inference", - "react_compiler_lowering", - "react_compiler_optimization", - "react_compiler_reactive_scopes", - "react_compiler_ssa", - "react_compiler_typeinference", - "react_compiler_validation", - "serde", - "serde_json", -] - -[[package]] -name = "react_compiler_ast" -version = "0.1.0" -dependencies = [ - "indexmap", - "serde", - "serde-transcode", - "serde_json", - "similar", - "walkdir", -] - -[[package]] -name = "react_compiler_diagnostics" -version = "0.1.0" -dependencies = [ - "serde", -] - -[[package]] -name = "react_compiler_hir" -version = "0.1.0" -dependencies = [ - "indexmap", - "react_compiler_diagnostics", - "serde", - "serde_json", -] - -[[package]] -name = "react_compiler_inference" -version = "0.1.0" -dependencies = [ - "indexmap", - "react_compiler_diagnostics", - "react_compiler_hir", - "react_compiler_lowering", - "react_compiler_optimization", - "react_compiler_ssa", - "react_compiler_utils", -] - -[[package]] -name = "react_compiler_lowering" -version = "0.1.0" -dependencies = [ - "indexmap", - "react_compiler_ast", - "react_compiler_diagnostics", - "react_compiler_hir", - "serde_json", -] - -[[package]] -name = "react_compiler_napi" -version = "0.1.0" -dependencies = [ - "napi", - "napi-build", - "napi-derive", - "react_compiler", - "react_compiler_ast", - "serde", - "serde_json", -] - -[[package]] -name = "react_compiler_optimization" -version = "0.1.0" -dependencies = [ - "indexmap", - "react_compiler_diagnostics", - "react_compiler_hir", - "react_compiler_lowering", - "react_compiler_ssa", -] - -[[package]] -name = "react_compiler_reactive_scopes" -version = "0.1.0" -dependencies = [ - "hmac", - "indexmap", - "react_compiler_ast", - "react_compiler_diagnostics", - "react_compiler_hir", - "serde_json", - "sha2", -] - -[[package]] -name = "react_compiler_ssa" -version = "0.1.0" -dependencies = [ - "indexmap", - "react_compiler_diagnostics", - "react_compiler_hir", - "react_compiler_lowering", -] - -[[package]] -name = "react_compiler_typeinference" -version = "0.1.0" -dependencies = [ - "react_compiler_diagnostics", - "react_compiler_hir", - "react_compiler_ssa", -] - -[[package]] -name = "react_compiler_utils" -version = "0.1.0" -dependencies = [ - "indexmap", -] - -[[package]] -name = "react_compiler_validation" -version = "0.1.0" -dependencies = [ - "indexmap", - "react_compiler_diagnostics", - "react_compiler_hir", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-transcode" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "similar" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml deleted file mode 100644 index 9bd5cf8837c3..000000000000 --- a/compiler/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[workspace] -members = [ - "crates/*", - "packages/babel-plugin-react-compiler-rust/native", -] -resolver = "3" - -# Sizes the shipped napi binary (index.node). Measured on arm64 macOS: -# default release is 11.2MB; fat LTO + one codegen unit + stripping symbols -# lands at 7.2MB with no runtime cost. Release builds get slower; debug -# builds (snap --rust, the e2e harness) are unaffected. -[profile.release] -lto = "fat" -codegen-units = 1 -strip = true - diff --git a/compiler/apps/playground/lib/compilation.ts b/compiler/apps/playground/lib/compilation.ts index 59658c8376c0..b2bee8bd66d4 100644 --- a/compiler/apps/playground/lib/compilation.ts +++ b/compiler/apps/playground/lib/compilation.ts @@ -16,8 +16,6 @@ import BabelPluginReactCompiler, { ErrorCategory, parseConfigPragmaForTests, ValueKind, - type CompilerDiagnosticDetail, - type CompilerErrorDetailOptions, type Hook, PluginOptions, CompilerPipelineValue, @@ -34,46 +32,6 @@ import type { PrintedCompilerPipelineValue, } from '../components/Editor/Output'; -type LoggedCompileErrorDetail = Extract< - LoggerEvent, - {kind: 'CompileError'} ->['detail']; - -/** - * logEvent() emits error details as plain objects (normalized for parity with - * the Rust compiler's logger output), not class instances. Rehydrate them into - * CompilerDiagnostic / CompilerErrorDetail so downstream consumers (error - * printing, Monaco diagnostics) can call methods like printErrorMessage(). - */ -function rehydrateLoggedDetail( - detail: LoggedCompileErrorDetail, -): CompilerErrorDetail | CompilerDiagnostic { - const category = detail.category as ErrorCategory; - const suggestions = - (detail.suggestions as CompilerErrorDetailOptions['suggestions']) ?? null; - if (detail.details != null) { - return new CompilerDiagnostic({ - category, - reason: detail.reason, - description: detail.description, - suggestions, - details: detail.details.map((d): CompilerDiagnosticDetail => { - if (d.kind === 'hint') { - return {kind: 'hint', message: d.message ?? ''}; - } - return {kind: 'error', loc: d.loc, message: d.message}; - }), - }); - } - return new CompilerErrorDetail({ - category, - reason: detail.reason, - description: detail.description, - loc: detail.loc ?? null, - suggestions, - }); -} - function parseInput( input: string, language: 'flow' | 'typescript', @@ -306,7 +264,7 @@ export function compile( debugLogIRs: logIR, logEvent: (_filename: string | null, event: LoggerEvent): void => { if (event.kind === 'CompileError') { - otherErrors.push(rehydrateLoggedDetail(event.detail)); + otherErrors.push(event.detail); } }, }, diff --git a/compiler/apps/playground/playwright.config.js b/compiler/apps/playground/playwright.config.js index 6f52840ea66c..10de19457ff0 100644 --- a/compiler/apps/playground/playwright.config.js +++ b/compiler/apps/playground/playwright.config.js @@ -23,7 +23,7 @@ export default defineConfig({ // Test directory testDir: path.join(__dirname, '__tests__/e2e'), // If a test fails, retry it additional 2 times - retries: 5, + retries: 3, // Artifacts folder where screenshots, videos, and traces are stored. outputDir: 'test-results/', // Note: we only use text snapshots, so its safe to omit the host environment name diff --git a/compiler/crates/TODO.md b/compiler/crates/TODO.md deleted file mode 100644 index bcb68b3def7d..000000000000 --- a/compiler/crates/TODO.md +++ /dev/null @@ -1,198 +0,0 @@ -# Rust port: e2e parity TODO - -Status snapshot (after the current stack lands): - -| Variant | Score | Failures | -| ------- | ------------ | -------- | -| Babel | 1792 / 1802 | 10 | -| SWC | 1786 / 1802 | 16 | -| OXC | 1704 / 1795 | 91 | - -The corpus grew by the three `ts-*` module-interop fixtures (1799 → -1802 on this branch). The Babel/SWC rows are measured on this branch -and their failure sets are byte-identical to the pre-stack baseline; -the OXC row predates the fixtures and has not been re-measured. - -`cargo test --workspace`: 84 passed, 0 failed. - -## SWC - -(Historical, pre-ts-interop-stack triage on the old staging base; current -snapshot at top.) - -The 15 remaining SWC e2e failures fall into three groups. Each line names the -fixture and the failure mode; the group it sits in dictates the appropriate -fix. - -### Group A: Fixture maintenance, not Rust bugs - -SWC compiles code that TS rejects, or vice versa, in ways where Rust's -behavior is arguably correct. The fix is to rename the fixture (drop the -`error.` prefix) and update the `.expect.md` snapshot so the suite stops -asserting the TS-specific output. - -- `error.bug-invariant-local-or-context-references.js` — TS fires - `CompilerError::invariant` ("expected all references ... consistently - local or context"). Rust handles the same code without tripping the - invariant. -- `error.todo-jsx-intrinsic-tag-matches-local-binding.js` — SWC pipeline - emits a Todo bailout (`[hoisting] EnterSSA: Expected identifier to be - defined before being used`) that the Babel path does not. -- `error.todo-repro-named-function-with-shadowed-local-same-name.js` — - Babel errors; SWC compiles. -- `new-mutability/error.todo-repro-named-function-with-shadowed-local-same-name.js` - — same as above with the new mutation-aliasing model enabled. -- `error.todo-rust-as-expression-assignment-target.tsx` — Babel errors; - SWC compiles. -- `fbt/error.todo-locally-require-fbt.js` — Babel emits the - `Invariant: <fbt> tags should be module-level imports` shape; SWC emits - `Todo: Local variables named 'fbt' may conflict with the fbt plugin`. - Different categories, both reasonable. - -### Group B: External dependency - -- `use-no-forget-multiple-with-eslint-suppression.js` — spurious - `import { c as _c }` in the TS reference output. Fixed on `main` by - [react#36500](https://github.com/facebook/react/pull/36500) (merged). - Will pass automatically once `pr-36173` rebases onto `main`; until then - the TS dist built from `pr-36173` still emits the unused import. - -### Group C: Real SWC frontend bugs - -Each line names the failure mode and a sketch of where to look. - -- `fbt/fbt-param-with-quotes.js` — SWC codegen emits double quotes - (`"fbt"`) and reformats multi-line JSX into a single line; Babel uses - single quotes and preserves the source layout. Semantically equivalent - output; the fix is either an SWC codegen flag for quote style or a - post-emit pass. Low impact, high effort. - -- `lone-surrogate-string-values.js` — TS preserves lone surrogates - (`\uD83E`); SWC emits `\uFFFD` because `Wtf8Atom::to_string_lossy()` in - `react_compiler_swc/src/convert_ast.rs::wtf8_to_string` replaces invalid - UTF-8 sequences. Real WTF-8 handling work that touches every call site - using that helper. Probably needs to detect lone surrogates and emit - `\uXXXX` escapes before they hit `String`. - -- `many-scopes-no-stack-overflow.js` — TS memoizes the function - (`const $ = _c(401);` with 401 memo slots); SWC pipeline bails out and - returns the uncompiled source. The fixture exists to test that the - compiler handles many sequential reactive scopes without stack overflow, - so the SWC variant should compile. Root cause unclear — needs - investigation in the SWC pipeline or the compiler core to see where the - bail happens. - -- `pattern4_bare_type.js` — Two unrelated bugs in one fixture: - 1. Operator-precedence stripping. `Math.round((x - y) * 1000)` becomes - `Math.round(x - y * 1000)`. SWC codegen drops the parentheses around - the subtraction. Probably in `convert_ast_reverse.rs`'s - BinaryExpression handling. - 2. Method return type annotation. `formatMetrics(): Metrics` becomes - `formatMetrics()`. The TS-type-on-binding-ident fix in commit - cc1ba1e1 only covered binding identifiers; class method signatures - are a separate code path. Same shape of fix; different - `convert_binding_ident`-equivalent call site. - -- `reduce-reactive-deps/hoist-deps-diff-ssa-instance1.tsx` — - `(x as HasA).a.value + 2` becomes `(x as HasA.a.value) + 2`. The member - expression's property chain gets absorbed into the type annotation when - `convert_ast_reverse` emits the cast. Likely a parenthesization / - precedence bug in the reverse converter or the SWC printer's handling - of `TSAsExpression` as the object of a `MemberExpression`. - -- `todo-round2_unicode_string.js` (prefixed `todo-`) — Hex escape format - (`\xC5`) vs unicode escape (`\u00C5`) for bytes 0x80-0xFF. Both valid JS - literals; codegen format choice in SWC's string printer. - -- `todo-round3_promote_used_temps.js` (prefixed `todo-`) — Class body - codegen. TS emits the class with fields and constructor; SWC emits an - empty class body and pulls fields/methods out into separate assignments. - Likely an interaction between SWC codegen and the compiler's - `promote_used_temps` pass. - -- `ts-non-null-expression-default-value.tsx` — Generic type parameter - support. `const x: ReadonlyMap<string, string> = ...` becomes - `const x = ...` (annotation dropped entirely). Our - `convert_ts_type_to_json` helper in cc1ba1e1 explicitly guards against - `TsTypeRef` with `type_params` to avoid silently emitting - `ReadonlyMap` without the params. The proper fix needs serialization of - `TSTypeParameterInstantiation` in `convert_ast.rs` AND deserialization - in `convert_ast_reverse.rs::convert_ts_type_from_json`. - -## Cross-frontend: TypeScript module interop statements - -Three `ts-*` fixtures pin how TS module-interop statements -(`import x = require(...)`, `export = x`, `export as namespace X`) must -behave: the statement is preserved in output and the file's functions -still compile. - -- **Babel/NAPI** and **SWC** now preserve these end to end. Both flow - the statements through `Statement::Unknown` (the raw Babel-shaped - carrier in `react_compiler_ast`); the SWC frontend rebuilds the swc - module declarations in `convert_ast_reverse.rs` and works around an - upstream swc_ecma_codegen bug that prints `TsNamespaceExportDecl` - as `export = X` (`react_compiler_swc/src/ts_namespace_export_fixup.rs`, - which also carries the guard test that flags when the upstream fix - lands). Fixtures renamed from `todo-ts-*` to `ts-*`; the - `SproutTodoFilter` entry for the namespace fixture remains (sprout's - evaluator cannot evaluate `export as namespace`). -- **OXC** remains deferred: `todo!()` panics in - `react_compiler_oxc/src/convert_ast.rs` (arms - `TSImportEqualsDeclaration` / `TSExportAssignment` / - `TSNamespaceExportDeclaration`; the sibling `TSGlobalDeclaration` - arm is also unmodeled but unreachable from Babel-parsed fixtures, - which represent `declare global` as `TSModuleDeclaration`). - -- `ts-import-equals-declaration.ts` -- `ts-export-assignment.ts` -- `ts-namespace-export-declaration.ts` - -## Babel - -(Historical, pre-ts-interop-stack numbers; current snapshot at top.) - -**TODO: scope this out.** Babel is at 1788 / 1795 (7 failures). These have -been the baseline throughout the SWC parity stack and were not touched, so the -failure list is whatever was on `pr-36173` before this work landed. - -Next step is to enumerate the failures by fixture and bucket them the same -way as SWC (fixture maintenance / external dependency / real bugs). Run: - -```bash -bash compiler/scripts/test-e2e.sh --no-color --variant babel -``` - -…and triage the resulting failures into A/B/C groups under this section. - -## OXC - -(Historical, pre-ts-interop-stack numbers; current snapshot at top.) - -**TODO: scope this out.** OXC is at 1704 / 1795 (91 failures). The CLI -`filename` fix in commit c30f0d6f bumped this by +2 from the 1702 baseline, -but everything else is unaddressed. - -Next step is to enumerate failures and identify OXC-specific clusters -(likely AST conversion gaps in `react_compiler_oxc` analogous to the SWC -work in this stack). Run: - -```bash -bash compiler/scripts/test-e2e.sh --no-color --variant oxc -``` - -…and bucket the resulting failures into A/B/C groups under this section. -Expect significant overlap with the SWC Group C bugs (cast wrappers, -type annotations, UTF-16/WTF-8 handling) since both frontends share the -post-conversion pipeline. - -## How this stack got here - -(Historical, pre-ts-interop-stack numbers; current snapshot at top.) - -- `compiler/scripts/test-e2e.sh --variant swc` baseline was 1742 / 1795 - (53 failures) before this stack. -- 9 commits in the current stack reduce that to 1780 / 1795 (15 failures, - -38 fixtures, 72% reduction). -- Babel variant: 1788 / 1795 throughout (no regressions). -- OXC variant: 1702 → 1704 (the CLI filename commit also benefited OXC). -- `cargo test --workspace`: 56 passed, 0 failed throughout. diff --git a/compiler/crates/react_compiler/Cargo.toml b/compiler/crates/react_compiler/Cargo.toml deleted file mode 100644 index 3b265b8d359f..000000000000 --- a/compiler/crates/react_compiler/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "react_compiler" -version = "0.1.0" -edition = "2024" - -[dependencies] -react_compiler_ast = { path = "../react_compiler_ast" } -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" } -react_compiler_hir = { path = "../react_compiler_hir" } -react_compiler_inference = { path = "../react_compiler_inference" } -react_compiler_lowering = { path = "../react_compiler_lowering" } -react_compiler_optimization = { path = "../react_compiler_optimization" } -react_compiler_reactive_scopes = { path = "../react_compiler_reactive_scopes" } -react_compiler_ssa = { path = "../react_compiler_ssa" } -react_compiler_typeinference = { path = "../react_compiler_typeinference" } -react_compiler_validation = { path = "../react_compiler_validation" } -indexmap = "2" -serde = { version = "1", features = ["derive"] } -serde_json = { version = "1", features = ["raw_value"] } diff --git a/compiler/crates/react_compiler/src/debug_print.rs b/compiler/crates/react_compiler/src/debug_print.rs deleted file mode 100644 index 66bd4cc2bcf4..000000000000 --- a/compiler/crates/react_compiler/src/debug_print.rs +++ /dev/null @@ -1,734 +0,0 @@ -use react_compiler_diagnostics::CompilerError; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::print::{self, PrintFormatter}; -use react_compiler_hir::{ - BasicBlock, BlockId, HirFunction, Instruction, ParamPattern, Place, Terminal, -}; - -// ============================================================================= -// DebugPrinter struct — thin wrapper around PrintFormatter for HIR-specific logic -// ============================================================================= - -struct DebugPrinter<'a> { - fmt: PrintFormatter<'a>, -} - -impl<'a> DebugPrinter<'a> { - fn new(env: &'a Environment) -> Self { - Self { - fmt: PrintFormatter::new(env), - } - } - - // ========================================================================= - // Function - // ========================================================================= - - fn format_function(&mut self, func: &HirFunction) { - self.fmt.indent(); - self.fmt.line(&format!( - "id: {}", - match &func.id { - Some(id) => format!("\"{}\"", id), - None => "null".to_string(), - } - )); - self.fmt.line(&format!( - "name_hint: {}", - match &func.name_hint { - Some(h) => format!("\"{}\"", h), - None => "null".to_string(), - } - )); - self.fmt.line(&format!("fn_type: {:?}", func.fn_type)); - self.fmt.line(&format!("generator: {}", func.generator)); - self.fmt.line(&format!("is_async: {}", func.is_async)); - self.fmt - .line(&format!("loc: {}", print::format_loc(&func.loc))); - - // params - self.fmt.line("params:"); - self.fmt.indent(); - for (i, param) in func.params.iter().enumerate() { - match param { - ParamPattern::Place(place) => { - self.fmt.format_place_field(&format!("[{}]", i), place); - } - ParamPattern::Spread(spread) => { - self.fmt.line(&format!("[{}] Spread:", i)); - self.fmt.indent(); - self.fmt.format_place_field("place", &spread.place); - self.fmt.dedent(); - } - } - } - self.fmt.dedent(); - - // returns - self.fmt.line("returns:"); - self.fmt.indent(); - self.fmt.format_place_field("value", &func.returns); - self.fmt.dedent(); - - // context - self.fmt.line("context:"); - self.fmt.indent(); - for (i, place) in func.context.iter().enumerate() { - self.fmt.format_place_field(&format!("[{}]", i), place); - } - self.fmt.dedent(); - - // aliasing_effects - match &func.aliasing_effects { - Some(effects) => { - self.fmt.line("aliasingEffects:"); - self.fmt.indent(); - for (i, eff) in effects.iter().enumerate() { - self.fmt - .line(&format!("[{}] {}", i, self.fmt.format_effect(eff))); - } - self.fmt.dedent(); - } - None => self.fmt.line("aliasingEffects: null"), - } - - // directives - self.fmt.line("directives:"); - self.fmt.indent(); - for (i, d) in func.directives.iter().enumerate() { - self.fmt.line(&format!("[{}] \"{}\"", i, d)); - } - self.fmt.dedent(); - - // return_type_annotation - self.fmt.line(&format!( - "returnTypeAnnotation: {}", - match &func.return_type_annotation { - Some(ann) => ann.clone(), - None => "null".to_string(), - } - )); - - self.fmt.line(""); - self.fmt.line("Blocks:"); - self.fmt.indent(); - for (block_id, block) in &func.body.blocks { - self.format_block(block_id, block, &func.instructions); - } - self.fmt.dedent(); - self.fmt.dedent(); - } - - // ========================================================================= - // Block - // ========================================================================= - - fn format_block( - &mut self, - block_id: &BlockId, - block: &BasicBlock, - instructions: &[Instruction], - ) { - self.fmt - .line(&format!("bb{} ({}):", block_id.0, block.kind)); - self.fmt.indent(); - - // preds - let preds: Vec<String> = block.preds.iter().map(|p| format!("bb{}", p.0)).collect(); - self.fmt.line(&format!("preds: [{}]", preds.join(", "))); - - // phis - self.fmt.line("phis:"); - self.fmt.indent(); - for phi in &block.phis { - self.format_phi(phi); - } - self.fmt.dedent(); - - // instructions - self.fmt.line("instructions:"); - self.fmt.indent(); - for (index, instr_id) in block.instructions.iter().enumerate() { - let instr = &instructions[instr_id.0 as usize]; - self.format_instruction(instr, index); - } - self.fmt.dedent(); - - // terminal - self.fmt.line("terminal:"); - self.fmt.indent(); - self.format_terminal(&block.terminal); - self.fmt.dedent(); - - self.fmt.dedent(); - } - - // ========================================================================= - // Phi - // ========================================================================= - - fn format_phi(&mut self, phi: &react_compiler_hir::Phi) { - self.fmt.line("Phi {"); - self.fmt.indent(); - self.fmt.format_place_field("place", &phi.place); - self.fmt.line("operands:"); - self.fmt.indent(); - for (block_id, place) in &phi.operands { - self.fmt.line(&format!("bb{}:", block_id.0)); - self.fmt.indent(); - self.fmt.format_place_field("value", place); - self.fmt.dedent(); - } - self.fmt.dedent(); - self.fmt.dedent(); - self.fmt.line("}"); - } - - // ========================================================================= - // Instruction - // ========================================================================= - - fn format_instruction(&mut self, instr: &Instruction, index: usize) { - self.fmt.line(&format!("[{}] Instruction {{", index)); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", instr.id.0)); - self.fmt.format_place_field("lvalue", &instr.lvalue); - self.fmt.line("value:"); - self.fmt.indent(); - // For the HIR printer, inner functions are formatted via format_function - self.fmt.format_instruction_value( - &instr.value, - Some(&|fmt: &mut PrintFormatter, func: &HirFunction| { - // We need to recursively format the inner function - // Use a temporary DebugPrinter that shares the formatter state - let mut inner = DebugPrinter { - fmt: PrintFormatter { - env: fmt.env, - seen_identifiers: std::mem::take(&mut fmt.seen_identifiers), - seen_scopes: std::mem::take(&mut fmt.seen_scopes), - output: Vec::new(), - indent_level: fmt.indent_level, - }, - }; - inner.format_function(func); - // Write the output lines into the parent formatter - for line in &inner.fmt.output { - fmt.line_raw(line); - } - // Copy back the seen state - fmt.seen_identifiers = inner.fmt.seen_identifiers; - fmt.seen_scopes = inner.fmt.seen_scopes; - }), - ); - self.fmt.dedent(); - match &instr.effects { - Some(effects) => { - self.fmt.line("effects:"); - self.fmt.indent(); - for (i, eff) in effects.iter().enumerate() { - self.fmt - .line(&format!("[{}] {}", i, self.fmt.format_effect(eff))); - } - self.fmt.dedent(); - } - None => self.fmt.line("effects: null"), - } - self.fmt - .line(&format!("loc: {}", print::format_loc(&instr.loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - - // ========================================================================= - // Terminal - // ========================================================================= - - fn format_terminal(&mut self, terminal: &Terminal) { - match terminal { - Terminal::If { - test, - consequent, - alternate, - fallthrough, - id, - loc, - } => { - self.fmt.line("If {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.format_place_field("test", test); - self.fmt.line(&format!("consequent: bb{}", consequent.0)); - self.fmt.line(&format!("alternate: bb{}", alternate.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Branch { - test, - consequent, - alternate, - fallthrough, - id, - loc, - } => { - self.fmt.line("Branch {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.format_place_field("test", test); - self.fmt.line(&format!("consequent: bb{}", consequent.0)); - self.fmt.line(&format!("alternate: bb{}", alternate.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Logical { - operator, - test, - fallthrough, - id, - loc, - } => { - self.fmt.line("Logical {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("operator: \"{}\"", operator)); - self.fmt.line(&format!("test: bb{}", test.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Ternary { - test, - fallthrough, - id, - loc, - } => { - self.fmt.line("Ternary {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("test: bb{}", test.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Optional { - optional, - test, - fallthrough, - id, - loc, - } => { - self.fmt.line("Optional {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("optional: {}", optional)); - self.fmt.line(&format!("test: bb{}", test.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Throw { value, id, loc } => { - self.fmt.line("Throw {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.format_place_field("value", value); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Return { - value, - return_variant, - id, - loc, - effects, - } => { - self.fmt.line("Return {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt - .line(&format!("returnVariant: {:?}", return_variant)); - self.fmt.format_place_field("value", value); - match effects { - Some(e) => { - self.fmt.line("effects:"); - self.fmt.indent(); - for (i, eff) in e.iter().enumerate() { - self.fmt - .line(&format!("[{}] {}", i, self.fmt.format_effect(eff))); - } - self.fmt.dedent(); - } - None => self.fmt.line("effects: null"), - } - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Goto { - block, - variant, - id, - loc, - } => { - self.fmt.line("Goto {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("block: bb{}", block.0)); - self.fmt.line(&format!("variant: {:?}", variant)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Switch { - test, - cases, - fallthrough, - id, - loc, - } => { - self.fmt.line("Switch {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.format_place_field("test", test); - self.fmt.line("cases:"); - self.fmt.indent(); - for (i, case) in cases.iter().enumerate() { - match &case.test { - Some(p) => { - self.fmt.line(&format!("[{}] Case {{", i)); - self.fmt.indent(); - self.fmt.format_place_field("test", p); - self.fmt.line(&format!("block: bb{}", case.block.0)); - self.fmt.dedent(); - self.fmt.line("}"); - } - None => { - self.fmt - .line(&format!("[{}] Default {{ block: bb{} }}", i, case.block.0)); - } - } - } - self.fmt.dedent(); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::DoWhile { - loop_block, - test, - fallthrough, - id, - loc, - } => { - self.fmt.line("DoWhile {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loop: bb{}", loop_block.0)); - self.fmt.line(&format!("test: bb{}", test.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::While { - test, - loop_block, - fallthrough, - id, - loc, - } => { - self.fmt.line("While {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("test: bb{}", test.0)); - self.fmt.line(&format!("loop: bb{}", loop_block.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::For { - init, - test, - update, - loop_block, - fallthrough, - id, - loc, - } => { - self.fmt.line("For {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("init: bb{}", init.0)); - self.fmt.line(&format!("test: bb{}", test.0)); - self.fmt.line(&format!( - "update: {}", - match update { - Some(u) => format!("bb{}", u.0), - None => "null".to_string(), - } - )); - self.fmt.line(&format!("loop: bb{}", loop_block.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::ForOf { - init, - test, - loop_block, - fallthrough, - id, - loc, - } => { - self.fmt.line("ForOf {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("init: bb{}", init.0)); - self.fmt.line(&format!("test: bb{}", test.0)); - self.fmt.line(&format!("loop: bb{}", loop_block.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::ForIn { - init, - loop_block, - fallthrough, - id, - loc, - } => { - self.fmt.line("ForIn {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("init: bb{}", init.0)); - self.fmt.line(&format!("loop: bb{}", loop_block.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Label { - block, - fallthrough, - id, - loc, - } => { - self.fmt.line("Label {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("block: bb{}", block.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Sequence { - block, - fallthrough, - id, - loc, - } => { - self.fmt.line("Sequence {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("block: bb{}", block.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Unreachable { id, loc } => { - self.fmt.line(&format!( - "Unreachable {{ id: {}, loc: {} }}", - id.0, - print::format_loc(loc) - )); - } - Terminal::Unsupported { id, loc } => { - self.fmt.line(&format!( - "Unsupported {{ id: {}, loc: {} }}", - id.0, - print::format_loc(loc) - )); - } - Terminal::MaybeThrow { - continuation, - handler, - id, - loc, - effects, - } => { - self.fmt.line("MaybeThrow {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt - .line(&format!("continuation: bb{}", continuation.0)); - self.fmt.line(&format!( - "handler: {}", - match handler { - Some(h) => format!("bb{}", h.0), - None => "null".to_string(), - } - )); - match effects { - Some(e) => { - self.fmt.line("effects:"); - self.fmt.indent(); - for (i, eff) in e.iter().enumerate() { - self.fmt - .line(&format!("[{}] {}", i, self.fmt.format_effect(eff))); - } - self.fmt.dedent(); - } - None => self.fmt.line("effects: null"), - } - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Scope { - fallthrough, - block, - scope, - id, - loc, - } => { - self.fmt.line("Scope {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.format_scope_field("scope", *scope); - self.fmt.line(&format!("block: bb{}", block.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::PrunedScope { - fallthrough, - block, - scope, - id, - loc, - } => { - self.fmt.line("PrunedScope {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.format_scope_field("scope", *scope); - self.fmt.line(&format!("block: bb{}", block.0)); - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - Terminal::Try { - block, - handler_binding, - handler, - fallthrough, - id, - loc, - } => { - self.fmt.line("Try {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("block: bb{}", block.0)); - self.fmt.line(&format!("handler: bb{}", handler.0)); - match handler_binding { - Some(p) => self.fmt.format_place_field("handlerBinding", p), - None => self.fmt.line("handlerBinding: null"), - } - self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - } - } -} - -// ============================================================================= -// Entry point -// ============================================================================= - -pub fn debug_hir(hir: &HirFunction, env: &Environment) -> String { - let mut printer = DebugPrinter::new(env); - printer.format_function(hir); - - // Print outlined functions (matches TS DebugPrintHIR.ts: printDebugHIR) - for outlined in env.get_outlined_functions() { - printer.fmt.line(""); - printer.format_function(&outlined.func); - } - - printer.fmt.line(""); - printer.fmt.line("Environment:"); - printer.fmt.indent(); - printer.fmt.format_errors(&env.errors); - printer.fmt.dedent(); - - printer.fmt.to_string_output() -} - -// ============================================================================= -// Error formatting (kept for backward compatibility) -// ============================================================================= - -pub fn format_errors(error: &CompilerError) -> String { - let env = Environment::new(); - let mut fmt = PrintFormatter::new(&env); - fmt.format_errors(error); - fmt.to_string_output() -} - -/// Format an HIR function into a reactive PrintFormatter. -/// This bridges the two debug printers so inner functions in FunctionExpression/ObjectMethod -/// can be printed within the reactive function output. -pub fn format_hir_function_into(reactive_fmt: &mut PrintFormatter, func: &HirFunction) { - // Create a temporary DebugPrinter that shares the same environment - let mut printer = DebugPrinter { - fmt: PrintFormatter { - env: reactive_fmt.env, - seen_identifiers: std::mem::take(&mut reactive_fmt.seen_identifiers), - seen_scopes: std::mem::take(&mut reactive_fmt.seen_scopes), - output: Vec::new(), - indent_level: reactive_fmt.indent_level, - }, - }; - printer.format_function(func); - - // Write the output lines into the reactive formatter - for line in &printer.fmt.output { - reactive_fmt.line_raw(line); - } - // Copy back the seen state - reactive_fmt.seen_identifiers = printer.fmt.seen_identifiers; - reactive_fmt.seen_scopes = printer.fmt.seen_scopes; -} - -// ============================================================================= -// Helpers for effect formatting (kept for backward compatibility) -// ============================================================================= - -#[allow(dead_code)] -fn format_place_short(place: &Place, env: &Environment) -> String { - let ident = &env.identifiers[place.identifier.0 as usize]; - let name = match &ident.name { - Some(name) => name.value().to_string(), - None => String::new(), - }; - let scope = match ident.scope { - Some(scope_id) => format!(":{}", scope_id.0), - None => String::new(), - }; - format!("{}${}{}", name, place.identifier.0, scope) -} diff --git a/compiler/crates/react_compiler/src/entrypoint/compile_result.rs b/compiler/crates/react_compiler/src/entrypoint/compile_result.rs deleted file mode 100644 index 4b74e5364783..000000000000 --- a/compiler/crates/react_compiler/src/entrypoint/compile_result.rs +++ /dev/null @@ -1,296 +0,0 @@ -use react_compiler_ast::File; -use react_compiler_ast::expressions::Identifier as AstIdentifier; -use react_compiler_ast::patterns::PatternLike; -use react_compiler_ast::statements::BlockStatement; -use react_compiler_diagnostics::SourceLocation; -use react_compiler_hir::ReactFunctionType; -use serde::Serialize; - -use crate::timing::TimingEntry; - -/// Source location with index and filename fields for logger event serialization. -/// Matches the Babel SourceLocation format that the TS compiler emits in logger events. -#[derive(Debug, Clone, Serialize)] -pub struct LoggerSourceLocation { - pub start: LoggerPosition, - pub end: LoggerPosition, - #[serde(skip_serializing_if = "Option::is_none")] - pub filename: Option<String>, - #[serde(rename = "identifierName", skip_serializing_if = "Option::is_none")] - pub identifier_name: Option<String>, -} - -#[derive(Debug, Clone, Serialize)] -pub struct LoggerPosition { - pub line: u32, - pub column: u32, - #[serde(skip_serializing_if = "Option::is_none")] - pub index: Option<u32>, -} - -impl LoggerSourceLocation { - /// Create from a diagnostics SourceLocation, adding index and filename. - pub fn from_loc( - loc: &SourceLocation, - filename: Option<&str>, - start_index: Option<u32>, - end_index: Option<u32>, - ) -> Self { - Self { - start: LoggerPosition { - line: loc.start.line, - column: loc.start.column, - index: start_index, - }, - end: LoggerPosition { - line: loc.end.line, - column: loc.end.column, - index: end_index, - }, - filename: filename.map(|s| s.to_string()), - identifier_name: None, - } - } - - /// Create from a diagnostics SourceLocation without index or filename. - pub fn from_loc_simple(loc: &SourceLocation) -> Self { - Self { - start: LoggerPosition { - line: loc.start.line, - column: loc.start.column, - index: None, - }, - end: LoggerPosition { - line: loc.end.line, - column: loc.end.column, - index: None, - }, - filename: None, - identifier_name: None, - } - } -} - -/// A variable rename from lowering, serialized for the JS shim. -#[derive(Debug, Clone, Serialize)] -pub struct BindingRenameInfo { - pub original: String, - pub renamed: String, - #[serde(rename = "declarationStart")] - pub declaration_start: u32, -} - -/// Main result type returned by the compile function. -/// Serialized to JSON and returned to the JS shim. -#[derive(Debug, Serialize)] -#[serde(tag = "kind", rename_all = "lowercase")] -pub enum CompileResult { - /// Compilation succeeded (or no functions needed compilation). - /// `ast` is None if no changes were made to the program. - /// The compiled Babel AST is returned by value so in-process Rust consumers - /// (the oxc/swc frontends) use it directly instead of round-tripping through - /// JSON. CompileResult still derives Serialize, so the napi consumer - /// serializes the whole result (inlining the File) as before. - Success { - ast: Option<File>, - events: Vec<LoggerEvent>, - /// Unified ordered log interleaving events and debug entries. - /// Items appear in the order they were emitted during compilation. - /// The JS side uses this as the single source of truth (preferred over - /// separate events/debugLogs arrays). - #[serde(rename = "orderedLog", skip_serializing_if = "Vec::is_empty")] - ordered_log: Vec<OrderedLogItem>, - /// Variable renames from lowering, for applying back to the Babel AST. - /// Each entry maps an original binding name to its renamed version, - /// identified by the binding's declaration start position in the source. - #[serde(skip_serializing_if = "Vec::is_empty")] - renames: Vec<BindingRenameInfo>, - /// Timing data for profiling. Only populated when __profiling is enabled. - #[serde(skip_serializing_if = "Vec::is_empty")] - timing: Vec<TimingEntry>, - }, - /// A fatal error occurred and panicThreshold dictates it should throw. - Error { - error: CompilerErrorInfo, - events: Vec<LoggerEvent>, - #[serde(rename = "orderedLog", skip_serializing_if = "Vec::is_empty")] - ordered_log: Vec<OrderedLogItem>, - /// Timing data for profiling. Only populated when __profiling is enabled. - #[serde(skip_serializing_if = "Vec::is_empty")] - timing: Vec<TimingEntry>, - }, -} - -/// An item in the ordered log, which can be either a logger event or a debug entry. -#[derive(Debug, Clone, Serialize)] -#[serde(tag = "type", rename_all = "camelCase")] -pub enum OrderedLogItem { - Event { event: LoggerEvent }, - Debug { entry: DebugLogEntry }, -} - -/// Structured error information for the JS shim. -#[derive(Debug, Clone, Serialize)] -pub struct CompilerErrorInfo { - pub reason: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option<String>, - pub details: Vec<CompilerErrorDetailInfo>, - /// When set, the JS shim should throw an Error with this exact message - /// instead of formatting through formatCompilerError(). This is used - /// for simulated unknown exceptions (throwUnknownException__testonly) - /// which in the TS compiler are plain Error objects, not CompilerErrors. - #[serde(rename = "rawMessage", skip_serializing_if = "Option::is_none")] - pub raw_message: Option<String>, - /// Pre-formatted error message produced by Rust, matching the JS - /// formatCompilerError() output. When present, the JS shim uses this - /// directly instead of calling formatCompilerError() on the JS side. - #[serde(rename = "formattedMessage", skip_serializing_if = "Option::is_none")] - pub formatted_message: Option<String>, -} - -/// Serializable error detail — flat plain object matching the TS -/// `formatDetailForLogging()` output. All fields are direct properties. -#[derive(Debug, Clone, Serialize)] -pub struct CompilerErrorDetailInfo { - pub category: String, - pub reason: String, - pub description: Option<String>, - pub severity: String, - pub suggestions: Option<Vec<LoggerSuggestionInfo>>, - #[serde(skip_serializing_if = "Option::is_none")] - pub details: Option<Vec<CompilerErrorItemInfo>>, - #[serde(skip_serializing_if = "Option::is_none")] - pub loc: Option<LoggerSourceLocation>, -} - -/// Serializable suggestion info for logger events. -#[derive(Debug, Clone, Serialize)] -pub struct LoggerSuggestionInfo { - pub description: String, - pub op: LoggerSuggestionOp, - pub range: (usize, usize), - #[serde(skip_serializing_if = "Option::is_none")] - pub text: Option<String>, -} - -/// Numeric enum matching TS `CompilerSuggestionOperation`. -#[derive(Debug, Clone, Copy)] -pub enum LoggerSuggestionOp { - InsertBefore = 0, - InsertAfter = 1, - Remove = 2, - Replace = 3, -} - -impl serde::Serialize for LoggerSuggestionOp { - fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { - serializer.serialize_u8(*self as u8) - } -} - -/// Individual error or hint item within a CompilerErrorDetailInfo. -#[derive(Debug, Clone, Serialize)] -pub struct CompilerErrorItemInfo { - pub kind: String, - pub loc: Option<LoggerSourceLocation>, - /// Serialized as `null` when None (not omitted), matching TS behavior. - pub message: Option<String>, -} - -/// Debug log entry for debugLogIRs support. -/// Currently only supports the 'debug' variant (string values). -#[derive(Debug, Clone, Serialize)] -pub struct DebugLogEntry { - pub kind: &'static str, - pub name: String, - pub value: String, -} - -impl DebugLogEntry { - pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self { - Self { - kind: "debug", - name: name.into(), - value: value.into(), - } - } -} - -/// Codegen output for a single compiled function. -/// Carries the generated AST fields needed to replace the original function. -#[derive(Debug, Clone)] -pub struct CodegenFunction { - pub loc: Option<SourceLocation>, - pub id: Option<AstIdentifier>, - pub name_hint: Option<String>, - pub params: Vec<PatternLike>, - pub body: BlockStatement, - pub generator: bool, - pub is_async: bool, - pub memo_slots_used: u32, - pub memo_blocks: u32, - pub memo_values: u32, - pub pruned_memo_blocks: u32, - pub pruned_memo_values: u32, - pub outlined: Vec<OutlinedFunction>, -} - -/// An outlined function extracted during compilation. -#[derive(Debug, Clone)] -pub struct OutlinedFunction { - pub func: CodegenFunction, - pub fn_type: Option<ReactFunctionType>, -} - -/// Logger events emitted during compilation. -/// These are returned to JS for the logger callback. -#[derive(Debug, Clone, Serialize)] -#[serde(tag = "kind")] -pub enum LoggerEvent { - CompileSuccess { - #[serde(rename = "fnLoc")] - fn_loc: Option<LoggerSourceLocation>, - #[serde(rename = "fnName")] - fn_name: Option<String>, - #[serde(rename = "memoSlots")] - memo_slots: u32, - #[serde(rename = "memoBlocks")] - memo_blocks: u32, - #[serde(rename = "memoValues")] - memo_values: u32, - #[serde(rename = "prunedMemoBlocks")] - pruned_memo_blocks: u32, - #[serde(rename = "prunedMemoValues")] - pruned_memo_values: u32, - }, - CompileError { - detail: CompilerErrorDetailInfo, - #[serde(rename = "fnLoc")] - fn_loc: Option<LoggerSourceLocation>, - }, - /// Same as CompileError but serializes fnLoc before detail (matching TS program.ts output) - #[serde(rename = "CompileError")] - CompileErrorWithLoc { - #[serde(rename = "fnLoc")] - fn_loc: LoggerSourceLocation, - detail: CompilerErrorDetailInfo, - }, - CompileSkip { - #[serde(rename = "fnLoc")] - fn_loc: Option<LoggerSourceLocation>, - reason: String, - #[serde(skip_serializing_if = "Option::is_none")] - loc: Option<LoggerSourceLocation>, - }, - CompileUnexpectedThrow { - #[serde(rename = "fnLoc")] - fn_loc: Option<LoggerSourceLocation>, - data: String, - }, - PipelineError { - #[serde(rename = "fnLoc")] - fn_loc: Option<LoggerSourceLocation>, - data: String, - }, -} diff --git a/compiler/crates/react_compiler/src/entrypoint/gating.rs b/compiler/crates/react_compiler/src/entrypoint/gating.rs deleted file mode 100644 index ed1ee7dc232c..000000000000 --- a/compiler/crates/react_compiler/src/entrypoint/gating.rs +++ /dev/null @@ -1,581 +0,0 @@ -// Gating rewrite logic for compiled functions. -// -// When gating is enabled, the compiled function is wrapped in a conditional: -// `gating() ? optimized_fn : original_fn` -// -// For function declarations referenced before their declaration, a special -// hoisting pattern is used (see `insert_additional_function_declaration`). -// -// Ported from `Entrypoint/Gating.ts`. - -use react_compiler_ast::common::BaseNode; -use react_compiler_ast::expressions::*; -use react_compiler_ast::patterns::PatternLike; -use react_compiler_ast::statements::*; -use react_compiler_diagnostics::CompilerDiagnostic; -use react_compiler_diagnostics::ErrorCategory; - -use super::imports::ProgramContext; -use super::plugin_options::GatingConfig; - -/// A compiled function node, can be any function type. -#[derive(Debug, Clone)] -pub enum CompiledFunctionNode { - FunctionDeclaration(FunctionDeclaration), - FunctionExpression(FunctionExpression), - ArrowFunctionExpression(ArrowFunctionExpression), -} - -/// Represents a compiled function that needs gating. -/// In the Rust version, we work with indices into the program body -/// rather than Babel paths. -pub struct GatingRewrite { - /// Index in program.body where the original function is - pub original_index: usize, - /// The compiled function AST node - pub compiled_fn: CompiledFunctionNode, - /// The gating config - pub gating: GatingConfig, - /// Whether the function is referenced before its declaration at top level - pub referenced_before_declared: bool, - /// Whether the parent statement is an ExportDefaultDeclaration - pub is_export_default: bool, -} - -/// Apply gating rewrites to the program. -/// This modifies program.body by replacing/inserting statements. -/// -/// Corresponds to `insertGatedFunctionDeclaration` in the TS version, -/// but batched: all rewrites are collected first, then applied in reverse -/// index order to maintain validity of earlier indices. -pub fn apply_gating_rewrites( - program: &mut react_compiler_ast::Program, - mut rewrites: Vec<GatingRewrite>, - context: &mut ProgramContext, -) -> Result<(), CompilerDiagnostic> { - // Sort rewrites in reverse order by original_index so that insertions - // at higher indices don't invalidate lower indices. - rewrites.sort_by(|a, b| b.original_index.cmp(&a.original_index)); - - for rewrite in rewrites { - let gating_imported_name = context - .add_import_specifier( - &rewrite.gating.source, - &rewrite.gating.import_specifier_name, - None, - ) - .name - .clone(); - - if rewrite.referenced_before_declared { - // The referenced-before-declared case only applies to FunctionDeclarations - if let CompiledFunctionNode::FunctionDeclaration(compiled) = rewrite.compiled_fn { - insert_additional_function_declaration( - &mut program.body, - rewrite.original_index, - compiled, - context, - &gating_imported_name, - )?; - } else { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected compiled node type to match input type: \ - got non-FunctionDeclaration but expected FunctionDeclaration", - None, - )); - } - } else { - let original_stmt = program.body[rewrite.original_index].clone(); - let original_fn = extract_function_node_from_stmt(&original_stmt)?; - - let gating_expression = - build_gating_expression(rewrite.compiled_fn, original_fn, &gating_imported_name); - - // Determine how to rewrite based on context - if !rewrite.is_export_default { - if let Some(fn_name) = get_fn_decl_name(&original_stmt) { - // Convert function declaration to: const fnName = gating() ? compiled : original - let var_decl = Statement::VariableDeclaration(VariableDeclaration { - base: BaseNode::default(), - declarations: vec![VariableDeclarator { - base: BaseNode::default(), - id: PatternLike::Identifier(make_identifier(&fn_name)), - init: Some(Box::new(gating_expression)), - definite: None, - }], - kind: VariableDeclarationKind::Const, - declare: None, - }); - program.body[rewrite.original_index] = var_decl; - } else { - // Replace with the conditional expression directly (e.g. arrow/expression) - let expr_stmt = Statement::ExpressionStatement(ExpressionStatement { - base: BaseNode::default(), - expression: Box::new(gating_expression), - }); - program.body[rewrite.original_index] = expr_stmt; - } - } else { - // ExportDefaultDeclaration case - if let Some(fn_name) = get_fn_decl_name_from_export_default(&original_stmt) { - // Named export default function: replace with const + re-export - // const fnName = gating() ? compiled : original; - // export default fnName; - let var_decl = Statement::VariableDeclaration(VariableDeclaration { - base: BaseNode::default(), - declarations: vec![VariableDeclarator { - base: BaseNode::default(), - id: PatternLike::Identifier(make_identifier(&fn_name)), - init: Some(Box::new(gating_expression)), - definite: None, - }], - kind: VariableDeclarationKind::Const, - declare: None, - }); - let re_export = Statement::ExportDefaultDeclaration( - react_compiler_ast::declarations::ExportDefaultDeclaration { - base: BaseNode::default(), - declaration: Box::new( - react_compiler_ast::declarations::ExportDefaultDecl::Expression( - Box::new(Expression::Identifier(make_identifier(&fn_name))), - ), - ), - export_kind: None, - }, - ); - // Replace the original statement with the var decl, then insert re-export after - program.body[rewrite.original_index] = var_decl; - program.body.insert(rewrite.original_index + 1, re_export); - } else { - // Anonymous export default or arrow: replace the declaration content - // with the conditional expression - let export_default = Statement::ExportDefaultDeclaration( - react_compiler_ast::declarations::ExportDefaultDeclaration { - base: BaseNode::default(), - declaration: Box::new( - react_compiler_ast::declarations::ExportDefaultDecl::Expression( - Box::new(gating_expression), - ), - ), - export_kind: None, - }, - ); - program.body[rewrite.original_index] = export_default; - } - } - } - } - Ok(()) -} - -/// Gating rewrite for function declarations which are referenced before their -/// declaration site. -/// -/// ```js -/// // original -/// export default React.memo(Foo); -/// function Foo() { ... } -/// -/// // React compiler optimized + gated -/// import {gating} from 'myGating'; -/// export default React.memo(Foo); -/// const gating_result = gating(); // <- inserted -/// function Foo_optimized() {} // <- inserted -/// function Foo_unoptimized() {} // <- renamed from Foo -/// function Foo() { // <- inserted, hoistable by JS engines -/// if (gating_result) return Foo_optimized(); -/// else return Foo_unoptimized(); -/// } -/// ``` -fn insert_additional_function_declaration( - body: &mut Vec<Statement>, - original_index: usize, - mut compiled: FunctionDeclaration, - context: &mut ProgramContext, - gating_function_identifier_name: &str, -) -> Result<(), CompilerDiagnostic> { - // Extract the original function declaration from body - let original_fn = match &body[original_index] { - Statement::FunctionDeclaration(fd) => fd.clone(), - Statement::ExportNamedDeclaration(end) => { - if let Some(decl) = &end.declaration { - if let react_compiler_ast::declarations::Declaration::FunctionDeclaration(fd) = - decl.as_ref() - { - fd.clone() - } else { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected function declaration in export", - None, - )); - } - } else { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected declaration in export", - None, - )); - } - } - _ => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected function declaration at original_index", - None, - )); - } - }; - - let original_fn_name = original_fn - .id - .as_ref() - .expect("Expected function declaration referenced elsewhere to have a named identifier"); - let compiled_id = compiled - .id - .as_ref() - .expect("Expected compiled function declaration to have a named identifier"); - assert_eq!( - original_fn.params.len(), - compiled.params.len(), - "Expected compiled function to have the same number of parameters as source" - ); - - let _ = compiled_id; // used above for the assert - - // Generate unique names - let gating_condition_name = - context.new_uid(&format!("{}_result", gating_function_identifier_name)); - let unoptimized_fn_name = context.new_uid(&format!("{}_unoptimized", original_fn_name.name)); - let optimized_fn_name = context.new_uid(&format!("{}_optimized", original_fn_name.name)); - - // Step 1: rename existing functions - compiled.id = Some(make_identifier(&optimized_fn_name)); - - // Rename the original function in-place to *_unoptimized - rename_fn_decl_at(body, original_index, &unoptimized_fn_name)?; - - // Step 2: build new params and args for the dispatcher function - let mut new_params: Vec<PatternLike> = Vec::new(); - let mut new_args_optimized: Vec<Expression> = Vec::new(); - let mut new_args_unoptimized: Vec<Expression> = Vec::new(); - - for (i, param) in original_fn.params.iter().enumerate() { - let arg_name = format!("arg{}", i); - match param { - PatternLike::RestElement(_) => { - new_params.push(PatternLike::RestElement( - react_compiler_ast::patterns::RestElement { - base: BaseNode::default(), - argument: Box::new(PatternLike::Identifier(make_identifier(&arg_name))), - type_annotation: None, - decorators: None, - }, - )); - new_args_optimized.push(Expression::SpreadElement(SpreadElement { - base: BaseNode::default(), - argument: Box::new(Expression::Identifier(make_identifier(&arg_name))), - })); - new_args_unoptimized.push(Expression::SpreadElement(SpreadElement { - base: BaseNode::default(), - argument: Box::new(Expression::Identifier(make_identifier(&arg_name))), - })); - } - _ => { - new_params.push(PatternLike::Identifier(make_identifier(&arg_name))); - new_args_optimized.push(Expression::Identifier(make_identifier(&arg_name))); - new_args_unoptimized.push(Expression::Identifier(make_identifier(&arg_name))); - } - } - } - - // Build the dispatcher function: - // function Foo(...args) { - // if (gating_result) return Foo_optimized(...args); - // else return Foo_unoptimized(...args); - // } - let dispatcher_fn = Statement::FunctionDeclaration(FunctionDeclaration { - base: BaseNode::default(), - id: Some(make_identifier(&original_fn_name.name)), - params: new_params, - body: BlockStatement { - base: BaseNode::default(), - body: vec![Statement::IfStatement(IfStatement { - base: BaseNode::default(), - test: Box::new(Expression::Identifier(make_identifier( - &gating_condition_name, - ))), - consequent: Box::new(Statement::ReturnStatement(ReturnStatement { - base: BaseNode::default(), - argument: Some(Box::new(Expression::CallExpression(CallExpression { - base: BaseNode::default(), - callee: Box::new(Expression::Identifier(make_identifier( - &optimized_fn_name, - ))), - arguments: new_args_optimized, - type_parameters: None, - type_arguments: None, - optional: None, - }))), - })), - alternate: Some(Box::new(Statement::ReturnStatement(ReturnStatement { - base: BaseNode::default(), - argument: Some(Box::new(Expression::CallExpression(CallExpression { - base: BaseNode::default(), - callee: Box::new(Expression::Identifier(make_identifier( - &unoptimized_fn_name, - ))), - arguments: new_args_unoptimized, - type_parameters: None, - type_arguments: None, - optional: None, - }))), - }))), - })], - directives: vec![], - }, - generator: false, - is_async: false, - declare: None, - return_type: None, - type_parameters: None, - predicate: None, - component_declaration: false, - hook_declaration: false, - }); - - // Build: const gating_result = gating(); - let gating_const = Statement::VariableDeclaration(VariableDeclaration { - base: BaseNode::default(), - declarations: vec![VariableDeclarator { - base: BaseNode::default(), - id: PatternLike::Identifier(make_identifier(&gating_condition_name)), - init: Some(Box::new(Expression::CallExpression(CallExpression { - base: BaseNode::default(), - callee: Box::new(Expression::Identifier(make_identifier( - gating_function_identifier_name, - ))), - arguments: vec![], - type_parameters: None, - type_arguments: None, - optional: None, - }))), - definite: None, - }], - kind: VariableDeclarationKind::Const, - declare: None, - }); - - // Build: the compiled (optimized) function declaration - let compiled_stmt = Statement::FunctionDeclaration(compiled); - - // Insert statements. In the TS version: - // fnPath.insertBefore(gating_const) - // fnPath.insertBefore(compiled) - // fnPath.insertAfter(dispatcher_fn) - // - // This means the final order is: - // [before original_index]: gating_const - // [before original_index]: compiled (optimized fn) - // [at original_index]: original fn (renamed to *_unoptimized) - // [after original_index]: dispatcher fn - // - // We insert in order: first the ones before, then the one after. - // Insert before original_index: gating_const, compiled - body.insert(original_index, compiled_stmt); - body.insert(original_index, gating_const); - // The original (now renamed) fn is now at original_index + 2 - // Insert dispatcher after it - body.insert(original_index + 3, dispatcher_fn); - Ok(()) -} - -/// Build a gating conditional expression: -/// `gating_fn() ? build_fn_expr(compiled) : build_fn_expr(original)` -fn build_gating_expression( - compiled: CompiledFunctionNode, - original: CompiledFunctionNode, - gating_name: &str, -) -> Expression { - Expression::ConditionalExpression(ConditionalExpression { - base: BaseNode::default(), - test: Box::new(Expression::CallExpression(CallExpression { - base: BaseNode::default(), - callee: Box::new(Expression::Identifier(make_identifier(gating_name))), - arguments: vec![], - type_parameters: None, - type_arguments: None, - optional: None, - })), - consequent: Box::new(build_function_expression(compiled)), - alternate: Box::new(build_function_expression(original)), - }) -} - -/// Convert a compiled function node to an expression. -/// Function declarations are converted to function expressions; -/// arrow functions and function expressions are returned as-is. -fn build_function_expression(node: CompiledFunctionNode) -> Expression { - match node { - CompiledFunctionNode::ArrowFunctionExpression(arrow) => { - Expression::ArrowFunctionExpression(arrow) - } - CompiledFunctionNode::FunctionExpression(func_expr) => { - Expression::FunctionExpression(func_expr) - } - CompiledFunctionNode::FunctionDeclaration(func_decl) => { - // Convert FunctionDeclaration to FunctionExpression - Expression::FunctionExpression(FunctionExpression { - base: func_decl.base, - params: func_decl.params, - body: func_decl.body, - id: func_decl.id, - generator: func_decl.generator, - is_async: func_decl.is_async, - return_type: func_decl.return_type, - type_parameters: func_decl.type_parameters, - predicate: func_decl.predicate, - }) - } - } -} - -/// Helper to create a simple Identifier with the given name and default BaseNode. -fn make_identifier(name: &str) -> Identifier { - Identifier { - base: BaseNode::default(), - name: name.to_string(), - type_annotation: None, - optional: None, - decorators: None, - } -} - -/// Extract the function name from a top-level Statement if it is a -/// FunctionDeclaration with an id. -fn get_fn_decl_name(stmt: &Statement) -> Option<String> { - match stmt { - Statement::FunctionDeclaration(fd) => fd.id.as_ref().map(|id| id.name.clone()), - _ => None, - } -} - -/// Extract the function name from an ExportDefaultDeclaration's declaration, -/// if it is a named FunctionDeclaration. -fn get_fn_decl_name_from_export_default(stmt: &Statement) -> Option<String> { - match stmt { - Statement::ExportDefaultDeclaration(ed) => match ed.declaration.as_ref() { - react_compiler_ast::declarations::ExportDefaultDecl::FunctionDeclaration(fd) => { - fd.id.as_ref().map(|id| id.name.clone()) - } - _ => None, - }, - _ => None, - } -} - -/// Extract a CompiledFunctionNode from a statement (for building the -/// "original" side of the gating expression). -fn extract_function_node_from_stmt( - stmt: &Statement, -) -> Result<CompiledFunctionNode, CompilerDiagnostic> { - match stmt { - Statement::FunctionDeclaration(fd) => { - Ok(CompiledFunctionNode::FunctionDeclaration(fd.clone())) - } - Statement::ExpressionStatement(es) => match es.expression.as_ref() { - Expression::ArrowFunctionExpression(arrow) => { - Ok(CompiledFunctionNode::ArrowFunctionExpression(arrow.clone())) - } - Expression::FunctionExpression(fe) => { - Ok(CompiledFunctionNode::FunctionExpression(fe.clone())) - } - _ => Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected function expression in expression statement for gating", - None, - )), - }, - Statement::ExportDefaultDeclaration(ed) => match ed.declaration.as_ref() { - react_compiler_ast::declarations::ExportDefaultDecl::FunctionDeclaration(fd) => { - Ok(CompiledFunctionNode::FunctionDeclaration(fd.clone())) - } - react_compiler_ast::declarations::ExportDefaultDecl::Expression(expr) => { - match expr.as_ref() { - Expression::ArrowFunctionExpression(arrow) => { - Ok(CompiledFunctionNode::ArrowFunctionExpression(arrow.clone())) - } - Expression::FunctionExpression(fe) => { - Ok(CompiledFunctionNode::FunctionExpression(fe.clone())) - } - _ => Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected function expression in export default for gating", - None, - )), - } - } - _ => Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected function in export default declaration for gating", - None, - )), - }, - Statement::VariableDeclaration(vd) => { - let init = vd.declarations[0] - .init - .as_ref() - .expect("Expected variable declarator to have an init for gating"); - match init.as_ref() { - Expression::ArrowFunctionExpression(arrow) => { - Ok(CompiledFunctionNode::ArrowFunctionExpression(arrow.clone())) - } - Expression::FunctionExpression(fe) => { - Ok(CompiledFunctionNode::FunctionExpression(fe.clone())) - } - _ => Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected function expression in variable declaration for gating", - None, - )), - } - } - _ => Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected statement type for gating rewrite", - None, - )), - } -} - -/// Rename the function declaration at `body[index]` in place. -/// Handles both bare FunctionDeclaration and ExportNamedDeclaration wrapping one. -fn rename_fn_decl_at( - body: &mut [Statement], - index: usize, - new_name: &str, -) -> Result<(), CompilerDiagnostic> { - match &mut body[index] { - Statement::FunctionDeclaration(fd) => { - fd.id = Some(make_identifier(new_name)); - } - Statement::ExportNamedDeclaration(end) => { - if let Some(decl) = &mut end.declaration { - if let react_compiler_ast::declarations::Declaration::FunctionDeclaration(fd) = - decl.as_mut() - { - fd.id = Some(make_identifier(new_name)); - } - } - } - _ => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected function declaration to rename", - None, - )); - } - } - Ok(()) -} diff --git a/compiler/crates/react_compiler/src/entrypoint/imports.rs b/compiler/crates/react_compiler/src/entrypoint/imports.rs deleted file mode 100644 index ab91b764895f..000000000000 --- a/compiler/crates/react_compiler/src/entrypoint/imports.rs +++ /dev/null @@ -1,505 +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. - */ -use std::collections::{HashMap, HashSet}; - -use react_compiler_ast::common::BaseNode; -use react_compiler_ast::declarations::{ - ImportDeclaration, ImportKind, ImportSpecifier, ImportSpecifierData, ModuleExportName, -}; -use react_compiler_ast::expressions::{CallExpression, Expression, Identifier}; -use react_compiler_ast::literals::StringLiteral; -use react_compiler_ast::patterns::{ - ObjectPattern, ObjectPatternProp, ObjectPatternProperty, PatternLike, -}; -use react_compiler_ast::scope::ScopeInfo; -use react_compiler_ast::statements::{ - Statement, VariableDeclaration, VariableDeclarationKind, VariableDeclarator, -}; -use react_compiler_ast::{Program, SourceType}; -use react_compiler_diagnostics::{ - CompilerError, CompilerErrorDetail, ErrorCategory, Position, SourceLocation, -}; - -use super::compile_result::{DebugLogEntry, LoggerEvent, OrderedLogItem}; -use super::plugin_options::{CompilerTarget, PluginOptions}; -use super::suppression::SuppressionRange; -use crate::timing::TimingData; - -/// An import specifier tracked by ProgramContext. -/// Corresponds to NonLocalImportSpecifier in the TS compiler. -#[derive(Debug, Clone)] -pub struct NonLocalImportSpecifier { - pub name: String, - pub module: String, - pub imported: String, -} - -/// Context for the program being compiled. -/// Tracks compiled functions, generated names, and import requirements. -/// Equivalent to ProgramContext class in Imports.ts. -pub struct ProgramContext { - pub opts: PluginOptions, - pub filename: Option<String>, - /// The source filename from the parser's sourceFilename option. - /// This is the filename stored on AST node `loc.filename` fields, - /// which may differ from `filename` (e.g., no path prefix). - source_filename: Option<String>, - pub code: Option<String>, - pub react_runtime_module: String, - pub suppressions: Vec<SuppressionRange>, - pub has_module_scope_opt_out: bool, - pub events: Vec<LoggerEvent>, - /// Unified ordered log that interleaves events and debug entries - /// in the order they were emitted during compilation. - pub ordered_log: Vec<OrderedLogItem>, - - // Pre-resolved import local names for codegen - pub instrument_fn_name: Option<String>, - pub instrument_gating_name: Option<String>, - pub hook_guard_name: Option<String>, - - // Variable renames from lowering, to be applied back to the Babel AST - pub renames: Vec<react_compiler_hir::environment::BindingRename>, - - /// Timing data for profiling. Accumulates across all function compilations. - pub timing: TimingData, - - /// Whether debug logging is enabled (HIR formatting after each pass). - pub debug_enabled: bool, - - // Internal state - already_compiled: HashSet<u32>, - known_referenced_names: HashSet<String>, - imports: HashMap<String, HashMap<String, NonLocalImportSpecifier>>, -} - -impl ProgramContext { - pub fn new( - opts: PluginOptions, - filename: Option<String>, - code: Option<String>, - suppressions: Vec<SuppressionRange>, - has_module_scope_opt_out: bool, - ) -> Self { - let react_runtime_module = get_react_compiler_runtime_module(&opts.target); - let profiling = opts.profiling; - let debug_enabled = opts.debug; - Self { - opts, - filename, - source_filename: None, - code, - react_runtime_module, - suppressions, - has_module_scope_opt_out, - events: Vec::new(), - ordered_log: Vec::new(), - instrument_fn_name: None, - instrument_gating_name: None, - hook_guard_name: None, - renames: Vec::new(), - timing: TimingData::new(profiling), - debug_enabled, - already_compiled: HashSet::new(), - known_referenced_names: HashSet::new(), - imports: HashMap::new(), - } - } - - /// Set the source filename (from AST node loc.filename). - pub fn set_source_filename(&mut self, filename: Option<String>) { - if self.source_filename.is_none() { - self.source_filename = filename; - } - } - - /// Get the source filename for logger events. - pub fn source_filename(&self) -> Option<String> { - self.source_filename.clone() - } - - /// Check if a function at the given start position has already been compiled. - /// This is a workaround for Babel not consistently respecting skip(). - pub fn is_already_compiled(&self, start: u32) -> bool { - self.already_compiled.contains(&start) - } - - /// Mark a function at the given start position as compiled. - pub fn mark_compiled(&mut self, start: u32) { - self.already_compiled.insert(start); - } - - /// Initialize known referenced names from scope bindings. - /// Call this after construction to seed conflict detection with program scope bindings. - pub fn init_from_scope(&mut self, scope: &ScopeInfo) { - // Register ALL bindings (not just program-scope) so that UID generation - // avoids name conflicts with any binding in the file. This matches - // Babel's generateUid() which checks all scopes. - for binding in &scope.bindings { - self.known_referenced_names.insert(binding.name.clone()); - } - } - - /// Check if a name conflicts with known references. - pub fn has_reference(&self, name: &str) -> bool { - self.known_referenced_names.contains(name) - } - - /// Generate a unique identifier name that doesn't conflict with existing bindings. - /// - /// For hook names (use*), preserves the original name to avoid breaking - /// hook-name-based type inference. For other names, prefixes with underscore - /// similar to Babel's generateUid. - pub fn new_uid(&mut self, name: &str) -> String { - if is_hook_name(name) { - // Don't prefix hooks with underscore, since InferTypes might - // type HookKind based on callee naming convention. - let mut uid = name.to_string(); - let mut i = 0; - while self.has_reference(&uid) { - uid = format!("{}_{}", name, i); - i += 1; - } - self.known_referenced_names.insert(uid.clone()); - uid - } else if !self.has_reference(name) { - self.known_referenced_names.insert(name.to_string()); - name.to_string() - } else { - // Generate unique name with underscore prefix (similar to Babel's generateUid). - // Babel strips leading underscores before prefixing, so: - // generateUid("_c") → strips to "c" → generates "_c", "_c2", "_c3", ... - // generateUid("foo") → generates "_foo", "_foo2", "_foo3", ... - let base = name.trim_start_matches('_'); - let mut uid = format!("_{}", base); - let mut i = 2; - while self.has_reference(&uid) { - uid = format!("_{}{}", base, i); - i += 1; - } - self.known_referenced_names.insert(uid.clone()); - uid - } - } - - /// Add the memo cache import (the `c` function from the compiler runtime). - pub fn add_memo_cache_import(&mut self) -> NonLocalImportSpecifier { - let module = self.react_runtime_module.clone(); - self.add_import_specifier(&module, "c", Some("_c")) - } - - /// Add an import specifier, reusing an existing one if it was already added. - /// - /// If `name_hint` is provided, it will be used as the basis for the local - /// name; otherwise `specifier` is used. - pub fn add_import_specifier( - &mut self, - module: &str, - specifier: &str, - name_hint: Option<&str>, - ) -> NonLocalImportSpecifier { - // Check if already imported - if let Some(module_imports) = self.imports.get(module) { - if let Some(existing) = module_imports.get(specifier) { - return existing.clone(); - } - } - - let name = self.new_uid(name_hint.unwrap_or(specifier)); - let binding = NonLocalImportSpecifier { - name, - module: module.to_string(), - imported: specifier.to_string(), - }; - - self.imports - .entry(module.to_string()) - .or_default() - .insert(specifier.to_string(), binding.clone()); - - binding - } - - /// Register a name as referenced so future uid generation avoids it. - pub fn add_new_reference(&mut self, name: String) { - self.known_referenced_names.insert(name); - } - - /// Get the set of known referenced names for seeding per-function Environment UID generation. - pub fn known_referenced_names(&self) -> &HashSet<String> { - &self.known_referenced_names - } - - /// Merge UID names generated during a function compilation back into the program context, - /// so subsequent function compilations avoid collisions. - pub fn merge_uid_known_names(&mut self, names: &HashSet<String>) { - self.known_referenced_names.extend(names.iter().cloned()); - } - - /// Log a compilation event. - pub fn log_event(&mut self, event: LoggerEvent) { - self.ordered_log.push(OrderedLogItem::Event { - event: event.clone(), - }); - self.events.push(event); - } - - /// Log a debug entry (for debugLogIRs support). - pub fn log_debug(&mut self, entry: DebugLogEntry) { - self.ordered_log.push(OrderedLogItem::Debug { entry }); - } - - /// Check if there are any pending imports to add to the program. - pub fn has_pending_imports(&self) -> bool { - !self.imports.is_empty() - } - - /// Get an immutable view of the generated imports. - pub fn imports(&self) -> &HashMap<String, HashMap<String, NonLocalImportSpecifier>> { - &self.imports - } -} - -/// Check for blocklisted import modules. -/// Returns a CompilerError if any blocklisted imports are found. -pub fn validate_restricted_imports( - program: &Program, - blocklisted: &Option<Vec<String>>, -) -> Option<CompilerError> { - let blocklisted = match blocklisted { - Some(b) if !b.is_empty() => b, - _ => return None, - }; - let restricted: HashSet<&str> = blocklisted.iter().map(|s| s.as_str()).collect(); - let mut error = CompilerError::new(); - - for stmt in &program.body { - if let Statement::ImportDeclaration(import) = stmt { - if restricted.contains(import.source.value.as_str()) { - let mut detail = CompilerErrorDetail::new( - ErrorCategory::Todo, - "Bailing out due to blocklisted import", - ) - .with_description(format!("Import from module {}", import.source.value)); - detail.loc = import.base.loc.as_ref().map(|loc| SourceLocation { - start: Position { - line: loc.start.line, - column: loc.start.column, - index: loc.start.index, - }, - end: Position { - line: loc.end.line, - column: loc.end.column, - index: loc.end.index, - }, - }); - error.push_error_detail(detail); - } - } - } - - if error.has_any_errors() { - Some(error) - } else { - None - } -} - -/// Insert import declarations into the program body. -/// Handles both ESM imports and CommonJS require. -/// -/// For existing imports of the same module (non-namespaced, value imports), -/// new specifiers are merged into the existing declaration. Otherwise, -/// new import/require statements are prepended to the program body. -pub fn add_imports_to_program(program: &mut Program, context: &ProgramContext) { - if context.imports.is_empty() { - return; - } - - // Collect existing non-namespaced imports by module name - let existing_import_indices: HashMap<String, usize> = program - .body - .iter() - .enumerate() - .filter_map(|(idx, stmt)| { - if let Statement::ImportDeclaration(import) = stmt { - if is_non_namespaced_import(import) { - return Some((import.source.value.clone(), idx)); - } - } - None - }) - .collect(); - - let mut stmts: Vec<Statement> = Vec::new(); - let mut sorted_modules: Vec<_> = context.imports.iter().collect(); - sorted_modules.sort_by(|(a, _), (b, _)| a.to_lowercase().cmp(&b.to_lowercase())); - - for (module_name, imports_map) in sorted_modules { - let sorted_imports = { - let mut sorted: Vec<_> = imports_map.values().collect(); - sorted.sort_by_key(|s| &s.imported); - sorted - }; - - let import_specifiers: Vec<ImportSpecifier> = sorted_imports - .iter() - .map(|spec| make_import_specifier(spec)) - .collect(); - - // If an existing import of this module exists, merge into it - if let Some(&idx) = existing_import_indices.get(module_name.as_str()) { - if let Statement::ImportDeclaration(ref mut import) = program.body[idx] { - import.specifiers.extend(import_specifiers); - } - } else if matches!(program.source_type, SourceType::Module) { - // ESM: import { ... } from 'module' - stmts.push(Statement::ImportDeclaration(ImportDeclaration { - base: BaseNode::typed("ImportDeclaration"), - specifiers: import_specifiers, - source: StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: module_name.clone(), - }, - import_kind: None, - assertions: None, - attributes: None, - })); - } else { - // CommonJS: const { imported: local, ... } = require('module') - let properties: Vec<ObjectPatternProperty> = sorted_imports - .iter() - .map(|spec| { - ObjectPatternProperty::ObjectProperty(ObjectPatternProp { - base: BaseNode::typed("ObjectProperty"), - key: Box::new(Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: spec.imported.clone(), - type_annotation: None, - optional: None, - decorators: None, - })), - value: Box::new(PatternLike::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: spec.name.clone(), - type_annotation: None, - optional: None, - decorators: None, - })), - computed: false, - shorthand: false, - decorators: None, - method: None, - }) - }) - .collect(); - - stmts.push(Statement::VariableDeclaration(VariableDeclaration { - base: BaseNode::typed("VariableDeclaration"), - kind: VariableDeclarationKind::Const, - declarations: vec![VariableDeclarator { - base: BaseNode::typed("VariableDeclarator"), - id: PatternLike::ObjectPattern(ObjectPattern { - base: BaseNode::typed("ObjectPattern"), - properties, - type_annotation: None, - decorators: None, - }), - init: Some(Box::new(Expression::CallExpression(CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: "require".to_string(), - type_annotation: None, - optional: None, - decorators: None, - })), - arguments: vec![Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: module_name.clone(), - })], - type_parameters: None, - type_arguments: None, - optional: None, - }))), - definite: None, - }], - declare: None, - })); - } - } - - // Prepend new import statements to the program body - if !stmts.is_empty() { - let mut new_body = stmts; - new_body.append(&mut program.body); - program.body = new_body; - } -} - -/// Create an ImportSpecifier AST node from a NonLocalImportSpecifier. -fn make_import_specifier(spec: &NonLocalImportSpecifier) -> ImportSpecifier { - ImportSpecifier::ImportSpecifier(ImportSpecifierData { - base: BaseNode::typed("ImportSpecifier"), - local: Identifier { - base: BaseNode::typed("Identifier"), - name: spec.name.clone(), - type_annotation: None, - optional: None, - decorators: None, - }, - imported: ModuleExportName::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: spec.imported.clone(), - type_annotation: None, - optional: None, - decorators: None, - }), - import_kind: None, - }) -} - -/// Check if an import declaration is a non-namespaced value import. -/// Matches `import { ... } from 'module'` but NOT: -/// - `import * as Foo from 'module'` (namespace) -/// - `import type { Foo } from 'module'` (type import) -/// - `import typeof { Foo } from 'module'` (typeof import) -fn is_non_namespaced_import(import: &ImportDeclaration) -> bool { - import - .specifiers - .iter() - .all(|s| matches!(s, ImportSpecifier::ImportSpecifier(_))) - && import - .import_kind - .as_ref() - .map_or(true, |k| matches!(k, ImportKind::Value)) -} - -/// Check if a name follows the React hook naming convention (use[A-Z0-9]...). -fn is_hook_name(name: &str) -> bool { - let bytes = name.as_bytes(); - bytes.len() >= 4 - && bytes[0] == b'u' - && bytes[1] == b's' - && bytes[2] == b'e' - && bytes - .get(3) - .map_or(false, |c| c.is_ascii_uppercase() || c.is_ascii_digit()) -} - -/// Get the runtime module name based on the compiler target. -pub fn get_react_compiler_runtime_module(target: &CompilerTarget) -> String { - match target { - CompilerTarget::Version(v) if v == "19" => "react/compiler-runtime".to_string(), - CompilerTarget::Version(v) if v == "17" || v == "18" => { - "react-compiler-runtime".to_string() - } - CompilerTarget::MetaInternal { runtime_module, .. } => runtime_module.clone(), - // Default to React 19 runtime for unrecognized versions - CompilerTarget::Version(_) => "react/compiler-runtime".to_string(), - } -} diff --git a/compiler/crates/react_compiler/src/entrypoint/mod.rs b/compiler/crates/react_compiler/src/entrypoint/mod.rs deleted file mode 100644 index 4cb694371248..000000000000 --- a/compiler/crates/react_compiler/src/entrypoint/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod compile_result; -pub mod gating; -pub mod imports; -pub mod pipeline; -pub mod plugin_options; -pub mod program; -pub mod suppression; -pub mod validate_source_locations; - -pub use compile_result::*; -pub use plugin_options::*; -pub use program::*; diff --git a/compiler/crates/react_compiler/src/entrypoint/pipeline.rs b/compiler/crates/react_compiler/src/entrypoint/pipeline.rs deleted file mode 100644 index 67bb397466f0..000000000000 --- a/compiler/crates/react_compiler/src/entrypoint/pipeline.rs +++ /dev/null @@ -1,1914 +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. - -//! Compilation pipeline for a single function. -//! -//! Analogous to TS `Pipeline.ts` (`compileFn` → `run` → `runWithEnvironment`). -//! Currently runs BuildHIR (lowering) and PruneMaybeThrows. - -use react_compiler_ast::scope::ScopeInfo; -use react_compiler_diagnostics::CompilerError; -use react_compiler_hir::ReactFunctionType; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::environment::OutputMode; -use react_compiler_hir::environment_config::EnvironmentConfig; -use react_compiler_lowering::FunctionNode; - -use super::compile_result::CodegenFunction; -use super::compile_result::CompilerErrorDetailInfo; -use super::compile_result::CompilerErrorItemInfo; -use super::compile_result::DebugLogEntry; -use super::compile_result::LoggerPosition; -use super::compile_result::LoggerSourceLocation; -use super::compile_result::OutlinedFunction; -use super::imports::ProgramContext; -use super::plugin_options::CompilerOutputMode; -use crate::debug_print; - -/// Run the compilation pipeline on a single function. -/// -/// Currently: creates an Environment, runs BuildHIR (lowering), and produces -/// debug output via the context. Returns a CodegenFunction with zeroed memo -/// stats on success (codegen is not yet implemented). -pub fn compile_fn( - func: &FunctionNode<'_>, - fn_name: Option<&str>, - scope_info: &ScopeInfo, - fn_type: ReactFunctionType, - mode: CompilerOutputMode, - env_config: &EnvironmentConfig, - context: &mut ProgramContext, -) -> Result<CodegenFunction, CompilerError> { - let mut env = Environment::with_config(env_config.clone()); - env.fn_type = fn_type; - env.output_mode = match mode { - CompilerOutputMode::Ssr => OutputMode::Ssr, - CompilerOutputMode::Client => OutputMode::Client, - CompilerOutputMode::Lint => OutputMode::Lint, - }; - env.code = context.code.clone(); - env.filename = context.filename.clone(); - env.instrument_fn_name = context.instrument_fn_name.clone(); - env.instrument_gating_name = context.instrument_gating_name.clone(); - env.hook_guard_name = context.hook_guard_name.clone(); - env.seed_uid_known_names(&context.known_referenced_names()); - - env.reference_node_ids = scope_info.ref_node_id_to_binding.keys().copied().collect(); - - context.timing.start("lower"); - let mut hir = react_compiler_lowering::lower(func, fn_name, scope_info, &mut env)?; - context.timing.stop(); - - // Copy renames from lowering to context (keep on env for codegen to apply to type annotations) - if !env.renames.is_empty() { - context.renames.extend(env.renames.iter().cloned()); - } - - // Check for Invariant errors after lowering, before logging HIR. - // In TS, Invariant errors throw from recordError(), aborting lower() before - // the HIR entry is logged. The thrown error contains ONLY the Invariant error, - // not other recorded (non-Invariant) errors. - if env.has_invariant_errors() { - return Err(env.take_invariant_errors()); - } - - if context.debug_enabled { - context.timing.start("debug_print:HIR"); - let debug_hir = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("HIR", debug_hir)); - context.timing.stop(); - } - - context.timing.start("PruneMaybeThrows"); - react_compiler_optimization::prune_maybe_throws(&mut hir, &mut env.functions)?; - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:PruneMaybeThrows"); - let debug_prune = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("PruneMaybeThrows", debug_prune)); - context.timing.stop(); - } - - context.timing.start("ValidateContextVariableLValues"); - react_compiler_validation::validate_context_variable_lvalues(&hir, &mut env)?; - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateContextVariableLValues", - "ok".to_string(), - )); - } - context.timing.stop(); - - context.timing.start("ValidateUseMemo"); - let void_memo_errors = react_compiler_validation::validate_use_memo(&hir, &mut env); - log_errors_as_events(&void_memo_errors, context); - if context.debug_enabled { - context.log_debug(DebugLogEntry::new("ValidateUseMemo", "ok".to_string())); - } - context.timing.stop(); - - context.timing.start("DropManualMemoization"); - react_compiler_optimization::drop_manual_memoization(&mut hir, &mut env)?; - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:DropManualMemoization"); - let debug_drop_memo = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("DropManualMemoization", debug_drop_memo)); - context.timing.stop(); - } - - context - .timing - .start("InlineImmediatelyInvokedFunctionExpressions"); - react_compiler_optimization::inline_immediately_invoked_function_expressions( - &mut hir, &mut env, - ); - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:InlineImmediatelyInvokedFunctionExpressions"); - let debug_inline_iifes = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "InlineImmediatelyInvokedFunctionExpressions", - debug_inline_iifes, - )); - context.timing.stop(); - } - - context.timing.start("MergeConsecutiveBlocks"); - react_compiler_optimization::merge_consecutive_blocks::merge_consecutive_blocks( - &mut hir, - &mut env.functions, - ); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:MergeConsecutiveBlocks"); - let debug_merge = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("MergeConsecutiveBlocks", debug_merge)); - context.timing.stop(); - } - - // TODO: port assertConsistentIdentifiers - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "AssertConsistentIdentifiers", - "ok".to_string(), - )); - } - // TODO: port assertTerminalSuccessorsExist - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "AssertTerminalSuccessorsExist", - "ok".to_string(), - )); - } - - context.timing.start("EnterSSA"); - react_compiler_ssa::enter_ssa(&mut hir, &mut env).map_err(|diag| { - let loc = diag.primary_location().cloned(); - let mut err = CompilerError::new(); - err.push_error_detail(react_compiler_diagnostics::CompilerErrorDetail { - category: diag.category, - reason: diag.reason, - description: diag.description, - loc, - suggestions: diag.suggestions, - }); - err - })?; - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:SSA"); - let debug_ssa = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("SSA", debug_ssa)); - context.timing.stop(); - } - - context.timing.start("EliminateRedundantPhi"); - react_compiler_ssa::eliminate_redundant_phi(&mut hir, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:EliminateRedundantPhi"); - let debug_eliminate_phi = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "EliminateRedundantPhi", - debug_eliminate_phi, - )); - context.timing.stop(); - } - - // TODO: port assertConsistentIdentifiers - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "AssertConsistentIdentifiers", - "ok".to_string(), - )); - } - - context.timing.start("ConstantPropagation"); - react_compiler_optimization::constant_propagation(&mut hir, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:ConstantPropagation"); - let debug_const_prop = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("ConstantPropagation", debug_const_prop)); - context.timing.stop(); - } - - context.timing.start("InferTypes"); - react_compiler_typeinference::infer_types(&mut hir, &mut env)?; - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:InferTypes"); - let debug_infer_types = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("InferTypes", debug_infer_types)); - context.timing.stop(); - } - - if env.enable_validations() { - if env.config.validate_hooks_usage { - context.timing.start("ValidateHooksUsage"); - react_compiler_validation::validate_hooks_usage(&hir, &mut env)?; - if context.debug_enabled { - context.log_debug(DebugLogEntry::new("ValidateHooksUsage", "ok".to_string())); - } - context.timing.stop(); - } - - if env.config.validate_no_capitalized_calls.is_some() { - context.timing.start("ValidateNoCapitalizedCalls"); - react_compiler_validation::validate_no_capitalized_calls(&hir, &mut env)?; - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateNoCapitalizedCalls", - "ok".to_string(), - )); - } - context.timing.stop(); - } - } - - context.timing.start("OptimizePropsMethodCalls"); - react_compiler_optimization::optimize_props_method_calls(&mut hir, &env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:OptimizePropsMethodCalls"); - let debug_optimize_props = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "OptimizePropsMethodCalls", - debug_optimize_props, - )); - context.timing.stop(); - } - - context.timing.start("AnalyseFunctions"); - let mut inner_logs: Vec<String> = Vec::new(); - let debug_inner = context.debug_enabled; - let analyse_result = react_compiler_inference::analyse_functions( - &mut hir, - &mut env, - &mut |inner_func, inner_env| { - if debug_inner { - inner_logs.push(debug_print::debug_hir(inner_func, inner_env)); - } - }, - ); - context.timing.stop(); - - // Always flush inner logs before propagating errors - if context.debug_enabled { - for inner_log in inner_logs { - context.log_debug(DebugLogEntry::new("AnalyseFunction (inner)", inner_log)); - } - } - - analyse_result?; - - if env.has_invariant_errors() { - return Err(env.take_invariant_errors()); - } - - if context.debug_enabled { - context.timing.start("debug_print:AnalyseFunctions"); - let debug_analyse_functions = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "AnalyseFunctions", - debug_analyse_functions, - )); - context.timing.stop(); - } - - context.timing.start("InferMutationAliasingEffects"); - react_compiler_inference::infer_mutation_aliasing_effects(&mut hir, &mut env, false)?; - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:InferMutationAliasingEffects"); - let debug_infer_effects = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "InferMutationAliasingEffects", - debug_infer_effects, - )); - context.timing.stop(); - } - - if env.output_mode == OutputMode::Ssr { - context.timing.start("OptimizeForSSR"); - react_compiler_optimization::optimize_for_ssr(&mut hir, &env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:OptimizeForSSR"); - let debug_ssr = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("OptimizeForSSR", debug_ssr)); - context.timing.stop(); - } - } - - context.timing.start("DeadCodeElimination"); - react_compiler_optimization::dead_code_elimination(&mut hir, &env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:DeadCodeElimination"); - let debug_dce = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("DeadCodeElimination", debug_dce)); - context.timing.stop(); - } - - context.timing.start("PruneMaybeThrows2"); - react_compiler_optimization::prune_maybe_throws(&mut hir, &mut env.functions)?; - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:PruneMaybeThrows2"); - let debug_prune2 = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("PruneMaybeThrows", debug_prune2)); - context.timing.stop(); - } - - context.timing.start("InferMutationAliasingRanges"); - react_compiler_inference::infer_mutation_aliasing_ranges(&mut hir, &mut env, false)?; - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:InferMutationAliasingRanges"); - let debug_infer_ranges = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "InferMutationAliasingRanges", - debug_infer_ranges, - )); - context.timing.stop(); - } - - if env.enable_validations() { - context - .timing - .start("ValidateLocalsNotReassignedAfterRender"); - react_compiler_validation::validate_locals_not_reassigned_after_render(&hir, &mut env); - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateLocalsNotReassignedAfterRender", - "ok".to_string(), - )); - } - context.timing.stop(); - - if env.config.validate_ref_access_during_render { - context.timing.start("ValidateNoRefAccessInRender"); - react_compiler_validation::validate_no_ref_access_in_render(&hir, &mut env); - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateNoRefAccessInRender", - "ok".to_string(), - )); - } - context.timing.stop(); - } - - if env.config.validate_no_set_state_in_render { - context.timing.start("ValidateNoSetStateInRender"); - react_compiler_validation::validate_no_set_state_in_render(&hir, &mut env)?; - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateNoSetStateInRender", - "ok".to_string(), - )); - } - context.timing.stop(); - } - - if env.config.validate_no_derived_computations_in_effects_exp - && env.output_mode == OutputMode::Lint - { - context - .timing - .start("ValidateNoDerivedComputationsInEffects"); - let errors = - react_compiler_validation::validate_no_derived_computations_in_effects_exp( - &hir, &env, - )?; - log_errors_as_events(&errors, context); - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateNoDerivedComputationsInEffects", - "ok".to_string(), - )); - } - context.timing.stop(); - } else if env.config.validate_no_derived_computations_in_effects { - context - .timing - .start("ValidateNoDerivedComputationsInEffects"); - react_compiler_validation::validate_no_derived_computations_in_effects(&hir, &mut env)?; - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateNoDerivedComputationsInEffects", - "ok".to_string(), - )); - } - context.timing.stop(); - } - - if env.config.validate_no_set_state_in_effects && env.output_mode == OutputMode::Lint { - context.timing.start("ValidateNoSetStateInEffects"); - let errors = react_compiler_validation::validate_no_set_state_in_effects(&hir, &env)?; - log_errors_as_events(&errors, context); - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateNoSetStateInEffects", - "ok".to_string(), - )); - } - context.timing.stop(); - } - - if env.config.validate_no_jsx_in_try_statements && env.output_mode == OutputMode::Lint { - context.timing.start("ValidateNoJSXInTryStatement"); - let errors = react_compiler_validation::validate_no_jsx_in_try_statement(&hir); - log_errors_as_events(&errors, context); - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateNoJSXInTryStatement", - "ok".to_string(), - )); - } - context.timing.stop(); - } - - context - .timing - .start("ValidateNoFreezingKnownMutableFunctions"); - react_compiler_validation::validate_no_freezing_known_mutable_functions(&hir, &mut env); - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateNoFreezingKnownMutableFunctions", - "ok".to_string(), - )); - } - context.timing.stop(); - } - - context.timing.start("InferReactivePlaces"); - react_compiler_inference::infer_reactive_places(&mut hir, &mut env)?; - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:InferReactivePlaces"); - let debug_reactive_places = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "InferReactivePlaces", - debug_reactive_places, - )); - context.timing.stop(); - } - - if env.enable_validations() { - context.timing.start("ValidateExhaustiveDependencies"); - react_compiler_validation::validate_exhaustive_dependencies(&mut hir, &mut env)?; - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateExhaustiveDependencies", - "ok".to_string(), - )); - } - context.timing.stop(); - } - - context - .timing - .start("RewriteInstructionKindsBasedOnReassignment"); - react_compiler_ssa::rewrite_instruction_kinds_based_on_reassignment(&mut hir, &env)?; - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:RewriteInstructionKindsBasedOnReassignment"); - let debug_rewrite = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "RewriteInstructionKindsBasedOnReassignment", - debug_rewrite, - )); - context.timing.stop(); - } - - if env.enable_validations() - && env.config.validate_static_components - && env.output_mode == OutputMode::Lint - { - context.timing.start("ValidateStaticComponents"); - let errors = react_compiler_validation::validate_static_components(&hir); - log_errors_as_events(&errors, context); - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidateStaticComponents", - "ok".to_string(), - )); - } - context.timing.stop(); - } - - if env.enable_memoization() { - context.timing.start("InferReactiveScopeVariables"); - react_compiler_inference::infer_reactive_scope_variables(&mut hir, &mut env)?; - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:InferReactiveScopeVariables"); - let debug_infer_scopes = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "InferReactiveScopeVariables", - debug_infer_scopes, - )); - context.timing.stop(); - } - } - - context - .timing - .start("MemoizeFbtAndMacroOperandsInSameScope"); - let fbt_operands = - react_compiler_inference::memoize_fbt_and_macro_operands_in_same_scope(&hir, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:MemoizeFbtAndMacroOperandsInSameScope"); - let debug_fbt = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "MemoizeFbtAndMacroOperandsInSameScope", - debug_fbt, - )); - context.timing.stop(); - } - - if env.config.enable_jsx_outlining { - context.timing.start("OutlineJsx"); - react_compiler_optimization::outline_jsx(&mut hir, &mut env); - context.timing.stop(); - } - - if env.config.enable_name_anonymous_functions { - context.timing.start("NameAnonymousFunctions"); - react_compiler_optimization::name_anonymous_functions(&mut hir, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:NameAnonymousFunctions"); - let debug_name_anon = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "NameAnonymousFunctions", - debug_name_anon, - )); - context.timing.stop(); - } - } - - if env.config.enable_function_outlining { - context.timing.start("OutlineFunctions"); - react_compiler_optimization::outline_functions(&mut hir, &mut env, &fbt_operands); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:OutlineFunctions"); - let debug_outline = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("OutlineFunctions", debug_outline)); - context.timing.stop(); - } - } - - context.timing.start("AlignMethodCallScopes"); - react_compiler_inference::align_method_call_scopes(&mut hir, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:AlignMethodCallScopes"); - let debug_align = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new("AlignMethodCallScopes", debug_align)); - context.timing.stop(); - } - - context.timing.start("AlignObjectMethodScopes"); - react_compiler_inference::align_object_method_scopes(&mut hir, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:AlignObjectMethodScopes"); - let debug_align_obj = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "AlignObjectMethodScopes", - debug_align_obj, - )); - context.timing.stop(); - } - - context.timing.start("PruneUnusedLabelsHIR"); - react_compiler_optimization::prune_unused_labels_hir(&mut hir); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:PruneUnusedLabelsHIR"); - let debug_prune_labels = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "PruneUnusedLabelsHIR", - debug_prune_labels, - )); - context.timing.stop(); - } - - context.timing.start("AlignReactiveScopesToBlockScopesHIR"); - react_compiler_inference::align_reactive_scopes_to_block_scopes_hir(&mut hir, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:AlignReactiveScopesToBlockScopesHIR"); - let debug_align_block_scopes = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "AlignReactiveScopesToBlockScopesHIR", - debug_align_block_scopes, - )); - context.timing.stop(); - } - - context.timing.start("MergeOverlappingReactiveScopesHIR"); - react_compiler_inference::merge_overlapping_reactive_scopes_hir(&mut hir, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:MergeOverlappingReactiveScopesHIR"); - let debug_merge_overlapping = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "MergeOverlappingReactiveScopesHIR", - debug_merge_overlapping, - )); - context.timing.stop(); - } - - // TODO: port assertValidBlockNesting - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "AssertValidBlockNesting", - "ok".to_string(), - )); - } - - context.timing.start("BuildReactiveScopeTerminalsHIR"); - react_compiler_inference::build_reactive_scope_terminals_hir(&mut hir, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:BuildReactiveScopeTerminalsHIR"); - let debug_build_scope_terminals = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "BuildReactiveScopeTerminalsHIR", - debug_build_scope_terminals, - )); - context.timing.stop(); - } - - // TODO: port assertValidBlockNesting - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "AssertValidBlockNesting", - "ok".to_string(), - )); - } - - context.timing.start("FlattenReactiveLoopsHIR"); - react_compiler_inference::flatten_reactive_loops_hir(&mut hir); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:FlattenReactiveLoopsHIR"); - let debug_flatten_loops = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "FlattenReactiveLoopsHIR", - debug_flatten_loops, - )); - context.timing.stop(); - } - - context.timing.start("FlattenScopesWithHooksOrUseHIR"); - react_compiler_inference::flatten_scopes_with_hooks_or_use_hir(&mut hir, &env)?; - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:FlattenScopesWithHooksOrUseHIR"); - let debug_flatten_hooks = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "FlattenScopesWithHooksOrUseHIR", - debug_flatten_hooks, - )); - context.timing.stop(); - } - - // TODO: port assertTerminalSuccessorsExist - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "AssertTerminalSuccessorsExist", - "ok".to_string(), - )); - } - // TODO: port assertTerminalPredsExist - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "AssertTerminalPredsExist", - "ok".to_string(), - )); - } - - context.timing.start("PropagateScopeDependenciesHIR"); - react_compiler_inference::propagate_scope_dependencies_hir(&mut hir, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:PropagateScopeDependenciesHIR"); - let debug_propagate_deps = debug_print::debug_hir(&hir, &env); - context.log_debug(DebugLogEntry::new( - "PropagateScopeDependenciesHIR", - debug_propagate_deps, - )); - context.timing.stop(); - } - - context.timing.start("BuildReactiveFunction"); - let mut reactive_fn = react_compiler_reactive_scopes::build_reactive_function(&hir, &env)?; - context.timing.stop(); - - let hir_formatter = |fmt: &mut react_compiler_hir::print::PrintFormatter, - func: &react_compiler_hir::HirFunction| { - debug_print::format_hir_function_into(fmt, func); - }; - - if context.debug_enabled { - context.timing.start("debug_print:BuildReactiveFunction"); - let debug_reactive = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new("BuildReactiveFunction", debug_reactive)); - context.timing.stop(); - } - - context.timing.start("AssertWellFormedBreakTargets"); - react_compiler_reactive_scopes::assert_well_formed_break_targets(&reactive_fn, &env); - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "AssertWellFormedBreakTargets", - "ok".to_string(), - )); - } - context.timing.stop(); - - context.timing.start("PruneUnusedLabels"); - react_compiler_reactive_scopes::prune_unused_labels(&mut reactive_fn, &env)?; - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:PruneUnusedLabels"); - let debug_prune_labels_reactive = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new( - "PruneUnusedLabels", - debug_prune_labels_reactive, - )); - context.timing.stop(); - } - - context.timing.start("AssertScopeInstructionsWithinScopes"); - react_compiler_reactive_scopes::assert_scope_instructions_within_scopes(&reactive_fn, &env)?; - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "AssertScopeInstructionsWithinScopes", - "ok".to_string(), - )); - } - context.timing.stop(); - - context.timing.start("PruneNonEscapingScopes"); - react_compiler_reactive_scopes::prune_non_escaping_scopes(&mut reactive_fn, &mut env)?; - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:PruneNonEscapingScopes"); - let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new("PruneNonEscapingScopes", debug)); - context.timing.stop(); - } - - context.timing.start("PruneNonReactiveDependencies"); - react_compiler_reactive_scopes::prune_non_reactive_dependencies(&mut reactive_fn, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:PruneNonReactiveDependencies"); - let debug_prune_non_reactive = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new( - "PruneNonReactiveDependencies", - debug_prune_non_reactive, - )); - context.timing.stop(); - } - - context.timing.start("PruneUnusedScopes"); - react_compiler_reactive_scopes::prune_unused_scopes(&mut reactive_fn, &env)?; - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:PruneUnusedScopes"); - let debug_prune_unused_scopes = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new( - "PruneUnusedScopes", - debug_prune_unused_scopes, - )); - context.timing.stop(); - } - - context - .timing - .start("MergeReactiveScopesThatInvalidateTogether"); - react_compiler_reactive_scopes::merge_reactive_scopes_that_invalidate_together( - &mut reactive_fn, - &mut env, - )?; - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:MergeReactiveScopesThatInvalidateTogether"); - let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new( - "MergeReactiveScopesThatInvalidateTogether", - debug, - )); - context.timing.stop(); - } - - context.timing.start("PruneAlwaysInvalidatingScopes"); - react_compiler_reactive_scopes::prune_always_invalidating_scopes(&mut reactive_fn, &env)?; - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:PruneAlwaysInvalidatingScopes"); - let debug_prune_always_inv = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new( - "PruneAlwaysInvalidatingScopes", - debug_prune_always_inv, - )); - context.timing.stop(); - } - - context.timing.start("PropagateEarlyReturns"); - react_compiler_reactive_scopes::propagate_early_returns(&mut reactive_fn, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:PropagateEarlyReturns"); - let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new("PropagateEarlyReturns", debug)); - context.timing.stop(); - } - - context.timing.start("PruneUnusedLValues"); - react_compiler_reactive_scopes::prune_unused_lvalues(&mut reactive_fn, &env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:PruneUnusedLValues"); - let debug_prune_lvalues = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new( - "PruneUnusedLValues", - debug_prune_lvalues, - )); - context.timing.stop(); - } - - context.timing.start("PromoteUsedTemporaries"); - react_compiler_reactive_scopes::promote_used_temporaries(&mut reactive_fn, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:PromoteUsedTemporaries"); - let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new("PromoteUsedTemporaries", debug)); - context.timing.stop(); - } - - context - .timing - .start("ExtractScopeDeclarationsFromDestructuring"); - react_compiler_reactive_scopes::extract_scope_declarations_from_destructuring( - &mut reactive_fn, - &mut env, - )?; - context.timing.stop(); - - if context.debug_enabled { - context - .timing - .start("debug_print:ExtractScopeDeclarationsFromDestructuring"); - let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new( - "ExtractScopeDeclarationsFromDestructuring", - debug, - )); - context.timing.stop(); - } - - context.timing.start("StabilizeBlockIds"); - react_compiler_reactive_scopes::stabilize_block_ids(&mut reactive_fn, &mut env); - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:StabilizeBlockIds"); - let debug_stabilize = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new("StabilizeBlockIds", debug_stabilize)); - context.timing.stop(); - } - - context.timing.start("RenameVariables"); - let unique_identifiers = - react_compiler_reactive_scopes::rename_variables(&mut reactive_fn, &mut env); - context.timing.stop(); - - for name in &unique_identifiers { - context.add_new_reference(name.clone()); - } - - if context.debug_enabled { - context.timing.start("debug_print:RenameVariables"); - let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new("RenameVariables", debug)); - context.timing.stop(); - } - - context.timing.start("PruneHoistedContexts"); - react_compiler_reactive_scopes::prune_hoisted_contexts(&mut reactive_fn, &mut env)?; - context.timing.stop(); - - if context.debug_enabled { - context.timing.start("debug_print:PruneHoistedContexts"); - let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter( - &reactive_fn, &env, Some(&hir_formatter), - ); - context.log_debug(DebugLogEntry::new("PruneHoistedContexts", debug)); - context.timing.stop(); - } - - if env.config.enable_preserve_existing_memoization_guarantees - || env.config.validate_preserve_existing_memoization_guarantees - { - context.timing.start("ValidatePreservedManualMemoization"); - react_compiler_validation::validate_preserved_manual_memoization(&reactive_fn, &mut env); - if context.debug_enabled { - context.log_debug(DebugLogEntry::new( - "ValidatePreservedManualMemoization", - "ok".to_string(), - )); - } - context.timing.stop(); - } - - context.timing.start("codegen"); - let codegen_result = react_compiler_reactive_scopes::codegen_function( - &reactive_fn, - &mut env, - unique_identifiers, - fbt_operands, - )?; - context.timing.stop(); - - // NOTE: we intentionally do NOT register the memo cache import here. - // The import is registered in apply_compiled_functions() only for functions - // that are actually applied to the output. Registering it here would cause - // a spurious `import { c as _c }` when a function compiles with memo slots - // but is later discarded (e.g., due to "use no memo" opt-out or errors), - // while other functions in the same file compile to 0 memo slots. - - if env.config.validate_source_locations { - super::validate_source_locations::validate_source_locations( - func, - &codegen_result, - &mut env, - ); - } - - // Simulate unexpected exception for testing (matches TS Pipeline.ts) - if env.config.throw_unknown_exception_testonly { - let mut err = CompilerError::new(); - err.push_error_detail(react_compiler_diagnostics::CompilerErrorDetail { - category: react_compiler_diagnostics::ErrorCategory::Invariant, - reason: "unexpected error".to_string(), - description: None, - loc: None, - suggestions: None, - }); - return Err(err); - } - - // Check for accumulated errors at the end of the pipeline - // (matches TS Pipeline.ts: env.hasErrors() → Err at the end) - if env.has_errors() { - // Merge UIDs even on error: in TS, Babel's scope.generateUid() permanently - // registers names in the scope's `uids` map regardless of whether the function - // compilation succeeds or fails. Without this merge, failed compilations would - // "leak" _temp names that subsequent successful compilations wouldn't see, - // causing numbering mismatches vs TS. - if let Some(uid_names) = env.take_uid_known_names() { - context.merge_uid_known_names(&uid_names); - } - return Err(env.take_errors()); - } - - // Re-compile outlined functions through the full pipeline. - // This mirrors TS behavior where outlined functions from JSX outlining - // are pushed back onto the compilation queue and compiled as components. - let mut compiled_outlined: Vec<OutlinedFunction> = Vec::new(); - for o in codegen_result.outlined { - let outlined_codegen = CodegenFunction { - loc: o.func.loc, - id: o.func.id, - name_hint: o.func.name_hint, - params: o.func.params, - body: o.func.body, - generator: o.func.generator, - is_async: o.func.is_async, - memo_slots_used: o.func.memo_slots_used, - memo_blocks: o.func.memo_blocks, - memo_values: o.func.memo_values, - pruned_memo_blocks: o.func.pruned_memo_blocks, - pruned_memo_values: o.func.pruned_memo_values, - outlined: Vec::new(), - }; - if let Some(fn_type) = o.fn_type { - let fn_name = outlined_codegen.id.as_ref().map(|id| id.name.clone()); - match compile_outlined_fn( - outlined_codegen, - fn_name.as_deref(), - fn_type, - mode, - env_config, - context, - ) { - Ok(compiled) => { - compiled_outlined.push(OutlinedFunction { - func: compiled, - fn_type: Some(fn_type), - }); - } - Err(_err) => { - // If re-compilation fails, skip the outlined function - } - } - } else { - compiled_outlined.push(OutlinedFunction { - func: outlined_codegen, - fn_type: o.fn_type, - }); - } - } - - if let Some(uid_names) = env.take_uid_known_names() { - context.merge_uid_known_names(&uid_names); - } - - Ok(CodegenFunction { - loc: codegen_result.loc, - id: codegen_result.id, - name_hint: codegen_result.name_hint, - params: codegen_result.params, - body: codegen_result.body, - generator: codegen_result.generator, - is_async: codegen_result.is_async, - memo_slots_used: codegen_result.memo_slots_used, - memo_blocks: codegen_result.memo_blocks, - memo_values: codegen_result.memo_values, - pruned_memo_blocks: codegen_result.pruned_memo_blocks, - pruned_memo_values: codegen_result.pruned_memo_values, - outlined: compiled_outlined, - }) -} - -/// Compile an outlined function's codegen AST through the full pipeline. -/// -/// Creates a fresh Environment, builds a synthetic ScopeInfo with unique fake -/// positions for identifier resolution, lowers from AST to HIR, then runs -/// the full compilation pipeline. This mirrors the TS behavior where outlined -/// functions are inserted into the program AST and re-compiled from scratch. -pub fn compile_outlined_fn( - mut codegen_fn: CodegenFunction, - fn_name: Option<&str>, - fn_type: ReactFunctionType, - mode: CompilerOutputMode, - env_config: &EnvironmentConfig, - context: &mut ProgramContext, -) -> Result<CodegenFunction, CompilerError> { - let mut env = Environment::with_config(env_config.clone()); - env.fn_type = fn_type; - env.output_mode = match mode { - CompilerOutputMode::Ssr => OutputMode::Ssr, - CompilerOutputMode::Client => OutputMode::Client, - CompilerOutputMode::Lint => OutputMode::Lint, - }; - - // Build a FunctionDeclaration from the codegen output - let mut outlined_decl = react_compiler_ast::statements::FunctionDeclaration { - base: react_compiler_ast::common::BaseNode::typed("FunctionDeclaration"), - id: codegen_fn.id.take(), - params: std::mem::take(&mut codegen_fn.params), - body: std::mem::replace( - &mut codegen_fn.body, - react_compiler_ast::statements::BlockStatement { - base: react_compiler_ast::common::BaseNode::typed("BlockStatement"), - body: Vec::new(), - directives: Vec::new(), - }, - ), - generator: codegen_fn.generator, - is_async: codegen_fn.is_async, - declare: None, - return_type: None, - type_parameters: None, - predicate: None, - component_declaration: false, - hook_declaration: false, - }; - - // Build scope info by assigning fake positions to all identifiers - let scope_info = build_outlined_scope_info(&mut outlined_decl); - - let func_node = react_compiler_lowering::FunctionNode::FunctionDeclaration(&outlined_decl); - let mut hir = react_compiler_lowering::lower(&func_node, fn_name, &scope_info, &mut env)?; - - if env.has_invariant_errors() { - return Err(env.take_invariant_errors()); - } - - run_pipeline_passes(&mut hir, &mut env, context) -} - -/// Build a ScopeInfo for an outlined function declaration by assigning unique -/// fake positions to all Identifier nodes and building the binding/reference maps. -fn build_outlined_scope_info( - func: &mut react_compiler_ast::statements::FunctionDeclaration, -) -> react_compiler_ast::scope::ScopeInfo { - use std::collections::HashMap; - - use react_compiler_ast::scope::*; - - let mut pos: u32 = 1; // reserve 0 for the function itself - func.base.start = Some(0); - - let mut fn_bindings: HashMap<String, BindingId> = HashMap::new(); - let mut bindings_list: Vec<BindingData> = Vec::new(); - let mut ref_to_binding: indexmap::IndexMap<u32, BindingId> = indexmap::IndexMap::new(); - - // Helper to add a binding - let _add_binding = - |name: &str, - kind: BindingKind, - p: u32, - fn_bindings: &mut HashMap<String, BindingId>, - bindings_list: &mut Vec<BindingData>, - ref_to_binding: &mut indexmap::IndexMap<u32, BindingId>| { - if fn_bindings.contains_key(name) { - // Already exists, just add reference - let bid = fn_bindings[name]; - ref_to_binding.insert(p, bid); - return; - } - let binding_id = BindingId(bindings_list.len() as u32); - fn_bindings.insert(name.to_string(), binding_id); - bindings_list.push(BindingData { - id: binding_id, - name: name.to_string(), - kind, - scope: ScopeId(1), - declaration_type: "VariableDeclarator".to_string(), - declaration_start: Some(p), - declaration_node_id: None, - import: None, - }); - ref_to_binding.insert(p, binding_id); - }; - - // Process params - add as Param bindings - for param in &mut func.params { - outlined_assign_pattern_positions( - param, - &mut pos, - BindingKind::Param, - &mut fn_bindings, - &mut bindings_list, - &mut ref_to_binding, - ); - } - - // Process body - walk all statements to assign positions and collect variable declarations - for stmt in &mut func.body.body { - outlined_assign_stmt_positions( - stmt, - &mut pos, - &mut fn_bindings, - &mut bindings_list, - &mut ref_to_binding, - ); - } - - let program_scope = ScopeData { - id: ScopeId(0), - parent: None, - kind: ScopeKind::Program, - bindings: HashMap::new(), - }; - let fn_scope = ScopeData { - id: ScopeId(1), - parent: Some(ScopeId(0)), - kind: ScopeKind::Function, - bindings: fn_bindings, - }; - - let mut node_to_scope: HashMap<u32, ScopeId> = HashMap::new(); - node_to_scope.insert(0, ScopeId(1)); - - // Mirror position maps into node-ID maps for outlined functions - let mut node_id_to_scope: HashMap<u32, ScopeId> = HashMap::new(); - node_id_to_scope.insert(0, ScopeId(1)); - let ref_node_id_to_binding: indexmap::IndexMap<u32, BindingId> = - ref_to_binding.iter().map(|(&k, &v)| (k, v)).collect(); - - ScopeInfo { - scopes: vec![program_scope, fn_scope], - bindings: bindings_list, - node_to_scope, - node_to_scope_end: HashMap::new(), - reference_to_binding: indexmap::IndexMap::new(), - ref_node_id_to_binding, - node_id_to_scope, - program_scope: ScopeId(0), - } -} - -/// Assign positions to identifiers in a pattern and register as bindings. -fn outlined_assign_pattern_positions( - pattern: &mut react_compiler_ast::patterns::PatternLike, - pos: &mut u32, - kind: react_compiler_ast::scope::BindingKind, - fn_bindings: &mut std::collections::HashMap<String, react_compiler_ast::scope::BindingId>, - bindings_list: &mut Vec<react_compiler_ast::scope::BindingData>, - ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>, -) { - use react_compiler_ast::patterns::PatternLike; - use react_compiler_ast::scope::*; - - match pattern { - PatternLike::Identifier(id) => { - let p = *pos; - *pos += 1; - id.base.start = Some(p); - id.base.node_id = Some(p); - // Add as a binding - if !fn_bindings.contains_key(&id.name) { - let binding_id = BindingId(bindings_list.len() as u32); - fn_bindings.insert(id.name.clone(), binding_id); - bindings_list.push(BindingData { - id: binding_id, - name: id.name.clone(), - kind: kind.clone(), - scope: ScopeId(1), - declaration_type: "VariableDeclarator".to_string(), - declaration_start: Some(p), - declaration_node_id: Some(p), - import: None, - }); - ref_to_binding.insert(p, binding_id); - } else { - let bid = fn_bindings[&id.name]; - ref_to_binding.insert(p, bid); - } - } - PatternLike::ObjectPattern(obj) => { - for prop in &mut obj.properties { - match prop { - react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty( - p_inner, - ) => { - outlined_assign_pattern_positions( - &mut p_inner.value, - pos, - kind.clone(), - fn_bindings, - bindings_list, - ref_to_binding, - ); - } - react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => { - outlined_assign_pattern_positions( - &mut r.argument, - pos, - kind.clone(), - fn_bindings, - bindings_list, - ref_to_binding, - ); - } - } - } - } - PatternLike::ArrayPattern(arr) => { - for elem in arr.elements.iter_mut().flatten() { - outlined_assign_pattern_positions( - elem, - pos, - kind.clone(), - fn_bindings, - bindings_list, - ref_to_binding, - ); - } - } - PatternLike::AssignmentPattern(assign) => { - outlined_assign_pattern_positions( - &mut assign.left, - pos, - kind.clone(), - fn_bindings, - bindings_list, - ref_to_binding, - ); - } - PatternLike::RestElement(rest) => { - outlined_assign_pattern_positions( - &mut rest.argument, - pos, - kind.clone(), - fn_bindings, - bindings_list, - ref_to_binding, - ); - } - _ => {} - } -} - -/// Assign positions to identifiers in a statement body. -fn outlined_assign_stmt_positions( - stmt: &mut react_compiler_ast::statements::Statement, - pos: &mut u32, - fn_bindings: &mut std::collections::HashMap<String, react_compiler_ast::scope::BindingId>, - bindings_list: &mut Vec<react_compiler_ast::scope::BindingData>, - ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>, -) { - use react_compiler_ast::statements::Statement; - - match stmt { - Statement::VariableDeclaration(decl) => { - for declarator in &mut decl.declarations { - // Process init first (references) - if let Some(init) = &mut declarator.init { - outlined_assign_expr_positions(init, pos, fn_bindings, ref_to_binding); - } - // Process pattern (declarations) - outlined_assign_pattern_positions( - &mut declarator.id, - pos, - react_compiler_ast::scope::BindingKind::Let, - fn_bindings, - bindings_list, - ref_to_binding, - ); - } - } - Statement::ReturnStatement(ret) => { - if let Some(arg) = &mut ret.argument { - outlined_assign_expr_positions(arg, pos, fn_bindings, ref_to_binding); - } - } - Statement::ExpressionStatement(expr_stmt) => { - outlined_assign_expr_positions( - &mut expr_stmt.expression, - pos, - fn_bindings, - ref_to_binding, - ); - } - _ => {} - } -} - -/// Assign positions to identifiers in an expression. -fn outlined_assign_expr_positions( - expr: &mut react_compiler_ast::expressions::Expression, - pos: &mut u32, - fn_bindings: &std::collections::HashMap<String, react_compiler_ast::scope::BindingId>, - ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>, -) { - use react_compiler_ast::expressions::*; - - match expr { - Expression::Identifier(id) => { - let p = *pos; - *pos += 1; - id.base.start = Some(p); - id.base.node_id = Some(p); - if let Some(&bid) = fn_bindings.get(&id.name) { - ref_to_binding.insert(p, bid); - } - } - Expression::JSXElement(jsx) => { - // Opening tag - outlined_assign_jsx_name_positions( - &mut jsx.opening_element.name, - pos, - fn_bindings, - ref_to_binding, - ); - for attr in &mut jsx.opening_element.attributes { - match attr { - react_compiler_ast::jsx::JSXAttributeItem::JSXAttribute(a) => { - if let Some(val) = &mut a.value { - outlined_assign_jsx_val_positions( - val, - pos, - fn_bindings, - ref_to_binding, - ); - } - } - react_compiler_ast::jsx::JSXAttributeItem::JSXSpreadAttribute(s) => { - outlined_assign_expr_positions( - &mut s.argument, - pos, - fn_bindings, - ref_to_binding, - ); - } - } - } - for child in &mut jsx.children { - outlined_assign_jsx_child_positions(child, pos, fn_bindings, ref_to_binding); - } - } - Expression::JSXFragment(frag) => { - for child in &mut frag.children { - outlined_assign_jsx_child_positions(child, pos, fn_bindings, ref_to_binding); - } - } - _ => {} - } -} - -fn outlined_assign_jsx_name_positions( - name: &mut react_compiler_ast::jsx::JSXElementName, - pos: &mut u32, - fn_bindings: &std::collections::HashMap<String, react_compiler_ast::scope::BindingId>, - ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>, -) { - match name { - react_compiler_ast::jsx::JSXElementName::JSXIdentifier(id) => { - let p = *pos; - *pos += 1; - id.base.start = Some(p); - id.base.node_id = Some(p); - if let Some(&bid) = fn_bindings.get(&id.name) { - ref_to_binding.insert(p, bid); - } - } - react_compiler_ast::jsx::JSXElementName::JSXMemberExpression(m) => { - outlined_assign_jsx_member_positions(m, pos, fn_bindings, ref_to_binding); - } - _ => {} - } -} - -fn outlined_assign_jsx_member_positions( - member: &mut react_compiler_ast::jsx::JSXMemberExpression, - pos: &mut u32, - fn_bindings: &std::collections::HashMap<String, react_compiler_ast::scope::BindingId>, - ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>, -) { - match &mut *member.object { - react_compiler_ast::jsx::JSXMemberExprObject::JSXIdentifier(id) => { - let p = *pos; - *pos += 1; - id.base.start = Some(p); - id.base.node_id = Some(p); - if let Some(&bid) = fn_bindings.get(&id.name) { - ref_to_binding.insert(p, bid); - } - } - react_compiler_ast::jsx::JSXMemberExprObject::JSXMemberExpression(inner) => { - outlined_assign_jsx_member_positions(inner, pos, fn_bindings, ref_to_binding); - } - } -} - -fn outlined_assign_jsx_val_positions( - val: &mut react_compiler_ast::jsx::JSXAttributeValue, - pos: &mut u32, - fn_bindings: &std::collections::HashMap<String, react_compiler_ast::scope::BindingId>, - ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>, -) { - match val { - react_compiler_ast::jsx::JSXAttributeValue::JSXExpressionContainer(c) => { - if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(e) = - &mut c.expression - { - outlined_assign_expr_positions(e, pos, fn_bindings, ref_to_binding); - } - } - react_compiler_ast::jsx::JSXAttributeValue::JSXElement(el) => { - let mut expr = react_compiler_ast::expressions::Expression::JSXElement(el.clone()); - outlined_assign_expr_positions(&mut expr, pos, fn_bindings, ref_to_binding); - if let react_compiler_ast::expressions::Expression::JSXElement(new_el) = expr { - **el = *new_el; - } - } - _ => {} - } -} - -fn outlined_assign_jsx_child_positions( - child: &mut react_compiler_ast::jsx::JSXChild, - pos: &mut u32, - fn_bindings: &std::collections::HashMap<String, react_compiler_ast::scope::BindingId>, - ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>, -) { - match child { - react_compiler_ast::jsx::JSXChild::JSXExpressionContainer(c) => { - if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(e) = - &mut c.expression - { - outlined_assign_expr_positions(e, pos, fn_bindings, ref_to_binding); - } - } - react_compiler_ast::jsx::JSXChild::JSXElement(el) => { - let mut expr = - react_compiler_ast::expressions::Expression::JSXElement(Box::new(*el.clone())); - outlined_assign_expr_positions(&mut expr, pos, fn_bindings, ref_to_binding); - if let react_compiler_ast::expressions::Expression::JSXElement(new_el) = expr { - **el = *new_el; - } - } - react_compiler_ast::jsx::JSXChild::JSXFragment(frag) => { - for inner in &mut frag.children { - outlined_assign_jsx_child_positions(inner, pos, fn_bindings, ref_to_binding); - } - } - _ => {} - } -} -// end of outlined function helpers - -/// Run the compilation pipeline passes on an HIR function (everything after lowering). -/// -/// This is extracted from `compile_fn` to allow reuse for outlined functions. -/// Returns the compiled CodegenFunction on success. -fn run_pipeline_passes( - hir: &mut react_compiler_hir::HirFunction, - env: &mut Environment, - context: &mut ProgramContext, -) -> Result<CodegenFunction, CompilerError> { - react_compiler_optimization::prune_maybe_throws(hir, &mut env.functions)?; - - react_compiler_optimization::drop_manual_memoization(hir, env)?; - - react_compiler_optimization::inline_immediately_invoked_function_expressions(hir, env); - - react_compiler_optimization::merge_consecutive_blocks::merge_consecutive_blocks( - hir, - &mut env.functions, - ); - - react_compiler_ssa::enter_ssa(hir, env).map_err(|diag| { - let loc = diag.primary_location().cloned(); - let mut err = CompilerError::new(); - err.push_error_detail(react_compiler_diagnostics::CompilerErrorDetail { - category: diag.category, - reason: diag.reason, - description: diag.description, - loc, - suggestions: diag.suggestions, - }); - err - })?; - - react_compiler_ssa::eliminate_redundant_phi(hir, env); - - react_compiler_optimization::constant_propagation(hir, env); - - react_compiler_typeinference::infer_types(hir, env)?; - - if env.enable_validations() { - if env.config.validate_hooks_usage { - react_compiler_validation::validate_hooks_usage(hir, env)?; - } - } - - react_compiler_optimization::optimize_props_method_calls(hir, env); - - react_compiler_inference::analyse_functions(hir, env, &mut |_inner_func, _inner_env| {})?; - - if env.has_invariant_errors() { - return Err(env.take_invariant_errors()); - } - - react_compiler_inference::infer_mutation_aliasing_effects(hir, env, false)?; - - if env.output_mode == OutputMode::Ssr { - react_compiler_optimization::optimize_for_ssr(hir, env); - } - - react_compiler_optimization::dead_code_elimination(hir, env); - - react_compiler_optimization::prune_maybe_throws(hir, &mut env.functions)?; - - react_compiler_inference::infer_mutation_aliasing_ranges(hir, env, false)?; - - if env.enable_validations() { - react_compiler_validation::validate_locals_not_reassigned_after_render(hir, env); - - if env.config.validate_ref_access_during_render { - react_compiler_validation::validate_no_ref_access_in_render(hir, env); - } - - if env.config.validate_no_set_state_in_render { - react_compiler_validation::validate_no_set_state_in_render(hir, env)?; - } - - react_compiler_validation::validate_no_freezing_known_mutable_functions(hir, env); - } - - react_compiler_inference::infer_reactive_places(hir, env)?; - - if env.enable_validations() { - react_compiler_validation::validate_exhaustive_dependencies(hir, env)?; - } - - react_compiler_ssa::rewrite_instruction_kinds_based_on_reassignment(hir, env)?; - - if env.enable_memoization() { - react_compiler_inference::infer_reactive_scope_variables(hir, env)?; - } - - let fbt_operands = - react_compiler_inference::memoize_fbt_and_macro_operands_in_same_scope(hir, env); - - // Don't run outline_jsx on outlined functions (they're already outlined) - - if env.config.enable_name_anonymous_functions { - react_compiler_optimization::name_anonymous_functions(hir, env); - } - - if env.config.enable_function_outlining { - react_compiler_optimization::outline_functions(hir, env, &fbt_operands); - } - - react_compiler_inference::align_method_call_scopes(hir, env); - react_compiler_inference::align_object_method_scopes(hir, env); - - react_compiler_optimization::prune_unused_labels_hir(hir); - - react_compiler_inference::align_reactive_scopes_to_block_scopes_hir(hir, env); - react_compiler_inference::merge_overlapping_reactive_scopes_hir(hir, env); - - react_compiler_inference::build_reactive_scope_terminals_hir(hir, env); - react_compiler_inference::flatten_reactive_loops_hir(hir); - react_compiler_inference::flatten_scopes_with_hooks_or_use_hir(hir, env)?; - react_compiler_inference::propagate_scope_dependencies_hir(hir, env); - let mut reactive_fn = react_compiler_reactive_scopes::build_reactive_function(hir, env)?; - - react_compiler_reactive_scopes::assert_well_formed_break_targets(&reactive_fn, env); - - react_compiler_reactive_scopes::prune_unused_labels(&mut reactive_fn, env)?; - - react_compiler_reactive_scopes::assert_scope_instructions_within_scopes(&reactive_fn, env)?; - - react_compiler_reactive_scopes::prune_non_escaping_scopes(&mut reactive_fn, env)?; - react_compiler_reactive_scopes::prune_non_reactive_dependencies(&mut reactive_fn, env); - react_compiler_reactive_scopes::prune_unused_scopes(&mut reactive_fn, env)?; - react_compiler_reactive_scopes::merge_reactive_scopes_that_invalidate_together( - &mut reactive_fn, - env, - )?; - react_compiler_reactive_scopes::prune_always_invalidating_scopes(&mut reactive_fn, env)?; - react_compiler_reactive_scopes::propagate_early_returns(&mut reactive_fn, env); - react_compiler_reactive_scopes::prune_unused_lvalues(&mut reactive_fn, env); - react_compiler_reactive_scopes::promote_used_temporaries(&mut reactive_fn, env); - react_compiler_reactive_scopes::extract_scope_declarations_from_destructuring( - &mut reactive_fn, - env, - )?; - react_compiler_reactive_scopes::stabilize_block_ids(&mut reactive_fn, env); - - let unique_identifiers = - react_compiler_reactive_scopes::rename_variables(&mut reactive_fn, env); - for name in &unique_identifiers { - context.add_new_reference(name.clone()); - } - - react_compiler_reactive_scopes::prune_hoisted_contexts(&mut reactive_fn, env)?; - - if env.config.enable_preserve_existing_memoization_guarantees - || env.config.validate_preserve_existing_memoization_guarantees - { - react_compiler_validation::validate_preserved_manual_memoization(&reactive_fn, env); - } - - let codegen_result = react_compiler_reactive_scopes::codegen_function( - &reactive_fn, - env, - unique_identifiers, - fbt_operands, - )?; - - Ok(CodegenFunction { - loc: codegen_result.loc, - id: codegen_result.id, - name_hint: codegen_result.name_hint, - params: codegen_result.params, - body: codegen_result.body, - generator: codegen_result.generator, - is_async: codegen_result.is_async, - memo_slots_used: codegen_result.memo_slots_used, - memo_blocks: codegen_result.memo_blocks, - memo_values: codegen_result.memo_values, - pruned_memo_blocks: codegen_result.pruned_memo_blocks, - pruned_memo_values: codegen_result.pruned_memo_values, - outlined: codegen_result - .outlined - .into_iter() - .map(|o| OutlinedFunction { - func: CodegenFunction { - loc: o.func.loc, - id: o.func.id, - name_hint: o.func.name_hint, - params: o.func.params, - body: o.func.body, - generator: o.func.generator, - is_async: o.func.is_async, - memo_slots_used: o.func.memo_slots_used, - memo_blocks: o.func.memo_blocks, - memo_values: o.func.memo_values, - pruned_memo_blocks: o.func.pruned_memo_blocks, - pruned_memo_values: o.func.pruned_memo_values, - outlined: Vec::new(), - }, - fn_type: o.fn_type, - }) - .collect(), - }) -} - -/// Log CompilerError diagnostics as CompileError events, matching TS `env.logErrors()` behavior. -/// These are logged for telemetry/lint output but not accumulated as compile errors. -fn log_errors_as_events(errors: &CompilerError, context: &mut ProgramContext) { - // Use the source_filename from the AST (set by parser's sourceFilename option). - // This is stored on the Environment during lowering. - let source_filename = context.source_filename(); - for detail in &errors.details { - let detail_info = match detail { - react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => { - let items: Option<Vec<CompilerErrorItemInfo>> = { - let v: Vec<CompilerErrorItemInfo> = d - .details - .iter() - .map(|item| match item { - react_compiler_diagnostics::CompilerDiagnosticDetail::Error { - loc, - message, - identifier_name, - } => CompilerErrorItemInfo { - kind: "error".to_string(), - loc: loc.as_ref().map(|l| LoggerSourceLocation { - start: LoggerPosition { - line: l.start.line, - column: l.start.column, - index: l.start.index, - }, - end: LoggerPosition { - line: l.end.line, - column: l.end.column, - index: l.end.index, - }, - filename: source_filename.clone(), - identifier_name: identifier_name.clone(), - }), - message: message.clone(), - }, - react_compiler_diagnostics::CompilerDiagnosticDetail::Hint { - message, - } => CompilerErrorItemInfo { - kind: "hint".to_string(), - loc: None, - message: Some(message.clone()), - }, - }) - .collect(); - if v.is_empty() { None } else { Some(v) } - }; - CompilerErrorDetailInfo { - category: format!("{:?}", d.category), - reason: d.reason.clone(), - description: d.description.clone(), - severity: format!("{:?}", d.logged_severity()), - suggestions: None, - details: items, - loc: None, - } - } - react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => { - CompilerErrorDetailInfo { - category: format!("{:?}", d.category), - reason: d.reason.clone(), - description: d.description.clone(), - severity: format!("{:?}", d.logged_severity()), - suggestions: None, - details: None, - loc: None, - } - } - }; - context.log_event(super::compile_result::LoggerEvent::CompileError { - fn_loc: None, - detail: detail_info, - }); - } -} diff --git a/compiler/crates/react_compiler/src/entrypoint/plugin_options.rs b/compiler/crates/react_compiler/src/entrypoint/plugin_options.rs deleted file mode 100644 index 5f447775a4a7..000000000000 --- a/compiler/crates/react_compiler/src/entrypoint/plugin_options.rs +++ /dev/null @@ -1,118 +0,0 @@ -use react_compiler_hir::environment_config::EnvironmentConfig; -use serde::{Deserialize, Serialize}; - -/// Target configuration for the compiler -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum CompilerTarget { - /// Standard React version target - Version(String), // "17", "18", "19" - /// Meta-internal target with custom runtime module - MetaInternal { - kind: String, // "donotuse_meta_internal" - #[serde(rename = "runtimeModule")] - runtime_module: String, - }, -} - -/// Gating configuration -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct GatingConfig { - pub source: String, - #[serde(rename = "importSpecifierName")] - pub import_specifier_name: String, -} - -/// Dynamic gating configuration -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DynamicGatingConfig { - pub source: String, -} - -/// Serializable plugin options, pre-resolved by the JS shim. -/// JS-only values (sources function, logger, etc.) are resolved before -/// being sent to Rust. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PluginOptions { - // Pre-resolved by JS - pub should_compile: bool, - pub enable_reanimated: bool, - pub is_dev: bool, - pub filename: Option<String>, - - // Pass-through options - #[serde(default = "default_compilation_mode")] - pub compilation_mode: String, - #[serde(default = "default_panic_threshold")] - pub panic_threshold: String, - #[serde(default = "default_target")] - pub target: CompilerTarget, - #[serde(default)] - pub gating: Option<GatingConfig>, - #[serde(default)] - pub dynamic_gating: Option<DynamicGatingConfig>, - #[serde(default)] - pub no_emit: bool, - #[serde(default)] - pub output_mode: Option<String>, - #[serde(default)] - pub eslint_suppression_rules: Option<Vec<String>>, - #[serde(default = "default_true")] - pub flow_suppressions: bool, - #[serde(default)] - pub ignore_use_no_forget: bool, - #[serde(default)] - pub custom_opt_out_directives: Option<Vec<String>>, - #[serde(default)] - pub environment: EnvironmentConfig, - - /// Source code of the file being compiled (passed from Babel plugin for fast refresh hash). - #[serde(default, rename = "__sourceCode")] - pub source_code: Option<String>, - - /// Enable profiling timing data collection. - #[serde(default, rename = "__profiling")] - pub profiling: bool, - - /// Enable debug logging (HIR formatting after each pass). - /// Only set to true when a logger with debugLogIRs is configured on the JS side. - #[serde(default, rename = "__debug")] - pub debug: bool, -} - -fn default_compilation_mode() -> String { - "infer".to_string() -} - -fn default_panic_threshold() -> String { - "none".to_string() -} - -fn default_target() -> CompilerTarget { - CompilerTarget::Version("19".to_string()) -} - -fn default_true() -> bool { - true -} - -/// Output mode for the compiler, derived from PluginOptions. -/// Matches the TS `compilerOutputMode` logic in Program.ts. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum CompilerOutputMode { - Ssr, - Client, - Lint, -} - -impl CompilerOutputMode { - pub fn from_opts(opts: &PluginOptions) -> Self { - match opts.output_mode.as_deref() { - Some("ssr") => Self::Ssr, - Some("lint") => Self::Lint, - _ if opts.no_emit => Self::Lint, - _ => Self::Client, - } - } -} diff --git a/compiler/crates/react_compiler/src/entrypoint/program.rs b/compiler/crates/react_compiler/src/entrypoint/program.rs deleted file mode 100644 index 8e6a16c20941..000000000000 --- a/compiler/crates/react_compiler/src/entrypoint/program.rs +++ /dev/null @@ -1,4185 +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. - -//! Main entrypoint for the React Compiler. -//! -//! This module is a port of Program.ts from the TypeScript compiler. It orchestrates -//! the compilation of a program by: -//! 1. Checking if compilation should be skipped -//! 2. Validating restricted imports -//! 3. Finding program-level suppressions -//! 4. Discovering functions to compile (components, hooks) -//! 5. Processing each function through the compilation pipeline -//! 6. Applying compiled functions back to the AST - -use std::collections::HashMap; -use std::collections::HashSet; - -use react_compiler_ast::File; -use react_compiler_ast::Program; -use react_compiler_ast::common::BaseNode; -use react_compiler_ast::declarations::Declaration; -use react_compiler_ast::declarations::ExportDefaultDecl; -use react_compiler_ast::declarations::ExportDefaultDeclaration; -use react_compiler_ast::declarations::ImportSpecifier; -use react_compiler_ast::declarations::ModuleExportName; -use react_compiler_ast::expressions::*; -use react_compiler_ast::patterns::PatternLike; -use react_compiler_ast::scope::ScopeId; -use react_compiler_ast::scope::ScopeInfo; -use react_compiler_ast::statements::*; -use react_compiler_ast::visitor::AstWalker; -use react_compiler_ast::visitor::MutVisitor; -use react_compiler_ast::visitor::VisitResult; -use react_compiler_ast::visitor::Visitor; -use react_compiler_ast::visitor::walk_program_mut; -use react_compiler_diagnostics::CompilerError; -use react_compiler_diagnostics::CompilerErrorDetail; -use react_compiler_diagnostics::CompilerErrorOrDiagnostic; -use react_compiler_diagnostics::ErrorCategory; -use react_compiler_diagnostics::SourceLocation; -use react_compiler_hir::ReactFunctionType; -use react_compiler_hir::environment_config::EnvironmentConfig; -use react_compiler_lowering::FunctionNode; - -use super::compile_result::BindingRenameInfo; -use super::compile_result::CodegenFunction; -use super::compile_result::CompileResult; -use super::compile_result::CompilerErrorDetailInfo; -use super::compile_result::CompilerErrorInfo; -use super::compile_result::CompilerErrorItemInfo; -use super::compile_result::DebugLogEntry; -use super::compile_result::LoggerEvent; -use super::compile_result::LoggerPosition; -use super::compile_result::LoggerSourceLocation; -use super::compile_result::LoggerSuggestionInfo; -use super::compile_result::LoggerSuggestionOp; -use super::compile_result::OrderedLogItem; -use super::imports::ProgramContext; -use super::imports::add_imports_to_program; -use super::imports::get_react_compiler_runtime_module; -use super::imports::validate_restricted_imports; -use super::pipeline; -use super::plugin_options::CompilerOutputMode; -use super::plugin_options::GatingConfig; -use super::plugin_options::PluginOptions; -use super::suppression::SuppressionRange; -use super::suppression::filter_suppressions_that_affect_function; -use super::suppression::find_program_suppressions; -use super::suppression::suppressions_to_compiler_error; - -// ----------------------------------------------------------------------- -// Constants -// ----------------------------------------------------------------------- - -const DEFAULT_ESLINT_SUPPRESSIONS: &[&str] = - &["react-hooks/exhaustive-deps", "react-hooks/rules-of-hooks"]; - -/// Directives that opt a function into memoization -const OPT_IN_DIRECTIVES: &[&str] = &["use forget", "use memo"]; - -/// Directives that opt a function out of memoization -const OPT_OUT_DIRECTIVES: &[&str] = &["use no forget", "use no memo"]; - -// ----------------------------------------------------------------------- -// Internal types -// ----------------------------------------------------------------------- - -/// A function found in the program that should be compiled -#[allow(dead_code)] -struct CompileSource<'a> { - kind: CompileSourceKind, - fn_node: FunctionNode<'a>, - /// Location of this function in the AST for logging - fn_name: Option<String>, - fn_loc: Option<SourceLocation>, - /// Original AST source location (with index and filename) for logger events. - fn_ast_loc: Option<react_compiler_ast::common::SourceLocation>, - fn_start: Option<u32>, - fn_end: Option<u32>, - fn_node_id: Option<u32>, - fn_type: ReactFunctionType, - /// Directives from the function body (for opt-in/opt-out checks) - body_directives: Vec<Directive>, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum CompileSourceKind { - Original, - #[allow(dead_code)] - Outlined, -} - -// ----------------------------------------------------------------------- -// Directive helpers -// ----------------------------------------------------------------------- - -/// Check if any opt-in directive is present in the given directives. -/// Returns the first matching directive, or None. -/// -/// Also checks for dynamic gating directives (`use memo if(...)`) -fn try_find_directive_enabling_memoization<'a>( - directives: &'a [Directive], - opts: &PluginOptions, -) -> Result<Option<&'a Directive>, CompilerError> { - // Check standard opt-in directives - let opt_in = directives - .iter() - .find(|d| OPT_IN_DIRECTIVES.contains(&d.value.value.as_str())); - if let Some(directive) = opt_in { - return Ok(Some(directive)); - } - - // Check dynamic gating directives - match find_directives_dynamic_gating(directives, opts) { - Ok(Some(result)) => Ok(Some(result.directive)), - Ok(None) => Ok(None), - Err(e) => Err(e), - } -} - -/// Check if any opt-out directive is present in the given directives. -fn find_directive_disabling_memoization<'a>( - directives: &'a [Directive], - opts: &PluginOptions, -) -> Option<&'a Directive> { - if let Some(ref custom_directives) = opts.custom_opt_out_directives { - directives - .iter() - .find(|d| custom_directives.contains(&d.value.value)) - } else { - directives - .iter() - .find(|d| OPT_OUT_DIRECTIVES.contains(&d.value.value.as_str())) - } -} - -/// Result of a dynamic gating directive parse. -struct DynamicGatingResult<'a> { - #[allow(dead_code)] - directive: &'a Directive, - gating: GatingConfig, -} - -/// Check for dynamic gating directives like `use memo if(identifier)`. -/// Returns the directive and gating config if found, or an error if malformed. -fn find_directives_dynamic_gating<'a>( - directives: &'a [Directive], - opts: &PluginOptions, -) -> Result<Option<DynamicGatingResult<'a>>, CompilerError> { - let dynamic_gating = match &opts.dynamic_gating { - Some(dg) => dg, - None => return Ok(None), - }; - - let mut errors: Vec<CompilerErrorDetail> = Vec::new(); - let mut matches: Vec<(&'a Directive, String)> = Vec::new(); - - for directive in directives { - if let Some(ident) = parse_dynamic_gating_directive(&directive.value.value) { - if is_valid_identifier(ident) { - matches.push((directive, ident.to_string())); - } else { - let mut detail = CompilerErrorDetail::new( - ErrorCategory::Gating, - "Dynamic gating directive is not a valid JavaScript identifier", - ) - .with_description(format!("Found '{}'", directive.value.value)); - detail.loc = directive.base.loc.as_ref().map(convert_loc); - errors.push(detail); - } - } - } - - if !errors.is_empty() { - let mut err = CompilerError::new(); - for e in errors { - err.push_error_detail(e); - } - return Err(err); - } - - if matches.len() > 1 { - let names: Vec<String> = matches.iter().map(|(d, _)| d.value.value.clone()).collect(); - let mut err = CompilerError::new(); - let mut detail = CompilerErrorDetail::new( - ErrorCategory::Gating, - "Multiple dynamic gating directives found", - ) - .with_description(format!( - "Expected a single directive but found [{}]", - names.join(", ") - )); - detail.loc = matches[0].0.base.loc.as_ref().map(convert_loc); - err.push_error_detail(detail); - return Err(err); - } - - if matches.len() == 1 { - Ok(Some(DynamicGatingResult { - directive: matches[0].0, - gating: GatingConfig { - source: dynamic_gating.source.clone(), - import_specifier_name: matches[0].1.clone(), - }, - })) - } else { - Ok(None) - } -} - -/// Parse a `use memo if(<condition>)` directive, returning the condition. -/// Exact equivalent of the TS DYNAMIC_GATING_DIRECTIVE regex -/// `^use memo if\(([^\)]*)\)$`: the condition may not contain `)` and the -/// directive must end at the closing paren. -fn parse_dynamic_gating_directive(value: &str) -> Option<&str> { - let condition = value.strip_prefix("use memo if(")?.strip_suffix(')')?; - if condition.contains(')') { - return None; - } - Some(condition) -} - -/// Simple check for valid JavaScript identifier (alphanumeric + underscore + $, starting with letter/$/_ ) -/// Also rejects reserved words like `true`, `false`, `null`, etc. -fn is_valid_identifier(s: &str) -> bool { - if s.is_empty() { - return false; - } - let mut chars = s.chars(); - let first = chars.next().unwrap(); - if !first.is_alphabetic() && first != '_' && first != '$' { - return false; - } - if !chars.all(|c| c.is_alphanumeric() || c == '_' || c == '$') { - return false; - } - // Check for reserved words (matching Babel's t.isValidIdentifier) - !matches!( - s, - "break" - | "case" - | "catch" - | "continue" - | "debugger" - | "default" - | "do" - | "else" - | "finally" - | "for" - | "function" - | "if" - | "in" - | "instanceof" - | "new" - | "return" - | "switch" - | "this" - | "throw" - | "try" - | "typeof" - | "var" - | "void" - | "while" - | "with" - | "class" - | "const" - | "enum" - | "export" - | "extends" - | "import" - | "super" - | "implements" - | "interface" - | "let" - | "package" - | "private" - | "protected" - | "public" - | "static" - | "yield" - | "null" - | "true" - | "false" - | "delete" - ) -} - -// ----------------------------------------------------------------------- -// Name helpers -// ----------------------------------------------------------------------- - -/// Check if a string follows the React hook naming convention (use[A-Z0-9]...). -fn is_hook_name(s: &str) -> bool { - let bytes = s.as_bytes(); - bytes.len() >= 4 - && bytes[0] == b'u' - && bytes[1] == b's' - && bytes[2] == b'e' - && bytes - .get(3) - .map_or(false, |c| c.is_ascii_uppercase() || c.is_ascii_digit()) -} - -/// Check if a name looks like a React component (starts with uppercase letter). -fn is_component_name(name: &str) -> bool { - name.chars() - .next() - .map_or(false, |c| c.is_ascii_uppercase()) -} - -/// Check if an expression is a hook call (identifier with hook name, or -/// member expression `PascalCase.useHook`). -fn expr_is_hook(expr: &Expression) -> bool { - match expr { - Expression::Identifier(id) => is_hook_name(&id.name), - Expression::MemberExpression(member) => { - if member.computed { - return false; - } - // Property must be a hook name - if !expr_is_hook(&member.property) { - return false; - } - // Object must be a PascalCase identifier - if let Expression::Identifier(obj) = member.object.as_ref() { - obj.name - .chars() - .next() - .map_or(false, |c| c.is_ascii_uppercase()) - } else { - false - } - } - _ => false, - } -} - -/// Check if an expression is a React API call (e.g., `forwardRef` or `React.forwardRef`). -#[allow(dead_code)] -fn is_react_api(expr: &Expression, function_name: &str) -> bool { - match expr { - Expression::Identifier(id) => id.name == function_name, - Expression::MemberExpression(member) => { - if let Expression::Identifier(obj) = member.object.as_ref() { - if obj.name == "React" { - if let Expression::Identifier(prop) = member.property.as_ref() { - return prop.name == function_name; - } - } - } - false - } - _ => false, - } -} - -/// Get the inferred function name from a function's context. -/// -/// For FunctionDeclaration: uses the `id` field. -/// For FunctionExpression/ArrowFunctionExpression: infers from parent context -/// (VariableDeclarator, etc.) which is passed explicitly since we don't have Babel paths. -fn get_function_name_from_id(id: Option<&Identifier>) -> Option<String> { - id.map(|id| id.name.clone()) -} - -// ----------------------------------------------------------------------- -// AST traversal helpers -// ----------------------------------------------------------------------- - -/// Check if an expression is a "non-node" return value (indicating the function -/// is not a React component). This matches the TS `isNonNode` function. -fn is_non_node(expr: &Expression) -> bool { - matches!( - expr, - Expression::ObjectExpression(_) - | Expression::ArrowFunctionExpression(_) - | Expression::FunctionExpression(_) - | Expression::BigIntLiteral(_) - | Expression::ClassExpression(_) - | Expression::NewExpression(_) - ) -} - -/// Recursively check if a function body returns a non-React-node value. -/// Walks all return statements in the function (not in nested functions). -/// The last return statement visited (in DFS order) determines the result, -/// rather than short-circuiting on the first non-node return. -fn returns_non_node_in_stmts(stmts: &[Statement]) -> bool { - let mut result = false; - for stmt in stmts { - returns_non_node_in_stmt(stmt, &mut result); - } - result -} - -fn returns_non_node_in_stmt(stmt: &Statement, result: &mut bool) { - match stmt { - Statement::ReturnStatement(ret) => { - *result = match &ret.argument { - Some(arg) => is_non_node(arg), - None => true, // bare `return;` with no argument is a non-node value - }; - } - Statement::BlockStatement(block) => { - for s in &block.body { - returns_non_node_in_stmt(s, result); - } - } - Statement::IfStatement(if_stmt) => { - returns_non_node_in_stmt(&if_stmt.consequent, result); - if let Some(ref alt) = if_stmt.alternate { - returns_non_node_in_stmt(alt, result); - } - } - Statement::ForStatement(for_stmt) => returns_non_node_in_stmt(&for_stmt.body, result), - Statement::WhileStatement(while_stmt) => returns_non_node_in_stmt(&while_stmt.body, result), - Statement::DoWhileStatement(do_while) => returns_non_node_in_stmt(&do_while.body, result), - Statement::ForInStatement(for_in) => returns_non_node_in_stmt(&for_in.body, result), - Statement::ForOfStatement(for_of) => returns_non_node_in_stmt(&for_of.body, result), - Statement::SwitchStatement(switch) => { - for case in &switch.cases { - for s in &case.consequent { - returns_non_node_in_stmt(s, result); - } - } - } - Statement::TryStatement(try_stmt) => { - for s in &try_stmt.block.body { - returns_non_node_in_stmt(s, result); - } - if let Some(ref handler) = try_stmt.handler { - for s in &handler.body.body { - returns_non_node_in_stmt(s, result); - } - } - if let Some(ref finalizer) = try_stmt.finalizer { - for s in &finalizer.body { - returns_non_node_in_stmt(s, result); - } - } - } - Statement::LabeledStatement(labeled) => returns_non_node_in_stmt(&labeled.body, result), - Statement::WithStatement(with) => returns_non_node_in_stmt(&with.body, result), - // Skip nested function/class declarations -- they have their own returns - Statement::FunctionDeclaration(_) | Statement::ClassDeclaration(_) => {} - // Unmodeled statements are opaque to return analysis; functions - // containing them bail out in lowering before this matters. - Statement::Unknown(_) => {} - _ => {} - } -} - -/// Check if a function returns non-node values. -/// For arrow functions with expression body, checks the expression directly. -/// For block bodies, walks the statements. -fn returns_non_node_fn(params: &[PatternLike], body: &FunctionBody) -> bool { - let _ = params; - match body { - FunctionBody::Block(block) => returns_non_node_in_stmts(&block.body), - FunctionBody::Expression(expr) => is_non_node(expr), - } -} - -/// Check if a function body calls hooks or creates JSX. -/// Traverses the function body (not nested functions) looking for: -/// - CallExpression where callee is a hook -/// - JSXElement or JSXFragment -fn calls_hooks_or_creates_jsx_in_stmts(stmts: &[Statement]) -> bool { - for stmt in stmts { - if calls_hooks_or_creates_jsx_in_stmt(stmt) { - return true; - } - } - false -} - -fn calls_hooks_or_creates_jsx_in_stmt(stmt: &Statement) -> bool { - match stmt { - Statement::ExpressionStatement(expr_stmt) => { - calls_hooks_or_creates_jsx_in_expr(&expr_stmt.expression) - } - Statement::ReturnStatement(ret) => { - if let Some(ref arg) = ret.argument { - calls_hooks_or_creates_jsx_in_expr(arg) - } else { - false - } - } - Statement::VariableDeclaration(var_decl) => { - for decl in &var_decl.declarations { - if let Some(ref init) = decl.init { - if calls_hooks_or_creates_jsx_in_expr(init) { - return true; - } - } - } - false - } - Statement::BlockStatement(block) => calls_hooks_or_creates_jsx_in_stmts(&block.body), - Statement::IfStatement(if_stmt) => { - calls_hooks_or_creates_jsx_in_expr(&if_stmt.test) - || calls_hooks_or_creates_jsx_in_stmt(&if_stmt.consequent) - || if_stmt - .alternate - .as_ref() - .map_or(false, |alt| calls_hooks_or_creates_jsx_in_stmt(alt)) - } - Statement::ForStatement(for_stmt) => { - if let Some(ref init) = for_stmt.init { - match init.as_ref() { - ForInit::Expression(expr) => { - if calls_hooks_or_creates_jsx_in_expr(expr) { - return true; - } - } - ForInit::VariableDeclaration(var_decl) => { - for decl in &var_decl.declarations { - if let Some(ref init) = decl.init { - if calls_hooks_or_creates_jsx_in_expr(init) { - return true; - } - } - } - } - } - } - if let Some(ref test) = for_stmt.test { - if calls_hooks_or_creates_jsx_in_expr(test) { - return true; - } - } - if let Some(ref update) = for_stmt.update { - if calls_hooks_or_creates_jsx_in_expr(update) { - return true; - } - } - calls_hooks_or_creates_jsx_in_stmt(&for_stmt.body) - } - Statement::WhileStatement(while_stmt) => { - calls_hooks_or_creates_jsx_in_expr(&while_stmt.test) - || calls_hooks_or_creates_jsx_in_stmt(&while_stmt.body) - } - Statement::DoWhileStatement(do_while) => { - calls_hooks_or_creates_jsx_in_stmt(&do_while.body) - || calls_hooks_or_creates_jsx_in_expr(&do_while.test) - } - Statement::ForInStatement(for_in) => { - calls_hooks_or_creates_jsx_in_expr(&for_in.right) - || calls_hooks_or_creates_jsx_in_stmt(&for_in.body) - } - Statement::ForOfStatement(for_of) => { - calls_hooks_or_creates_jsx_in_expr(&for_of.right) - || calls_hooks_or_creates_jsx_in_stmt(&for_of.body) - } - Statement::SwitchStatement(switch) => { - if calls_hooks_or_creates_jsx_in_expr(&switch.discriminant) { - return true; - } - for case in &switch.cases { - if let Some(ref test) = case.test { - if calls_hooks_or_creates_jsx_in_expr(test) { - return true; - } - } - if calls_hooks_or_creates_jsx_in_stmts(&case.consequent) { - return true; - } - } - false - } - Statement::ThrowStatement(throw) => calls_hooks_or_creates_jsx_in_expr(&throw.argument), - Statement::TryStatement(try_stmt) => { - if calls_hooks_or_creates_jsx_in_stmts(&try_stmt.block.body) { - return true; - } - if let Some(ref handler) = try_stmt.handler { - if calls_hooks_or_creates_jsx_in_stmts(&handler.body.body) { - return true; - } - } - if let Some(ref finalizer) = try_stmt.finalizer { - if calls_hooks_or_creates_jsx_in_stmts(&finalizer.body) { - return true; - } - } - false - } - Statement::LabeledStatement(labeled) => calls_hooks_or_creates_jsx_in_stmt(&labeled.body), - Statement::WithStatement(with) => { - calls_hooks_or_creates_jsx_in_expr(&with.object) - || calls_hooks_or_creates_jsx_in_stmt(&with.body) - } - // Recurse into class body to find JSX/hooks in methods (matching TS behavior - // where Babel's traverse enters class bodies, only skipping nested functions) - Statement::FunctionDeclaration(_) => false, - Statement::ClassDeclaration(class) => calls_hooks_or_creates_jsx_in_class_body(&class.body), - // Unmodeled statements are preserved verbatim and never compiled, so - // hook/JSX content inside them cannot affect compilation decisions. - Statement::Unknown(_) => false, - _ => false, - } -} - -fn calls_hooks_or_creates_jsx_in_expr(expr: &Expression) -> bool { - match expr { - // JSX creates - Expression::JSXElement(_) | Expression::JSXFragment(_) => true, - - // Hook calls - Expression::CallExpression(call) => { - if expr_is_hook(&call.callee) { - return true; - } - // Also check arguments for JSX/hooks (but not nested functions) - if calls_hooks_or_creates_jsx_in_expr(&call.callee) { - return true; - } - for arg in &call.arguments { - // Skip function arguments -- they are nested functions - if matches!( - arg, - Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) - ) { - continue; - } - if calls_hooks_or_creates_jsx_in_expr(arg) { - return true; - } - } - false - } - Expression::OptionalCallExpression(call) => { - // Note: OptionalCallExpression is NOT treated as a hook call for - // the purpose of determining function type. The TS code only checks - // regular CallExpression nodes in callsHooksOrCreatesJsx. - // We still recurse into the callee and arguments to find other - // hook calls or JSX. - if calls_hooks_or_creates_jsx_in_expr(&call.callee) { - return true; - } - for arg in &call.arguments { - if matches!( - arg, - Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) - ) { - continue; - } - if calls_hooks_or_creates_jsx_in_expr(arg) { - return true; - } - } - false - } - - // Binary/logical - Expression::BinaryExpression(bin) => { - calls_hooks_or_creates_jsx_in_expr(&bin.left) - || calls_hooks_or_creates_jsx_in_expr(&bin.right) - } - Expression::LogicalExpression(log) => { - calls_hooks_or_creates_jsx_in_expr(&log.left) - || calls_hooks_or_creates_jsx_in_expr(&log.right) - } - Expression::ConditionalExpression(cond) => { - calls_hooks_or_creates_jsx_in_expr(&cond.test) - || calls_hooks_or_creates_jsx_in_expr(&cond.consequent) - || calls_hooks_or_creates_jsx_in_expr(&cond.alternate) - } - Expression::AssignmentExpression(assign) => { - calls_hooks_or_creates_jsx_in_expr(&assign.right) - } - Expression::SequenceExpression(seq) => seq - .expressions - .iter() - .any(|e| calls_hooks_or_creates_jsx_in_expr(e)), - Expression::UnaryExpression(unary) => calls_hooks_or_creates_jsx_in_expr(&unary.argument), - Expression::UpdateExpression(update) => { - calls_hooks_or_creates_jsx_in_expr(&update.argument) - } - Expression::MemberExpression(member) => { - calls_hooks_or_creates_jsx_in_expr(&member.object) - || calls_hooks_or_creates_jsx_in_expr(&member.property) - } - Expression::OptionalMemberExpression(member) => { - calls_hooks_or_creates_jsx_in_expr(&member.object) - || calls_hooks_or_creates_jsx_in_expr(&member.property) - } - Expression::SpreadElement(spread) => calls_hooks_or_creates_jsx_in_expr(&spread.argument), - Expression::AwaitExpression(await_expr) => { - calls_hooks_or_creates_jsx_in_expr(&await_expr.argument) - } - Expression::YieldExpression(yield_expr) => yield_expr - .argument - .as_ref() - .map_or(false, |arg| calls_hooks_or_creates_jsx_in_expr(arg)), - Expression::TaggedTemplateExpression(tagged) => { - calls_hooks_or_creates_jsx_in_expr(&tagged.tag) - } - Expression::TemplateLiteral(tl) => tl - .expressions - .iter() - .any(|e| calls_hooks_or_creates_jsx_in_expr(e)), - Expression::ArrayExpression(arr) => arr.elements.iter().any(|e| { - e.as_ref() - .map_or(false, |e| calls_hooks_or_creates_jsx_in_expr(e)) - }), - Expression::ObjectExpression(obj) => obj.properties.iter().any(|prop| match prop { - ObjectExpressionProperty::ObjectProperty(p) => { - calls_hooks_or_creates_jsx_in_expr(&p.value) - } - ObjectExpressionProperty::SpreadElement(s) => { - calls_hooks_or_creates_jsx_in_expr(&s.argument) - } - // ObjectMethod: traverse into its body to find hooks/JSX. - // This matches the TS behavior where Babel's traverse enters - // ObjectMethod (only FunctionDeclaration, FunctionExpression, - // and ArrowFunctionExpression are skipped). - ObjectExpressionProperty::ObjectMethod(m) => { - calls_hooks_or_creates_jsx_in_stmts(&m.body.body) - } - }), - Expression::ParenthesizedExpression(paren) => { - calls_hooks_or_creates_jsx_in_expr(&paren.expression) - } - Expression::TSAsExpression(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression), - Expression::TSSatisfiesExpression(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression), - Expression::TSNonNullExpression(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression), - Expression::TSTypeAssertion(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression), - Expression::TSInstantiationExpression(ts) => { - calls_hooks_or_creates_jsx_in_expr(&ts.expression) - } - Expression::TypeCastExpression(tc) => calls_hooks_or_creates_jsx_in_expr(&tc.expression), - Expression::NewExpression(new) => { - if calls_hooks_or_creates_jsx_in_expr(&new.callee) { - return true; - } - new.arguments.iter().any(|a| { - if matches!( - a, - Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) - ) { - return false; - } - calls_hooks_or_creates_jsx_in_expr(a) - }) - } - - // Skip nested functions - Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) => false, - - // Recurse into class body to find JSX/hooks in methods - Expression::ClassExpression(class) => calls_hooks_or_creates_jsx_in_class_body(&class.body), - - // Leaf expressions - _ => false, - } -} - -/// Recursively search a ClassBody for JSX elements or hook calls. -/// Class body members are stored as serde_json::Value since they aren't fully typed. -/// We search the JSON tree, skipping nested function nodes (matching TS behavior where -/// Babel's traverse skips ArrowFunctionExpression, FunctionExpression, FunctionDeclaration -/// but recurses into class methods). -fn calls_hooks_or_creates_jsx_in_class_body( - body: &react_compiler_ast::expressions::ClassBody, -) -> bool { - body.body - .iter() - .any(|member| calls_hooks_or_creates_jsx_in_json(&member.parse_value())) -} - -fn calls_hooks_or_creates_jsx_in_json(value: &serde_json::Value) -> bool { - match value { - serde_json::Value::Object(obj) => { - // Check the node type - if let Some(serde_json::Value::String(node_type)) = obj.get("type") { - match node_type.as_str() { - // JSX nodes - "JSXElement" | "JSXFragment" => return true, - // Skip nested function nodes (matching TS skipNestedFunctions) - "ArrowFunctionExpression" | "FunctionExpression" | "FunctionDeclaration" => { - return false; - } - // Hook calls: check if callee name starts with "use" - "CallExpression" => { - if let Some(callee) = obj.get("callee") { - if json_expr_is_hook(callee) { - return true; - } - } - } - _ => {} - } - } - // Recurse into all values of the object - obj.values().any(|v| calls_hooks_or_creates_jsx_in_json(v)) - } - serde_json::Value::Array(arr) => arr.iter().any(|v| calls_hooks_or_creates_jsx_in_json(v)), - _ => false, - } -} - -/// Check if a JSON expression node looks like a hook call. -/// Handles both Identifier (e.g. `useState`) and MemberExpression -/// (e.g. `React.useState`) patterns, reusing `is_hook_name` for -/// consistent naming checks. -fn json_expr_is_hook(callee: &serde_json::Value) -> bool { - if let serde_json::Value::Object(obj) = callee { - if let Some(serde_json::Value::String(node_type)) = obj.get("type") { - if node_type == "Identifier" { - if let Some(serde_json::Value::String(name)) = obj.get("name") { - return is_hook_name(name); - } - } else if node_type == "MemberExpression" { - // Check for PascalCase.useHook pattern (non-computed) - let computed = obj - .get("computed") - .and_then(|v| v.as_bool()) - .unwrap_or(false); - if computed { - return false; - } - // Property must be a hook name - if let Some(serde_json::Value::Object(prop)) = obj.get("property") { - if prop.get("type").and_then(|v| v.as_str()) == Some("Identifier") { - if let Some(name) = prop.get("name").and_then(|v| v.as_str()) { - if !is_hook_name(name) { - return false; - } - // Object must be PascalCase identifier - if let Some(serde_json::Value::Object(obj_node)) = obj.get("object") { - if obj_node.get("type").and_then(|v| v.as_str()) - == Some("Identifier") - { - if let Some(obj_name) = - obj_node.get("name").and_then(|v| v.as_str()) - { - return is_component_name(obj_name); - } - } - } - } - } - } - } - } - } - false -} - -/// Check if a function body calls hooks or creates JSX. -fn calls_hooks_or_creates_jsx(params: &[PatternLike], body: &FunctionBody) -> bool { - // Check default param values (TS traverses the whole function node including params) - if calls_hooks_or_creates_jsx_in_params(params) { - return true; - } - match body { - FunctionBody::Block(block) => calls_hooks_or_creates_jsx_in_stmts(&block.body), - FunctionBody::Expression(expr) => calls_hooks_or_creates_jsx_in_expr(expr), - } -} - -/// Check if any parameter default values contain hooks or JSX. -fn calls_hooks_or_creates_jsx_in_params(params: &[PatternLike]) -> bool { - for param in params { - if calls_hooks_or_creates_jsx_in_pattern(param) { - return true; - } - } - false -} - -fn calls_hooks_or_creates_jsx_in_pattern(pattern: &PatternLike) -> bool { - match pattern { - PatternLike::AssignmentPattern(assign) => { - // Check the default value expression - calls_hooks_or_creates_jsx_in_expr(&assign.right) - || calls_hooks_or_creates_jsx_in_pattern(&assign.left) - } - PatternLike::ObjectPattern(obj) => obj.properties.iter().any(|prop| match prop { - react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => { - calls_hooks_or_creates_jsx_in_pattern(&p.value) - } - react_compiler_ast::patterns::ObjectPatternProperty::RestElement(rest) => { - calls_hooks_or_creates_jsx_in_pattern(&rest.argument) - } - }), - PatternLike::ArrayPattern(arr) => arr.elements.iter().any(|elem| { - elem.as_ref() - .map_or(false, |e| calls_hooks_or_creates_jsx_in_pattern(e)) - }), - PatternLike::RestElement(rest) => calls_hooks_or_creates_jsx_in_pattern(&rest.argument), - PatternLike::Identifier(_) - | PatternLike::MemberExpression(_) - | PatternLike::TSAsExpression(_) - | PatternLike::TSSatisfiesExpression(_) - | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) - | PatternLike::TypeCastExpression(_) => false, - } -} - -/// Check if the function parameters are valid for a React component. -/// Components can have 0 params, 1 param (props), or 2 params (props + ref). -/// Check if a parameter's type annotation is valid for a React component prop. -/// Returns false for primitive type annotations that indicate this is NOT a component. -fn is_valid_props_annotation(param: &PatternLike) -> bool { - let type_annotation = match param { - PatternLike::Identifier(id) => id.type_annotation.as_ref(), - PatternLike::ObjectPattern(op) => op.type_annotation.as_ref(), - PatternLike::ArrayPattern(ap) => ap.type_annotation.as_ref(), - PatternLike::AssignmentPattern(ap) => ap.type_annotation.as_ref(), - PatternLike::RestElement(re) => re.type_annotation.as_ref(), - PatternLike::MemberExpression(_) - | PatternLike::TSAsExpression(_) - | PatternLike::TSSatisfiesExpression(_) - | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) - | PatternLike::TypeCastExpression(_) => None, - }; - let annot = match type_annotation { - Some(raw) => raw.parse_value(), - None => return true, // No annotation = valid - }; - let annot_type = match annot.get("type").and_then(|v| v.as_str()) { - Some(t) => t, - None => return true, - }; - match annot_type { - "TSTypeAnnotation" => { - let inner_type = annot - .get("typeAnnotation") - .and_then(|v| v.get("type")) - .and_then(|v| v.as_str()) - .unwrap_or(""); - !matches!( - inner_type, - "TSArrayType" - | "TSBigIntKeyword" - | "TSBooleanKeyword" - | "TSConstructorType" - | "TSFunctionType" - | "TSLiteralType" - | "TSNeverKeyword" - | "TSNumberKeyword" - | "TSStringKeyword" - | "TSSymbolKeyword" - | "TSTupleType" - ) - } - "TypeAnnotation" => { - let inner_type = annot - .get("typeAnnotation") - .and_then(|v| v.get("type")) - .and_then(|v| v.as_str()) - .unwrap_or(""); - !matches!( - inner_type, - "ArrayTypeAnnotation" - | "BooleanLiteralTypeAnnotation" - | "BooleanTypeAnnotation" - | "EmptyTypeAnnotation" - | "FunctionTypeAnnotation" - | "NullLiteralTypeAnnotation" - | "NumberLiteralTypeAnnotation" - | "NumberTypeAnnotation" - | "StringLiteralTypeAnnotation" - | "StringTypeAnnotation" - | "SymbolTypeAnnotation" - | "ThisTypeAnnotation" - | "TupleTypeAnnotation" - ) - } - "Noop" => true, - _ => true, - } -} - -fn is_valid_component_params(params: &[PatternLike]) -> bool { - if params.is_empty() { - return true; - } - if params.len() > 2 { - return false; - } - // First param cannot be a rest element - if matches!(params[0], PatternLike::RestElement(_)) { - return false; - } - // Check type annotation on first param - if !is_valid_props_annotation(¶ms[0]) { - return false; - } - if params.len() == 1 { - return true; - } - // If second param exists, it should look like a ref - if let PatternLike::Identifier(ref id) = params[1] { - id.name.contains("ref") || id.name.contains("Ref") - } else { - false - } -} - -// ----------------------------------------------------------------------- -// Unified function body type for traversal -// ----------------------------------------------------------------------- - -/// Abstraction over function body types to simplify traversal code -enum FunctionBody<'a> { - Block(&'a BlockStatement), - Expression(&'a Expression), -} - -// ----------------------------------------------------------------------- -// Function type detection -// ----------------------------------------------------------------------- - -/// Determine the React function type for a function, given the compilation mode -/// and the function's name and context. -/// -/// This is the Rust equivalent of `getReactFunctionType` in Program.ts. -fn get_react_function_type( - name: Option<&str>, - params: &[PatternLike], - body: &FunctionBody, - body_directives: &[Directive], - is_declaration: bool, - parent_callee_name: Option<&str>, - opts: &PluginOptions, - is_component_declaration: bool, - is_hook_declaration: bool, -) -> Option<ReactFunctionType> { - // Check for opt-in directives in the function body - if let FunctionBody::Block(_) = body { - let opt_in = try_find_directive_enabling_memoization(body_directives, opts); - if let Ok(Some(_)) = opt_in { - // If there's an opt-in directive, use name heuristics but fall back to Other - return Some( - get_component_or_hook_like(name, params, body, parent_callee_name) - .unwrap_or(ReactFunctionType::Other), - ); - } - } - - // Component and hook declarations are known components/hooks - // (Flow `component Foo() { ... }` and `hook useFoo() { ... }` syntax, - // detected via __componentDeclaration / __hookDeclaration from the Hermes parser) - let component_syntax_type = if is_declaration { - if is_component_declaration { - Some(ReactFunctionType::Component) - } else if is_hook_declaration { - Some(ReactFunctionType::Hook) - } else { - None - } - } else { - None - }; - - match opts.compilation_mode.as_str() { - "annotation" => { - // opt-ins were checked above - None - } - "infer" => { - // Check if this is a component or hook-like function - component_syntax_type - .or_else(|| get_component_or_hook_like(name, params, body, parent_callee_name)) - } - "syntax" => { - // In syntax mode, only compile declared components/hooks - component_syntax_type - } - "all" => Some( - get_component_or_hook_like(name, params, body, parent_callee_name) - .unwrap_or(ReactFunctionType::Other), - ), - _ => None, - } -} - -/// Determine if a function looks like a React component or hook based on -/// naming conventions and code patterns. -/// -/// Adapted from the ESLint rule at -/// https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js -fn get_component_or_hook_like( - name: Option<&str>, - params: &[PatternLike], - body: &FunctionBody, - parent_callee_name: Option<&str>, -) -> Option<ReactFunctionType> { - if let Some(fn_name) = name { - if is_component_name(fn_name) { - // Check if it actually looks like a component - let is_component = calls_hooks_or_creates_jsx(params, body) - && is_valid_component_params(params) - && !returns_non_node_fn(params, body); - return if is_component { - Some(ReactFunctionType::Component) - } else { - None - }; - } else if is_hook_name(fn_name) { - // Hooks have hook invocations or JSX, but can take any # of arguments - return if calls_hooks_or_creates_jsx(params, body) { - Some(ReactFunctionType::Hook) - } else { - None - }; - } - } - - // For unnamed functions, check if they are forwardRef/memo callbacks - if let Some(callee_name) = parent_callee_name { - if callee_name == "forwardRef" || callee_name == "memo" { - return if calls_hooks_or_creates_jsx(params, body) { - Some(ReactFunctionType::Component) - } else { - None - }; - } - } - - None -} - -/// Extract the callee name from a CallExpression if it's a React API call -/// (forwardRef, memo, React.forwardRef, React.memo). -fn get_callee_name_if_react_api(callee: &Expression) -> Option<&str> { - match callee { - Expression::Identifier(id) => { - if id.name == "forwardRef" || id.name == "memo" { - Some(&id.name) - } else { - None - } - } - Expression::MemberExpression(member) => { - if let Expression::Identifier(obj) = member.object.as_ref() { - if obj.name == "React" { - if let Expression::Identifier(prop) = member.property.as_ref() { - if prop.name == "forwardRef" || prop.name == "memo" { - return Some(&prop.name); - } - } - } - } - None - } - _ => None, - } -} - -// ----------------------------------------------------------------------- -// SourceLocation conversion -// ----------------------------------------------------------------------- - -/// Convert an AST SourceLocation to a diagnostics SourceLocation -fn convert_loc(loc: &react_compiler_ast::common::SourceLocation) -> SourceLocation { - SourceLocation { - start: react_compiler_diagnostics::Position { - line: loc.start.line, - column: loc.start.column, - index: loc.start.index, - }, - end: react_compiler_diagnostics::Position { - line: loc.end.line, - column: loc.end.column, - index: loc.end.index, - }, - } -} - -fn base_node_loc(base: &BaseNode) -> Option<SourceLocation> { - base.loc.as_ref().map(convert_loc) -} - -// ----------------------------------------------------------------------- -// Error handling -// ----------------------------------------------------------------------- - -/// Convert CompilerDiagnostic details into serializable CompilerErrorItemInfo items. -fn diagnostic_details_to_items( - d: &react_compiler_diagnostics::CompilerDiagnostic, - filename: Option<&str>, -) -> Option<Vec<CompilerErrorItemInfo>> { - let items: Vec<CompilerErrorItemInfo> = d - .details - .iter() - .map(|item| match item { - react_compiler_diagnostics::CompilerDiagnosticDetail::Error { - loc, - message, - identifier_name, - } => CompilerErrorItemInfo { - kind: "error".to_string(), - loc: loc.as_ref().map(|l| { - let mut logger_loc = diag_loc_to_logger_loc(l, filename); - logger_loc.identifier_name = identifier_name.clone(); - logger_loc - }), - message: message.clone(), - }, - react_compiler_diagnostics::CompilerDiagnosticDetail::Hint { message } => { - CompilerErrorItemInfo { - kind: "hint".to_string(), - loc: None, - message: Some(message.clone()), - } - } - }) - .collect(); - if items.is_empty() { None } else { Some(items) } -} - -/// Convert an optional AST SourceLocation to a LoggerSourceLocation with filename. -fn to_logger_loc( - ast_loc: Option<&react_compiler_ast::common::SourceLocation>, - filename: Option<&str>, -) -> Option<LoggerSourceLocation> { - ast_loc.map(|loc| LoggerSourceLocation { - start: LoggerPosition { - line: loc.start.line, - column: loc.start.column, - index: loc.start.index, - }, - end: LoggerPosition { - line: loc.end.line, - column: loc.end.column, - index: loc.end.index, - }, - filename: filename.map(|s| s.to_string()), - identifier_name: loc.identifier_name.clone(), - }) -} - -/// Convert a diagnostics SourceLocation to a LoggerSourceLocation with filename. -fn diag_loc_to_logger_loc(loc: &SourceLocation, filename: Option<&str>) -> LoggerSourceLocation { - LoggerSourceLocation { - start: LoggerPosition { - line: loc.start.line, - column: loc.start.column, - index: loc.start.index, - }, - end: LoggerPosition { - line: loc.end.line, - column: loc.end.column, - index: loc.end.index, - }, - filename: filename.map(|s| s.to_string()), - identifier_name: None, - } -} - -/// Convert diagnostic suggestions to logger suggestion infos. -fn suggestions_to_logger( - suggestions: &Option<Vec<react_compiler_diagnostics::CompilerSuggestion>>, -) -> Option<Vec<LoggerSuggestionInfo>> { - suggestions.as_ref().map(|suggestions| { - suggestions - .iter() - .map(|s| { - let op = match s.op { - react_compiler_diagnostics::CompilerSuggestionOperation::InsertBefore => { - LoggerSuggestionOp::InsertBefore - } - react_compiler_diagnostics::CompilerSuggestionOperation::InsertAfter => { - LoggerSuggestionOp::InsertAfter - } - react_compiler_diagnostics::CompilerSuggestionOperation::Remove => { - LoggerSuggestionOp::Remove - } - react_compiler_diagnostics::CompilerSuggestionOperation::Replace => { - LoggerSuggestionOp::Replace - } - }; - LoggerSuggestionInfo { - description: s.description.clone(), - op, - range: s.range, - text: s.text.clone(), - } - }) - .collect() - }) -} - -/// Log an error as LoggerEvent(s) directly onto the ProgramContext. -fn log_error( - err: &CompilerError, - fn_ast_loc: Option<&react_compiler_ast::common::SourceLocation>, - context: &mut ProgramContext, -) { - // Use the filename from the AST node's loc (set by parser's sourceFilename option), - // not from plugin options (which may have a different prefix like '/'). - let source_filename = fn_ast_loc.and_then(|loc| loc.filename.as_deref()); - let fn_loc = to_logger_loc(fn_ast_loc, source_filename); - - // Detect simulated unknown exception (throwUnknownException__testonly). - // In TS, non-CompilerError exceptions are logged as PipelineError with the - // error message as data. Emit the same event shape. - let is_simulated_unknown = err.details.len() == 1 - && err.details.iter().all(|d| match d { - CompilerErrorOrDiagnostic::ErrorDetail(d) => { - d.category == ErrorCategory::Invariant && d.reason == "unexpected error" - } - _ => false, - }); - if is_simulated_unknown { - context.log_event(LoggerEvent::PipelineError { - fn_loc: fn_loc.clone(), - data: "Error: unexpected error".to_string(), - }); - return; - } - - for detail in &err.details { - let detail_info = match detail { - CompilerErrorOrDiagnostic::Diagnostic(d) => CompilerErrorDetailInfo { - category: format!("{:?}", d.category), - reason: d.reason.clone(), - description: d.description.clone(), - severity: format!("{:?}", d.logged_severity()), - suggestions: suggestions_to_logger(&d.suggestions), - details: diagnostic_details_to_items(d, source_filename), - loc: None, - }, - CompilerErrorOrDiagnostic::ErrorDetail(d) => CompilerErrorDetailInfo { - category: format!("{:?}", d.category), - reason: d.reason.clone(), - description: d.description.clone(), - severity: format!("{:?}", d.logged_severity()), - suggestions: suggestions_to_logger(&d.suggestions), - details: None, - loc: d - .loc - .as_ref() - .map(|l| diag_loc_to_logger_loc(l, source_filename)), - }, - }; - // Use CompileErrorWithLoc when fn_loc is present to match TS field ordering - if let Some(ref loc) = fn_loc { - context.log_event(LoggerEvent::CompileErrorWithLoc { - fn_loc: loc.clone(), - detail: detail_info, - }); - } else { - context.log_event(LoggerEvent::CompileError { - fn_loc: None, - detail: detail_info, - }); - } - } -} - -/// Handle an error according to the panicThreshold setting. -/// Returns Some(CompileResult::Error) if the error should be surfaced as fatal, -/// otherwise returns None (error was logged only). -fn handle_error( - err: &CompilerError, - fn_ast_loc: Option<&react_compiler_ast::common::SourceLocation>, - context: &mut ProgramContext, -) -> Option<CompileResult> { - // Log the error - log_error(err, fn_ast_loc, context); - - let should_panic = match context.opts.panic_threshold.as_str() { - "all_errors" => true, - "critical_errors" => err.has_errors(), - _ => false, - }; - - // Config errors always cause a panic - let is_config_error = err.details.iter().any(|d| match d { - CompilerErrorOrDiagnostic::Diagnostic(d) => d.category == ErrorCategory::Config, - CompilerErrorOrDiagnostic::ErrorDetail(d) => d.category == ErrorCategory::Config, - }); - - if should_panic || is_config_error { - let source_fn = context.source_filename(); - let mut error_info = compiler_error_to_info(err, source_fn.as_deref()); - - // Detect simulated unknown exception (throwUnknownException__testonly). - // In the TS compiler, this throws a plain Error('unexpected error'), not - // a CompilerError. Set rawMessage so the JS side throws with the raw - // message instead of formatting through formatCompilerError(). - let is_simulated_unknown = err.details.len() == 1 - && err.details.iter().all(|d| match d { - CompilerErrorOrDiagnostic::ErrorDetail(d) => { - d.category == ErrorCategory::Invariant && d.reason == "unexpected error" - } - _ => false, - }); - if is_simulated_unknown { - error_info.raw_message = Some("unexpected error".to_string()); - } - - // Pre-format the error message in Rust when possible, so the JS - // shim can use it directly instead of calling formatCompilerError(). - if error_info.raw_message.is_none() { - if let Some(ref source) = context.code { - error_info.formatted_message = Some( - react_compiler_diagnostics::code_frame::format_compiler_error( - err, - source, - source_fn.as_deref(), - ), - ); - } - } - - Some(CompileResult::Error { - error: error_info, - events: context.events.clone(), - ordered_log: context.ordered_log.clone(), - timing: Vec::new(), - }) - } else { - None - } -} - -/// Convert a diagnostics CompilerError to a serializable CompilerErrorInfo. -fn compiler_error_to_info(err: &CompilerError, filename: Option<&str>) -> CompilerErrorInfo { - let details: Vec<CompilerErrorDetailInfo> = err - .details - .iter() - .map(|d| match d { - CompilerErrorOrDiagnostic::Diagnostic(d) => CompilerErrorDetailInfo { - category: format!("{:?}", d.category), - reason: d.reason.clone(), - description: d.description.clone(), - severity: format!("{:?}", d.severity()), - suggestions: suggestions_to_logger(&d.suggestions), - details: diagnostic_details_to_items(d, filename), - loc: None, - }, - CompilerErrorOrDiagnostic::ErrorDetail(d) => CompilerErrorDetailInfo { - category: format!("{:?}", d.category), - reason: d.reason.clone(), - description: d.description.clone(), - severity: format!("{:?}", d.severity()), - suggestions: suggestions_to_logger(&d.suggestions), - details: None, - loc: d.loc.as_ref().map(|l| diag_loc_to_logger_loc(l, filename)), - }, - }) - .collect(); - - let (reason, description) = details - .first() - .map(|d| (d.reason.clone(), d.description.clone())) - .unwrap_or_else(|| ("Unknown error".to_string(), None)); - - CompilerErrorInfo { - reason, - description, - details, - raw_message: None, - formatted_message: None, - } -} - -// ----------------------------------------------------------------------- -// Compilation pipeline stubs -// ----------------------------------------------------------------------- - -/// Attempt to compile a single function. -/// -/// Returns `CodegenFunction` on success or `CompilerError` on failure. -/// Debug log entries are accumulated on `context.debug_logs`. -fn try_compile_function( - source: &CompileSource<'_>, - scope_info: &ScopeInfo, - output_mode: CompilerOutputMode, - env_config: &EnvironmentConfig, - context: &mut ProgramContext, -) -> Result<CodegenFunction, CompilerError> { - // Check for suppressions that affect this function - if let (Some(start), Some(end)) = (source.fn_start, source.fn_end) { - let affecting = filter_suppressions_that_affect_function(&context.suppressions, start, end); - if !affecting.is_empty() { - let owned: Vec<SuppressionRange> = affecting.into_iter().cloned().collect(); - let mut err = suppressions_to_compiler_error(&owned); - // Suppression errors are returned (not thrown), so they should NOT - // trigger CompileUnexpectedThrow. - err.is_thrown = false; - return Err(err); - } - } - - // Run the compilation pipeline - pipeline::compile_fn( - &source.fn_node, - source.fn_name.as_deref(), - scope_info, - source.fn_type, - output_mode, - env_config, - context, - ) -} - -/// Process a single function: check directives, attempt compilation, handle results. -/// -/// Returns `Ok(Some(codegen_fn))` when the function was compiled and should be applied, -/// `Ok(None)` when the function was skipped or lint-only, -/// or `Err(CompileResult)` if a fatal error should short-circuit the program. -fn process_fn( - source: &CompileSource<'_>, - scope_info: &ScopeInfo, - output_mode: CompilerOutputMode, - env_config: &EnvironmentConfig, - context: &mut ProgramContext, -) -> Result<Option<CodegenFunction>, CompileResult> { - // Parse directives from the function body - let opt_in_result = - try_find_directive_enabling_memoization(&source.body_directives, &context.opts); - let opt_out = find_directive_disabling_memoization(&source.body_directives, &context.opts); - - // If parsing opt-in directive fails, handle the error and skip - let opt_in = match opt_in_result { - Ok(d) => d, - Err(err) => { - // Apply panic threshold logic (same as compilation errors) - if let Some(result) = handle_error(&err, source.fn_ast_loc.as_ref(), context) { - return Err(result); - } - return Ok(None); - } - }; - - // Attempt compilation - let compile_result = try_compile_function(source, scope_info, output_mode, env_config, context); - - match compile_result { - Err(err) => { - // Emit CompileUnexpectedThrow for errors that were "thrown" from a pass - // (not accumulated via env.record_error) and have all non-Invariant details. - // Matches TS tryCompileFunction() catch block behavior. - if err.is_thrown && err.is_all_non_invariant() { - let source_filename = source - .fn_ast_loc - .as_ref() - .and_then(|loc| loc.filename.as_deref()); - context.log_event(LoggerEvent::CompileUnexpectedThrow { - fn_loc: to_logger_loc(source.fn_ast_loc.as_ref(), source_filename), - data: err.to_string_for_event(), - }); - } - - if opt_out.is_some() { - // If there's an opt-out, just log the error (don't escalate) - log_error(&err, source.fn_ast_loc.as_ref(), context); - } else { - // Apply panic threshold logic - if let Some(result) = handle_error(&err, source.fn_ast_loc.as_ref(), context) { - return Err(result); - } - } - Ok(None) - } - Ok(codegen_fn) => { - // Check opt-out - if !context.opts.ignore_use_no_forget && opt_out.is_some() { - let opt_out_value = &opt_out.unwrap().value.value; - let source_filename = source - .fn_ast_loc - .as_ref() - .and_then(|loc| loc.filename.as_deref()); - context.log_event(LoggerEvent::CompileSkip { - fn_loc: to_logger_loc(source.fn_ast_loc.as_ref(), source_filename), - reason: format!("Skipped due to '{}' directive.", opt_out_value), - loc: opt_out.and_then(|d| to_logger_loc(d.base.loc.as_ref(), source_filename)), - }); - // The function is skipped due to opt-out. Do NOT register the memo - // cache import here — it will be registered in apply_compiled_functions() - // only for functions that are actually applied to the output. - return Ok(None); - } - - // Log success with memo stats from CodegenFunction - let source_filename = source - .fn_ast_loc - .as_ref() - .and_then(|loc| loc.filename.as_deref()); - context.log_event(LoggerEvent::CompileSuccess { - fn_loc: to_logger_loc(source.fn_ast_loc.as_ref(), source_filename), - fn_name: codegen_fn.id.as_ref().map(|id| id.name.clone()), - memo_slots: codegen_fn.memo_slots_used, - memo_blocks: codegen_fn.memo_blocks, - memo_values: codegen_fn.memo_values, - pruned_memo_blocks: codegen_fn.pruned_memo_blocks, - pruned_memo_values: codegen_fn.pruned_memo_values, - }); - - // Check module scope opt-out - if context.has_module_scope_opt_out { - return Ok(None); - } - - // Check output mode — lint mode doesn't apply compiled functions - if output_mode == CompilerOutputMode::Lint { - return Ok(None); - } - - // Check annotation mode - if context.opts.compilation_mode == "annotation" && opt_in.is_none() { - return Ok(None); - } - - Ok(Some(codegen_fn)) - } - } -} - -// ----------------------------------------------------------------------- -// Import checking -// ----------------------------------------------------------------------- - -/// Check if the program already has a `c` import from the React Compiler runtime module. -/// If so, the file was already compiled and should be skipped. -fn has_memo_cache_function_import(program: &Program, module_name: &str) -> bool { - for stmt in &program.body { - if let Statement::ImportDeclaration(import) = stmt { - if import.source.value == module_name { - for specifier in &import.specifiers { - if let ImportSpecifier::ImportSpecifier(data) = specifier { - let imported_name = match &data.imported { - ModuleExportName::Identifier(id) => &id.name, - ModuleExportName::StringLiteral(s) => &s.value, - }; - if imported_name == "c" { - return true; - } - } - } - } - } - } - false -} - -/// Check if compilation should be skipped for this program. -fn should_skip_compilation(program: &Program, options: &PluginOptions) -> bool { - let runtime_module = get_react_compiler_runtime_module(&options.target); - has_memo_cache_function_import(program, &runtime_module) -} - -// ----------------------------------------------------------------------- -// Function discovery -// ----------------------------------------------------------------------- - -/// Information about an expression that might be a function to compile -struct FunctionInfo<'a> { - name: Option<String>, - fn_node: FunctionNode<'a>, - params: &'a [PatternLike], - body: FunctionBody<'a>, - body_directives: Vec<Directive>, - base: &'a BaseNode, - parent_callee_name: Option<String>, - /// True if the node has `__componentDeclaration` set by the Hermes parser (Flow component syntax) - is_component_declaration: bool, - /// True if the node has `__hookDeclaration` set by the Hermes parser (Flow hook syntax) - is_hook_declaration: bool, -} - -/// Extract function info from a FunctionDeclaration -fn fn_info_from_decl(decl: &FunctionDeclaration) -> FunctionInfo<'_> { - FunctionInfo { - name: get_function_name_from_id(decl.id.as_ref()), - fn_node: FunctionNode::FunctionDeclaration(decl), - params: &decl.params, - body: FunctionBody::Block(&decl.body), - body_directives: decl.body.directives.clone(), - base: &decl.base, - parent_callee_name: None, - is_component_declaration: decl.component_declaration, - is_hook_declaration: decl.hook_declaration, - } -} - -/// Extract function info from a FunctionExpression -fn fn_info_from_func_expr<'a>( - expr: &'a FunctionExpression, - inferred_name: Option<String>, - parent_callee_name: Option<String>, -) -> FunctionInfo<'a> { - FunctionInfo { - name: inferred_name, - fn_node: FunctionNode::FunctionExpression(expr), - params: &expr.params, - body: FunctionBody::Block(&expr.body), - body_directives: expr.body.directives.clone(), - base: &expr.base, - parent_callee_name, - is_component_declaration: false, - is_hook_declaration: false, - } -} - -/// Extract function info from an ArrowFunctionExpression -fn fn_info_from_arrow<'a>( - expr: &'a ArrowFunctionExpression, - inferred_name: Option<String>, - parent_callee_name: Option<String>, -) -> FunctionInfo<'a> { - let (body, directives) = match expr.body.as_ref() { - ArrowFunctionBody::BlockStatement(block) => { - (FunctionBody::Block(block), block.directives.clone()) - } - ArrowFunctionBody::Expression(e) => (FunctionBody::Expression(e), Vec::new()), - }; - FunctionInfo { - name: inferred_name, - fn_node: FunctionNode::ArrowFunctionExpression(expr), - params: &expr.params, - body, - body_directives: directives, - base: &expr.base, - parent_callee_name, - is_component_declaration: false, - is_hook_declaration: false, - } -} - -/// Try to create a CompileSource from function info -fn try_make_compile_source<'a>( - info: FunctionInfo<'a>, - opts: &PluginOptions, - context: &mut ProgramContext, -) -> Option<CompileSource<'a>> { - // Skip if already compiled (identified by node_id) - if let Some(nid) = info.base.node_id { - if context.is_already_compiled(nid) { - return None; - } - } - - let fn_type = get_react_function_type( - info.name.as_deref(), - info.params, - &info.body, - &info.body_directives, - info.is_component_declaration || info.is_hook_declaration, - info.parent_callee_name.as_deref(), - opts, - info.is_component_declaration, - info.is_hook_declaration, - )?; - - // Mark as compiled - if let Some(nid) = info.base.node_id { - context.mark_compiled(nid); - } - - Some(CompileSource { - kind: CompileSourceKind::Original, - fn_node: info.fn_node, - fn_name: info.name, - fn_loc: base_node_loc(info.base), - fn_ast_loc: info.base.loc.clone(), - fn_start: info.base.start, - fn_end: info.base.end, - fn_node_id: info.base.node_id, - fn_type, - body_directives: info.body_directives, - }) -} - -/// Get the variable declarator name (for inferring function names from `const Foo = () => {}`) -fn get_declarator_name(decl: &VariableDeclarator) -> Option<String> { - match &decl.id { - PatternLike::Identifier(id) => Some(id.name.clone()), - _ => None, - } -} - -// ----------------------------------------------------------------------- -// FunctionDiscoveryVisitor — uses AstWalker to find compilable functions -// ----------------------------------------------------------------------- - -/// Visitor that discovers functions to compile, matching the TypeScript -/// compiler's Babel `program.traverse` behavior. -/// -/// Dynamically controls body traversal via `traverse_function_bodies()`: -/// functions that are queued for compilation have their bodies skipped -/// (matching Babel's `fn.skip()`), while non-compiled functions have their -/// bodies traversed to find nested component/hook declarations. -/// -/// Tracks parent context via: -/// - `current_declarator_name`: set by `enter_variable_declarator`, used to -/// infer function names from `const Foo = () => {}`. -/// - `parent_callee_stack`: set by `enter_call_expression`, used to detect -/// forwardRef/memo wrappers around function expressions. -/// -/// In 'all' mode, uses `scope_stack.len() > 1` to reject functions that are -/// not at program scope. The walker pushes the program scope first, then -/// nested scopes for for/switch/etc. — so `len() > 1` means the function -/// is inside a nested scope (not at program level), matching Babel's -/// `fn.scope.getProgramParent() !== fn.scope.parent` check. -struct FunctionDiscoveryVisitor<'a, 'ast> { - opts: &'a PluginOptions, - context: &'a mut ProgramContext, - queue: Vec<CompileSource<'ast>>, - /// The inferred name from the current VariableDeclarator, if any. - current_declarator_name: Option<String>, - /// Stack tracking callee names of enclosing CallExpressions. - /// `Some(name)` when the callee is a React API (forwardRef/memo), - /// `None` for other calls. - parent_callee_stack: Vec<Option<String>>, - /// Depth counter for loop expression positions (while.test, for-in.right, etc.). - /// When > 0, functions are treated as non-program-scope in 'all' mode. - loop_expression_depth: usize, - /// Set by enter_* hooks: true when the function was queued for compilation, - /// meaning the walker should NOT traverse its body (matching Babel's fn.skip()). - /// When false, the walker DOES traverse the body to find nested declarations. - skip_body: bool, -} - -impl<'a, 'ast> FunctionDiscoveryVisitor<'a, 'ast> { - fn new(opts: &'a PluginOptions, context: &'a mut ProgramContext) -> Self { - Self { - opts, - context, - queue: Vec::new(), - current_declarator_name: None, - parent_callee_stack: Vec::new(), - loop_expression_depth: 0, - skip_body: false, - } - } - - /// Check if in 'all' mode and the function is inside a nested scope. - /// The walker pushes the function's own scope BEFORE calling enter hooks, - /// so scope_stack = [program, ...parents, function_scope]. A top-level - /// function has len=2 (program + function). Anything deeper means it's - /// inside a nested scope (for/switch/etc.) and should be rejected. - /// Also rejects functions found in loop expression positions (while.test, - /// for-in.right, etc.) where Babel treats the scope as non-program. - fn is_rejected_by_scope_check(&self, scope_stack: &[ScopeId]) -> bool { - self.opts.compilation_mode == "all" - && (scope_stack.len() > 2 || self.loop_expression_depth > 0) - } - - /// Get the current parent callee name (forwardRef/memo) if any. - fn current_parent_callee(&self) -> Option<String> { - self.parent_callee_stack.last().and_then(|opt| opt.clone()) - } -} - -impl<'a, 'ast> Visitor<'ast> for FunctionDiscoveryVisitor<'a, 'ast> { - fn traverse_function_bodies(&self) -> bool { - // Dynamic: only skip the body of functions that were queued for compilation. - // Non-queued functions have their bodies traversed to find nested declarations - // (matching Babel behavior where fn.skip() is only called for compiled functions). - !self.skip_body - } - - fn enter_loop_expression(&mut self) { - self.loop_expression_depth += 1; - } - - fn leave_loop_expression(&mut self) { - self.loop_expression_depth -= 1; - } - - fn enter_variable_declarator( - &mut self, - node: &'ast VariableDeclarator, - _scope_stack: &[ScopeId], - ) { - // Only infer the declarator name when the init is a direct function - // expression, arrow, or call expression (for forwardRef/memo wrappers). - // TS checks `path.parentPath.isVariableDeclarator()` which only matches - // when the function IS the init, not when it's nested inside an object, - // array, or other expression. - if let Some(ref init) = node.init { - match init.as_ref() { - Expression::FunctionExpression(_) - | Expression::ArrowFunctionExpression(_) - | Expression::CallExpression(_) => { - self.current_declarator_name = get_declarator_name(node); - } - _ => {} - } - } - } - - fn leave_variable_declarator( - &mut self, - _node: &'ast VariableDeclarator, - _scope_stack: &[ScopeId], - ) { - self.current_declarator_name = None; - } - - fn enter_call_expression(&mut self, node: &'ast CallExpression, _scope_stack: &[ScopeId]) { - let callee_name = get_callee_name_if_react_api(&node.callee).map(|s| s.to_string()); - // In TS, the declarator name only flows through forwardRef/memo calls - // (path.parentPath.isCallExpression() checks the callee). For any other - // call expression, clear the name so nested functions don't inherit it. - if callee_name.is_none() { - self.current_declarator_name = None; - } - self.parent_callee_stack.push(callee_name); - } - - fn leave_call_expression(&mut self, _node: &'ast CallExpression, _scope_stack: &[ScopeId]) { - let was_react_api = self - .parent_callee_stack - .pop() - .and_then(|name| name) - .is_some(); - // After a forwardRef/memo call finishes, clear the declarator name. - // The name is only valid within the call's arguments — if a function - // inside consumed it via .take(), great; if not, it shouldn't leak - // to sibling or subsequent expressions. - if was_react_api { - self.current_declarator_name = None; - } - } - - fn enter_function_declaration( - &mut self, - node: &'ast FunctionDeclaration, - scope_stack: &[ScopeId], - ) { - self.skip_body = false; - if self.is_rejected_by_scope_check(scope_stack) { - return; - } - let info = fn_info_from_decl(node); - if let Some(source) = try_make_compile_source(info, self.opts, self.context) { - self.queue.push(source); - self.skip_body = true; - } - } - - fn enter_function_expression( - &mut self, - node: &'ast FunctionExpression, - scope_stack: &[ScopeId], - ) { - self.skip_body = false; - if self.is_rejected_by_scope_check(scope_stack) { - return; - } - // TS getFunctionName for FunctionExpressions only returns names from parent - // context (VariableDeclarator, AssignmentExpression, Property) — never from - // the expression's own `id`. So we only use current_declarator_name here. - let inferred_name = self.current_declarator_name.take(); - let parent_callee = self.current_parent_callee(); - let info = fn_info_from_func_expr(node, inferred_name, parent_callee); - if let Some(source) = try_make_compile_source(info, self.opts, self.context) { - self.queue.push(source); - self.skip_body = true; - } - } - - fn enter_arrow_function_expression( - &mut self, - node: &'ast ArrowFunctionExpression, - scope_stack: &[ScopeId], - ) { - self.skip_body = false; - if self.is_rejected_by_scope_check(scope_stack) { - return; - } - let inferred_name = self.current_declarator_name.take(); - let parent_callee = self.current_parent_callee(); - let info = fn_info_from_arrow(node, inferred_name, parent_callee); - if let Some(source) = try_make_compile_source(info, self.opts, self.context) { - self.queue.push(source); - self.skip_body = true; - } - } - - fn enter_object_method( - &mut self, - _node: &'ast react_compiler_ast::expressions::ObjectMethod, - _scope_stack: &[ScopeId], - ) { - self.skip_body = false; - } -} - -/// Find all functions in the program that should be compiled. -/// -/// Uses the `AstWalker` with a `FunctionDiscoveryVisitor` to traverse -/// the entire program, discovering functions at any depth. The visitor -/// dynamically controls body traversal: compiled functions have their -/// bodies skipped (matching Babel's `fn.skip()`), while non-compiled -/// functions have their bodies traversed to find nested declarations. -/// -/// The visitor tracks parent context (VariableDeclarator names for -/// `const Foo = () => {}`, CallExpression callees for forwardRef/memo -/// wrappers) via enter/leave hooks. -/// -/// Skips classes and their contents (the walker does not recurse into -/// class bodies). -fn find_functions_to_compile<'a>( - program: &'a Program, - opts: &PluginOptions, - context: &mut ProgramContext, - scope: &ScopeInfo, -) -> Vec<CompileSource<'a>> { - let mut visitor = FunctionDiscoveryVisitor::new(opts, context); - let mut walker = AstWalker::new(scope); - walker.walk_program(&mut visitor, program); - visitor.queue -} - -// ----------------------------------------------------------------------- -// Main entry point -// ----------------------------------------------------------------------- - -/// A successfully compiled function, ready to be applied to the AST. -struct CompiledFunction<'a> { - #[allow(dead_code)] - kind: CompileSourceKind, - #[allow(dead_code)] - source: &'a CompileSource<'a>, - codegen_fn: CodegenFunction, -} - -/// The type of the original function node, used to determine what kind of -/// replacement node to create. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum OriginalFnKind { - FunctionDeclaration, - FunctionExpression, - ArrowFunctionExpression, -} - -/// Owned representation of a compiled function for AST replacement. -/// Does not borrow from the original program, so we can mutate the AST. -struct CompiledFnForReplacement { - /// Start position of the original function (retained for range queries). - fn_start: Option<u32>, - /// Node ID of the original function, used to find it in the AST. - fn_node_id: Option<u32>, - /// The kind of the original function node. - original_kind: OriginalFnKind, - /// The compiled codegen output. - codegen_fn: CodegenFunction, - /// Whether this is an original function (vs outlined). Gating only applies to original. - #[allow(dead_code)] - source_kind: CompileSourceKind, - /// The function name, if any. - fn_name: Option<String>, - /// Gating configuration (from dynamic gating or plugin options). - gating: Option<GatingConfig>, -} - -/// Check if a compiled function is referenced before its declaration at the top level. -/// This is needed for the gating rewrite: hoisted function declarations that are -/// referenced before their declaration site need a special gating pattern. -fn get_functions_referenced_before_declaration( - program: &Program, - compiled_fns: &[CompiledFnForReplacement], -) -> HashSet<u32> { - // Collect function names and their node_ids for compiled FunctionDeclarations - let mut fn_names: HashMap<String, u32> = HashMap::new(); - for compiled in compiled_fns { - if compiled.original_kind == OriginalFnKind::FunctionDeclaration { - if let Some(ref name) = compiled.fn_name { - if let Some(nid) = compiled.fn_node_id { - fn_names.insert(name.clone(), nid); - } - } - } - } - - if fn_names.is_empty() { - return HashSet::new(); - } - - let mut referenced_before_decl: HashSet<u32> = HashSet::new(); - - // Walk through program body in order. For each statement, check if it references - // any of the function names before the function's declaration. - for stmt in &program.body { - // Check if this statement IS one of the function declarations - if let Statement::FunctionDeclaration(f) = stmt { - if let Some(ref id) = f.id { - fn_names.remove(&id.name); - } - } - // For all remaining tracked names, check if the statement references them - // at the top level (not inside nested functions) - for (_name, nid) in &fn_names { - if stmt_references_identifier_at_top_level(stmt, _name) { - referenced_before_decl.insert(*nid); - } - } - } - - referenced_before_decl -} - -/// Check if a statement references an identifier at the top level (not inside nested functions). -fn stmt_references_identifier_at_top_level(stmt: &Statement, name: &str) -> bool { - match stmt { - Statement::FunctionDeclaration(_) => { - // Don't look inside function declarations (they create their own scope) - false - } - Statement::ExportDefaultDeclaration(export) => match export.declaration.as_ref() { - ExportDefaultDecl::Expression(e) => expr_references_identifier_at_top_level(e, name), - _ => false, - }, - Statement::ExportNamedDeclaration(export) => { - if let Some(ref decl) = export.declaration { - match decl.as_ref() { - Declaration::VariableDeclaration(var_decl) => { - var_decl.declarations.iter().any(|d| { - d.init - .as_ref() - .map_or(false, |e| expr_references_identifier_at_top_level(e, name)) - }) - } - _ => false, - } - } else { - // export { Name } - check specifiers - export.specifiers.iter().any(|s| { - if let react_compiler_ast::declarations::ExportSpecifier::ExportSpecifier( - spec, - ) = s - { - match &spec.local { - ModuleExportName::Identifier(id) => id.name == name, - _ => false, - } - } else { - false - } - }) - } - } - Statement::VariableDeclaration(var_decl) => var_decl.declarations.iter().any(|d| { - d.init - .as_ref() - .map_or(false, |e| expr_references_identifier_at_top_level(e, name)) - }), - Statement::ExpressionStatement(expr_stmt) => { - expr_references_identifier_at_top_level(&expr_stmt.expression, name) - } - Statement::ReturnStatement(ret) => ret - .argument - .as_ref() - .map_or(false, |e| expr_references_identifier_at_top_level(e, name)), - // Unmodeled statements (e.g. `export = X`) can reference top-level - // bindings; scan the raw node for a matching Identifier so the - // gating reference-before-declaration analysis does not miss them. - Statement::Unknown(unknown) => { - raw_node_references_identifier(&unknown.raw().parse_value(), name) - } - _ => false, - } -} - -/// Conservatively detect an `Identifier` node with the given name anywhere in -/// a raw unmodeled subtree. -fn raw_node_references_identifier(value: &serde_json::Value, name: &str) -> bool { - match value { - serde_json::Value::Object(map) => { - if map.get("type").and_then(serde_json::Value::as_str) == Some("Identifier") - && map.get("name").and_then(serde_json::Value::as_str) == Some(name) - { - return true; - } - map.values() - .any(|v| raw_node_references_identifier(v, name)) - } - serde_json::Value::Array(items) => items - .iter() - .any(|v| raw_node_references_identifier(v, name)), - _ => false, - } -} - -/// Check if an expression references an identifier at the top level. -fn expr_references_identifier_at_top_level(expr: &Expression, name: &str) -> bool { - match expr { - Expression::Identifier(id) => id.name == name, - Expression::CallExpression(call) => { - expr_references_identifier_at_top_level(&call.callee, name) - || call - .arguments - .iter() - .any(|a| expr_references_identifier_at_top_level(a, name)) - } - Expression::MemberExpression(member) => { - expr_references_identifier_at_top_level(&member.object, name) - } - Expression::ConditionalExpression(cond) => { - expr_references_identifier_at_top_level(&cond.test, name) - || expr_references_identifier_at_top_level(&cond.consequent, name) - || expr_references_identifier_at_top_level(&cond.alternate, name) - } - Expression::BinaryExpression(bin) => { - expr_references_identifier_at_top_level(&bin.left, name) - || expr_references_identifier_at_top_level(&bin.right, name) - } - Expression::LogicalExpression(log) => { - expr_references_identifier_at_top_level(&log.left, name) - || expr_references_identifier_at_top_level(&log.right, name) - } - // Don't recurse into function expressions/arrows (they create their own scope) - Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) => false, - _ => false, - } -} - -/// Build a function expression from a codegen function (compiled output). -fn build_compiled_function_expression(codegen: &CodegenFunction) -> Expression { - Expression::FunctionExpression(FunctionExpression { - base: BaseNode::typed("FunctionExpression"), - id: codegen.id.clone(), - params: codegen.params.clone(), - body: codegen.body.clone(), - generator: codegen.generator, - is_async: codegen.is_async, - return_type: None, - type_parameters: None, - predicate: None, - }) -} - -/// Build a function expression that preserves the original function's structure. -/// For FunctionDeclarations, converts to FunctionExpression. -/// For ArrowFunctionExpressions, keeps as-is. -fn clone_original_fn_as_expression(stmt: &Statement, node_id: u32) -> Option<Expression> { - match stmt { - Statement::FunctionDeclaration(f) => { - if f.base.node_id == Some(node_id) { - return Some(Expression::FunctionExpression(FunctionExpression { - base: BaseNode::typed("FunctionExpression"), - id: f.id.clone(), - params: f.params.clone(), - body: f.body.clone(), - generator: f.generator, - is_async: f.is_async, - return_type: None, - type_parameters: None, - predicate: None, - })); - } - None - } - Statement::VariableDeclaration(var_decl) => { - for d in &var_decl.declarations { - if let Some(ref init) = d.init { - if let Some(e) = clone_original_expr_as_expression(init, node_id) { - return Some(e); - } - } - } - None - } - Statement::ExportDefaultDeclaration(export) => match export.declaration.as_ref() { - ExportDefaultDecl::FunctionDeclaration(f) => { - if f.base.node_id == Some(node_id) { - return Some(Expression::FunctionExpression(FunctionExpression { - base: BaseNode::typed("FunctionExpression"), - id: f.id.clone(), - params: f.params.clone(), - body: f.body.clone(), - generator: f.generator, - is_async: f.is_async, - return_type: None, - type_parameters: None, - predicate: None, - })); - } - None - } - ExportDefaultDecl::Expression(e) => clone_original_expr_as_expression(e, node_id), - _ => None, - }, - Statement::ExportNamedDeclaration(export) => { - if let Some(ref decl) = export.declaration { - match decl.as_ref() { - Declaration::FunctionDeclaration(f) => { - if f.base.node_id == Some(node_id) { - return Some(Expression::FunctionExpression(FunctionExpression { - base: BaseNode::typed("FunctionExpression"), - id: f.id.clone(), - params: f.params.clone(), - body: f.body.clone(), - generator: f.generator, - is_async: f.is_async, - return_type: None, - type_parameters: None, - predicate: None, - })); - } - None - } - Declaration::VariableDeclaration(var_decl) => { - for d in &var_decl.declarations { - if let Some(ref init) = d.init { - if let Some(e) = clone_original_expr_as_expression(init, node_id) { - return Some(e); - } - } - } - None - } - _ => None, - } - } else { - None - } - } - Statement::ExpressionStatement(expr_stmt) => { - clone_original_expr_as_expression(&expr_stmt.expression, node_id) - } - // Recurse into block-containing statements - Statement::BlockStatement(block) => { - for s in &block.body { - if let Some(e) = clone_original_fn_as_expression(s, node_id) { - return Some(e); - } - } - None - } - Statement::IfStatement(if_stmt) => { - if let Some(e) = clone_original_expr_as_expression(&if_stmt.test, node_id) { - return Some(e); - } - if let Some(e) = clone_original_fn_as_expression(&if_stmt.consequent, node_id) { - return Some(e); - } - if let Some(ref alt) = if_stmt.alternate { - if let Some(e) = clone_original_fn_as_expression(alt, node_id) { - return Some(e); - } - } - None - } - Statement::TryStatement(try_stmt) => { - for s in &try_stmt.block.body { - if let Some(e) = clone_original_fn_as_expression(s, node_id) { - return Some(e); - } - } - if let Some(ref handler) = try_stmt.handler { - for s in &handler.body.body { - if let Some(e) = clone_original_fn_as_expression(s, node_id) { - return Some(e); - } - } - } - if let Some(ref finalizer) = try_stmt.finalizer { - for s in &finalizer.body { - if let Some(e) = clone_original_fn_as_expression(s, node_id) { - return Some(e); - } - } - } - None - } - Statement::SwitchStatement(switch_stmt) => { - if let Some(e) = clone_original_expr_as_expression(&switch_stmt.discriminant, node_id) { - return Some(e); - } - for case in &switch_stmt.cases { - for s in &case.consequent { - if let Some(e) = clone_original_fn_as_expression(s, node_id) { - return Some(e); - } - } - } - None - } - Statement::LabeledStatement(labeled) => { - clone_original_fn_as_expression(&labeled.body, node_id) - } - Statement::ForStatement(for_stmt) => { - if let Some(ref init) = for_stmt.init { - match init.as_ref() { - ForInit::VariableDeclaration(var_decl) => { - for d in &var_decl.declarations { - if let Some(ref init_expr) = d.init { - if let Some(e) = - clone_original_expr_as_expression(init_expr, node_id) - { - return Some(e); - } - } - } - } - ForInit::Expression(expr) => { - if let Some(e) = clone_original_expr_as_expression(expr, node_id) { - return Some(e); - } - } - } - } - if let Some(ref test) = for_stmt.test { - if let Some(e) = clone_original_expr_as_expression(test, node_id) { - return Some(e); - } - } - if let Some(ref update) = for_stmt.update { - if let Some(e) = clone_original_expr_as_expression(update, node_id) { - return Some(e); - } - } - clone_original_fn_as_expression(&for_stmt.body, node_id) - } - Statement::WhileStatement(while_stmt) => { - if let Some(e) = clone_original_expr_as_expression(&while_stmt.test, node_id) { - return Some(e); - } - clone_original_fn_as_expression(&while_stmt.body, node_id) - } - Statement::DoWhileStatement(do_while) => { - if let Some(e) = clone_original_expr_as_expression(&do_while.test, node_id) { - return Some(e); - } - clone_original_fn_as_expression(&do_while.body, node_id) - } - Statement::ForInStatement(for_in) => { - if let Some(e) = clone_original_expr_as_expression(&for_in.right, node_id) { - return Some(e); - } - clone_original_fn_as_expression(&for_in.body, node_id) - } - Statement::ForOfStatement(for_of) => { - if let Some(e) = clone_original_expr_as_expression(&for_of.right, node_id) { - return Some(e); - } - clone_original_fn_as_expression(&for_of.body, node_id) - } - Statement::WithStatement(with_stmt) => { - if let Some(e) = clone_original_expr_as_expression(&with_stmt.object, node_id) { - return Some(e); - } - clone_original_fn_as_expression(&with_stmt.body, node_id) - } - Statement::ReturnStatement(ret) => { - if let Some(ref arg) = ret.argument { - clone_original_expr_as_expression(arg, node_id) - } else { - None - } - } - Statement::ThrowStatement(throw_stmt) => { - clone_original_expr_as_expression(&throw_stmt.argument, node_id) - } - _ => None, - } -} - -/// Clone an expression node for use as the original (fallback) in gating. -fn clone_original_expr_as_expression(expr: &Expression, node_id: u32) -> Option<Expression> { - match expr { - Expression::FunctionExpression(f) => { - if f.base.node_id == Some(node_id) { - return Some(Expression::FunctionExpression(f.clone())); - } - None - } - Expression::ArrowFunctionExpression(f) => { - if f.base.node_id == Some(node_id) { - return Some(Expression::ArrowFunctionExpression(f.clone())); - } - None - } - Expression::CallExpression(call) => { - for arg in &call.arguments { - if let Some(e) = clone_original_expr_as_expression(arg, node_id) { - return Some(e); - } - } - None - } - Expression::ObjectExpression(obj) => { - for prop in &obj.properties { - match prop { - ObjectExpressionProperty::ObjectProperty(p) => { - if let Some(e) = clone_original_expr_as_expression(&p.value, node_id) { - return Some(e); - } - } - ObjectExpressionProperty::SpreadElement(s) => { - if let Some(e) = clone_original_expr_as_expression(&s.argument, node_id) { - return Some(e); - } - } - _ => {} - } - } - None - } - Expression::ArrayExpression(arr) => { - for elem in arr.elements.iter().flatten() { - if let Some(e) = clone_original_expr_as_expression(elem, node_id) { - return Some(e); - } - } - None - } - Expression::AssignmentExpression(assign) => { - clone_original_expr_as_expression(&assign.right, node_id) - } - Expression::SequenceExpression(seq) => { - for e in &seq.expressions { - if let Some(e) = clone_original_expr_as_expression(e, node_id) { - return Some(e); - } - } - None - } - Expression::ConditionalExpression(cond) => { - if let Some(e) = clone_original_expr_as_expression(&cond.consequent, node_id) { - return Some(e); - } - clone_original_expr_as_expression(&cond.alternate, node_id) - } - Expression::ParenthesizedExpression(paren) => { - clone_original_expr_as_expression(&paren.expression, node_id) - } - _ => None, - } -} - -/// Build a compiled arrow/function expression from a codegen function, -/// matching the original expression kind. -fn build_compiled_expression_matching_kind( - codegen: &CodegenFunction, - original_kind: OriginalFnKind, -) -> Expression { - match original_kind { - OriginalFnKind::ArrowFunctionExpression => { - Expression::ArrowFunctionExpression(ArrowFunctionExpression { - base: BaseNode::typed("ArrowFunctionExpression"), - params: codegen.params.clone(), - body: Box::new(ArrowFunctionBody::BlockStatement(codegen.body.clone())), - id: None, - generator: codegen.generator, - is_async: codegen.is_async, - expression: Some(false), - return_type: None, - type_parameters: None, - predicate: None, - }) - } - _ => build_compiled_function_expression(codegen), - } -} - -/// Apply compiled functions back to the AST by replacing original function nodes -/// with their compiled versions, inserting outlined functions, and adding imports. -fn apply_compiled_functions( - compiled_fns: &[CompiledFnForReplacement], - program: &mut Program, - context: &mut ProgramContext, -) { - if compiled_fns.is_empty() { - return; - } - - // Check if any compiled functions have gating enabled - let has_gating = compiled_fns.iter().any(|cf| cf.gating.is_some()); - - // If gating is enabled, determine which functions are referenced before declaration - let referenced_before_decl = if has_gating { - get_functions_referenced_before_declaration(program, compiled_fns) - } else { - HashSet::new() - }; - - // For gated functions, we need to clone the original function expressions - // BEFORE we start mutating the AST. - let original_expressions: Vec<Option<Expression>> = if has_gating { - compiled_fns - .iter() - .map(|compiled| { - if compiled.gating.is_some() { - if let Some(node_id) = compiled.fn_node_id { - for stmt in program.body.iter() { - if let Some(expr) = clone_original_fn_as_expression(stmt, node_id) { - return Some(expr); - } - } - } - None - } else { - None - } - }) - .collect() - } else { - compiled_fns.iter().map(|_| None).collect() - }; - - // Collect outlined functions to insert (as FunctionDeclarations). - // For FunctionDeclarations: insert right after the parent (matching TS insertAfter behavior) - // For FunctionExpression/ArrowFunctionExpression: append at end of program body - // (matching TS pushContainer behavior) - let mut outlined_decls: Vec<(Option<u32>, OriginalFnKind, FunctionDeclaration)> = Vec::new(); // (node_id, kind, decl) - - // Replace each compiled function in the AST - for (idx, compiled) in compiled_fns.iter().enumerate() { - // Collect outlined functions for this compiled function - for outlined in &compiled.codegen_fn.outlined { - let outlined_decl = FunctionDeclaration { - base: BaseNode::typed("FunctionDeclaration"), - id: outlined.func.id.clone(), - params: outlined.func.params.clone(), - body: outlined.func.body.clone(), - generator: outlined.func.generator, - is_async: outlined.func.is_async, - declare: None, - return_type: None, - type_parameters: None, - predicate: None, - component_declaration: false, - hook_declaration: false, - }; - outlined_decls.push((compiled.fn_node_id, compiled.original_kind, outlined_decl)); - } - - if let Some(ref gating_config) = compiled.gating { - let is_ref_before_decl = compiled - .fn_node_id - .map_or(false, |nid| referenced_before_decl.contains(&nid)); - - if is_ref_before_decl && compiled.original_kind == OriginalFnKind::FunctionDeclaration { - // Use the hoisted function declaration gating pattern - apply_gated_function_hoisted(program, compiled, gating_config, context); - } else { - // Use the conditional expression gating pattern - let original_expr = original_expressions[idx].clone(); - apply_gated_function_conditional( - program, - compiled, - gating_config, - original_expr, - context, - ); - } - } else { - // No gating: replace the function directly (original behavior) - if let Some(node_id) = compiled.fn_node_id { - let mut visitor = ReplaceFnVisitor { node_id, compiled }; - walk_program_mut(&mut visitor, program); - } - } - } - - // Insert outlined function declarations. - // For FunctionDeclarations: insert right after the parent function at the same scope level. - // This requires recursive search since the parent may be nested inside other functions. - // Matches TS behavior: `originalFn.insertAfter(outlinedFn)`. - // For FunctionExpression/ArrowFunctionExpression: push to program body (top level). - // Matches TS behavior: `program.pushContainer('body', [fn])`. - - for (parent_node_id, original_kind, outlined_decl) in outlined_decls { - let outlined_stmt = Statement::FunctionDeclaration(outlined_decl); - match original_kind { - OriginalFnKind::FunctionDeclaration => { - if let Some(nid) = parent_node_id { - if !insert_after_fn_recursive(&mut program.body, nid, outlined_stmt.clone()) { - program.body.push(outlined_stmt); - } - } else { - program.body.push(outlined_stmt); - } - } - OriginalFnKind::FunctionExpression | OriginalFnKind::ArrowFunctionExpression => { - program.body.push(outlined_stmt); - } - } - } - - // Register the memo cache import and rename useMemoCache references. - let needs_memo_import = compiled_fns - .iter() - .any(|cf| cf.codegen_fn.memo_slots_used > 0); - if needs_memo_import { - let import_spec = context.add_memo_cache_import(); - let local_name = import_spec.name; - let mut visitor = RenameIdentifierVisitor { - old_name: "useMemoCache", - new_name: &local_name, - }; - walk_program_mut(&mut visitor, program); - } - - // Instrumentation and hook guard imports are pre-registered in compile_program - // before compilation, so they are already in the imports map. No post-hoc - // renaming needed since codegen uses the pre-resolved local names. - - add_imports_to_program(program, context); -} - -/// Apply the conditional expression gating pattern. -/// -/// For function declarations (non-export-default, non-hoisted): -/// `function Foo(props) { ... }` -> `const Foo = gating() ? function Foo(...) { compiled } : function Foo(...) { original };` -/// -/// For export default function with name: -/// `export default function Foo(props) { ... }` -> `const Foo = gating() ? ... : ...; export default Foo;` -/// -/// For export named function: -/// `export function Foo(props) { ... }` -> `export const Foo = gating() ? ... : ...;` -/// -/// For arrow/function expressions: -/// Replace the expression inline with `gating() ? compiled : original` -fn apply_gated_function_conditional( - program: &mut Program, - compiled: &CompiledFnForReplacement, - gating_config: &GatingConfig, - original_expr: Option<Expression>, - context: &mut ProgramContext, -) { - let _start = match compiled.fn_start { - Some(s) => s, - None => return, - }; - let node_id = match compiled.fn_node_id { - Some(nid) => nid, - None => return, - }; - - // Add the gating import - let gating_import = context.add_import_specifier( - &gating_config.source, - &gating_config.import_specifier_name, - None, - ); - let gating_callee_name = gating_import.name; - - // Build the compiled expression - let compiled_expr = - build_compiled_expression_matching_kind(&compiled.codegen_fn, compiled.original_kind); - - // Build the original (fallback) expression - let original_expr = match original_expr { - Some(e) => e, - None => return, // shouldn't happen - }; - - // Build: gating() ? compiled : original - let gating_expression = Expression::ConditionalExpression(ConditionalExpression { - base: BaseNode::typed("ConditionalExpression"), - test: Box::new(Expression::CallExpression(CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: gating_callee_name, - type_annotation: None, - optional: None, - decorators: None, - })), - arguments: vec![], - type_parameters: None, - type_arguments: None, - optional: None, - })), - consequent: Box::new(compiled_expr), - alternate: Box::new(original_expr), - }); - - // Find and replace the function in the program body. - // We need to track if this was an export default function with a name, - // because we need to insert `export default Name;` after the replacement. - let mut export_default_name: Option<(usize, String)> = None; - - for (idx, stmt) in program.body.iter().enumerate() { - if let Statement::ExportDefaultDeclaration(export) = stmt { - if let ExportDefaultDecl::FunctionDeclaration(f) = export.declaration.as_ref() { - if f.base.node_id == Some(node_id) { - if let Some(ref fn_id) = f.id { - export_default_name = Some((idx, fn_id.name.clone())); - } - } - } - } - } - - let mut visitor = ReplaceWithGatedVisitor { - node_id, - gating_expression: &gating_expression, - }; - walk_program_mut(&mut visitor, program); - - // If this was an export default function with a name, insert `export default Name;` after - if let Some((idx, name)) = export_default_name { - program.body.insert( - idx + 1, - Statement::ExportDefaultDeclaration(ExportDefaultDeclaration { - base: BaseNode::typed("ExportDefaultDeclaration"), - declaration: Box::new(ExportDefaultDecl::Expression(Box::new( - Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name, - type_annotation: None, - optional: None, - decorators: None, - }), - ))), - export_kind: None, - }), - ); - } -} - -/// Visitor that replaces a function with a gated conditional expression. -struct ReplaceWithGatedVisitor<'a> { - node_id: u32, - gating_expression: &'a Expression, -} - -impl MutVisitor for ReplaceWithGatedVisitor<'_> { - fn visit_statement(&mut self, stmt: &mut Statement) -> VisitResult { - // FunctionDeclaration → replace with `const Foo = gating() ? ... : ...;` - if let Statement::FunctionDeclaration(f) = &*stmt { - if f.base.node_id == Some(self.node_id) { - let fn_name = f.id.clone().unwrap_or_else(|| Identifier { - base: BaseNode::typed("Identifier"), - name: "anonymous".to_string(), - type_annotation: None, - optional: None, - decorators: None, - }); - let mut base = BaseNode::typed("VariableDeclaration"); - base.leading_comments = f.base.leading_comments.clone(); - base.trailing_comments = f.base.trailing_comments.clone(); - base.inner_comments = f.base.inner_comments.clone(); - *stmt = Statement::VariableDeclaration(VariableDeclaration { - base, - kind: VariableDeclarationKind::Const, - declarations: vec![VariableDeclarator { - base: BaseNode::typed("VariableDeclarator"), - id: PatternLike::Identifier(fn_name), - init: Some(Box::new(self.gating_expression.clone())), - definite: None, - }], - declare: None, - }); - return VisitResult::Stop; - } - } - - // ExportDefaultDeclaration with FunctionDeclaration - if let Statement::ExportDefaultDeclaration(export) = stmt { - let is_fn_decl_match = matches!( - export.declaration.as_ref(), - ExportDefaultDecl::FunctionDeclaration(f) if f.base.node_id == Some(self.node_id) - ); - if is_fn_decl_match { - if let ExportDefaultDecl::FunctionDeclaration(f) = export.declaration.as_ref() { - let fn_name = f.id.clone(); - if let Some(fn_id) = fn_name { - let mut base = BaseNode::typed("VariableDeclaration"); - base.leading_comments = export.base.leading_comments.clone(); - base.trailing_comments = export.base.trailing_comments.clone(); - base.inner_comments = export.base.inner_comments.clone(); - *stmt = Statement::VariableDeclaration(VariableDeclaration { - base, - kind: VariableDeclarationKind::Const, - declarations: vec![VariableDeclarator { - base: BaseNode::typed("VariableDeclarator"), - id: PatternLike::Identifier(fn_id), - init: Some(Box::new(self.gating_expression.clone())), - definite: None, - }], - declare: None, - }); - return VisitResult::Stop; - } else { - export.declaration = Box::new(ExportDefaultDecl::Expression(Box::new( - self.gating_expression.clone(), - ))); - return VisitResult::Stop; - } - } - } - // Expression case handled by walker recursion into visit_expression - } - - // ExportNamedDeclaration with FunctionDeclaration - if let Statement::ExportNamedDeclaration(export) = stmt { - if let Some(ref mut decl) = export.declaration { - if let Declaration::FunctionDeclaration(f) = decl.as_mut() { - if f.base.node_id == Some(self.node_id) { - let fn_name = f.id.clone().unwrap_or_else(|| Identifier { - base: BaseNode::typed("Identifier"), - name: "anonymous".to_string(), - type_annotation: None, - optional: None, - decorators: None, - }); - *decl = Box::new(Declaration::VariableDeclaration(VariableDeclaration { - base: BaseNode::typed("VariableDeclaration"), - kind: VariableDeclarationKind::Const, - declarations: vec![VariableDeclarator { - base: BaseNode::typed("VariableDeclarator"), - id: PatternLike::Identifier(fn_name), - init: Some(Box::new(self.gating_expression.clone())), - definite: None, - }], - declare: None, - })); - return VisitResult::Stop; - } - } - } - } - - VisitResult::Continue - } - - fn visit_expression(&mut self, expr: &mut Expression) -> VisitResult { - match expr { - Expression::FunctionExpression(f) if f.base.node_id == Some(self.node_id) => { - *expr = self.gating_expression.clone(); - VisitResult::Stop - } - Expression::ArrowFunctionExpression(f) if f.base.node_id == Some(self.node_id) => { - *expr = self.gating_expression.clone(); - VisitResult::Stop - } - _ => VisitResult::Continue, - } - } -} - -/// Apply the hoisted function declaration gating pattern. -/// -/// This is used when a function declaration is referenced before its declaration site. -/// Instead of wrapping in a conditional expression (which would break hoisting), we: -/// 1. Rename the original function to `Foo_unoptimized` -/// 2. Insert a compiled function as `Foo_optimized` -/// 3. Insert a `const gating_result = gating()` before -/// 4. Insert a new `function Foo(arg0, ...) { if (gating_result) return Foo_optimized(...); else return Foo_unoptimized(...); }` after -fn apply_gated_function_hoisted( - program: &mut Program, - compiled: &CompiledFnForReplacement, - gating_config: &GatingConfig, - context: &mut ProgramContext, -) { - let _start = match compiled.fn_start { - Some(s) => s, - None => return, - }; - let node_id = match compiled.fn_node_id { - Some(nid) => nid, - None => return, - }; - - let original_fn_name = match &compiled.fn_name { - Some(name) => name.clone(), - None => return, - }; - - // Add the gating import - let gating_import = context.add_import_specifier( - &gating_config.source, - &gating_config.import_specifier_name, - None, - ); - let gating_callee_name = gating_import.name.clone(); - - // Generate unique names - let gating_result_name = context.new_uid(&format!("{}_result", gating_callee_name)); - let unoptimized_name = context.new_uid(&format!("{}_unoptimized", original_fn_name)); - let optimized_name = context.new_uid(&format!("{}_optimized", original_fn_name)); - - // Find the original function declaration and determine its params - let mut original_params: Vec<PatternLike> = Vec::new(); - let mut fn_stmt_idx: Option<usize> = None; - - for (idx, stmt) in program.body.iter().enumerate() { - if let Statement::FunctionDeclaration(f) = stmt { - if f.base.node_id == Some(node_id) { - original_params = f.params.clone(); - fn_stmt_idx = Some(idx); - break; - } - } - } - - let fn_idx = match fn_stmt_idx { - Some(idx) => idx, - None => return, - }; - - // Rename the original function to `_unoptimized` - if let Statement::FunctionDeclaration(f) = &mut program.body[fn_idx] { - if let Some(ref mut id) = f.id { - id.name = unoptimized_name.clone(); - } - } - - // Build the optimized function declaration (compiled version with renamed id) - let compiled_fn_decl = FunctionDeclaration { - base: BaseNode::typed("FunctionDeclaration"), - id: Some(Identifier { - base: BaseNode::typed("Identifier"), - name: optimized_name.clone(), - type_annotation: None, - optional: None, - decorators: None, - }), - params: compiled.codegen_fn.params.clone(), - body: compiled.codegen_fn.body.clone(), - generator: compiled.codegen_fn.generator, - is_async: compiled.codegen_fn.is_async, - declare: None, - return_type: None, - type_parameters: None, - predicate: None, - component_declaration: false, - hook_declaration: false, - }; - - // Build the gating result variable: `const gating_result = gating();` - let gating_result_stmt = Statement::VariableDeclaration(VariableDeclaration { - base: BaseNode::typed("VariableDeclaration"), - kind: VariableDeclarationKind::Const, - declarations: vec![VariableDeclarator { - base: BaseNode::typed("VariableDeclarator"), - id: PatternLike::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: gating_result_name.clone(), - type_annotation: None, - optional: None, - decorators: None, - }), - init: Some(Box::new(Expression::CallExpression(CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: gating_callee_name, - type_annotation: None, - optional: None, - decorators: None, - })), - arguments: vec![], - type_parameters: None, - type_arguments: None, - optional: None, - }))), - definite: None, - }], - declare: None, - }); - - // Build new params and args for the dispatcher function - let num_params = original_params.len(); - let mut new_params: Vec<PatternLike> = Vec::new(); - let mut optimized_args: Vec<Expression> = Vec::new(); - let mut unoptimized_args: Vec<Expression> = Vec::new(); - - for i in 0..num_params { - let arg_name = format!("arg{}", i); - let is_rest = matches!(&original_params[i], PatternLike::RestElement(_)); - - if is_rest { - new_params.push(PatternLike::RestElement( - react_compiler_ast::patterns::RestElement { - base: BaseNode::typed("RestElement"), - argument: Box::new(PatternLike::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: arg_name.clone(), - type_annotation: None, - optional: None, - decorators: None, - })), - type_annotation: None, - decorators: None, - }, - )); - optimized_args.push(Expression::SpreadElement(SpreadElement { - base: BaseNode::typed("SpreadElement"), - argument: Box::new(Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: arg_name.clone(), - type_annotation: None, - optional: None, - decorators: None, - })), - })); - unoptimized_args.push(Expression::SpreadElement(SpreadElement { - base: BaseNode::typed("SpreadElement"), - argument: Box::new(Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: arg_name, - type_annotation: None, - optional: None, - decorators: None, - })), - })); - } else { - new_params.push(PatternLike::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: arg_name.clone(), - type_annotation: None, - optional: None, - decorators: None, - })); - optimized_args.push(Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: arg_name.clone(), - type_annotation: None, - optional: None, - decorators: None, - })); - unoptimized_args.push(Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: arg_name, - type_annotation: None, - optional: None, - decorators: None, - })); - } - } - - // Build the dispatcher function: - // function Foo(arg0, ...) { - // if (gating_result) return Foo_optimized(arg0, ...); - // else return Foo_unoptimized(arg0, ...); - // } - let dispatcher_fn = Statement::FunctionDeclaration(FunctionDeclaration { - base: BaseNode::typed("FunctionDeclaration"), - id: Some(Identifier { - base: BaseNode::typed("Identifier"), - name: original_fn_name, - type_annotation: None, - optional: None, - decorators: None, - }), - params: new_params, - body: BlockStatement { - base: BaseNode::typed("BlockStatement"), - body: vec![Statement::IfStatement(IfStatement { - base: BaseNode::typed("IfStatement"), - test: Box::new(Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: gating_result_name, - type_annotation: None, - optional: None, - decorators: None, - })), - consequent: Box::new(Statement::ReturnStatement(ReturnStatement { - base: BaseNode::typed("ReturnStatement"), - argument: Some(Box::new(Expression::CallExpression(CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: optimized_name.clone(), - type_annotation: None, - optional: None, - decorators: None, - })), - arguments: optimized_args, - type_parameters: None, - type_arguments: None, - optional: None, - }))), - })), - alternate: Some(Box::new(Statement::ReturnStatement(ReturnStatement { - base: BaseNode::typed("ReturnStatement"), - argument: Some(Box::new(Expression::CallExpression(CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(Expression::Identifier(Identifier { - base: BaseNode::typed("Identifier"), - name: unoptimized_name, - type_annotation: None, - optional: None, - decorators: None, - })), - arguments: unoptimized_args, - type_parameters: None, - type_arguments: None, - optional: None, - }))), - }))), - })], - directives: vec![], - }, - generator: false, - is_async: false, - declare: None, - return_type: None, - type_parameters: None, - predicate: None, - component_declaration: false, - hook_declaration: false, - }); - - // Insert nodes. The TS code uses insertBefore for the gating result and optimized fn, - // and insertAfter for the dispatcher. The order in the output should be: - // ... (existing statements before fn_idx) ... - // const gating_result = gating(); <- inserted before - // function Foo_optimized() { ... } <- inserted before - // function Foo_unoptimized() { ... } <- the original (renamed) - // function Foo(arg0) { ... } <- inserted after - // ... (existing statements after fn_idx) ... - // - // insertBefore inserts before the target, and insertAfter inserts after. - // We insert in reverse order for insertAfter. - - // Insert dispatcher after the original (now renamed) function - program.body.insert(fn_idx + 1, dispatcher_fn); - - // Insert optimized function before the original - program - .body - .insert(fn_idx, Statement::FunctionDeclaration(compiled_fn_decl)); - - // Insert gating result before the optimized function - program.body.insert(fn_idx, gating_result_stmt); -} - -/// Recursively search for a function at `start` position and insert `new_stmt` -/// right after it in the same block. Returns true if successfully inserted. -/// Searches through all nested structures: function bodies, object method bodies, etc. -fn insert_after_fn_recursive( - stmts: &mut Vec<Statement>, - node_id: u32, - new_stmt: Statement, -) -> bool { - // Check this level first - if let Some(pos) = stmts - .iter() - .position(|s| stmt_has_fn_with_node_id(s, node_id)) - { - stmts.insert(pos + 1, new_stmt); - return true; - } - // Recurse into every statement that can contain nested blocks - for stmt in stmts.iter_mut() { - if insert_after_fn_in_stmt(stmt, node_id, &new_stmt) { - return true; - } - } - false -} - -fn insert_after_fn_in_stmt(stmt: &mut Statement, node_id: u32, new_stmt: &Statement) -> bool { - match stmt { - Statement::FunctionDeclaration(f) => { - insert_after_fn_in_block(&mut f.body, node_id, new_stmt) - } - Statement::BlockStatement(b) => insert_after_fn_in_block(b, node_id, new_stmt), - Statement::ExpressionStatement(e) => { - insert_after_fn_in_expr(&mut e.expression, node_id, new_stmt) - } - Statement::ReturnStatement(r) => { - if let Some(arg) = &mut r.argument { - insert_after_fn_in_expr(arg, node_id, new_stmt) - } else { - false - } - } - Statement::VariableDeclaration(v) => { - for decl in &mut v.declarations { - if let Some(init) = &mut decl.init { - if insert_after_fn_in_expr(init, node_id, new_stmt) { - return true; - } - } - } - false - } - Statement::ExportDefaultDeclaration(e) => match e.declaration.as_mut() { - ExportDefaultDecl::FunctionDeclaration(f) => { - insert_after_fn_in_block(&mut f.body, node_id, new_stmt) - } - ExportDefaultDecl::Expression(expr) => insert_after_fn_in_expr(expr, node_id, new_stmt), - _ => false, - }, - Statement::ExportNamedDeclaration(e) => { - if let Some(decl) = &mut e.declaration { - match decl.as_mut() { - Declaration::FunctionDeclaration(f) => { - insert_after_fn_in_block(&mut f.body, node_id, new_stmt) - } - Declaration::VariableDeclaration(v) => { - for d in &mut v.declarations { - if let Some(init) = &mut d.init { - if insert_after_fn_in_expr(init, node_id, new_stmt) { - return true; - } - } - } - false - } - _ => false, - } - } else { - false - } - } - Statement::IfStatement(i) => { - insert_after_fn_in_stmt(&mut i.consequent, node_id, new_stmt) - || i.alternate - .as_mut() - .map_or(false, |a| insert_after_fn_in_stmt(a, node_id, new_stmt)) - } - Statement::ForStatement(f) => insert_after_fn_in_stmt(&mut f.body, node_id, new_stmt), - Statement::WhileStatement(w) => insert_after_fn_in_stmt(&mut w.body, node_id, new_stmt), - Statement::TryStatement(t) => { - if insert_after_fn_in_block(&mut t.block, node_id, new_stmt) { - return true; - } - if let Some(h) = &mut t.handler { - if insert_after_fn_in_block(&mut h.body, node_id, new_stmt) { - return true; - } - } - if let Some(f) = &mut t.finalizer { - if insert_after_fn_in_block(f, node_id, new_stmt) { - return true; - } - } - false - } - _ => false, - } -} - -fn insert_after_fn_in_block( - block: &mut react_compiler_ast::statements::BlockStatement, - node_id: u32, - new_stmt: &Statement, -) -> bool { - if let Some(pos) = block - .body - .iter() - .position(|s| stmt_has_fn_with_node_id(s, node_id)) - { - block.body.insert(pos + 1, new_stmt.clone()); - return true; - } - for stmt in block.body.iter_mut() { - if insert_after_fn_in_stmt(stmt, node_id, new_stmt) { - return true; - } - } - false -} - -fn insert_after_fn_in_expr( - expr: &mut react_compiler_ast::expressions::Expression, - node_id: u32, - new_stmt: &Statement, -) -> bool { - use react_compiler_ast::expressions::Expression; - match expr { - Expression::ObjectExpression(obj) => { - for prop in &mut obj.properties { - match prop { - react_compiler_ast::expressions::ObjectExpressionProperty::ObjectMethod(m) => { - if insert_after_fn_in_block(&mut m.body, node_id, new_stmt) { - return true; - } - } - react_compiler_ast::expressions::ObjectExpressionProperty::ObjectProperty( - p, - ) => { - if insert_after_fn_in_expr(&mut p.value, node_id, new_stmt) { - return true; - } - } - _ => {} - } - } - false - } - Expression::ArrayExpression(arr) => { - for elem in arr.elements.iter_mut().flatten() { - if insert_after_fn_in_expr(elem, node_id, new_stmt) { - return true; - } - } - false - } - Expression::ArrowFunctionExpression(arrow) => match arrow.body.as_mut() { - react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => { - insert_after_fn_in_block(block, node_id, new_stmt) - } - react_compiler_ast::expressions::ArrowFunctionBody::Expression(e) => { - insert_after_fn_in_expr(e, node_id, new_stmt) - } - }, - Expression::FunctionExpression(f) => { - insert_after_fn_in_block(&mut f.body, node_id, new_stmt) - } - Expression::CallExpression(c) => { - for arg in &mut c.arguments { - if insert_after_fn_in_expr(arg, node_id, new_stmt) { - return true; - } - } - insert_after_fn_in_expr(&mut c.callee, node_id, new_stmt) - } - Expression::ConditionalExpression(c) => { - insert_after_fn_in_expr(&mut c.consequent, node_id, new_stmt) - || insert_after_fn_in_expr(&mut c.alternate, node_id, new_stmt) - } - Expression::AssignmentExpression(a) => { - insert_after_fn_in_expr(&mut a.right, node_id, new_stmt) - } - Expression::TypeCastExpression(tc) => { - insert_after_fn_in_expr(&mut tc.expression, node_id, new_stmt) - } - Expression::ParenthesizedExpression(p) => { - insert_after_fn_in_expr(&mut p.expression, node_id, new_stmt) - } - Expression::TSAsExpression(ts) => { - insert_after_fn_in_expr(&mut ts.expression, node_id, new_stmt) - } - Expression::SequenceExpression(s) => { - for expr in &mut s.expressions { - if insert_after_fn_in_expr(expr, node_id, new_stmt) { - return true; - } - } - false - } - _ => false, - } -} - -/// Check if a statement contains a function whose BaseNode.node_id matches. -fn stmt_has_fn_with_node_id(stmt: &Statement, node_id: u32) -> bool { - match stmt { - Statement::FunctionDeclaration(f) => f.base.node_id == Some(node_id), - Statement::VariableDeclaration(var_decl) => var_decl.declarations.iter().any(|decl| { - if let Some(ref init) = decl.init { - expr_has_fn_with_node_id(init, node_id) - } else { - false - } - }), - Statement::ExportDefaultDeclaration(export) => match export.declaration.as_ref() { - ExportDefaultDecl::FunctionDeclaration(f) => f.base.node_id == Some(node_id), - ExportDefaultDecl::Expression(e) => expr_has_fn_with_node_id(e, node_id), - _ => false, - }, - Statement::ExportNamedDeclaration(export) => { - if let Some(ref decl) = export.declaration { - match decl.as_ref() { - Declaration::FunctionDeclaration(f) => f.base.node_id == Some(node_id), - Declaration::VariableDeclaration(var_decl) => { - var_decl.declarations.iter().any(|d| { - if let Some(ref init) = d.init { - expr_has_fn_with_node_id(init, node_id) - } else { - false - } - }) - } - _ => false, - } - } else { - false - } - } - Statement::ExpressionStatement(expr_stmt) => { - expr_has_fn_with_node_id(&expr_stmt.expression, node_id) - } - // Recurse into block-containing statements - Statement::BlockStatement(block) => block - .body - .iter() - .any(|s| stmt_has_fn_with_node_id(s, node_id)), - Statement::IfStatement(if_stmt) => { - expr_has_fn_with_node_id(&if_stmt.test, node_id) - || stmt_has_fn_with_node_id(&if_stmt.consequent, node_id) - || if_stmt - .alternate - .as_ref() - .map_or(false, |alt| stmt_has_fn_with_node_id(alt, node_id)) - } - Statement::TryStatement(try_stmt) => { - try_stmt - .block - .body - .iter() - .any(|s| stmt_has_fn_with_node_id(s, node_id)) - || try_stmt.handler.as_ref().map_or(false, |h| { - h.body - .body - .iter() - .any(|s| stmt_has_fn_with_node_id(s, node_id)) - }) - || try_stmt.finalizer.as_ref().map_or(false, |f| { - f.body.iter().any(|s| stmt_has_fn_with_node_id(s, node_id)) - }) - } - Statement::SwitchStatement(switch_stmt) => { - expr_has_fn_with_node_id(&switch_stmt.discriminant, node_id) - || switch_stmt.cases.iter().any(|case| { - case.consequent - .iter() - .any(|s| stmt_has_fn_with_node_id(s, node_id)) - }) - } - Statement::LabeledStatement(labeled) => stmt_has_fn_with_node_id(&labeled.body, node_id), - Statement::ForStatement(for_stmt) => { - if let Some(ref init) = for_stmt.init { - match init.as_ref() { - ForInit::VariableDeclaration(var_decl) => { - if var_decl.declarations.iter().any(|d| { - d.init - .as_ref() - .map_or(false, |e| expr_has_fn_with_node_id(e, node_id)) - }) { - return true; - } - } - ForInit::Expression(expr) => { - if expr_has_fn_with_node_id(expr, node_id) { - return true; - } - } - } - } - if for_stmt - .test - .as_ref() - .map_or(false, |t| expr_has_fn_with_node_id(t, node_id)) - { - return true; - } - if for_stmt - .update - .as_ref() - .map_or(false, |u| expr_has_fn_with_node_id(u, node_id)) - { - return true; - } - stmt_has_fn_with_node_id(&for_stmt.body, node_id) - } - Statement::WhileStatement(while_stmt) => { - expr_has_fn_with_node_id(&while_stmt.test, node_id) - || stmt_has_fn_with_node_id(&while_stmt.body, node_id) - } - Statement::DoWhileStatement(do_while) => { - expr_has_fn_with_node_id(&do_while.test, node_id) - || stmt_has_fn_with_node_id(&do_while.body, node_id) - } - Statement::ForInStatement(for_in) => { - expr_has_fn_with_node_id(&for_in.right, node_id) - || stmt_has_fn_with_node_id(&for_in.body, node_id) - } - Statement::ForOfStatement(for_of) => { - expr_has_fn_with_node_id(&for_of.right, node_id) - || stmt_has_fn_with_node_id(&for_of.body, node_id) - } - Statement::WithStatement(with_stmt) => { - expr_has_fn_with_node_id(&with_stmt.object, node_id) - || stmt_has_fn_with_node_id(&with_stmt.body, node_id) - } - Statement::ReturnStatement(ret) => ret - .argument - .as_ref() - .map_or(false, |arg| expr_has_fn_with_node_id(arg, node_id)), - Statement::ThrowStatement(throw_stmt) => { - expr_has_fn_with_node_id(&throw_stmt.argument, node_id) - } - _ => false, - } -} - -/// Check if an expression contains a function whose BaseNode.node_id matches. -fn expr_has_fn_with_node_id(expr: &Expression, node_id: u32) -> bool { - match expr { - Expression::FunctionExpression(f) => f.base.node_id == Some(node_id), - Expression::ArrowFunctionExpression(f) => f.base.node_id == Some(node_id), - // Check for forwardRef/memo wrappers: the inner function - Expression::CallExpression(call) => call - .arguments - .iter() - .any(|arg| expr_has_fn_with_node_id(arg, node_id)), - _ => false, - } -} - -/// Visitor that replaces a compiled function in the AST by matching `base.node_id`. -struct ReplaceFnVisitor<'a> { - node_id: u32, - compiled: &'a CompiledFnForReplacement, -} - -impl MutVisitor for ReplaceFnVisitor<'_> { - fn visit_statement(&mut self, stmt: &mut Statement) -> VisitResult { - match stmt { - Statement::FunctionDeclaration(f) if f.base.node_id == Some(self.node_id) => { - f.id = self.compiled.codegen_fn.id.clone(); - f.params = self.compiled.codegen_fn.params.clone(); - f.body = self.compiled.codegen_fn.body.clone(); - f.generator = self.compiled.codegen_fn.generator; - f.is_async = self.compiled.codegen_fn.is_async; - f.return_type = None; - f.type_parameters = None; - f.predicate = None; - f.declare = None; - return VisitResult::Stop; - } - Statement::ExportDefaultDeclaration(export) => { - if let ExportDefaultDecl::FunctionDeclaration(f) = export.declaration.as_mut() { - if f.base.node_id == Some(self.node_id) { - f.id = self.compiled.codegen_fn.id.clone(); - f.params = self.compiled.codegen_fn.params.clone(); - f.body = self.compiled.codegen_fn.body.clone(); - f.generator = self.compiled.codegen_fn.generator; - f.is_async = self.compiled.codegen_fn.is_async; - f.return_type = None; - f.type_parameters = None; - f.predicate = None; - f.declare = None; - return VisitResult::Stop; - } - } - } - Statement::ExportNamedDeclaration(export) => { - if let Some(ref mut decl) = export.declaration { - if let Declaration::FunctionDeclaration(f) = decl.as_mut() { - if f.base.node_id == Some(self.node_id) { - f.id = self.compiled.codegen_fn.id.clone(); - f.params = self.compiled.codegen_fn.params.clone(); - f.body = self.compiled.codegen_fn.body.clone(); - f.generator = self.compiled.codegen_fn.generator; - f.is_async = self.compiled.codegen_fn.is_async; - f.return_type = None; - f.type_parameters = None; - f.predicate = None; - f.declare = None; - return VisitResult::Stop; - } - } - } - } - _ => {} - } - VisitResult::Continue - } - - fn visit_expression(&mut self, expr: &mut Expression) -> VisitResult { - match expr { - Expression::FunctionExpression(f) if f.base.node_id == Some(self.node_id) => { - f.id = self.compiled.codegen_fn.id.clone(); - f.params = self.compiled.codegen_fn.params.clone(); - f.body = self.compiled.codegen_fn.body.clone(); - f.generator = self.compiled.codegen_fn.generator; - f.is_async = self.compiled.codegen_fn.is_async; - f.return_type = None; - f.type_parameters = None; - VisitResult::Stop - } - Expression::ArrowFunctionExpression(f) if f.base.node_id == Some(self.node_id) => { - f.params = self.compiled.codegen_fn.params.clone(); - f.body = Box::new(ArrowFunctionBody::BlockStatement( - self.compiled.codegen_fn.body.clone(), - )); - f.generator = self.compiled.codegen_fn.generator; - f.is_async = self.compiled.codegen_fn.is_async; - f.expression = Some(false); - f.return_type = None; - f.type_parameters = None; - f.predicate = None; - VisitResult::Stop - } - _ => VisitResult::Continue, - } - } -} - -/// Visitor that renames all occurrences of an identifier in expression position. -struct RenameIdentifierVisitor<'a> { - old_name: &'a str, - new_name: &'a str, -} - -impl MutVisitor for RenameIdentifierVisitor<'_> { - fn visit_identifier(&mut self, node: &mut Identifier) -> VisitResult { - if node.name == self.old_name { - node.name = self.new_name.to_string(); - } - VisitResult::Continue - } -} - -/// Main entry point for the React Compiler. -/// -/// Receives a full program AST, scope information (unused for now), and resolved options. -/// Returns a CompileResult indicating whether the AST was modified, -/// along with any logger events. -/// -/// This function implements the logic from the TS entrypoint (Program.ts): -/// - shouldSkipCompilation: check for existing runtime imports -/// - validateRestrictedImports: check for blocklisted imports -/// - findProgramSuppressions: find eslint/flow suppression comments -/// - findFunctionsToCompile: traverse program to find components and hooks -/// - processFn: per-function compilation with directive and suppression handling -/// - applyCompiledFunctions: replace original functions with compiled versions -pub fn compile_program(mut file: File, scope: ScopeInfo, options: PluginOptions) -> CompileResult { - // Compute output mode once, up front - let output_mode = CompilerOutputMode::from_opts(&options); - - // Create a temporary context for early-return paths (before full context is set up) - let early_events: Vec<LoggerEvent> = Vec::new(); - let mut early_ordered_log: Vec<OrderedLogItem> = Vec::new(); - - // Log environment config for debugLogIRs - if options.debug { - early_ordered_log.push(OrderedLogItem::Debug { - entry: DebugLogEntry::new( - "EnvironmentConfig", - serde_json::to_string_pretty(&options.environment).unwrap_or_default(), - ), - }); - } - - // Check if we should compile this file at all (pre-resolved by JS shim) - if !options.should_compile { - return CompileResult::Success { - ast: None, - events: early_events, - ordered_log: early_ordered_log, - renames: Vec::new(), - timing: Vec::new(), - }; - } - - let program = &file.program; - - // Check for existing runtime imports (file already compiled) - if should_skip_compilation(program, &options) { - return CompileResult::Success { - ast: None, - events: early_events, - ordered_log: early_ordered_log, - renames: Vec::new(), - timing: Vec::new(), - }; - } - - // Validate restricted imports from the environment config - let restricted_imports = options.environment.validate_blocklisted_imports.clone(); - - // Determine if we should check for eslint suppressions - let validate_exhaustive = options - .environment - .validate_exhaustive_memoization_dependencies; - let validate_hooks = options.environment.validate_hooks_usage; - - let eslint_rules: Option<Vec<String>> = if validate_exhaustive && validate_hooks { - // Don't check for ESLint suppressions if both validations are enabled - None - } else { - Some(options.eslint_suppression_rules.clone().unwrap_or_else(|| { - DEFAULT_ESLINT_SUPPRESSIONS - .iter() - .map(|s| s.to_string()) - .collect() - })) - }; - - // Find program-level suppressions from comments - let suppressions = find_program_suppressions( - &file.comments, - eslint_rules.as_deref(), - options.flow_suppressions, - ); - - // Check for module-scope opt-out directive - let has_module_scope_opt_out = - find_directive_disabling_memoization(&program.directives, &options).is_some(); - - // Create program context - let mut context = ProgramContext::new( - options.clone(), - options.filename.clone(), - // Pass the source code for fast refresh hash computation. - options.source_code.clone(), - suppressions, - has_module_scope_opt_out, - ); - - // Extract the source filename from the AST (set by parser's sourceFilename option). - // This is the bare filename (e.g., "foo.ts") without path prefixes, which the TS - // compiler uses in logger event source locations. - let source_filename = program - .base - .loc - .as_ref() - .and_then(|loc| loc.filename.clone()) - .or_else(|| { - // Fallback: try the first statement's loc - program.body.first().and_then(|stmt| { - let base = match stmt { - react_compiler_ast::statements::Statement::ExpressionStatement(s) => &s.base, - react_compiler_ast::statements::Statement::VariableDeclaration(s) => &s.base, - react_compiler_ast::statements::Statement::FunctionDeclaration(s) => &s.base, - _ => return None, - }; - base.loc.as_ref().and_then(|loc| loc.filename.clone()) - }) - }); - context.set_source_filename(source_filename); - - // Initialize known referenced names from scope bindings for UID collision detection - context.init_from_scope(&scope); - - // Seed context with early ordered log entries - context.ordered_log.extend(early_ordered_log); - - // Validate restricted imports (needs context for handle_error) - if let Some(err) = validate_restricted_imports(program, &restricted_imports) { - if let Some(result) = handle_error(&err, None, &mut context) { - return result; - } - return CompileResult::Success { - ast: None, - events: context.events, - ordered_log: context.ordered_log, - renames: convert_renames(&context.renames), - timing: Vec::new(), - }; - } - - // Pre-register instrumentation imports to get stable local names. - // These are needed before compilation so codegen can use the correct names. - let instrument_fn_name: Option<String>; - let instrument_gating_name: Option<String>; - let hook_guard_name: Option<String>; - - if let Some(ref instrument_config) = options.environment.enable_emit_instrument_forget { - let fn_spec = context.add_import_specifier( - &instrument_config.fn_.source, - &instrument_config.fn_.import_specifier_name, - None, - ); - instrument_fn_name = Some(fn_spec.name.clone()); - instrument_gating_name = instrument_config.gating.as_ref().map(|g| { - let spec = context.add_import_specifier(&g.source, &g.import_specifier_name, None); - spec.name.clone() - }); - } else { - instrument_fn_name = None; - instrument_gating_name = None; - } - - if let Some(ref hook_guard_config) = options.environment.enable_emit_hook_guards { - let spec = context.add_import_specifier( - &hook_guard_config.source, - &hook_guard_config.import_specifier_name, - None, - ); - hook_guard_name = Some(spec.name.clone()); - } else { - hook_guard_name = None; - } - - // Store pre-resolved names on context for pipeline access - context.instrument_fn_name = instrument_fn_name; - context.instrument_gating_name = instrument_gating_name; - context.hook_guard_name = hook_guard_name; - - // Find all functions to compile - let queue = find_functions_to_compile(program, &options, &mut context, &scope); - - // Clone env_config once for all function compilations (avoids per-function clone - // while satisfying the borrow checker — compile_fn needs &mut context + &env_config) - let env_config = options.environment.clone(); - - // Process each function and collect compiled results - let mut compiled_fns: Vec<CompiledFunction<'_>> = Vec::new(); - - for source in &queue { - match process_fn(source, &scope, output_mode, &env_config, &mut context) { - Ok(Some(codegen_fn)) => { - compiled_fns.push(CompiledFunction { - kind: source.kind, - source, - codegen_fn, - }); - } - Ok(None) => { - // Function was skipped or lint-only - } - Err(fatal_result) => { - return fatal_result; - } - } - } - - // Emit CompileSuccess events for JSX-outlined functions (fn_type.is_some()). - // In TS, outlined functions from outlineJSX are appended to the compilation queue - // and processed after all original functions, so their events appear at the end. - // Regular outlined functions (from OutlineFunctions pass) don't get separate events. - for compiled in &compiled_fns { - for outlined in &compiled.codegen_fn.outlined { - if outlined.fn_type.is_some() { - context.log_event(LoggerEvent::CompileSuccess { - fn_loc: None, - fn_name: outlined.func.id.as_ref().map(|id| id.name.clone()), - memo_slots: outlined.func.memo_slots_used, - memo_blocks: outlined.func.memo_blocks, - memo_values: outlined.func.memo_values, - pruned_memo_blocks: outlined.func.pruned_memo_blocks, - pruned_memo_values: outlined.func.pruned_memo_values, - }); - } - } - } - - // TS invariant: if there's a module scope opt-out, no functions should have been compiled - if has_module_scope_opt_out { - if !compiled_fns.is_empty() { - let mut err = CompilerError::new(); - err.push_error_detail(CompilerErrorDetail::new( - ErrorCategory::Invariant, - "Unexpected compiled functions when module scope opt-out is present", - )); - handle_error(&err, None, &mut context); - } - return CompileResult::Success { - ast: None, - events: context.events, - ordered_log: context.ordered_log, - renames: convert_renames(&context.renames), - timing: Vec::new(), - }; - } - - // Determine gating for each compiled function. - // In the TS compiler, dynamic gating from directives takes precedence over plugin-level gating. - // Gating only applies to 'original' functions, not 'outlined' ones. - let function_gating_config = options.gating.clone(); - - // Convert compiled functions to owned representations (dropping borrows) - // so we can mutate the AST. - let replacements: Vec<CompiledFnForReplacement> = compiled_fns - .into_iter() - .map(|cf| { - let original_kind = match cf.source.fn_node { - FunctionNode::FunctionDeclaration(_) => OriginalFnKind::FunctionDeclaration, - FunctionNode::FunctionExpression(_) => OriginalFnKind::FunctionExpression, - FunctionNode::ArrowFunctionExpression(_) => OriginalFnKind::ArrowFunctionExpression, - }; - // Determine per-function gating: dynamic gating from directives OR plugin-level gating. - // Dynamic gating (from `use memo if(identifier)`) takes precedence. - let gating = if cf.kind == CompileSourceKind::Original { - // Check body directives for dynamic gating - let dynamic_gating = - find_directives_dynamic_gating(&cf.source.body_directives, &options) - .ok() - .flatten() - .map(|r| r.gating); - dynamic_gating.or_else(|| function_gating_config.clone()) - } else { - None - }; - CompiledFnForReplacement { - fn_start: cf.source.fn_start, - fn_node_id: cf.source.fn_node_id, - original_kind, - codegen_fn: cf.codegen_fn, - source_kind: cf.kind, - fn_name: cf.source.fn_name.clone(), - gating, - } - }) - .collect(); - // Drop queue (and its borrows from file.program) - drop(queue); - - if replacements.is_empty() { - // No functions to replace. Return renames for the Babel plugin to apply - // (e.g., variable shadowing renames in lint mode). Imports are NOT added - // when there are no replacements — matching TS behavior where - // addImportsToProgram is only called when compiledFns.length > 0. - return CompileResult::Success { - ast: None, - events: context.events, - ordered_log: context.ordered_log, - renames: convert_renames(&context.renames), - timing: Vec::new(), - }; - } - - // Now we can mutate file.program - apply_compiled_functions(&replacements, &mut file.program, &mut context); - - let timing_entries = context.timing.into_entries(); - - // Return the compiled File by value; in-process Rust consumers use it - // directly, and the napi consumer serializes the whole result as before. - CompileResult::Success { - ast: Some(file), - events: context.events, - ordered_log: context.ordered_log, - renames: convert_renames(&context.renames), - timing: timing_entries, - } -} - -/// Convert internal BindingRename structs to the serializable BindingRenameInfo format. -fn convert_renames( - renames: &[react_compiler_hir::environment::BindingRename], -) -> Vec<BindingRenameInfo> { - renames - .iter() - .map(|r| BindingRenameInfo { - original: r.original.clone(), - renamed: r.renamed.clone(), - declaration_start: r.declaration_start, - }) - .collect() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_is_hook_name() { - assert!(is_hook_name("useState")); - assert!(is_hook_name("useEffect")); - assert!(is_hook_name("use0Something")); - assert!(!is_hook_name("use")); - assert!(!is_hook_name("useless")); // lowercase after use - assert!(!is_hook_name("foo")); - assert!(!is_hook_name("")); - } - - #[test] - fn test_is_component_name() { - assert!(is_component_name("MyComponent")); - assert!(is_component_name("App")); - assert!(!is_component_name("myComponent")); - assert!(!is_component_name("app")); - assert!(!is_component_name("")); - } - - #[test] - fn test_is_valid_identifier() { - assert!(is_valid_identifier("foo")); - assert!(is_valid_identifier("_bar")); - assert!(is_valid_identifier("$baz")); - assert!(is_valid_identifier("foo123")); - assert!(!is_valid_identifier("")); - assert!(!is_valid_identifier("123foo")); - assert!(!is_valid_identifier("foo bar")); - } - - #[test] - fn test_is_valid_component_params_empty() { - assert!(is_valid_component_params(&[])); - } - - #[test] - fn test_is_valid_component_params_one_identifier() { - let params = vec![PatternLike::Identifier(Identifier { - base: BaseNode::default(), - name: "props".to_string(), - type_annotation: None, - optional: None, - decorators: None, - })]; - assert!(is_valid_component_params(¶ms)); - } - - #[test] - fn test_is_valid_component_params_too_many() { - let params = vec![ - PatternLike::Identifier(Identifier { - base: BaseNode::default(), - name: "a".to_string(), - type_annotation: None, - optional: None, - decorators: None, - }), - PatternLike::Identifier(Identifier { - base: BaseNode::default(), - name: "b".to_string(), - type_annotation: None, - optional: None, - decorators: None, - }), - PatternLike::Identifier(Identifier { - base: BaseNode::default(), - name: "c".to_string(), - type_annotation: None, - optional: None, - decorators: None, - }), - ]; - assert!(!is_valid_component_params(¶ms)); - } - - #[test] - fn test_is_valid_component_params_with_ref() { - let params = vec![ - PatternLike::Identifier(Identifier { - base: BaseNode::default(), - name: "props".to_string(), - type_annotation: None, - optional: None, - decorators: None, - }), - PatternLike::Identifier(Identifier { - base: BaseNode::default(), - name: "ref".to_string(), - type_annotation: None, - optional: None, - decorators: None, - }), - ]; - assert!(is_valid_component_params(¶ms)); - } - - #[test] - fn test_should_skip_compilation_no_import() { - let program = Program { - base: BaseNode::default(), - body: vec![], - directives: vec![], - source_type: react_compiler_ast::SourceType::Module, - interpreter: None, - source_file: None, - }; - let options = PluginOptions { - should_compile: true, - enable_reanimated: false, - is_dev: false, - filename: None, - compilation_mode: "infer".to_string(), - panic_threshold: "none".to_string(), - target: super::super::plugin_options::CompilerTarget::Version("19".to_string()), - gating: None, - dynamic_gating: None, - no_emit: false, - output_mode: None, - eslint_suppression_rules: None, - flow_suppressions: true, - ignore_use_no_forget: false, - custom_opt_out_directives: None, - environment: EnvironmentConfig::default(), - source_code: None, - profiling: false, - debug: false, - }; - assert!(!should_skip_compilation(&program, &options)); - } -} diff --git a/compiler/crates/react_compiler/src/entrypoint/suppression.rs b/compiler/crates/react_compiler/src/entrypoint/suppression.rs deleted file mode 100644 index 7ee7f574404d..000000000000 --- a/compiler/crates/react_compiler/src/entrypoint/suppression.rs +++ /dev/null @@ -1,305 +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. - */ -use react_compiler_ast::common::{Comment, CommentData}; -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, CompilerSuggestion, - CompilerSuggestionOperation, ErrorCategory, -}; - -#[derive(Debug, Clone)] -pub enum SuppressionSource { - Eslint, - Flow, -} - -/// Captures the start and end range of a pair of eslint-disable ... eslint-enable comments. -/// In the case of a CommentLine or a relevant Flow suppression, both the disable and enable -/// point to the same comment. -/// -/// The enable comment can be missing in the case where only a disable block is present, -/// ie the rest of the file has potential React violations. -#[derive(Debug, Clone)] -pub struct SuppressionRange { - pub disable_comment: CommentData, - pub enable_comment: Option<CommentData>, - pub source: SuppressionSource, -} - -fn comment_data(comment: &Comment) -> &CommentData { - match comment { - Comment::CommentBlock(data) | Comment::CommentLine(data) => data, - } -} - -/// Check if a comment value matches `eslint-disable-next-line <rule>` for any rule in `rule_names`. -fn matches_eslint_disable_next_line(value: &str, rule_names: &[String]) -> bool { - if let Some(rest) = value.strip_prefix("eslint-disable-next-line ") { - return rule_names - .iter() - .any(|name| rest.starts_with(name.as_str())); - } - // Also check with leading space (comment values often have leading whitespace) - let trimmed = value.trim_start(); - if let Some(rest) = trimmed.strip_prefix("eslint-disable-next-line ") { - return rule_names - .iter() - .any(|name| rest.starts_with(name.as_str())); - } - false -} - -/// Check if a comment value matches `eslint-disable <rule>` for any rule in `rule_names`. -fn matches_eslint_disable(value: &str, rule_names: &[String]) -> bool { - if let Some(rest) = value.strip_prefix("eslint-disable ") { - return rule_names - .iter() - .any(|name| rest.starts_with(name.as_str())); - } - let trimmed = value.trim_start(); - if let Some(rest) = trimmed.strip_prefix("eslint-disable ") { - return rule_names - .iter() - .any(|name| rest.starts_with(name.as_str())); - } - false -} - -/// Check if a comment value matches `eslint-enable <rule>` for any rule in `rule_names`. -fn matches_eslint_enable(value: &str, rule_names: &[String]) -> bool { - if let Some(rest) = value.strip_prefix("eslint-enable ") { - return rule_names - .iter() - .any(|name| rest.starts_with(name.as_str())); - } - let trimmed = value.trim_start(); - if let Some(rest) = trimmed.strip_prefix("eslint-enable ") { - return rule_names - .iter() - .any(|name| rest.starts_with(name.as_str())); - } - false -} - -/// Check if a comment value matches a Flow suppression pattern. -/// Matches: $FlowFixMe[react-rule, $FlowFixMe_xxx[react-rule, -/// $FlowExpectedError[react-rule, $FlowIssue[react-rule -fn matches_flow_suppression(value: &str) -> bool { - // Find "$Flow" anywhere in the value - let Some(idx) = value.find("$Flow") else { - return false; - }; - let after_dollar_flow = &value[idx + "$Flow".len()..]; - - // Match FlowFixMe (with optional word chars), FlowExpectedError, or FlowIssue - let after_kind = if after_dollar_flow.starts_with("FixMe") { - // Skip "FixMe" + any word characters - let rest = &after_dollar_flow["FixMe".len()..]; - let word_end = rest - .find(|c: char| !c.is_alphanumeric() && c != '_') - .unwrap_or(rest.len()); - &rest[word_end..] - } else if after_dollar_flow.starts_with("ExpectedError") { - &after_dollar_flow["ExpectedError".len()..] - } else if after_dollar_flow.starts_with("Issue") { - &after_dollar_flow["Issue".len()..] - } else { - return false; - }; - - // Must be followed by "[react-rule" - after_kind.starts_with("[react-rule") -} - -/// Parse eslint-disable/enable and Flow suppression comments from program comments. -/// Equivalent to findProgramSuppressions in Suppression.ts -pub fn find_program_suppressions( - comments: &[Comment], - rule_names: Option<&[String]>, - flow_suppressions: bool, -) -> Vec<SuppressionRange> { - let mut suppression_ranges: Vec<SuppressionRange> = Vec::new(); - let mut disable_comment: Option<CommentData> = None; - let mut enable_comment: Option<CommentData> = None; - let mut source: Option<SuppressionSource> = None; - - let has_rules = matches!(rule_names, Some(names) if !names.is_empty()); - - for comment in comments { - let data = comment_data(comment); - - if data.start.is_none() || data.end.is_none() { - continue; - } - - // Check for eslint-disable-next-line (only if not already within a block) - if disable_comment.is_none() && has_rules { - if let Some(names) = rule_names { - if matches_eslint_disable_next_line(&data.value, names) { - disable_comment = Some(data.clone()); - enable_comment = Some(data.clone()); - source = Some(SuppressionSource::Eslint); - } - } - } - - // Check for Flow suppression (only if not already within a block) - if flow_suppressions && disable_comment.is_none() && matches_flow_suppression(&data.value) { - disable_comment = Some(data.clone()); - enable_comment = Some(data.clone()); - source = Some(SuppressionSource::Flow); - } - - // Check for eslint-disable (block start) - if has_rules { - if let Some(names) = rule_names { - if matches_eslint_disable(&data.value, names) { - disable_comment = Some(data.clone()); - source = Some(SuppressionSource::Eslint); - } - } - } - - // Check for eslint-enable (block end) - if has_rules { - if let Some(names) = rule_names { - if matches_eslint_enable(&data.value, names) { - if matches!(source, Some(SuppressionSource::Eslint)) { - enable_comment = Some(data.clone()); - } - } - } - } - - // If we have a complete suppression, push it - if disable_comment.is_some() && source.is_some() { - suppression_ranges.push(SuppressionRange { - disable_comment: disable_comment.take().unwrap(), - enable_comment: enable_comment.take(), - source: source.take().unwrap(), - }); - } - } - - suppression_ranges -} - -/// Check if suppression ranges overlap with a function's source range. -/// A suppression affects a function if: -/// 1. The suppression is within the function's body -/// 2. The suppression wraps the function -pub fn filter_suppressions_that_affect_function( - suppressions: &[SuppressionRange], - fn_start: u32, - fn_end: u32, -) -> Vec<&SuppressionRange> { - let mut suppressions_in_scope: Vec<&SuppressionRange> = Vec::new(); - - for suppression in suppressions { - let disable_start = match suppression.disable_comment.start { - Some(s) => s, - None => continue, - }; - - // The suppression is within the function - if disable_start > fn_start - && (suppression.enable_comment.is_none() - || suppression - .enable_comment - .as_ref() - .and_then(|c| c.end) - .map_or(false, |end| end < fn_end)) - { - suppressions_in_scope.push(suppression); - } - - // The suppression wraps the function - if disable_start < fn_start - && (suppression.enable_comment.is_none() - || suppression - .enable_comment - .as_ref() - .and_then(|c| c.end) - .map_or(false, |end| end > fn_end)) - { - suppressions_in_scope.push(suppression); - } - } - - suppressions_in_scope -} - -/// Convert suppression ranges to a CompilerError. -pub fn suppressions_to_compiler_error(suppressions: &[SuppressionRange]) -> CompilerError { - assert!( - !suppressions.is_empty(), - "Expected at least one suppression comment source range" - ); - - let mut error = CompilerError::new(); - - for suppression in suppressions { - let (disable_start, disable_end) = match ( - suppression.disable_comment.start, - suppression.disable_comment.end, - ) { - (Some(s), Some(e)) => (s, e), - _ => continue, - }; - - let (reason, suggestion) = match suppression.source { - SuppressionSource::Eslint => ( - "React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled", - "Remove the ESLint suppression and address the React error", - ), - SuppressionSource::Flow => ( - "React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow", - "Remove the Flow suppression and address the React error", - ), - }; - - let description = format!( - "React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `{}`", - suppression.disable_comment.value.trim() - ); - - let mut diagnostic = - CompilerDiagnostic::new(ErrorCategory::Suppression, reason, Some(description)); - - diagnostic.suggestions = Some(vec![CompilerSuggestion { - description: suggestion.to_string(), - range: (disable_start as usize, disable_end as usize), - op: CompilerSuggestionOperation::Remove, - text: None, - }]); - - // Add error detail with location info - let loc = suppression.disable_comment.loc.as_ref().map(|l| { - react_compiler_diagnostics::SourceLocation { - start: react_compiler_diagnostics::Position { - line: l.start.line, - column: l.start.column, - index: l.start.index, - }, - end: react_compiler_diagnostics::Position { - line: l.end.line, - column: l.end.column, - index: l.end.index, - }, - } - }); - - diagnostic = diagnostic.with_detail(CompilerDiagnosticDetail::Error { - loc, - message: Some("Found React rule suppression".to_string()), - identifier_name: None, - }); - - error.push_diagnostic(diagnostic); - } - - error -} diff --git a/compiler/crates/react_compiler/src/entrypoint/validate_source_locations.rs b/compiler/crates/react_compiler/src/entrypoint/validate_source_locations.rs deleted file mode 100644 index 01fed078d31d..000000000000 --- a/compiler/crates/react_compiler/src/entrypoint/validate_source_locations.rs +++ /dev/null @@ -1,1342 +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. - -//! Validates that important source locations from the original code are preserved -//! in the generated AST. This ensures that Istanbul coverage instrumentation can -//! properly map back to the original source code. -//! -//! This validation is test-only, enabled via `@validateSourceLocations` pragma. -//! -//! Analogous to TS `ValidateSourceLocations.ts`. - -use std::collections::{HashMap, HashSet}; - -use react_compiler_ast::common::SourceLocation as AstSourceLocation; -use react_compiler_ast::expressions::{ - ArrowFunctionBody, ArrowFunctionExpression, Expression, FunctionExpression, - ObjectExpressionProperty, -}; -use react_compiler_ast::patterns::PatternLike; -use react_compiler_ast::statements::{ForInOfLeft, ForInit, Statement, VariableDeclaration}; -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, Position as DiagPosition, - SourceLocation as DiagSourceLocation, -}; -use react_compiler_hir::environment::Environment; -use react_compiler_lowering::FunctionNode; -use react_compiler_reactive_scopes::codegen_reactive_function::CodegenFunction; - -/// Validate that important source locations are preserved in the generated AST. -pub fn validate_source_locations( - func: &FunctionNode<'_>, - codegen: &CodegenFunction, - env: &mut Environment, -) { - // Step 1: Collect important locations from the original source - let important_original = collect_important_original_locations(func); - - // Step 2: Collect all locations from the generated AST - let mut generated = HashMap::<String, HashSet<String>>::new(); - collect_generated_from_block(&codegen.body.body, &mut generated); - for outlined in &codegen.outlined { - collect_generated_from_block(&outlined.func.body.body, &mut generated); - } - - // Step 3: Validate that all important locations are preserved - let strict_node_types: HashSet<&str> = - ["VariableDeclaration", "VariableDeclarator", "Identifier"] - .into_iter() - .collect(); - - // Sort entries by source position to match TS output order - // (JS Map preserves insertion order, which is AST traversal order = source order) - let mut sorted_entries: Vec<&ImportantLocation> = important_original.values().collect(); - sorted_entries.sort_by(|a, b| { - a.loc - .start - .line - .cmp(&b.loc.start.line) - .then(a.loc.start.column.cmp(&b.loc.start.column)) - // Outer nodes (larger spans) before inner nodes, matching depth-first traversal - .then(b.loc.end.line.cmp(&a.loc.end.line)) - .then(b.loc.end.column.cmp(&a.loc.end.column)) - }); - - for entry in &sorted_entries { - let generated_node_types = generated.get(&entry.key); - - if generated_node_types.is_none() { - // Location is completely missing - let mut node_types_str: Vec<&str> = entry.node_types.iter().copied().collect(); - node_types_str.sort(); - report_missing_location(env, &entry.loc, &node_types_str.join(", ")); - } else { - let generated_node_types = generated_node_types.unwrap(); - // Location exists, check each strict node type - for &node_type in &entry.node_types { - if strict_node_types.contains(node_type) - && !generated_node_types.contains(node_type) - { - // For strict node types, the specific node type must be present. - // Check if any generated node type is also an important original node type. - let has_valid_node_type = generated_node_types - .iter() - .any(|gen_type| entry.node_types.contains(gen_type.as_str())); - - if has_valid_node_type { - report_missing_location(env, &entry.loc, node_type); - } else { - report_wrong_node_type(env, &entry.loc, node_type, generated_node_types); - } - } - } - } - } -} - -// ---- Types ---- - -struct ImportantLocation { - key: String, - loc: AstSourceLocation, - node_types: HashSet<&'static str>, -} - -// ---- Location key ---- - -fn location_key(loc: &AstSourceLocation) -> String { - format!( - "{}:{}-{}:{}", - loc.start.line, loc.start.column, loc.end.line, loc.end.column - ) -} - -// ---- AST to diagnostics SourceLocation conversion ---- - -fn ast_to_diag_loc(loc: &AstSourceLocation) -> DiagSourceLocation { - DiagSourceLocation { - start: DiagPosition { - line: loc.start.line, - column: loc.start.column, - index: loc.start.index, - }, - end: DiagPosition { - line: loc.end.line, - column: loc.end.column, - index: loc.end.index, - }, - } -} - -// ---- Error reporting ---- - -fn report_missing_location(env: &mut Environment, loc: &AstSourceLocation, node_type: &str) { - let diag_loc = ast_to_diag_loc(loc); - env.record_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::Todo, - "Important source location missing in generated code", - Some(format!( - "Source location for {} is missing in the generated output. \ - This can cause coverage instrumentation to fail to track this \ - code properly, resulting in inaccurate coverage reports.", - node_type - )), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: Some(diag_loc), - message: None, - identifier_name: None, - }), - ); -} - -fn report_wrong_node_type( - env: &mut Environment, - loc: &AstSourceLocation, - expected_type: &str, - actual_types: &HashSet<String>, -) { - let diag_loc = ast_to_diag_loc(loc); - let mut actual: Vec<&str> = actual_types.iter().map(|s| s.as_str()).collect(); - actual.sort(); - env.record_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::Todo, - "Important source location has wrong node type in generated code", - Some(format!( - "Source location for {} exists in the generated output but with wrong \ - node type(s): {}. This can cause coverage instrumentation to fail to \ - track this code properly, resulting in inaccurate coverage reports.", - expected_type, - actual.join(", ") - )), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: Some(diag_loc), - message: None, - identifier_name: None, - }), - ); -} - -// ---- Important type checking ---- - -/// Returns the Babel type name if this statement variant is an "important instrumented type". -fn important_statement_type(stmt: &Statement) -> Option<&'static str> { - match stmt { - Statement::ExpressionStatement(_) => Some("ExpressionStatement"), - Statement::BreakStatement(_) => Some("BreakStatement"), - Statement::ContinueStatement(_) => Some("ContinueStatement"), - Statement::ReturnStatement(_) => Some("ReturnStatement"), - Statement::ThrowStatement(_) => Some("ThrowStatement"), - Statement::TryStatement(_) => Some("TryStatement"), - Statement::IfStatement(_) => Some("IfStatement"), - Statement::ForStatement(_) => Some("ForStatement"), - Statement::ForInStatement(_) => Some("ForInStatement"), - Statement::ForOfStatement(_) => Some("ForOfStatement"), - Statement::WhileStatement(_) => Some("WhileStatement"), - Statement::DoWhileStatement(_) => Some("DoWhileStatement"), - Statement::SwitchStatement(_) => Some("SwitchStatement"), - Statement::WithStatement(_) => Some("WithStatement"), - Statement::FunctionDeclaration(_) => Some("FunctionDeclaration"), - Statement::LabeledStatement(_) => Some("LabeledStatement"), - Statement::VariableDeclaration(_) => Some("VariableDeclaration"), - _ => None, - } -} - -/// Returns the Babel type name if this expression variant is an "important instrumented type". -fn important_expression_type(expr: &Expression) -> Option<&'static str> { - match expr { - Expression::ArrowFunctionExpression(_) => Some("ArrowFunctionExpression"), - Expression::FunctionExpression(_) => Some("FunctionExpression"), - Expression::ConditionalExpression(_) => Some("ConditionalExpression"), - Expression::LogicalExpression(_) => Some("LogicalExpression"), - Expression::Identifier(_) => Some("Identifier"), - Expression::AssignmentPattern(_) => Some("AssignmentPattern"), - _ => None, - } -} - -// ---- Manual memoization check ---- - -fn is_manual_memoization(expr: &Expression) -> bool { - if let Expression::CallExpression(call) = expr { - match call.callee.as_ref() { - Expression::Identifier(id) => id.name == "useMemo" || id.name == "useCallback", - Expression::MemberExpression(mem) => { - if let (Expression::Identifier(obj), Expression::Identifier(prop)) = - (mem.object.as_ref(), &*mem.property) - { - obj.name == "React" && (prop.name == "useMemo" || prop.name == "useCallback") - } else { - false - } - } - _ => false, - } - } else { - false - } -} - -// ============================================================================ -// Step 1: Collect important original locations -// ============================================================================ - -fn collect_important_original_locations( - func: &FunctionNode<'_>, -) -> HashMap<String, ImportantLocation> { - let mut locations = HashMap::new(); - - // Note: TS uses func.traverse() which visits DESCENDANTS only, not the root - // function node itself. So we don't record the root function as important. - match func { - FunctionNode::FunctionDeclaration(f) => { - if let Some(id) = &f.id { - record_important("Identifier", &id.base.loc, &mut locations); - } - for param in &f.params { - collect_original_pattern(param, &mut locations); - } - collect_original_block(&f.body.body, false, &mut locations); - } - FunctionNode::FunctionExpression(f) => { - if let Some(id) = &f.id { - record_important("Identifier", &id.base.loc, &mut locations); - } - for param in &f.params { - collect_original_pattern(param, &mut locations); - } - collect_original_block(&f.body.body, false, &mut locations); - } - FunctionNode::ArrowFunctionExpression(f) => { - for param in &f.params { - collect_original_pattern(param, &mut locations); - } - match f.body.as_ref() { - ArrowFunctionBody::BlockStatement(block) => { - collect_original_block(&block.body, false, &mut locations); - } - ArrowFunctionBody::Expression(expr) => { - collect_original_expression(expr, &mut locations); - } - } - } - } - - locations -} - -fn record_important( - node_type: &'static str, - loc: &Option<AstSourceLocation>, - locations: &mut HashMap<String, ImportantLocation>, -) { - if let Some(loc) = loc { - let key = location_key(loc); - if let Some(existing) = locations.get_mut(&key) { - existing.node_types.insert(node_type); - } else { - let mut node_types = HashSet::new(); - node_types.insert(node_type); - locations.insert( - key.clone(), - ImportantLocation { - key, - loc: loc.clone(), - node_types, - }, - ); - } - } -} - -fn collect_original_block( - stmts: &[Statement], - in_single_return_arrow: bool, - locations: &mut HashMap<String, ImportantLocation>, -) { - for stmt in stmts { - collect_original_statement(stmt, in_single_return_arrow, locations); - } -} - -fn collect_original_statement( - stmt: &Statement, - in_single_return_arrow: bool, - locations: &mut HashMap<String, ImportantLocation>, -) { - // Record this statement if it's an important type - if let Some(type_name) = important_statement_type(stmt) { - // Skip return statements inside arrow functions that will be simplified - // to expression body: () => { return expr } -> () => expr - if type_name == "ReturnStatement" && in_single_return_arrow { - if let Statement::ReturnStatement(ret) = stmt { - if ret.argument.is_some() { - // Skip recording, but still recurse into children - if let Some(arg) = &ret.argument { - collect_original_expression(arg, locations); - } - return; - } - } - } - - // Skip manual memoization - if type_name == "ExpressionStatement" { - if let Statement::ExpressionStatement(expr_stmt) = stmt { - if is_manual_memoization(&expr_stmt.expression) { - // Still recurse into children - collect_original_expression(&expr_stmt.expression, locations); - return; - } - } - } - - let base_loc = statement_loc(stmt); - record_important(type_name, base_loc, locations); - } - - // Recurse into children - match stmt { - Statement::BlockStatement(node) => { - collect_original_block(&node.body, false, locations); - } - Statement::ReturnStatement(node) => { - if let Some(arg) = &node.argument { - collect_original_expression(arg, locations); - } - } - Statement::ExpressionStatement(node) => { - collect_original_expression(&node.expression, locations); - } - Statement::IfStatement(node) => { - collect_original_expression(&node.test, locations); - collect_original_statement(&node.consequent, false, locations); - if let Some(alt) = &node.alternate { - collect_original_statement(alt, false, locations); - } - } - Statement::ForStatement(node) => { - if let Some(init) = &node.init { - match init.as_ref() { - ForInit::VariableDeclaration(decl) => { - collect_original_var_declaration(decl, locations); - } - ForInit::Expression(expr) => { - collect_original_expression(expr, locations); - } - } - } - if let Some(test) = &node.test { - collect_original_expression(test, locations); - } - if let Some(update) = &node.update { - collect_original_expression(update, locations); - } - collect_original_statement(&node.body, false, locations); - } - Statement::WhileStatement(node) => { - collect_original_expression(&node.test, locations); - collect_original_statement(&node.body, false, locations); - } - Statement::DoWhileStatement(node) => { - collect_original_statement(&node.body, false, locations); - collect_original_expression(&node.test, locations); - } - Statement::ForInStatement(node) => { - if let ForInOfLeft::Pattern(pat) = node.left.as_ref() { - collect_original_pattern(pat, locations); - } - collect_original_expression(&node.right, locations); - collect_original_statement(&node.body, false, locations); - } - Statement::ForOfStatement(node) => { - if let ForInOfLeft::Pattern(pat) = node.left.as_ref() { - collect_original_pattern(pat, locations); - } - collect_original_expression(&node.right, locations); - collect_original_statement(&node.body, false, locations); - } - Statement::SwitchStatement(node) => { - collect_original_expression(&node.discriminant, locations); - for case in &node.cases { - // SwitchCase is an important type - record_important("SwitchCase", &case.base.loc, locations); - if let Some(test) = &case.test { - collect_original_expression(test, locations); - } - collect_original_block(&case.consequent, false, locations); - } - } - Statement::ThrowStatement(node) => { - collect_original_expression(&node.argument, locations); - } - Statement::TryStatement(node) => { - collect_original_block(&node.block.body, false, locations); - if let Some(handler) = &node.handler { - if let Some(param) = &handler.param { - collect_original_pattern(param, locations); - } - collect_original_block(&handler.body.body, false, locations); - } - if let Some(finalizer) = &node.finalizer { - collect_original_block(&finalizer.body, false, locations); - } - } - Statement::LabeledStatement(node) => { - // Label identifier - record_important("Identifier", &node.label.base.loc, locations); - collect_original_statement(&node.body, false, locations); - } - Statement::VariableDeclaration(node) => { - collect_original_var_declaration(node, locations); - } - Statement::FunctionDeclaration(node) => { - if let Some(id) = &node.id { - record_important("Identifier", &id.base.loc, locations); - } - for param in &node.params { - collect_original_pattern(param, locations); - } - collect_original_block(&node.body.body, false, locations); - } - Statement::WithStatement(node) => { - collect_original_expression(&node.object, locations); - collect_original_statement(&node.body, false, locations); - } - // Non-runtime statements: no children to recurse into - _ => {} - } -} - -fn collect_original_var_declaration( - decl: &VariableDeclaration, - locations: &mut HashMap<String, ImportantLocation>, -) { - for declarator in &decl.declarations { - // VariableDeclarator is an important type - record_important("VariableDeclarator", &declarator.base.loc, locations); - collect_original_pattern(&declarator.id, locations); - if let Some(init) = &declarator.init { - collect_original_expression(init, locations); - } - } -} - -fn collect_original_expression( - expr: &Expression, - locations: &mut HashMap<String, ImportantLocation>, -) { - // Record this expression if it's an important type - if let Some(type_name) = important_expression_type(expr) { - // Skip manual memoization - if !is_manual_memoization(expr) { - let base_loc = expression_loc(expr); - record_important(type_name, base_loc, locations); - } - } - - // Recurse into children - match expr { - Expression::Identifier(_) => { - // Already recorded above if important. No children. - } - Expression::CallExpression(node) => { - collect_original_expression(&node.callee, locations); - for arg in &node.arguments { - collect_original_expression(arg, locations); - } - } - Expression::MemberExpression(node) => { - collect_original_expression(&node.object, locations); - if node.computed { - collect_original_expression(&node.property, locations); - } else { - // Non-computed property is an Identifier - record it - if let Expression::Identifier(id) = node.property.as_ref() { - record_important("Identifier", &id.base.loc, locations); - } - } - } - Expression::OptionalCallExpression(node) => { - collect_original_expression(&node.callee, locations); - for arg in &node.arguments { - collect_original_expression(arg, locations); - } - } - Expression::OptionalMemberExpression(node) => { - collect_original_expression(&node.object, locations); - if node.computed { - collect_original_expression(&node.property, locations); - } else if let Expression::Identifier(id) = node.property.as_ref() { - record_important("Identifier", &id.base.loc, locations); - } - } - Expression::BinaryExpression(node) => { - collect_original_expression(&node.left, locations); - collect_original_expression(&node.right, locations); - } - Expression::LogicalExpression(node) => { - collect_original_expression(&node.left, locations); - collect_original_expression(&node.right, locations); - } - Expression::UnaryExpression(node) => { - collect_original_expression(&node.argument, locations); - } - Expression::UpdateExpression(node) => { - collect_original_expression(&node.argument, locations); - } - Expression::ConditionalExpression(node) => { - collect_original_expression(&node.test, locations); - collect_original_expression(&node.consequent, locations); - collect_original_expression(&node.alternate, locations); - } - Expression::AssignmentExpression(node) => { - collect_original_pattern(&node.left, locations); - collect_original_expression(&node.right, locations); - } - Expression::SequenceExpression(node) => { - for e in &node.expressions { - collect_original_expression(e, locations); - } - } - Expression::ArrowFunctionExpression(node) => { - collect_original_arrow_children(node, locations); - } - Expression::FunctionExpression(node) => { - collect_original_fn_expr_children(node, locations); - } - Expression::ObjectExpression(node) => { - for prop in &node.properties { - match prop { - ObjectExpressionProperty::ObjectProperty(p) => { - if p.computed { - collect_original_expression(&p.key, locations); - } else if let Expression::Identifier(id) = p.key.as_ref() { - record_important("Identifier", &id.base.loc, locations); - } - collect_original_expression(&p.value, locations); - } - ObjectExpressionProperty::ObjectMethod(m) => { - // ObjectMethod is an important type - record_important("ObjectMethod", &m.base.loc, locations); - for param in &m.params { - collect_original_pattern(param, locations); - } - collect_original_block(&m.body.body, false, locations); - } - ObjectExpressionProperty::SpreadElement(s) => { - collect_original_expression(&s.argument, locations); - } - } - } - } - Expression::ArrayExpression(node) => { - for elem in node.elements.iter().flatten() { - collect_original_expression(elem, locations); - } - } - Expression::NewExpression(node) => { - collect_original_expression(&node.callee, locations); - for arg in &node.arguments { - collect_original_expression(arg, locations); - } - } - Expression::TemplateLiteral(node) => { - for e in &node.expressions { - collect_original_expression(e, locations); - } - } - Expression::TaggedTemplateExpression(node) => { - collect_original_expression(&node.tag, locations); - for e in &node.quasi.expressions { - collect_original_expression(e, locations); - } - } - Expression::AwaitExpression(node) => { - collect_original_expression(&node.argument, locations); - } - Expression::YieldExpression(node) => { - if let Some(arg) = &node.argument { - collect_original_expression(arg, locations); - } - } - Expression::SpreadElement(node) => { - collect_original_expression(&node.argument, locations); - } - Expression::ParenthesizedExpression(node) => { - collect_original_expression(&node.expression, locations); - } - Expression::AssignmentPattern(node) => { - collect_original_pattern(&node.left, locations); - collect_original_expression(&node.right, locations); - } - Expression::ClassExpression(node) => { - if let Some(sc) = &node.super_class { - collect_original_expression(sc, locations); - } - } - // TS/Flow wrappers — traverse inner expression - Expression::TSAsExpression(node) => { - collect_original_expression(&node.expression, locations); - } - Expression::TSSatisfiesExpression(node) => { - collect_original_expression(&node.expression, locations); - } - Expression::TSNonNullExpression(node) => { - collect_original_expression(&node.expression, locations); - } - Expression::TSTypeAssertion(node) => { - collect_original_expression(&node.expression, locations); - } - Expression::TSInstantiationExpression(node) => { - collect_original_expression(&node.expression, locations); - } - Expression::TypeCastExpression(node) => { - collect_original_expression(&node.expression, locations); - } - // Leaf nodes and JSX - _ => {} - } -} - -fn collect_original_arrow_children( - arrow: &ArrowFunctionExpression, - locations: &mut HashMap<String, ImportantLocation>, -) { - for param in &arrow.params { - collect_original_pattern(param, locations); - } - match arrow.body.as_ref() { - ArrowFunctionBody::BlockStatement(block) => { - let is_single_return = block.body.len() == 1 && block.directives.is_empty(); - collect_original_block(&block.body, is_single_return, locations); - } - ArrowFunctionBody::Expression(expr) => { - collect_original_expression(expr, locations); - } - } -} - -fn collect_original_fn_expr_children( - func: &FunctionExpression, - locations: &mut HashMap<String, ImportantLocation>, -) { - if let Some(id) = &func.id { - record_important("Identifier", &id.base.loc, locations); - } - for param in &func.params { - collect_original_pattern(param, locations); - } - collect_original_block(&func.body.body, false, locations); -} - -fn collect_original_pattern( - pattern: &PatternLike, - locations: &mut HashMap<String, ImportantLocation>, -) { - match pattern { - PatternLike::Identifier(id) => { - record_important("Identifier", &id.base.loc, locations); - } - PatternLike::AssignmentPattern(ap) => { - record_important("AssignmentPattern", &ap.base.loc, locations); - collect_original_pattern(&ap.left, locations); - collect_original_expression(&ap.right, locations); - } - PatternLike::ObjectPattern(op) => { - for prop in &op.properties { - match prop { - react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => { - if p.computed { - collect_original_expression(&p.key, locations); - } else if let Expression::Identifier(id) = p.key.as_ref() { - record_important("Identifier", &id.base.loc, locations); - } - collect_original_pattern(&p.value, locations); - } - react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => { - collect_original_pattern(&r.argument, locations); - } - } - } - } - PatternLike::ArrayPattern(ap) => { - for elem in ap.elements.iter().flatten() { - collect_original_pattern(elem, locations); - } - } - PatternLike::RestElement(r) => { - collect_original_pattern(&r.argument, locations); - } - PatternLike::MemberExpression(m) => { - collect_original_expression(&Expression::MemberExpression(m.clone()), locations); - } - PatternLike::TSAsExpression(_) - | PatternLike::TSSatisfiesExpression(_) - | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) - | PatternLike::TypeCastExpression(_) => {} - } -} - -// ---- Helpers to get loc from statement/expression ---- - -fn statement_loc(stmt: &Statement) -> &Option<AstSourceLocation> { - match stmt { - Statement::BlockStatement(n) => &n.base.loc, - Statement::ReturnStatement(n) => &n.base.loc, - Statement::IfStatement(n) => &n.base.loc, - Statement::ForStatement(n) => &n.base.loc, - Statement::WhileStatement(n) => &n.base.loc, - Statement::DoWhileStatement(n) => &n.base.loc, - Statement::ForInStatement(n) => &n.base.loc, - Statement::ForOfStatement(n) => &n.base.loc, - Statement::SwitchStatement(n) => &n.base.loc, - Statement::ThrowStatement(n) => &n.base.loc, - Statement::TryStatement(n) => &n.base.loc, - Statement::BreakStatement(n) => &n.base.loc, - Statement::ContinueStatement(n) => &n.base.loc, - Statement::LabeledStatement(n) => &n.base.loc, - Statement::ExpressionStatement(n) => &n.base.loc, - Statement::EmptyStatement(n) => &n.base.loc, - Statement::DebuggerStatement(n) => &n.base.loc, - Statement::WithStatement(n) => &n.base.loc, - Statement::VariableDeclaration(n) => &n.base.loc, - Statement::FunctionDeclaration(n) => &n.base.loc, - Statement::ClassDeclaration(n) => &n.base.loc, - Statement::ImportDeclaration(n) => &n.base.loc, - Statement::ExportNamedDeclaration(n) => &n.base.loc, - Statement::ExportDefaultDeclaration(n) => &n.base.loc, - Statement::ExportAllDeclaration(n) => &n.base.loc, - Statement::TSTypeAliasDeclaration(n) => &n.base.loc, - Statement::TSInterfaceDeclaration(n) => &n.base.loc, - Statement::TSEnumDeclaration(n) => &n.base.loc, - Statement::TSModuleDeclaration(n) => &n.base.loc, - Statement::TSDeclareFunction(n) => &n.base.loc, - Statement::TypeAlias(n) => &n.base.loc, - Statement::OpaqueType(n) => &n.base.loc, - Statement::InterfaceDeclaration(n) => &n.base.loc, - Statement::DeclareVariable(n) => &n.base.loc, - Statement::DeclareFunction(n) => &n.base.loc, - Statement::DeclareClass(n) => &n.base.loc, - Statement::DeclareModule(n) => &n.base.loc, - Statement::DeclareModuleExports(n) => &n.base.loc, - Statement::DeclareExportDeclaration(n) => &n.base.loc, - Statement::DeclareExportAllDeclaration(n) => &n.base.loc, - Statement::DeclareInterface(n) => &n.base.loc, - Statement::DeclareTypeAlias(n) => &n.base.loc, - Statement::DeclareOpaqueType(n) => &n.base.loc, - Statement::EnumDeclaration(n) => &n.base.loc, - Statement::Unknown(n) => &n.base().loc, - } -} - -fn expression_loc(expr: &Expression) -> &Option<AstSourceLocation> { - match expr { - Expression::Identifier(n) => &n.base.loc, - Expression::StringLiteral(n) => &n.base.loc, - Expression::NumericLiteral(n) => &n.base.loc, - Expression::BooleanLiteral(n) => &n.base.loc, - Expression::NullLiteral(n) => &n.base.loc, - Expression::BigIntLiteral(n) => &n.base.loc, - Expression::RegExpLiteral(n) => &n.base.loc, - Expression::CallExpression(n) => &n.base.loc, - Expression::MemberExpression(n) => &n.base.loc, - Expression::OptionalCallExpression(n) => &n.base.loc, - Expression::OptionalMemberExpression(n) => &n.base.loc, - Expression::BinaryExpression(n) => &n.base.loc, - Expression::LogicalExpression(n) => &n.base.loc, - Expression::UnaryExpression(n) => &n.base.loc, - Expression::UpdateExpression(n) => &n.base.loc, - Expression::ConditionalExpression(n) => &n.base.loc, - Expression::AssignmentExpression(n) => &n.base.loc, - Expression::SequenceExpression(n) => &n.base.loc, - Expression::ArrowFunctionExpression(n) => &n.base.loc, - Expression::FunctionExpression(n) => &n.base.loc, - Expression::ObjectExpression(n) => &n.base.loc, - Expression::ArrayExpression(n) => &n.base.loc, - Expression::NewExpression(n) => &n.base.loc, - Expression::TemplateLiteral(n) => &n.base.loc, - Expression::TaggedTemplateExpression(n) => &n.base.loc, - Expression::AwaitExpression(n) => &n.base.loc, - Expression::YieldExpression(n) => &n.base.loc, - Expression::SpreadElement(n) => &n.base.loc, - Expression::MetaProperty(n) => &n.base.loc, - Expression::ClassExpression(n) => &n.base.loc, - Expression::PrivateName(n) => &n.base.loc, - Expression::Super(n) => &n.base.loc, - Expression::Import(n) => &n.base.loc, - Expression::ThisExpression(n) => &n.base.loc, - Expression::ParenthesizedExpression(n) => &n.base.loc, - Expression::AssignmentPattern(n) => &n.base.loc, - Expression::JSXElement(n) => &n.base.loc, - Expression::JSXFragment(n) => &n.base.loc, - Expression::TSAsExpression(n) => &n.base.loc, - Expression::TSSatisfiesExpression(n) => &n.base.loc, - Expression::TSNonNullExpression(n) => &n.base.loc, - Expression::TSTypeAssertion(n) => &n.base.loc, - Expression::TSInstantiationExpression(n) => &n.base.loc, - Expression::TypeCastExpression(n) => &n.base.loc, - } -} - -// ============================================================================ -// Step 2: Collect generated locations (ALL node types, not just important ones) -// ============================================================================ - -fn collect_generated_from_block( - stmts: &[Statement], - locations: &mut HashMap<String, HashSet<String>>, -) { - for stmt in stmts { - collect_generated_statement(stmt, locations); - } -} - -fn record_generated( - type_name: &str, - loc: &Option<AstSourceLocation>, - locations: &mut HashMap<String, HashSet<String>>, -) { - if let Some(loc) = loc { - let key = location_key(loc); - locations - .entry(key) - .or_default() - .insert(type_name.to_string()); - } -} - -fn collect_generated_statement(stmt: &Statement, locations: &mut HashMap<String, HashSet<String>>) { - // Record this statement's location - let type_name = statement_type_name(stmt); - record_generated(type_name, statement_loc(stmt), locations); - - // Recurse into children (same structure as original, but record ALL types) - match stmt { - Statement::BlockStatement(node) => { - collect_generated_from_block(&node.body, locations); - } - Statement::ReturnStatement(node) => { - if let Some(arg) = &node.argument { - collect_generated_expression(arg, locations); - } - } - Statement::ExpressionStatement(node) => { - collect_generated_expression(&node.expression, locations); - } - Statement::IfStatement(node) => { - collect_generated_expression(&node.test, locations); - collect_generated_statement(&node.consequent, locations); - if let Some(alt) = &node.alternate { - collect_generated_statement(alt, locations); - } - } - Statement::ForStatement(node) => { - if let Some(init) = &node.init { - match init.as_ref() { - ForInit::VariableDeclaration(decl) => { - collect_generated_var_declaration(decl, locations); - } - ForInit::Expression(expr) => { - collect_generated_expression(expr, locations); - } - } - } - if let Some(test) = &node.test { - collect_generated_expression(test, locations); - } - if let Some(update) = &node.update { - collect_generated_expression(update, locations); - } - collect_generated_statement(&node.body, locations); - } - Statement::WhileStatement(node) => { - collect_generated_expression(&node.test, locations); - collect_generated_statement(&node.body, locations); - } - Statement::DoWhileStatement(node) => { - collect_generated_statement(&node.body, locations); - collect_generated_expression(&node.test, locations); - } - Statement::ForInStatement(node) => { - match node.left.as_ref() { - ForInOfLeft::VariableDeclaration(decl) => { - collect_generated_var_declaration(decl, locations); - } - ForInOfLeft::Pattern(pat) => { - collect_generated_pattern(pat, locations); - } - } - collect_generated_expression(&node.right, locations); - collect_generated_statement(&node.body, locations); - } - Statement::ForOfStatement(node) => { - match node.left.as_ref() { - ForInOfLeft::VariableDeclaration(decl) => { - collect_generated_var_declaration(decl, locations); - } - ForInOfLeft::Pattern(pat) => { - collect_generated_pattern(pat, locations); - } - } - collect_generated_expression(&node.right, locations); - collect_generated_statement(&node.body, locations); - } - Statement::SwitchStatement(node) => { - collect_generated_expression(&node.discriminant, locations); - for case in &node.cases { - record_generated("SwitchCase", &case.base.loc, locations); - if let Some(test) = &case.test { - collect_generated_expression(test, locations); - } - collect_generated_from_block(&case.consequent, locations); - } - } - Statement::ThrowStatement(node) => { - collect_generated_expression(&node.argument, locations); - } - Statement::TryStatement(node) => { - collect_generated_from_block(&node.block.body, locations); - if let Some(handler) = &node.handler { - if let Some(param) = &handler.param { - collect_generated_pattern(param, locations); - } - collect_generated_from_block(&handler.body.body, locations); - } - if let Some(finalizer) = &node.finalizer { - collect_generated_from_block(&finalizer.body, locations); - } - } - Statement::LabeledStatement(node) => { - record_generated("Identifier", &node.label.base.loc, locations); - collect_generated_statement(&node.body, locations); - } - Statement::VariableDeclaration(node) => { - collect_generated_var_declaration(node, locations); - } - Statement::FunctionDeclaration(node) => { - if let Some(id) = &node.id { - record_generated("Identifier", &id.base.loc, locations); - } - for param in &node.params { - collect_generated_pattern(param, locations); - } - collect_generated_from_block(&node.body.body, locations); - } - Statement::WithStatement(node) => { - collect_generated_expression(&node.object, locations); - collect_generated_statement(&node.body, locations); - } - Statement::ClassDeclaration(node) => { - if let Some(id) = &node.id { - record_generated("Identifier", &id.base.loc, locations); - } - if let Some(sc) = &node.super_class { - collect_generated_expression(sc, locations); - } - } - _ => {} - } -} - -fn collect_generated_var_declaration( - decl: &VariableDeclaration, - locations: &mut HashMap<String, HashSet<String>>, -) { - for declarator in &decl.declarations { - record_generated("VariableDeclarator", &declarator.base.loc, locations); - collect_generated_pattern(&declarator.id, locations); - if let Some(init) = &declarator.init { - collect_generated_expression(init, locations); - } - } -} - -fn collect_generated_expression( - expr: &Expression, - locations: &mut HashMap<String, HashSet<String>>, -) { - let type_name = expression_type_name(expr); - record_generated(type_name, expression_loc(expr), locations); - - match expr { - Expression::Identifier(_) => {} - Expression::CallExpression(node) => { - collect_generated_expression(&node.callee, locations); - for arg in &node.arguments { - collect_generated_expression(arg, locations); - } - } - Expression::MemberExpression(node) => { - collect_generated_expression(&node.object, locations); - collect_generated_expression(&node.property, locations); - } - Expression::OptionalCallExpression(node) => { - collect_generated_expression(&node.callee, locations); - for arg in &node.arguments { - collect_generated_expression(arg, locations); - } - } - Expression::OptionalMemberExpression(node) => { - collect_generated_expression(&node.object, locations); - collect_generated_expression(&node.property, locations); - } - Expression::BinaryExpression(node) => { - collect_generated_expression(&node.left, locations); - collect_generated_expression(&node.right, locations); - } - Expression::LogicalExpression(node) => { - collect_generated_expression(&node.left, locations); - collect_generated_expression(&node.right, locations); - } - Expression::UnaryExpression(node) => { - collect_generated_expression(&node.argument, locations); - } - Expression::UpdateExpression(node) => { - collect_generated_expression(&node.argument, locations); - } - Expression::ConditionalExpression(node) => { - collect_generated_expression(&node.test, locations); - collect_generated_expression(&node.consequent, locations); - collect_generated_expression(&node.alternate, locations); - } - Expression::AssignmentExpression(node) => { - collect_generated_pattern(&node.left, locations); - collect_generated_expression(&node.right, locations); - } - Expression::SequenceExpression(node) => { - for e in &node.expressions { - collect_generated_expression(e, locations); - } - } - Expression::ArrowFunctionExpression(node) => { - for param in &node.params { - collect_generated_pattern(param, locations); - } - match node.body.as_ref() { - ArrowFunctionBody::BlockStatement(block) => { - collect_generated_from_block(&block.body, locations); - } - ArrowFunctionBody::Expression(e) => { - collect_generated_expression(e, locations); - } - } - } - Expression::FunctionExpression(node) => { - if let Some(id) = &node.id { - record_generated("Identifier", &id.base.loc, locations); - } - for param in &node.params { - collect_generated_pattern(param, locations); - } - collect_generated_from_block(&node.body.body, locations); - } - Expression::ObjectExpression(node) => { - for prop in &node.properties { - match prop { - ObjectExpressionProperty::ObjectProperty(p) => { - collect_generated_expression(&p.key, locations); - collect_generated_expression(&p.value, locations); - } - ObjectExpressionProperty::ObjectMethod(m) => { - record_generated("ObjectMethod", &m.base.loc, locations); - for param in &m.params { - collect_generated_pattern(param, locations); - } - collect_generated_from_block(&m.body.body, locations); - } - ObjectExpressionProperty::SpreadElement(s) => { - collect_generated_expression(&s.argument, locations); - } - } - } - } - Expression::ArrayExpression(node) => { - for elem in node.elements.iter().flatten() { - collect_generated_expression(elem, locations); - } - } - Expression::NewExpression(node) => { - collect_generated_expression(&node.callee, locations); - for arg in &node.arguments { - collect_generated_expression(arg, locations); - } - } - Expression::TemplateLiteral(node) => { - for e in &node.expressions { - collect_generated_expression(e, locations); - } - } - Expression::TaggedTemplateExpression(node) => { - collect_generated_expression(&node.tag, locations); - for e in &node.quasi.expressions { - collect_generated_expression(e, locations); - } - } - Expression::AwaitExpression(node) => { - collect_generated_expression(&node.argument, locations); - } - Expression::YieldExpression(node) => { - if let Some(arg) = &node.argument { - collect_generated_expression(arg, locations); - } - } - Expression::SpreadElement(node) => { - collect_generated_expression(&node.argument, locations); - } - Expression::ParenthesizedExpression(node) => { - collect_generated_expression(&node.expression, locations); - } - Expression::AssignmentPattern(node) => { - collect_generated_pattern(&node.left, locations); - collect_generated_expression(&node.right, locations); - } - Expression::ClassExpression(node) => { - if let Some(sc) = &node.super_class { - collect_generated_expression(sc, locations); - } - } - Expression::TSAsExpression(node) => { - collect_generated_expression(&node.expression, locations); - } - Expression::TSSatisfiesExpression(node) => { - collect_generated_expression(&node.expression, locations); - } - Expression::TSNonNullExpression(node) => { - collect_generated_expression(&node.expression, locations); - } - Expression::TSTypeAssertion(node) => { - collect_generated_expression(&node.expression, locations); - } - Expression::TSInstantiationExpression(node) => { - collect_generated_expression(&node.expression, locations); - } - Expression::TypeCastExpression(node) => { - collect_generated_expression(&node.expression, locations); - } - // Leaf nodes and JSX - _ => {} - } -} - -fn collect_generated_pattern( - pattern: &PatternLike, - locations: &mut HashMap<String, HashSet<String>>, -) { - match pattern { - PatternLike::Identifier(id) => { - record_generated("Identifier", &id.base.loc, locations); - } - PatternLike::AssignmentPattern(ap) => { - record_generated("AssignmentPattern", &ap.base.loc, locations); - collect_generated_pattern(&ap.left, locations); - collect_generated_expression(&ap.right, locations); - } - PatternLike::ObjectPattern(op) => { - record_generated("ObjectPattern", &op.base.loc, locations); - for prop in &op.properties { - match prop { - react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => { - record_generated("ObjectProperty", &p.base.loc, locations); - collect_generated_expression(&p.key, locations); - collect_generated_pattern(&p.value, locations); - } - react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => { - record_generated("RestElement", &r.base.loc, locations); - collect_generated_pattern(&r.argument, locations); - } - } - } - } - PatternLike::ArrayPattern(ap) => { - record_generated("ArrayPattern", &ap.base.loc, locations); - for elem in ap.elements.iter().flatten() { - collect_generated_pattern(elem, locations); - } - } - PatternLike::RestElement(r) => { - record_generated("RestElement", &r.base.loc, locations); - collect_generated_pattern(&r.argument, locations); - } - PatternLike::MemberExpression(m) => { - record_generated("MemberExpression", &m.base.loc, locations); - collect_generated_expression(&m.object, locations); - collect_generated_expression(&m.property, locations); - } - PatternLike::TSAsExpression(_) - | PatternLike::TSSatisfiesExpression(_) - | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) - | PatternLike::TypeCastExpression(_) => {} - } -} - -// ---- Type name helpers ---- - -fn statement_type_name(stmt: &Statement) -> &'static str { - match stmt { - Statement::BlockStatement(_) => "BlockStatement", - Statement::ReturnStatement(_) => "ReturnStatement", - Statement::IfStatement(_) => "IfStatement", - Statement::ForStatement(_) => "ForStatement", - Statement::WhileStatement(_) => "WhileStatement", - Statement::DoWhileStatement(_) => "DoWhileStatement", - Statement::ForInStatement(_) => "ForInStatement", - Statement::ForOfStatement(_) => "ForOfStatement", - Statement::SwitchStatement(_) => "SwitchStatement", - Statement::ThrowStatement(_) => "ThrowStatement", - Statement::TryStatement(_) => "TryStatement", - Statement::BreakStatement(_) => "BreakStatement", - Statement::ContinueStatement(_) => "ContinueStatement", - Statement::LabeledStatement(_) => "LabeledStatement", - Statement::ExpressionStatement(_) => "ExpressionStatement", - Statement::EmptyStatement(_) => "EmptyStatement", - Statement::DebuggerStatement(_) => "DebuggerStatement", - Statement::WithStatement(_) => "WithStatement", - Statement::VariableDeclaration(_) => "VariableDeclaration", - Statement::FunctionDeclaration(_) => "FunctionDeclaration", - Statement::ClassDeclaration(_) => "ClassDeclaration", - Statement::ImportDeclaration(_) => "ImportDeclaration", - Statement::ExportNamedDeclaration(_) => "ExportNamedDeclaration", - Statement::ExportDefaultDeclaration(_) => "ExportDefaultDeclaration", - Statement::ExportAllDeclaration(_) => "ExportAllDeclaration", - Statement::TSTypeAliasDeclaration(_) => "TSTypeAliasDeclaration", - Statement::TSInterfaceDeclaration(_) => "TSInterfaceDeclaration", - Statement::TSEnumDeclaration(_) => "TSEnumDeclaration", - Statement::TSModuleDeclaration(_) => "TSModuleDeclaration", - Statement::TSDeclareFunction(_) => "TSDeclareFunction", - Statement::TypeAlias(_) => "TypeAlias", - Statement::OpaqueType(_) => "OpaqueType", - Statement::InterfaceDeclaration(_) => "InterfaceDeclaration", - Statement::DeclareVariable(_) => "DeclareVariable", - Statement::DeclareFunction(_) => "DeclareFunction", - Statement::DeclareClass(_) => "DeclareClass", - Statement::DeclareModule(_) => "DeclareModule", - Statement::DeclareModuleExports(_) => "DeclareModuleExports", - Statement::DeclareExportDeclaration(_) => "DeclareExportDeclaration", - Statement::DeclareExportAllDeclaration(_) => "DeclareExportAllDeclaration", - Statement::DeclareInterface(_) => "DeclareInterface", - Statement::DeclareTypeAlias(_) => "DeclareTypeAlias", - Statement::DeclareOpaqueType(_) => "DeclareOpaqueType", - Statement::EnumDeclaration(_) => "EnumDeclaration", - // The real Babel `type` lives in the raw node, but this function - // returns &'static str; "Unknown" is the static stand-in. - Statement::Unknown(_) => "Unknown", - } -} - -fn expression_type_name(expr: &Expression) -> &'static str { - match expr { - Expression::Identifier(_) => "Identifier", - Expression::StringLiteral(_) => "StringLiteral", - Expression::NumericLiteral(_) => "NumericLiteral", - Expression::BooleanLiteral(_) => "BooleanLiteral", - Expression::NullLiteral(_) => "NullLiteral", - Expression::BigIntLiteral(_) => "BigIntLiteral", - Expression::RegExpLiteral(_) => "RegExpLiteral", - Expression::CallExpression(_) => "CallExpression", - Expression::MemberExpression(_) => "MemberExpression", - Expression::OptionalCallExpression(_) => "OptionalCallExpression", - Expression::OptionalMemberExpression(_) => "OptionalMemberExpression", - Expression::BinaryExpression(_) => "BinaryExpression", - Expression::LogicalExpression(_) => "LogicalExpression", - Expression::UnaryExpression(_) => "UnaryExpression", - Expression::UpdateExpression(_) => "UpdateExpression", - Expression::ConditionalExpression(_) => "ConditionalExpression", - Expression::AssignmentExpression(_) => "AssignmentExpression", - Expression::SequenceExpression(_) => "SequenceExpression", - Expression::ArrowFunctionExpression(_) => "ArrowFunctionExpression", - Expression::FunctionExpression(_) => "FunctionExpression", - Expression::ObjectExpression(_) => "ObjectExpression", - Expression::ArrayExpression(_) => "ArrayExpression", - Expression::NewExpression(_) => "NewExpression", - Expression::TemplateLiteral(_) => "TemplateLiteral", - Expression::TaggedTemplateExpression(_) => "TaggedTemplateExpression", - Expression::AwaitExpression(_) => "AwaitExpression", - Expression::YieldExpression(_) => "YieldExpression", - Expression::SpreadElement(_) => "SpreadElement", - Expression::MetaProperty(_) => "MetaProperty", - Expression::ClassExpression(_) => "ClassExpression", - Expression::PrivateName(_) => "PrivateName", - Expression::Super(_) => "Super", - Expression::Import(_) => "Import", - Expression::ThisExpression(_) => "ThisExpression", - Expression::ParenthesizedExpression(_) => "ParenthesizedExpression", - Expression::AssignmentPattern(_) => "AssignmentPattern", - Expression::JSXElement(_) => "JSXElement", - Expression::JSXFragment(_) => "JSXFragment", - Expression::TSAsExpression(_) => "TSAsExpression", - Expression::TSSatisfiesExpression(_) => "TSSatisfiesExpression", - Expression::TSNonNullExpression(_) => "TSNonNullExpression", - Expression::TSTypeAssertion(_) => "TSTypeAssertion", - Expression::TSInstantiationExpression(_) => "TSInstantiationExpression", - Expression::TypeCastExpression(_) => "TypeCastExpression", - } -} diff --git a/compiler/crates/react_compiler/src/fixture_utils.rs b/compiler/crates/react_compiler/src/fixture_utils.rs deleted file mode 100644 index 58edc622f434..000000000000 --- a/compiler/crates/react_compiler/src/fixture_utils.rs +++ /dev/null @@ -1,239 +0,0 @@ -use react_compiler_ast::File; -use react_compiler_ast::declarations::{Declaration, ExportDefaultDecl}; -use react_compiler_ast::expressions::Expression; -use react_compiler_ast::statements::Statement; -use react_compiler_lowering::FunctionNode; - -/// Count the number of top-level functions in an AST file. -/// -/// "Top-level" means: -/// - FunctionDeclaration at program body level -/// - FunctionExpression/ArrowFunctionExpression in a VariableDeclarator at program body level -/// - FunctionDeclaration inside ExportNamedDeclaration -/// - FunctionDeclaration/FunctionExpression/ArrowFunctionExpression inside ExportDefaultDeclaration -/// - VariableDeclaration with function expressions inside ExportNamedDeclaration -/// -/// This matches the TS test binary's traversal behavior. -pub fn count_top_level_functions(ast: &File) -> usize { - let mut count = 0; - for stmt in &ast.program.body { - count += count_functions_in_statement(stmt); - } - count -} - -fn count_functions_in_statement(stmt: &Statement) -> usize { - match stmt { - Statement::FunctionDeclaration(_) => 1, - Statement::VariableDeclaration(var_decl) => { - let mut count = 0; - for declarator in &var_decl.declarations { - if let Some(init) = &declarator.init { - if is_function_expression(init) { - count += 1; - } - } - } - count - } - Statement::ExportNamedDeclaration(export) => { - if let Some(decl) = &export.declaration { - match decl.as_ref() { - Declaration::FunctionDeclaration(_) => 1, - Declaration::VariableDeclaration(var_decl) => { - let mut count = 0; - for declarator in &var_decl.declarations { - if let Some(init) = &declarator.init { - if is_function_expression(init) { - count += 1; - } - } - } - count - } - _ => 0, - } - } else { - 0 - } - } - Statement::ExportDefaultDeclaration(export) => match export.declaration.as_ref() { - ExportDefaultDecl::FunctionDeclaration(_) => 1, - ExportDefaultDecl::Expression(expr) => { - if is_function_expression(expr) { - 1 - } else { - 0 - } - } - _ => 0, - }, - // Expression statements with function expressions (uncommon but possible) - Statement::ExpressionStatement(expr_stmt) => { - if is_function_expression(&expr_stmt.expression) { - 1 - } else { - 0 - } - } - _ => 0, - } -} - -fn is_function_expression(expr: &Expression) -> bool { - matches!( - expr, - Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) - ) -} - -/// Extract the nth top-level function from an AST file as a `FunctionNode`. -/// Also returns the inferred name (e.g. from a variable declarator). -/// Returns None if function_index is out of bounds. -pub fn extract_function( - ast: &File, - function_index: usize, -) -> Option<(FunctionNode<'_>, Option<&str>)> { - let mut index = 0usize; - - for stmt in &ast.program.body { - match stmt { - Statement::FunctionDeclaration(func_decl) => { - if index == function_index { - let name = func_decl.id.as_ref().map(|id| id.name.as_str()); - return Some((FunctionNode::FunctionDeclaration(func_decl), name)); - } - index += 1; - } - Statement::VariableDeclaration(var_decl) => { - for declarator in &var_decl.declarations { - if let Some(init) = &declarator.init { - match init.as_ref() { - Expression::FunctionExpression(func) => { - if index == function_index { - let name = match &declarator.id { - react_compiler_ast::patterns::PatternLike::Identifier( - ident, - ) => Some(ident.name.as_str()), - _ => func.id.as_ref().map(|id| id.name.as_str()), - }; - return Some((FunctionNode::FunctionExpression(func), name)); - } - index += 1; - } - Expression::ArrowFunctionExpression(arrow) => { - if index == function_index { - let name = match &declarator.id { - react_compiler_ast::patterns::PatternLike::Identifier( - ident, - ) => Some(ident.name.as_str()), - _ => None, - }; - return Some(( - FunctionNode::ArrowFunctionExpression(arrow), - name, - )); - } - index += 1; - } - _ => {} - } - } - } - } - Statement::ExportNamedDeclaration(export) => { - if let Some(decl) = &export.declaration { - match decl.as_ref() { - Declaration::FunctionDeclaration(func_decl) => { - if index == function_index { - let name = func_decl.id.as_ref().map(|id| id.name.as_str()); - return Some((FunctionNode::FunctionDeclaration(func_decl), name)); - } - index += 1; - } - Declaration::VariableDeclaration(var_decl) => { - for declarator in &var_decl.declarations { - if let Some(init) = &declarator.init { - match init.as_ref() { - Expression::FunctionExpression(func) => { - if index == function_index { - let name = match &declarator.id { - react_compiler_ast::patterns::PatternLike::Identifier(ident) => Some(ident.name.as_str()), - _ => func.id.as_ref().map(|id| id.name.as_str()), - }; - return Some(( - FunctionNode::FunctionExpression(func), - name, - )); - } - index += 1; - } - Expression::ArrowFunctionExpression(arrow) => { - if index == function_index { - let name = match &declarator.id { - react_compiler_ast::patterns::PatternLike::Identifier(ident) => Some(ident.name.as_str()), - _ => None, - }; - return Some(( - FunctionNode::ArrowFunctionExpression(arrow), - name, - )); - } - index += 1; - } - _ => {} - } - } - } - } - _ => {} - } - } - } - Statement::ExportDefaultDeclaration(export) => match export.declaration.as_ref() { - ExportDefaultDecl::FunctionDeclaration(func_decl) => { - if index == function_index { - let name = func_decl.id.as_ref().map(|id| id.name.as_str()); - return Some((FunctionNode::FunctionDeclaration(func_decl), name)); - } - index += 1; - } - ExportDefaultDecl::Expression(expr) => match expr.as_ref() { - Expression::FunctionExpression(func) => { - if index == function_index { - let name = func.id.as_ref().map(|id| id.name.as_str()); - return Some((FunctionNode::FunctionExpression(func), name)); - } - index += 1; - } - Expression::ArrowFunctionExpression(arrow) => { - if index == function_index { - return Some((FunctionNode::ArrowFunctionExpression(arrow), None)); - } - index += 1; - } - _ => {} - }, - _ => {} - }, - Statement::ExpressionStatement(expr_stmt) => match expr_stmt.expression.as_ref() { - Expression::FunctionExpression(func) => { - if index == function_index { - let name = func.id.as_ref().map(|id| id.name.as_str()); - return Some((FunctionNode::FunctionExpression(func), name)); - } - index += 1; - } - Expression::ArrowFunctionExpression(arrow) => { - if index == function_index { - return Some((FunctionNode::ArrowFunctionExpression(arrow), None)); - } - index += 1; - } - _ => {} - }, - _ => {} - } - } - None -} diff --git a/compiler/crates/react_compiler/src/lib.rs b/compiler/crates/react_compiler/src/lib.rs deleted file mode 100644 index d1c8e0e4e746..000000000000 --- a/compiler/crates/react_compiler/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod debug_print; -pub mod entrypoint; -pub mod fixture_utils; -pub mod timing; - -// Re-export from new crates for backwards compatibility -pub use react_compiler_diagnostics; -pub use react_compiler_hir; -pub use react_compiler_hir as hir; -pub use react_compiler_hir::environment; -pub use react_compiler_lowering::lower; diff --git a/compiler/crates/react_compiler/src/timing.rs b/compiler/crates/react_compiler/src/timing.rs deleted file mode 100644 index 5825ea95f17a..000000000000 --- a/compiler/crates/react_compiler/src/timing.rs +++ /dev/null @@ -1,75 +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. - -//! Simple timing accumulator for profiling compiler passes. -//! -//! Uses `std::time::Instant` unconditionally (cheap when not storing results). -//! Controlled by the `__profiling` flag in plugin options. - -use serde::Serialize; -use std::time::{Duration, Instant}; - -/// A single timing entry recording how long a named phase took. -#[derive(Debug, Clone, Serialize)] -pub struct TimingEntry { - pub name: String, - pub duration_us: u64, -} - -/// Accumulates timing data for compiler passes. -pub struct TimingData { - enabled: bool, - entries: Vec<(String, Duration)>, - current_name: Option<String>, - current_start: Option<Instant>, -} - -impl TimingData { - /// Create a new TimingData. If `enabled` is false, all operations are no-ops. - pub fn new(enabled: bool) -> Self { - Self { - enabled, - entries: Vec::new(), - current_name: None, - current_start: None, - } - } - - /// Start timing a named phase. Stops any currently running phase first. - pub fn start(&mut self, name: &str) { - if !self.enabled { - return; - } - // Stop any currently running phase - if self.current_start.is_some() { - self.stop(); - } - self.current_name = Some(name.to_string()); - self.current_start = Some(Instant::now()); - } - - /// Stop the currently running phase and record its duration. - pub fn stop(&mut self) { - if !self.enabled { - return; - } - if let (Some(name), Some(start)) = (self.current_name.take(), self.current_start.take()) { - self.entries.push((name, start.elapsed())); - } - } - - /// Consume this TimingData and return the collected entries. - pub fn into_entries(mut self) -> Vec<TimingEntry> { - // Stop any still-running phase - self.stop(); - self.entries - .into_iter() - .map(|(name, duration)| TimingEntry { - name, - duration_us: duration.as_micros() as u64, - }) - .collect() - } -} diff --git a/compiler/crates/react_compiler_ast/Cargo.toml b/compiler/crates/react_compiler_ast/Cargo.toml deleted file mode 100644 index 426f00f924a5..000000000000 --- a/compiler/crates/react_compiler_ast/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "react_compiler_ast" -version = "0.1.0" -edition = "2024" - -[dependencies] -serde = { version = "1", features = ["derive"] } -serde_json = { version = "1", features = ["raw_value", "unbounded_depth"] } -serde-transcode = "1" -indexmap = { version = "2", features = ["serde"] } - -[dev-dependencies] -walkdir = "2" -similar = "2" diff --git a/compiler/crates/react_compiler_ast/src/common.rs b/compiler/crates/react_compiler_ast/src/common.rs deleted file mode 100644 index add3bbca3f4c..000000000000 --- a/compiler/crates/react_compiler_ast/src/common.rs +++ /dev/null @@ -1,193 +0,0 @@ -use serde::Deserialize; -use serde::Serialize; - -/// An AST subtree the compiler does not model with typed nodes (type -/// annotations, class bodies, parser extras). Wraps JSON text: serialization -/// is verbatim pass-through and deserialization streams the subtree into text -/// without retaining a `serde_json::Value` tree. Consumers that inspect these -/// subtrees parse on demand via [`RawNode::parse_value`]; paths that do so -/// repeatedly per traversal pay a parse each time, so cache the parsed Value -/// at the call site if it shows up in profiles. -/// -/// Deserialize is hand-implemented with a transcode rather than capturing a -/// `RawValue` directly: most nodes sit under `#[serde(tag = "type")]` enums, -/// whose content buffering breaks `RawValue`'s text-borrowing capture. -#[derive(Debug, Clone, Serialize)] -#[serde(transparent)] -pub struct RawNode(pub Box<serde_json::value::RawValue>); - -impl<'de> serde::Deserialize<'de> for RawNode { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: serde::Deserializer<'de>, - { - let mut buf = Vec::new(); - let mut ser = serde_json::Serializer::new(&mut buf); - serde_transcode::transcode(deserializer, &mut ser).map_err(serde::de::Error::custom)?; - let text = String::from_utf8(buf).map_err(serde::de::Error::custom)?; - serde_json::value::RawValue::from_string(text) - .map(RawNode) - .map_err(serde::de::Error::custom) - } -} - -impl RawNode { - pub fn from_value(value: &serde_json::Value) -> Self { - RawNode( - serde_json::value::RawValue::from_string(value.to_string()) - .expect("serde_json::Value always serializes to valid JSON"), - ) - } - - pub fn null() -> Self { - RawNode( - serde_json::value::RawValue::from_string("null".to_string()) - .expect("null is valid JSON"), - ) - } - - /// The raw JSON text of this subtree. - pub fn get(&self) -> &str { - self.0.get() - } - - /// Parse the subtree into a `serde_json::Value` for structural inspection. - /// RawNode text is valid JSON by construction, so failure here means a - /// broken invariant, not bad input; fail loudly rather than degrade. - pub fn parse_value(&self) -> serde_json::Value { - from_json_str_unbounded(self.0.get()).expect("RawNode holds valid JSON by construction") - } - - /// The node's `"type"` field, without parsing the whole subtree into a Value. - pub fn type_name(&self) -> Option<String> { - #[derive(Deserialize)] - struct TypeProbe { - #[serde(rename = "type")] - type_name: Option<String>, - } - from_json_str_unbounded::<TypeProbe>(self.0.get()) - .ok() - .and_then(|p| p.type_name) - } -} - -/// Parse JSON text with serde_json's recursion limit disabled. Every internal -/// reparse of [`RawNode`] text must go through this: the napi entrypoint -/// deserializes arbitrarily deep ASTs with the limit disabled (on a 64MB -/// stack), and the tolerant statement path's reparses must not quietly -/// reintroduce the default limit. -pub fn from_json_str_unbounded<'de, T: serde::Deserialize<'de>>( - s: &'de str, -) -> serde_json::Result<T> { - let mut deserializer = serde_json::Deserializer::from_str(s); - deserializer.disable_recursion_limit(); - T::deserialize(&mut deserializer) -} - -/// Custom deserializer that distinguishes "field absent" from "field: null". -/// - JSON field absent → `None` (via `#[serde(default)]`) -/// - JSON field `null` → `Some(RawNode("null"))` -/// - JSON field with value → `Some(raw value)` -/// -/// Use with `#[serde(default, skip_serializing_if = "Option::is_none", deserialize_with = "nullable_value")]` -pub fn nullable_value<'de, D>(deserializer: D) -> Result<Option<RawNode>, D::Error> -where - D: serde::Deserializer<'de>, -{ - RawNode::deserialize(deserializer).map(Some) -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Position { - pub line: u32, - pub column: u32, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub index: Option<u32>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SourceLocation { - pub start: Position, - pub end: Position, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub filename: Option<String>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "identifierName" - )] - pub identifier_name: Option<String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum Comment { - CommentBlock(CommentData), - CommentLine(CommentData), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CommentData { - pub value: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub start: Option<u32>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub end: Option<u32>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub loc: Option<SourceLocation>, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct BaseNode { - // NOTE: When creating AST nodes for code generation output, use - // `BaseNode::typed("NodeTypeName")` instead of `BaseNode::default()` - // to ensure the "type" field is emitted during serialization. - /// The node type string (e.g. "BlockStatement"). - /// When deserialized through a `#[serde(tag = "type")]` enum, the enum - /// consumes the "type" field so this defaults to None. When deserialized - /// directly, this captures the "type" field for round-trip fidelity. - #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] - pub node_type: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub start: Option<u32>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub end: Option<u32>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub loc: Option<SourceLocation>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub range: Option<(u32, u32)>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extra: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "leadingComments" - )] - pub leading_comments: Option<Vec<Comment>>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "innerComments" - )] - pub inner_comments: Option<Vec<Comment>>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "trailingComments" - )] - pub trailing_comments: Option<Vec<Comment>>, - #[serde(default, skip_serializing_if = "Option::is_none", rename = "_nodeId")] - pub node_id: Option<u32>, -} - -impl BaseNode { - /// Create a BaseNode with the given type name. - /// Use this when creating AST nodes for code generation to ensure the - /// `"type"` field is present in serialized output. - pub fn typed(type_name: &str) -> Self { - Self { - node_type: Some(type_name.to_string()), - ..Default::default() - } - } -} diff --git a/compiler/crates/react_compiler_ast/src/declarations.rs b/compiler/crates/react_compiler_ast/src/declarations.rs deleted file mode 100644 index 87c7c7aef915..000000000000 --- a/compiler/crates/react_compiler_ast/src/declarations.rs +++ /dev/null @@ -1,470 +0,0 @@ -use serde::Deserialize; -use serde::Serialize; - -use crate::common::BaseNode; -use crate::common::RawNode; -use crate::expressions::Expression; -use crate::expressions::Identifier; -use crate::literals::StringLiteral; - -/// Union of Declaration types that can appear in export declarations -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum Declaration { - FunctionDeclaration(crate::statements::FunctionDeclaration), - ClassDeclaration(crate::statements::ClassDeclaration), - VariableDeclaration(crate::statements::VariableDeclaration), - TSTypeAliasDeclaration(TSTypeAliasDeclaration), - TSInterfaceDeclaration(TSInterfaceDeclaration), - TSEnumDeclaration(TSEnumDeclaration), - TSModuleDeclaration(TSModuleDeclaration), - TSDeclareFunction(TSDeclareFunction), - TypeAlias(TypeAlias), - OpaqueType(OpaqueType), - InterfaceDeclaration(InterfaceDeclaration), - EnumDeclaration(EnumDeclaration), -} - -/// The declaration/expression that can appear in `export default <decl>` -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum ExportDefaultDecl { - FunctionDeclaration(crate::statements::FunctionDeclaration), - ClassDeclaration(crate::statements::ClassDeclaration), - EnumDeclaration(EnumDeclaration), - #[serde(untagged)] - Expression(Box<Expression>), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ImportDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub specifiers: Vec<ImportSpecifier>, - pub source: StringLiteral, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "importKind" - )] - pub import_kind: Option<ImportKind>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub assertions: Option<Vec<ImportAttribute>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub attributes: Option<Vec<ImportAttribute>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ImportKind { - Value, - Type, - Typeof, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum ImportSpecifier { - ImportSpecifier(ImportSpecifierData), - ImportDefaultSpecifier(ImportDefaultSpecifierData), - ImportNamespaceSpecifier(ImportNamespaceSpecifierData), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ImportSpecifierData { - #[serde(flatten)] - pub base: BaseNode, - pub local: Identifier, - pub imported: ModuleExportName, - #[serde(default, rename = "importKind")] - pub import_kind: Option<ImportKind>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ImportDefaultSpecifierData { - #[serde(flatten)] - pub base: BaseNode, - pub local: Identifier, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ImportNamespaceSpecifierData { - #[serde(flatten)] - pub base: BaseNode, - pub local: Identifier, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ImportAttribute { - #[serde(flatten)] - pub base: BaseNode, - pub key: Identifier, - pub value: StringLiteral, -} - -/// Identifier or StringLiteral used as module export names -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum ModuleExportName { - Identifier(Identifier), - StringLiteral(StringLiteral), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ExportNamedDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub declaration: Option<Box<Declaration>>, - pub specifiers: Vec<ExportSpecifier>, - pub source: Option<StringLiteral>, - #[serde(default, rename = "exportKind")] - pub export_kind: Option<ExportKind>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub assertions: Option<Vec<ImportAttribute>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub attributes: Option<Vec<ImportAttribute>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ExportKind { - Value, - Type, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum ExportSpecifier { - ExportSpecifier(ExportSpecifierData), - ExportDefaultSpecifier(ExportDefaultSpecifierData), - ExportNamespaceSpecifier(ExportNamespaceSpecifierData), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ExportSpecifierData { - #[serde(flatten)] - pub base: BaseNode, - pub local: ModuleExportName, - pub exported: ModuleExportName, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "exportKind" - )] - pub export_kind: Option<ExportKind>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ExportDefaultSpecifierData { - #[serde(flatten)] - pub base: BaseNode, - pub exported: Identifier, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ExportNamespaceSpecifierData { - #[serde(flatten)] - pub base: BaseNode, - pub exported: ModuleExportName, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ExportDefaultDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub declaration: Box<ExportDefaultDecl>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "exportKind" - )] - pub export_kind: Option<ExportKind>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ExportAllDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub source: StringLiteral, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "exportKind" - )] - pub export_kind: Option<ExportKind>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub assertions: Option<Vec<ImportAttribute>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub attributes: Option<Vec<ImportAttribute>>, -} - -// TypeScript declarations (pass-through via RawNode for bodies) -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TSTypeAliasDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - #[serde(rename = "typeAnnotation")] - pub type_annotation: RawNode, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub declare: Option<bool>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TSInterfaceDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - pub body: RawNode, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extends: Option<Vec<RawNode>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub declare: Option<bool>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TSEnumDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - pub members: Vec<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub declare: Option<bool>, - #[serde(default, skip_serializing_if = "Option::is_none", rename = "const")] - pub is_const: Option<bool>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TSModuleDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub id: RawNode, - pub body: RawNode, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub declare: Option<bool>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub global: Option<bool>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TSDeclareFunction { - #[serde(flatten)] - pub base: BaseNode, - pub id: Option<Identifier>, - pub params: Vec<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none", rename = "async")] - pub is_async: Option<bool>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub declare: Option<bool>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub generator: Option<bool>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "returnType" - )] - pub return_type: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, -} - -// Flow declarations (pass-through) -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TypeAlias { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - pub right: RawNode, - #[serde(default, rename = "typeParameters")] - pub type_parameters: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct OpaqueType { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - #[serde(rename = "supertype")] - pub supertype: Option<RawNode>, - pub impltype: RawNode, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct InterfaceDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - pub body: RawNode, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extends: Option<Vec<RawNode>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub mixins: Option<Vec<RawNode>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub implements: Option<Vec<RawNode>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DeclareVariable { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DeclareFunction { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - #[serde( - default, - skip_serializing_if = "Option::is_none", - deserialize_with = "crate::common::nullable_value" - )] - pub predicate: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DeclareClass { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - pub body: RawNode, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extends: Option<Vec<RawNode>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub mixins: Option<Vec<RawNode>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub implements: Option<Vec<RawNode>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DeclareModule { - #[serde(flatten)] - pub base: BaseNode, - pub id: RawNode, - pub body: RawNode, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub kind: Option<String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DeclareModuleExports { - #[serde(flatten)] - pub base: BaseNode, - #[serde(rename = "typeAnnotation")] - pub type_annotation: RawNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DeclareExportDeclaration { - #[serde(flatten)] - pub base: BaseNode, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub declaration: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub specifiers: Option<Vec<RawNode>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub source: Option<StringLiteral>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub default: Option<bool>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DeclareExportAllDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub source: StringLiteral, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DeclareInterface { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - pub body: RawNode, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extends: Option<Vec<RawNode>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub mixins: Option<Vec<RawNode>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub implements: Option<Vec<RawNode>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DeclareTypeAlias { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - pub right: RawNode, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DeclareOpaqueType { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub supertype: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub impltype: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct EnumDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, - pub body: RawNode, -} diff --git a/compiler/crates/react_compiler_ast/src/expressions.rs b/compiler/crates/react_compiler_ast/src/expressions.rs deleted file mode 100644 index bd5331fb0896..000000000000 --- a/compiler/crates/react_compiler_ast/src/expressions.rs +++ /dev/null @@ -1,562 +0,0 @@ -use serde::Deserialize; -use serde::Serialize; - -use crate::common::BaseNode; -use crate::common::RawNode; -use crate::jsx::JSXElement; -use crate::jsx::JSXFragment; -use crate::literals::*; -use crate::operators::*; -use crate::patterns::AssignmentPattern; -use crate::patterns::PatternLike; -use crate::statements::BlockStatement; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Identifier { - #[serde(flatten)] - pub base: BaseNode, - pub name: String, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeAnnotation" - )] - pub type_annotation: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub optional: Option<bool>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub decorators: Option<Vec<RawNode>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum Expression { - Identifier(Identifier), - StringLiteral(StringLiteral), - NumericLiteral(NumericLiteral), - BooleanLiteral(BooleanLiteral), - NullLiteral(NullLiteral), - BigIntLiteral(BigIntLiteral), - RegExpLiteral(RegExpLiteral), - CallExpression(CallExpression), - MemberExpression(MemberExpression), - OptionalCallExpression(OptionalCallExpression), - OptionalMemberExpression(OptionalMemberExpression), - BinaryExpression(BinaryExpression), - LogicalExpression(LogicalExpression), - UnaryExpression(UnaryExpression), - UpdateExpression(UpdateExpression), - ConditionalExpression(ConditionalExpression), - AssignmentExpression(AssignmentExpression), - SequenceExpression(SequenceExpression), - ArrowFunctionExpression(ArrowFunctionExpression), - FunctionExpression(FunctionExpression), - ObjectExpression(ObjectExpression), - ArrayExpression(ArrayExpression), - NewExpression(NewExpression), - TemplateLiteral(TemplateLiteral), - TaggedTemplateExpression(TaggedTemplateExpression), - AwaitExpression(AwaitExpression), - YieldExpression(YieldExpression), - SpreadElement(SpreadElement), - MetaProperty(MetaProperty), - ClassExpression(ClassExpression), - PrivateName(PrivateName), - Super(Super), - Import(Import), - ThisExpression(ThisExpression), - ParenthesizedExpression(ParenthesizedExpression), - // JSX expressions - JSXElement(Box<JSXElement>), - JSXFragment(JSXFragment), - // Pattern (can appear in expression position in error recovery) - AssignmentPattern(AssignmentPattern), - // TypeScript expressions - TSAsExpression(TSAsExpression), - TSSatisfiesExpression(TSSatisfiesExpression), - TSNonNullExpression(TSNonNullExpression), - TSTypeAssertion(TSTypeAssertion), - TSInstantiationExpression(TSInstantiationExpression), - // Flow expressions - TypeCastExpression(TypeCastExpression), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CallExpression { - #[serde(flatten)] - pub base: BaseNode, - pub callee: Box<Expression>, - pub arguments: Vec<Expression>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeArguments" - )] - pub type_arguments: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub optional: Option<bool>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MemberExpression { - #[serde(flatten)] - pub base: BaseNode, - pub object: Box<Expression>, - pub property: Box<Expression>, - pub computed: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct OptionalCallExpression { - #[serde(flatten)] - pub base: BaseNode, - pub callee: Box<Expression>, - pub arguments: Vec<Expression>, - pub optional: bool, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeArguments" - )] - pub type_arguments: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct OptionalMemberExpression { - #[serde(flatten)] - pub base: BaseNode, - pub object: Box<Expression>, - pub property: Box<Expression>, - pub computed: bool, - pub optional: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BinaryExpression { - #[serde(flatten)] - pub base: BaseNode, - pub operator: BinaryOperator, - pub left: Box<Expression>, - pub right: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LogicalExpression { - #[serde(flatten)] - pub base: BaseNode, - pub operator: LogicalOperator, - pub left: Box<Expression>, - pub right: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct UnaryExpression { - #[serde(flatten)] - pub base: BaseNode, - pub operator: UnaryOperator, - pub prefix: bool, - pub argument: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct UpdateExpression { - #[serde(flatten)] - pub base: BaseNode, - pub operator: UpdateOperator, - pub argument: Box<Expression>, - pub prefix: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ConditionalExpression { - #[serde(flatten)] - pub base: BaseNode, - pub test: Box<Expression>, - pub consequent: Box<Expression>, - pub alternate: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AssignmentExpression { - #[serde(flatten)] - pub base: BaseNode, - pub operator: AssignmentOperator, - pub left: Box<PatternLike>, - pub right: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SequenceExpression { - #[serde(flatten)] - pub base: BaseNode, - pub expressions: Vec<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ArrowFunctionExpression { - #[serde(flatten)] - pub base: BaseNode, - pub params: Vec<PatternLike>, - pub body: Box<ArrowFunctionBody>, - #[serde(default)] - pub id: Option<Identifier>, - #[serde(default)] - pub generator: bool, - #[serde(default, rename = "async")] - pub is_async: bool, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub expression: Option<bool>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "returnType" - )] - pub return_type: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "predicate", - deserialize_with = "crate::common::nullable_value" - )] - pub predicate: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum ArrowFunctionBody { - BlockStatement(BlockStatement), - #[serde(untagged)] - Expression(Box<Expression>), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FunctionExpression { - #[serde(flatten)] - pub base: BaseNode, - pub params: Vec<PatternLike>, - pub body: BlockStatement, - #[serde(default)] - pub id: Option<Identifier>, - #[serde(default)] - pub generator: bool, - #[serde(default, rename = "async")] - pub is_async: bool, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "returnType" - )] - pub return_type: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "predicate", - deserialize_with = "crate::common::nullable_value" - )] - pub predicate: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ObjectExpression { - #[serde(flatten)] - pub base: BaseNode, - pub properties: Vec<ObjectExpressionProperty>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum ObjectExpressionProperty { - ObjectProperty(ObjectProperty), - ObjectMethod(ObjectMethod), - SpreadElement(SpreadElement), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ObjectProperty { - #[serde(flatten)] - pub base: BaseNode, - pub key: Box<Expression>, - pub value: Box<Expression>, - pub computed: bool, - pub shorthand: bool, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub decorators: Option<Vec<RawNode>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub method: Option<bool>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ObjectMethod { - #[serde(flatten)] - pub base: BaseNode, - pub method: bool, - pub kind: ObjectMethodKind, - pub key: Box<Expression>, - pub params: Vec<PatternLike>, - pub body: BlockStatement, - pub computed: bool, - #[serde(default)] - pub id: Option<Identifier>, - #[serde(default)] - pub generator: bool, - #[serde(default, rename = "async")] - pub is_async: bool, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub decorators: Option<Vec<RawNode>>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "returnType" - )] - pub return_type: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "predicate", - deserialize_with = "crate::common::nullable_value" - )] - pub predicate: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ObjectMethodKind { - Method, - Get, - Set, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ArrayExpression { - #[serde(flatten)] - pub base: BaseNode, - pub elements: Vec<Option<Expression>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NewExpression { - #[serde(flatten)] - pub base: BaseNode, - pub callee: Box<Expression>, - pub arguments: Vec<Expression>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - deserialize_with = "crate::common::nullable_value", - rename = "typeArguments" - )] - pub type_arguments: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TemplateLiteral { - #[serde(flatten)] - pub base: BaseNode, - pub quasis: Vec<TemplateElement>, - pub expressions: Vec<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TaggedTemplateExpression { - #[serde(flatten)] - pub base: BaseNode, - pub tag: Box<Expression>, - pub quasi: TemplateLiteral, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AwaitExpression { - #[serde(flatten)] - pub base: BaseNode, - pub argument: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct YieldExpression { - #[serde(flatten)] - pub base: BaseNode, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub argument: Option<Box<Expression>>, - pub delegate: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SpreadElement { - #[serde(flatten)] - pub base: BaseNode, - pub argument: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MetaProperty { - #[serde(flatten)] - pub base: BaseNode, - pub meta: Identifier, - pub property: Identifier, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ClassExpression { - #[serde(flatten)] - pub base: BaseNode, - #[serde(default)] - pub id: Option<Identifier>, - #[serde(rename = "superClass")] - pub super_class: Option<Box<Expression>>, - pub body: ClassBody, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub decorators: Option<Vec<RawNode>>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "implements" - )] - pub implements: Option<Vec<RawNode>>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "superTypeParameters" - )] - pub super_type_parameters: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ClassBody { - #[serde(flatten)] - pub base: BaseNode, - pub body: Vec<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PrivateName { - #[serde(flatten)] - pub base: BaseNode, - pub id: Identifier, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Super { - #[serde(flatten)] - pub base: BaseNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Import { - #[serde(flatten)] - pub base: BaseNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ThisExpression { - #[serde(flatten)] - pub base: BaseNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ParenthesizedExpression { - #[serde(flatten)] - pub base: BaseNode, - pub expression: Box<Expression>, -} - -// TypeScript expression nodes (pass-through with RawNode for type args) -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TSAsExpression { - #[serde(flatten)] - pub base: BaseNode, - pub expression: Box<Expression>, - #[serde(rename = "typeAnnotation")] - pub type_annotation: RawNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TSSatisfiesExpression { - #[serde(flatten)] - pub base: BaseNode, - pub expression: Box<Expression>, - #[serde(rename = "typeAnnotation")] - pub type_annotation: RawNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TSNonNullExpression { - #[serde(flatten)] - pub base: BaseNode, - pub expression: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TSTypeAssertion { - #[serde(flatten)] - pub base: BaseNode, - pub expression: Box<Expression>, - #[serde(rename = "typeAnnotation")] - pub type_annotation: RawNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TSInstantiationExpression { - #[serde(flatten)] - pub base: BaseNode, - pub expression: Box<Expression>, - #[serde(rename = "typeParameters")] - pub type_parameters: RawNode, -} - -// Flow expression nodes -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TypeCastExpression { - #[serde(flatten)] - pub base: BaseNode, - pub expression: Box<Expression>, - #[serde(rename = "typeAnnotation")] - pub type_annotation: RawNode, -} diff --git a/compiler/crates/react_compiler_ast/src/jsx.rs b/compiler/crates/react_compiler_ast/src/jsx.rs deleted file mode 100644 index ffec6e5ce09a..000000000000 --- a/compiler/crates/react_compiler_ast/src/jsx.rs +++ /dev/null @@ -1,190 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::common::BaseNode; -use crate::common::RawNode; -use crate::expressions::Expression; -use crate::literals::StringLiteral; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXElement { - #[serde(flatten)] - pub base: BaseNode, - #[serde(rename = "openingElement")] - pub opening_element: JSXOpeningElement, - #[serde(rename = "closingElement")] - pub closing_element: Option<JSXClosingElement>, - pub children: Vec<JSXChild>, - #[serde( - rename = "selfClosing", - default, - skip_serializing_if = "Option::is_none" - )] - pub self_closing: Option<bool>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXFragment { - #[serde(flatten)] - pub base: BaseNode, - #[serde(rename = "openingFragment")] - pub opening_fragment: JSXOpeningFragment, - #[serde(rename = "closingFragment")] - pub closing_fragment: JSXClosingFragment, - pub children: Vec<JSXChild>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXOpeningElement { - #[serde(flatten)] - pub base: BaseNode, - pub name: JSXElementName, - pub attributes: Vec<JSXAttributeItem>, - #[serde(rename = "selfClosing")] - pub self_closing: bool, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXClosingElement { - #[serde(flatten)] - pub base: BaseNode, - pub name: JSXElementName, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXOpeningFragment { - #[serde(flatten)] - pub base: BaseNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXClosingFragment { - #[serde(flatten)] - pub base: BaseNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum JSXElementName { - JSXIdentifier(JSXIdentifier), - JSXMemberExpression(JSXMemberExpression), - JSXNamespacedName(JSXNamespacedName), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum JSXChild { - JSXElement(Box<JSXElement>), - JSXFragment(JSXFragment), - JSXExpressionContainer(JSXExpressionContainer), - JSXSpreadChild(JSXSpreadChild), - JSXText(JSXText), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum JSXAttributeItem { - JSXAttribute(JSXAttribute), - JSXSpreadAttribute(JSXSpreadAttribute), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXAttribute { - #[serde(flatten)] - pub base: BaseNode, - pub name: JSXAttributeName, - pub value: Option<JSXAttributeValue>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum JSXAttributeName { - JSXIdentifier(JSXIdentifier), - JSXNamespacedName(JSXNamespacedName), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum JSXAttributeValue { - StringLiteral(StringLiteral), - JSXExpressionContainer(JSXExpressionContainer), - JSXElement(Box<JSXElement>), - JSXFragment(JSXFragment), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXSpreadAttribute { - #[serde(flatten)] - pub base: BaseNode, - pub argument: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXExpressionContainer { - #[serde(flatten)] - pub base: BaseNode, - pub expression: JSXExpressionContainerExpr, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum JSXExpressionContainerExpr { - JSXEmptyExpression(JSXEmptyExpression), - #[serde(untagged)] - Expression(Box<Expression>), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXSpreadChild { - #[serde(flatten)] - pub base: BaseNode, - pub expression: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXText { - #[serde(flatten)] - pub base: BaseNode, - pub value: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXEmptyExpression { - #[serde(flatten)] - pub base: BaseNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXIdentifier { - #[serde(flatten)] - pub base: BaseNode, - pub name: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXMemberExpression { - #[serde(flatten)] - pub base: BaseNode, - pub object: Box<JSXMemberExprObject>, - pub property: JSXIdentifier, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum JSXMemberExprObject { - JSXIdentifier(JSXIdentifier), - JSXMemberExpression(Box<JSXMemberExpression>), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JSXNamespacedName { - #[serde(flatten)] - pub base: BaseNode, - pub namespace: JSXIdentifier, - pub name: JSXIdentifier, -} diff --git a/compiler/crates/react_compiler_ast/src/lib.rs b/compiler/crates/react_compiler_ast/src/lib.rs deleted file mode 100644 index 1dce3979b2d7..000000000000 --- a/compiler/crates/react_compiler_ast/src/lib.rs +++ /dev/null @@ -1,60 +0,0 @@ -pub mod common; -pub mod declarations; -pub mod expressions; -pub mod jsx; -pub mod literals; -pub mod operators; -pub mod patterns; -pub mod scope; -pub mod statements; -pub mod visitor; - -use serde::{Deserialize, Serialize}; - -use crate::common::{BaseNode, Comment}; -use crate::statements::{Directive, Statement}; - -/// The root type returned by @babel/parser -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct File { - #[serde(flatten)] - pub base: BaseNode, - pub program: Program, - #[serde(default)] - pub comments: Vec<Comment>, - #[serde(default)] - pub errors: Vec<serde_json::Value>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Program { - #[serde(flatten)] - pub base: BaseNode, - pub body: Vec<Statement>, - #[serde(default)] - pub directives: Vec<Directive>, - #[serde(rename = "sourceType")] - pub source_type: SourceType, - #[serde(default)] - pub interpreter: Option<InterpreterDirective>, - #[serde( - rename = "sourceFile", - default, - skip_serializing_if = "Option::is_none" - )] - pub source_file: Option<String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum SourceType { - Module, - Script, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct InterpreterDirective { - #[serde(flatten)] - pub base: BaseNode, - pub value: String, -} diff --git a/compiler/crates/react_compiler_ast/src/literals.rs b/compiler/crates/react_compiler_ast/src/literals.rs deleted file mode 100644 index 356543d40965..000000000000 --- a/compiler/crates/react_compiler_ast/src/literals.rs +++ /dev/null @@ -1,84 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::common::BaseNode; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct StringLiteral { - #[serde(flatten)] - pub base: BaseNode, - pub value: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NumericLiteral { - #[serde(flatten)] - pub base: BaseNode, - pub value: f64, - /// Babel's extra field containing the raw source text. - /// Used to recover exact f64 values that serde_json may parse imprecisely. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extra: Option<NumericLiteralExtra>, -} - -impl NumericLiteral { - /// Get the f64 value, preferring re-parsing from `extra.raw` when available - /// to avoid serde_json float parsing precision issues. - pub fn precise_value(&self) -> f64 { - if let Some(extra) = &self.extra { - if let Ok(v) = extra.raw.parse::<f64>() { - return v; - } - } - self.value - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NumericLiteralExtra { - pub raw: String, - #[serde(default, rename = "rawValue")] - pub raw_value: Option<f64>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BooleanLiteral { - #[serde(flatten)] - pub base: BaseNode, - pub value: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NullLiteral { - #[serde(flatten)] - pub base: BaseNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BigIntLiteral { - #[serde(flatten)] - pub base: BaseNode, - pub value: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RegExpLiteral { - #[serde(flatten)] - pub base: BaseNode, - pub pattern: String, - pub flags: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TemplateElement { - #[serde(flatten)] - pub base: BaseNode, - pub value: TemplateElementValue, - pub tail: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TemplateElementValue { - pub raw: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cooked: Option<String>, -} diff --git a/compiler/crates/react_compiler_ast/src/operators.rs b/compiler/crates/react_compiler_ast/src/operators.rs deleted file mode 100644 index d52dbb49128c..000000000000 --- a/compiler/crates/react_compiler_ast/src/operators.rs +++ /dev/null @@ -1,125 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum BinaryOperator { - #[serde(rename = "+")] - Add, - #[serde(rename = "-")] - Sub, - #[serde(rename = "*")] - Mul, - #[serde(rename = "/")] - Div, - #[serde(rename = "%")] - Rem, - #[serde(rename = "**")] - Exp, - #[serde(rename = "==")] - Eq, - #[serde(rename = "===")] - StrictEq, - #[serde(rename = "!=")] - Neq, - #[serde(rename = "!==")] - StrictNeq, - #[serde(rename = "<")] - Lt, - #[serde(rename = "<=")] - Lte, - #[serde(rename = ">")] - Gt, - #[serde(rename = ">=")] - Gte, - #[serde(rename = "<<")] - Shl, - #[serde(rename = ">>")] - Shr, - #[serde(rename = ">>>")] - UShr, - #[serde(rename = "|")] - BitOr, - #[serde(rename = "^")] - BitXor, - #[serde(rename = "&")] - BitAnd, - #[serde(rename = "in")] - In, - #[serde(rename = "instanceof")] - Instanceof, - #[serde(rename = "|>")] - Pipeline, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum LogicalOperator { - #[serde(rename = "||")] - Or, - #[serde(rename = "&&")] - And, - #[serde(rename = "??")] - NullishCoalescing, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum UnaryOperator { - #[serde(rename = "-")] - Neg, - #[serde(rename = "+")] - Plus, - #[serde(rename = "!")] - Not, - #[serde(rename = "~")] - BitNot, - #[serde(rename = "typeof")] - TypeOf, - #[serde(rename = "void")] - Void, - #[serde(rename = "delete")] - Delete, - #[serde(rename = "throw")] - Throw, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum UpdateOperator { - #[serde(rename = "++")] - Increment, - #[serde(rename = "--")] - Decrement, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum AssignmentOperator { - #[serde(rename = "=")] - Assign, - #[serde(rename = "+=")] - AddAssign, - #[serde(rename = "-=")] - SubAssign, - #[serde(rename = "*=")] - MulAssign, - #[serde(rename = "/=")] - DivAssign, - #[serde(rename = "%=")] - RemAssign, - #[serde(rename = "**=")] - ExpAssign, - #[serde(rename = "<<=")] - ShlAssign, - #[serde(rename = ">>=")] - ShrAssign, - #[serde(rename = ">>>=")] - UShrAssign, - #[serde(rename = "|=")] - BitOrAssign, - #[serde(rename = "^=")] - BitXorAssign, - #[serde(rename = "&=")] - BitAndAssign, - #[serde(rename = "||=")] - OrAssign, - #[serde(rename = "&&=")] - AndAssign, - #[serde(rename = "??=")] - NullishAssign, -} diff --git a/compiler/crates/react_compiler_ast/src/patterns.rs b/compiler/crates/react_compiler_ast/src/patterns.rs deleted file mode 100644 index bfeef6eec7c1..000000000000 --- a/compiler/crates/react_compiler_ast/src/patterns.rs +++ /dev/null @@ -1,108 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::common::BaseNode; -use crate::common::RawNode; -use crate::expressions::{Expression, Identifier}; - -/// Covers assignment targets and patterns. -/// In Babel, LVal includes Identifier, MemberExpression, ObjectPattern, ArrayPattern, -/// RestElement, AssignmentPattern. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum PatternLike { - Identifier(Identifier), - ObjectPattern(ObjectPattern), - ArrayPattern(ArrayPattern), - AssignmentPattern(AssignmentPattern), - RestElement(RestElement), - // Expressions can appear in pattern positions (e.g., MemberExpression as LVal) - MemberExpression(crate::expressions::MemberExpression), - TSAsExpression(crate::expressions::TSAsExpression), - TSSatisfiesExpression(crate::expressions::TSSatisfiesExpression), - TSNonNullExpression(crate::expressions::TSNonNullExpression), - TSTypeAssertion(crate::expressions::TSTypeAssertion), - // Flow's analogue of the TS cast wrappers: `(expr: SomeType)`. - TypeCastExpression(crate::expressions::TypeCastExpression), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ObjectPattern { - #[serde(flatten)] - pub base: BaseNode, - pub properties: Vec<ObjectPatternProperty>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeAnnotation" - )] - pub type_annotation: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub decorators: Option<Vec<RawNode>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum ObjectPatternProperty { - ObjectProperty(ObjectPatternProp), - RestElement(RestElement), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ObjectPatternProp { - #[serde(flatten)] - pub base: BaseNode, - pub key: Box<Expression>, - pub value: Box<PatternLike>, - pub computed: bool, - pub shorthand: bool, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub decorators: Option<Vec<RawNode>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub method: Option<bool>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ArrayPattern { - #[serde(flatten)] - pub base: BaseNode, - pub elements: Vec<Option<PatternLike>>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeAnnotation" - )] - pub type_annotation: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub decorators: Option<Vec<RawNode>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AssignmentPattern { - #[serde(flatten)] - pub base: BaseNode, - pub left: Box<PatternLike>, - pub right: Box<Expression>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeAnnotation" - )] - pub type_annotation: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub decorators: Option<Vec<RawNode>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RestElement { - #[serde(flatten)] - pub base: BaseNode, - pub argument: Box<PatternLike>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeAnnotation" - )] - pub type_annotation: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub decorators: Option<Vec<RawNode>>, -} diff --git a/compiler/crates/react_compiler_ast/src/scope.rs b/compiler/crates/react_compiler_ast/src/scope.rs deleted file mode 100644 index a9543d0b6cc1..000000000000 --- a/compiler/crates/react_compiler_ast/src/scope.rs +++ /dev/null @@ -1,339 +0,0 @@ -use std::collections::HashMap; - -use indexmap::IndexMap; -use serde::Deserialize; -use serde::Serialize; - -/// Identifies a scope in the scope table. Copy-able, used as an index. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct ScopeId(pub u32); - -/// Identifies a binding (variable declaration) in the binding table. Copy-able, used as an index. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct BindingId(pub u32); - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ScopeData { - pub id: ScopeId, - pub parent: Option<ScopeId>, - pub kind: ScopeKind, - /// Bindings declared directly in this scope, keyed by name. - /// Maps to BindingId for lookup in the binding table. - pub bindings: HashMap<String, BindingId>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ScopeKind { - Program, - Function, - Block, - #[serde(rename = "for")] - For, - Class, - Switch, - Catch, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct BindingData { - pub id: BindingId, - pub name: String, - pub kind: BindingKind, - /// The scope this binding is declared in. - pub scope: ScopeId, - /// The type of the declaration AST node (e.g., "FunctionDeclaration", - /// "VariableDeclarator"). Used by the compiler to distinguish function - /// declarations from variable declarations during hoisting. - pub declaration_type: String, - /// The start offset of the binding's declaration identifier. - /// Used to distinguish declaration sites from references in `reference_to_binding`. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub declaration_start: Option<u32>, - /// The node-ID of the binding's declaration identifier. - /// Preferred over `declaration_start` for distinguishing declarations from - /// references, as positions can collide for synthetic nodes at position 0. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub declaration_node_id: Option<u32>, - /// For import bindings: the source module and import details. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub import: Option<ImportBindingData>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum BindingKind { - Var, - Let, - Const, - Param, - /// Import bindings (import declarations). - Module, - /// Function declarations (hoisted). - Hoisted, - /// Other local bindings (class declarations, etc.). - Local, - /// Binding kind not recognized by the serializer. - Unknown, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ImportBindingData { - /// The module specifier string (e.g., "react" in `import {useState} from 'react'`). - pub source: String, - pub kind: ImportBindingKind, - /// For named imports: the imported name (e.g., "bar" in `import {bar as baz} from 'foo'`). - /// None for default and namespace imports. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub imported: Option<String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ImportBindingKind { - Default, - Named, - Namespace, -} - -/// Complete scope information for a program. Stored separately from the AST -/// and linked via position-based lookup maps. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ScopeInfo { - /// All scopes, indexed by ScopeId. scopes[id.0] gives the ScopeData for that scope. - pub scopes: Vec<ScopeData>, - /// All bindings, indexed by BindingId. bindings[id.0] gives the BindingData. - pub bindings: Vec<BindingData>, - - /// Maps an AST node's start offset to the scope it creates. - /// - /// **NOT for identity lookups** — use `node_id_to_scope` (via `resolve_scope_for_node`) - /// instead. Retained only for position-range containment queries - /// (e.g., "is reference R inside function scope S?"). - pub node_to_scope: HashMap<u32, ScopeId>, - - /// Maps an AST node's start offset to the node's end offset. - /// Parallel to `node_to_scope` — used for position-range containment checks. - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - pub node_to_scope_end: HashMap<u32, u32>, - - /// **DEPRECATED** — retained only for Babel bridge JSON deserialization. - /// All backends pass empty maps; only the Babel bridge populates this. - /// Use `ref_node_id_to_binding` for all lookups and iteration. - #[serde(default)] - pub reference_to_binding: IndexMap<u32, BindingId>, - - /// Maps an identifier reference's node-ID to the binding it resolves to. - /// Only present for identifiers that resolve to a binding (not globals). - /// Uses IndexMap to preserve insertion order. - #[serde( - default, - skip_serializing_if = "IndexMap::is_empty", - rename = "refNodeIdToBinding" - )] - pub ref_node_id_to_binding: IndexMap<u32, BindingId>, - - /// Maps a scope-creating AST node's node-ID to the scope it creates. - #[serde( - default, - skip_serializing_if = "HashMap::is_empty", - rename = "nodeIdToScope" - )] - pub node_id_to_scope: HashMap<u32, ScopeId>, - - /// The program-level (module) scope. Always scopes[0]. - pub program_scope: ScopeId, -} - -impl ScopeInfo { - /// Look up a binding by name starting from the given scope, - /// walking up the parent chain. Returns None for globals. - pub fn get_binding(&self, scope_id: ScopeId, name: &str) -> Option<BindingId> { - let mut current = Some(scope_id); - while let Some(id) = current { - let scope = &self.scopes[id.0 as usize]; - if let Some(&binding_id) = scope.bindings.get(name) { - return Some(binding_id); - } - current = scope.parent; - } - None - } - - /// Look up the scope for an AST node by its unique node ID. - pub fn resolve_scope_by_node_id(&self, node_id: u32) -> Option<ScopeId> { - self.node_id_to_scope.get(&node_id).copied() - } - - /// Resolve the scope for an AST node by node_id. - /// Returns None if node_id is None (the node has no scope entry) or if the - /// node_id doesn't map to any scope. This is expected for AST nodes that - /// don't create their own scope — e.g., a function body BlockStatement in - /// Babel shares the function's scope and never gets a _nodeId assigned by - /// scope extraction. - pub fn resolve_scope_for_node(&self, node_id: Option<u32>) -> Option<ScopeId> { - let nid = node_id?; - self.node_id_to_scope.get(&nid).copied() - } - - /// Look up the binding for an identifier reference by its unique node ID. - /// Returns None for globals/unresolved references. - pub fn resolve_reference_by_node_id(&self, node_id: u32) -> Option<BindingId> { - self.ref_node_id_to_binding.get(&node_id).copied() - } - - /// Resolve the binding for an identifier by node_id. - /// Returns None if node_id is None or if the identifier doesn't resolve to - /// a binding (i.e., it's a global/unresolved reference). - pub fn resolve_reference_id_for_node(&self, node_id: Option<u32>) -> Option<BindingId> { - let nid = node_id?; - self.ref_node_id_to_binding.get(&nid).copied() - } - - /// Resolve the binding for an identifier by node_id. - /// Returns None if node_id is None or if the identifier doesn't resolve to - /// a binding (i.e., it's a global/unresolved reference). - pub fn resolve_reference_for_node(&self, node_id: Option<u32>) -> Option<&BindingData> { - self.resolve_reference_id_for_node(node_id) - .map(|id| &self.bindings[id.0 as usize]) - } - - /// Find a binding by name within the descendants of a given scope. - pub fn find_binding_in_descendants( - &self, - name: &str, - ancestor: ScopeId, - ) -> Option<&BindingData> { - let mut descendants = std::collections::HashSet::new(); - descendants.insert(ancestor); - let mut changed = true; - while changed { - changed = false; - for (i, scope) in self.scopes.iter().enumerate() { - let sid = ScopeId(i as u32); - if let Some(parent) = scope.parent { - if descendants.contains(&parent) && !descendants.contains(&sid) { - descendants.insert(sid); - changed = true; - } - } - } - } - for sid in &descendants { - let scope = &self.scopes[sid.0 as usize]; - if let Some(id) = scope.bindings.get(name) { - return Some(&self.bindings[id.0 as usize]); - } - } - None - } - - /// Like find_binding_in_descendants, but returns the BindingData with its id - /// for use in resolve_binding. - pub fn find_binding_id_in_descendants( - &self, - name: &str, - ancestor: ScopeId, - ) -> Option<(BindingId, &BindingData)> { - let mut descendants = std::collections::HashSet::new(); - descendants.insert(ancestor); - let mut changed = true; - while changed { - changed = false; - for (i, scope) in self.scopes.iter().enumerate() { - let sid = ScopeId(i as u32); - if let Some(parent) = scope.parent { - if descendants.contains(&parent) && !descendants.contains(&sid) { - descendants.insert(sid); - changed = true; - } - } - } - } - for sid in &descendants { - let scope = &self.scopes[sid.0 as usize]; - if let Some(&id) = scope.bindings.get(name) { - return Some((id, &self.bindings[id.0 as usize])); - } - } - None - } - - /// Get all bindings declared in a scope (for hoisting iteration). - pub fn scope_bindings(&self, scope_id: ScopeId) -> impl Iterator<Item = &BindingData> { - self.scopes[scope_id.0 as usize] - .bindings - .values() - .map(|id| &self.bindings[id.0 as usize]) - } - - /// Get bindings from a scope AND its direct child block scopes. - /// In Babel, a function body's BlockStatement shares the function's scope, - /// so all bindings (var, const, let) appear in one scope. But our scope - /// extraction may split them: function scope has params/var, a child block - /// scope has const/let. This method merges them to match TS behavior. - pub fn scope_bindings_with_children( - &self, - scope_id: ScopeId, - ) -> impl Iterator<Item = &BindingData> { - let mut binding_ids: Vec<BindingId> = Vec::new(); - // Add bindings from the scope itself - for &id in self.scopes[scope_id.0 as usize].bindings.values() { - binding_ids.push(id); - } - // Add bindings from direct child block scopes - for scope in self.scopes.iter() { - if scope.parent == Some(scope_id) && matches!(scope.kind, ScopeKind::Block) { - for &id in scope.bindings.values() { - binding_ids.push(id); - } - } - } - binding_ids - .into_iter() - .map(|id| &self.bindings[id.0 as usize]) - } - - /// Find a block scope by matching variable names declared within it. - /// Used for synthetic blocks (position 0) where position-based lookup fails. - /// The `is_claimed` predicate allows skipping scopes already matched to other blocks. - pub fn find_block_scope_by_bindings( - &self, - names: &[&str], - ancestor: ScopeId, - is_claimed: impl Fn(ScopeId) -> bool, - ) -> Option<ScopeId> { - let mut descendants = std::collections::HashSet::new(); - descendants.insert(ancestor); - let mut changed = true; - while changed { - changed = false; - for (i, scope) in self.scopes.iter().enumerate() { - let sid = ScopeId(i as u32); - if let Some(parent) = scope.parent { - if descendants.contains(&parent) && !descendants.contains(&sid) { - descendants.insert(sid); - changed = true; - } - } - } - } - for sid in &descendants { - let scope = &self.scopes[sid.0 as usize]; - if matches!(scope.kind, ScopeKind::Function) { - continue; - } - if is_claimed(*sid) { - continue; - } - let all_match = names.iter().all(|name| scope.bindings.contains_key(*name)); - if all_match { - return Some(*sid); - } - } - None - } -} diff --git a/compiler/crates/react_compiler_ast/src/statements.rs b/compiler/crates/react_compiler_ast/src/statements.rs deleted file mode 100644 index 5fa17e98640d..000000000000 --- a/compiler/crates/react_compiler_ast/src/statements.rs +++ /dev/null @@ -1,753 +0,0 @@ -use serde::Deserialize; -use serde::Deserializer; -use serde::Serialize; -use serde::Serializer; -use serde::de::Error as _; - -use crate::common::BaseNode; -use crate::common::RawNode; -use crate::expressions::Expression; -use crate::expressions::Identifier; -use crate::patterns::PatternLike; - -fn is_false(v: &bool) -> bool { - !v -} - -#[derive(Debug, Clone, Serialize)] -#[serde(tag = "type")] -pub enum Statement { - // Statements - BlockStatement(BlockStatement), - ReturnStatement(ReturnStatement), - IfStatement(IfStatement), - ForStatement(ForStatement), - WhileStatement(WhileStatement), - DoWhileStatement(DoWhileStatement), - ForInStatement(ForInStatement), - ForOfStatement(ForOfStatement), - SwitchStatement(SwitchStatement), - ThrowStatement(ThrowStatement), - TryStatement(TryStatement), - BreakStatement(BreakStatement), - ContinueStatement(ContinueStatement), - LabeledStatement(LabeledStatement), - ExpressionStatement(ExpressionStatement), - EmptyStatement(EmptyStatement), - DebuggerStatement(DebuggerStatement), - WithStatement(WithStatement), - // Declarations are also statements - VariableDeclaration(VariableDeclaration), - FunctionDeclaration(FunctionDeclaration), - ClassDeclaration(ClassDeclaration), - // Import/export declarations - ImportDeclaration(crate::declarations::ImportDeclaration), - ExportNamedDeclaration(crate::declarations::ExportNamedDeclaration), - ExportDefaultDeclaration(crate::declarations::ExportDefaultDeclaration), - ExportAllDeclaration(crate::declarations::ExportAllDeclaration), - // TypeScript declarations - TSTypeAliasDeclaration(crate::declarations::TSTypeAliasDeclaration), - TSInterfaceDeclaration(crate::declarations::TSInterfaceDeclaration), - TSEnumDeclaration(crate::declarations::TSEnumDeclaration), - TSModuleDeclaration(crate::declarations::TSModuleDeclaration), - TSDeclareFunction(crate::declarations::TSDeclareFunction), - // Flow declarations - TypeAlias(crate::declarations::TypeAlias), - OpaqueType(crate::declarations::OpaqueType), - InterfaceDeclaration(crate::declarations::InterfaceDeclaration), - DeclareVariable(crate::declarations::DeclareVariable), - DeclareFunction(crate::declarations::DeclareFunction), - DeclareClass(crate::declarations::DeclareClass), - DeclareModule(crate::declarations::DeclareModule), - DeclareModuleExports(crate::declarations::DeclareModuleExports), - DeclareExportDeclaration(crate::declarations::DeclareExportDeclaration), - DeclareExportAllDeclaration(crate::declarations::DeclareExportAllDeclaration), - DeclareInterface(crate::declarations::DeclareInterface), - DeclareTypeAlias(crate::declarations::DeclareTypeAlias), - DeclareOpaqueType(crate::declarations::DeclareOpaqueType), - EnumDeclaration(crate::declarations::EnumDeclaration), - /// Catch-all for statement `type`s the typed AST does not model, e.g. the - /// TypeScript module-interop statements `import x = require(...)`, - /// `export = x`, and `export as namespace X`. Carries the complete raw - /// Babel node so the Babel path can preserve unmodeled top-level - /// statements verbatim instead of failing the whole file. - /// - /// Deserialization dispatches through [`KnownStatement`]: a modeled `type` - /// whose body is malformed errors with the typed variant's precise message - /// rather than degrading to `Unknown`. Adding a variant to this enum - /// requires adding it to the `known_statements!` list below, which is the - /// single source for the dispatch enum, its `From` mapping, and - /// [`KNOWN_STATEMENT_TYPES`]. A variant added here but not there degrades - /// to `Unknown` silently; that is the one drift case structure cannot - /// catch. - #[serde(untagged)] - Unknown(UnknownStatement), -} - -// NOTE: `Deserialize` for `Statement` is hand-written below; the -// `#[serde(tag = "type")]` and `#[serde(untagged)]` attributes on the enum -// configure only the derived `Serialize`. - -#[derive(Debug, Clone)] -pub struct UnknownStatement { - raw: RawNode, - base: BaseNode, -} - -impl UnknownStatement { - pub fn from_raw(raw: RawNode) -> Result<Self, String> { - match raw.type_name() { - Some(_) => { - // Parsing into BaseNode reads only the fields BaseNode declares, - // not the whole (arbitrarily large) unknown subtree. - let base = crate::common::from_json_str_unbounded::<BaseNode>(raw.get()) - .map_err(|err| format!("failed to read unknown statement base: {err}"))?; - Ok(Self { raw, base }) - } - None => Err("unknown statement is missing a string `type` field".to_string()), - } - } - - /// The node's `type` discriminant, read from the captured [`BaseNode`]. - /// Falls back to `"Unknown"` rather than panicking if the raw node was - /// mutated out from under it. - pub fn node_type(&self) -> &str { - self.base.node_type.as_deref().unwrap_or("Unknown") - } - - pub fn raw(&self) -> &RawNode { - &self.raw - } - - /// Mutate the raw node, then refresh the cached [`BaseNode`] so `base()` - /// and `node_type()` cannot drift from `raw`. Mutations that remove the - /// string `type` field are rejected and rolled back. - pub fn with_raw_mut<R>(&mut self, f: impl FnOnce(&mut RawNode) -> R) -> Result<R, String> { - let saved = self.raw.clone(); - let result = f(&mut self.raw); - if self.raw.type_name().is_none() { - self.raw = saved; - return Err("unknown statement mutation removed the string `type` field".to_string()); - } - match crate::common::from_json_str_unbounded::<BaseNode>(self.raw.get()) { - Ok(base) => { - self.base = base; - Ok(result) - } - Err(err) => { - self.raw = saved; - Err(format!("failed to refresh unknown statement base: {err}")) - } - } - } - - pub fn base(&self) -> &BaseNode { - &self.base - } -} - -impl Serialize for UnknownStatement { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - self.raw.serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for UnknownStatement { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - let raw = RawNode::deserialize(deserializer)?; - Self::from_raw(raw).map_err(D::Error::custom) - } -} - -impl<'de> Deserialize<'de> for Statement { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - let raw = RawNode::deserialize(deserializer)?; - let node_type = raw - .type_name() - .ok_or_else(|| D::Error::custom("statement is missing a string `type` field"))?; - - if is_known_statement_type(&node_type) { - let known: KnownStatement = - crate::common::from_json_str_unbounded(raw.get()).map_err(D::Error::custom)?; - Ok(known.into()) - } else { - UnknownStatement::from_raw(raw) - .map(Statement::Unknown) - .map_err(D::Error::custom) - } - } -} - -/// Single source of truth for the statement `type` tags [`Statement`] models. -/// Generates the [`KnownStatement`] dispatch enum, its `From` mapping, and -/// [`KNOWN_STATEMENT_TYPES`] from one list, so the three cannot drift from -/// each other. A variant added to [`Statement`] but not listed here still -/// degrades to [`Statement::Unknown`] silently; that residual gap is -/// documented on the variant. -macro_rules! known_statements { - ($($variant:ident => $ty:ty),+ $(,)?) => { - const KNOWN_STATEMENT_TYPES: &[&str] = &[$(stringify!($variant)),+]; - - /// Whether `node_type` is a statement `type` tag modeled by - /// [`Statement`], i.e. one that deserializes into a typed variant - /// rather than the [`Statement::Unknown`] catch-all. Callers that - /// need to discriminate statements from other node kinds must use - /// this instead of attempting a `Statement` deserialization: with - /// the tolerant catch-all, that attempt succeeds for any object - /// carrying a string `type` tag. - pub fn is_known_statement_type(node_type: &str) -> bool { - KNOWN_STATEMENT_TYPES.contains(&node_type) - } - - #[derive(Debug, Deserialize)] - #[serde(tag = "type")] - enum KnownStatement { - $($variant($ty),)+ - } - - impl From<KnownStatement> for Statement { - fn from(value: KnownStatement) -> Self { - match value { - $(KnownStatement::$variant(s) => Statement::$variant(s),)+ - } - } - } - }; -} - -known_statements! { - BlockStatement => BlockStatement, - ReturnStatement => ReturnStatement, - IfStatement => IfStatement, - ForStatement => ForStatement, - WhileStatement => WhileStatement, - DoWhileStatement => DoWhileStatement, - ForInStatement => ForInStatement, - ForOfStatement => ForOfStatement, - SwitchStatement => SwitchStatement, - ThrowStatement => ThrowStatement, - TryStatement => TryStatement, - BreakStatement => BreakStatement, - ContinueStatement => ContinueStatement, - LabeledStatement => LabeledStatement, - ExpressionStatement => ExpressionStatement, - EmptyStatement => EmptyStatement, - DebuggerStatement => DebuggerStatement, - WithStatement => WithStatement, - VariableDeclaration => VariableDeclaration, - FunctionDeclaration => FunctionDeclaration, - ClassDeclaration => ClassDeclaration, - ImportDeclaration => crate::declarations::ImportDeclaration, - ExportNamedDeclaration => crate::declarations::ExportNamedDeclaration, - ExportDefaultDeclaration => crate::declarations::ExportDefaultDeclaration, - ExportAllDeclaration => crate::declarations::ExportAllDeclaration, - TSTypeAliasDeclaration => crate::declarations::TSTypeAliasDeclaration, - TSInterfaceDeclaration => crate::declarations::TSInterfaceDeclaration, - TSEnumDeclaration => crate::declarations::TSEnumDeclaration, - TSModuleDeclaration => crate::declarations::TSModuleDeclaration, - TSDeclareFunction => crate::declarations::TSDeclareFunction, - TypeAlias => crate::declarations::TypeAlias, - OpaqueType => crate::declarations::OpaqueType, - InterfaceDeclaration => crate::declarations::InterfaceDeclaration, - DeclareVariable => crate::declarations::DeclareVariable, - DeclareFunction => crate::declarations::DeclareFunction, - DeclareClass => crate::declarations::DeclareClass, - DeclareModule => crate::declarations::DeclareModule, - DeclareModuleExports => crate::declarations::DeclareModuleExports, - DeclareExportDeclaration => crate::declarations::DeclareExportDeclaration, - DeclareExportAllDeclaration => crate::declarations::DeclareExportAllDeclaration, - DeclareInterface => crate::declarations::DeclareInterface, - DeclareTypeAlias => crate::declarations::DeclareTypeAlias, - DeclareOpaqueType => crate::declarations::DeclareOpaqueType, - EnumDeclaration => crate::declarations::EnumDeclaration, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BlockStatement { - #[serde(flatten)] - pub base: BaseNode, - pub body: Vec<Statement>, - #[serde(default)] - pub directives: Vec<Directive>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Directive { - #[serde(flatten)] - pub base: BaseNode, - pub value: DirectiveLiteral, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DirectiveLiteral { - #[serde(flatten)] - pub base: BaseNode, - pub value: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ReturnStatement { - #[serde(flatten)] - pub base: BaseNode, - pub argument: Option<Box<Expression>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ExpressionStatement { - #[serde(flatten)] - pub base: BaseNode, - pub expression: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct IfStatement { - #[serde(flatten)] - pub base: BaseNode, - pub test: Box<Expression>, - pub consequent: Box<Statement>, - pub alternate: Option<Box<Statement>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ForStatement { - #[serde(flatten)] - pub base: BaseNode, - pub init: Option<Box<ForInit>>, - pub test: Option<Box<Expression>>, - pub update: Option<Box<Expression>>, - pub body: Box<Statement>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum ForInit { - VariableDeclaration(VariableDeclaration), - #[serde(untagged)] - Expression(Box<Expression>), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct WhileStatement { - #[serde(flatten)] - pub base: BaseNode, - pub test: Box<Expression>, - pub body: Box<Statement>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DoWhileStatement { - #[serde(flatten)] - pub base: BaseNode, - pub test: Box<Expression>, - pub body: Box<Statement>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ForInStatement { - #[serde(flatten)] - pub base: BaseNode, - pub left: Box<ForInOfLeft>, - pub right: Box<Expression>, - pub body: Box<Statement>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ForOfStatement { - #[serde(flatten)] - pub base: BaseNode, - pub left: Box<ForInOfLeft>, - pub right: Box<Expression>, - pub body: Box<Statement>, - #[serde(default, rename = "await")] - pub is_await: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum ForInOfLeft { - VariableDeclaration(VariableDeclaration), - #[serde(untagged)] - Pattern(Box<PatternLike>), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SwitchStatement { - #[serde(flatten)] - pub base: BaseNode, - pub discriminant: Box<Expression>, - pub cases: Vec<SwitchCase>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SwitchCase { - #[serde(flatten)] - pub base: BaseNode, - pub test: Option<Box<Expression>>, - pub consequent: Vec<Statement>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ThrowStatement { - #[serde(flatten)] - pub base: BaseNode, - pub argument: Box<Expression>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TryStatement { - #[serde(flatten)] - pub base: BaseNode, - pub block: BlockStatement, - pub handler: Option<CatchClause>, - pub finalizer: Option<BlockStatement>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CatchClause { - #[serde(flatten)] - pub base: BaseNode, - pub param: Option<PatternLike>, - pub body: BlockStatement, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BreakStatement { - #[serde(flatten)] - pub base: BaseNode, - pub label: Option<Identifier>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ContinueStatement { - #[serde(flatten)] - pub base: BaseNode, - pub label: Option<Identifier>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LabeledStatement { - #[serde(flatten)] - pub base: BaseNode, - pub label: Identifier, - pub body: Box<Statement>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct EmptyStatement { - #[serde(flatten)] - pub base: BaseNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DebuggerStatement { - #[serde(flatten)] - pub base: BaseNode, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct WithStatement { - #[serde(flatten)] - pub base: BaseNode, - pub object: Box<Expression>, - pub body: Box<Statement>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct VariableDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub declarations: Vec<VariableDeclarator>, - pub kind: VariableDeclarationKind, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub declare: Option<bool>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum VariableDeclarationKind { - Var, - Let, - Const, - Using, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct VariableDeclarator { - #[serde(flatten)] - pub base: BaseNode, - pub id: PatternLike, - pub init: Option<Box<Expression>>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub definite: Option<bool>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FunctionDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub id: Option<Identifier>, - pub params: Vec<PatternLike>, - pub body: BlockStatement, - #[serde(default)] - pub generator: bool, - #[serde(default, rename = "async")] - pub is_async: bool, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub declare: Option<bool>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "returnType" - )] - pub return_type: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "predicate", - deserialize_with = "crate::common::nullable_value" - )] - pub predicate: Option<RawNode>, - /// Set by the Hermes parser for Flow `component Foo(...) { ... }` syntax - #[serde( - default, - skip_serializing_if = "is_false", - rename = "__componentDeclaration" - )] - pub component_declaration: bool, - /// Set by the Hermes parser for Flow `hook useFoo(...) { ... }` syntax - #[serde( - default, - skip_serializing_if = "is_false", - rename = "__hookDeclaration" - )] - pub hook_declaration: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ClassDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub id: Option<Identifier>, - #[serde(rename = "superClass")] - pub super_class: Option<Box<Expression>>, - pub body: crate::expressions::ClassBody, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub decorators: Option<Vec<RawNode>>, - #[serde(default, skip_serializing_if = "Option::is_none", rename = "abstract")] - pub is_abstract: Option<bool>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub declare: Option<bool>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "implements" - )] - pub implements: Option<Vec<RawNode>>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "superTypeParameters" - )] - pub super_type_parameters: Option<RawNode>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - rename = "typeParameters" - )] - pub type_parameters: Option<RawNode>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub mixins: Option<Vec<RawNode>>, -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::Statement; - use crate::common::RawNode; - - #[test] - fn unknown_statement_round_trips_at_program_level() { - let input = json!({ - "type": "File", - "comments": [], - "errors": [], - "program": { - "type": "Program", - "sourceType": "module", - "interpreter": null, - "body": [ - { - "type": "TSImportEqualsDeclaration", - "start": 0, - "end": 39, - "importKind": "value", - "isExport": false, - "id": { "type": "Identifier", "name": "lib" }, - "moduleReference": { - "type": "TSExternalModuleReference", - "expression": { "type": "StringLiteral", "value": "shared-runtime" } - } - } - ], - "directives": [] - } - }); - - let file: crate::File = serde_json::from_value(input.clone()).unwrap(); - - match &file.program.body[0] { - Statement::Unknown(unknown) => { - assert_eq!(unknown.node_type(), "TSImportEqualsDeclaration"); - } - other => panic!("expected Unknown, got {other:?}"), - } - assert_eq!(serde_json::to_value(&file).unwrap(), input); - } - - #[test] - fn unknown_statement_round_trips_inside_function_block() { - let input = json!({ - "type": "FunctionDeclaration", - "id": null, - "generator": false, - "async": false, - "params": [], - "body": { - "type": "BlockStatement", - "body": [ - { - "type": "TSExportAssignment", - "expression": { "type": "Identifier", "name": "x" } - } - ], - "directives": [] - } - }); - - let stmt: Statement = serde_json::from_value(input.clone()).unwrap(); - let Statement::FunctionDeclaration(function) = &stmt else { - panic!("expected function declaration, got {stmt:?}"); - }; - assert!(matches!(function.body.body[0], Statement::Unknown(_))); - assert_eq!(serde_json::to_value(&stmt).unwrap(), input); - } - - /// The public discrimination helper mirrors the deserializer's dispatch: - /// exactly the macro-listed statement tags are "known". - #[test] - fn is_known_statement_type_matches_macro_list() { - assert!(super::is_known_statement_type("IfStatement")); - assert!(super::is_known_statement_type("VariableDeclaration")); - assert!(!super::is_known_statement_type("CallExpression")); - assert!(!super::is_known_statement_type("TSImportEqualsDeclaration")); - } - - #[test] - fn known_statement_type_uses_typed_variant() { - let stmt: Statement = serde_json::from_value(json!({ - "type": "EmptyStatement" - })) - .unwrap(); - - assert!(matches!(stmt, Statement::EmptyStatement(_))); - } - - #[test] - fn malformed_known_statement_type_errors() { - let err = serde_json::from_value::<Statement>(json!({ - "type": "IfStatement", - "consequent": { - "type": "EmptyStatement" - } - })) - .unwrap_err(); - - assert!( - err.to_string().contains("missing field `test`"), - "unexpected error: {err}" - ); - } - - #[test] - fn statement_without_type_field_errors() { - let err = serde_json::from_value::<Statement>(json!({ - "start": 0, - "end": 1 - })) - .unwrap_err(); - - assert!( - err.to_string().contains("`type`"), - "unexpected error: {err}" - ); - } - - #[test] - fn non_object_statement_errors() { - let err = serde_json::from_value::<Statement>(json!([1, 2])).unwrap_err(); - assert!( - err.to_string().contains("`type`"), - "unexpected error: {err}" - ); - } - - #[test] - fn non_string_type_field_errors() { - let err = serde_json::from_value::<Statement>(json!({ "type": 7 })).unwrap_err(); - assert!( - err.to_string().contains("`type`"), - "unexpected error: {err}" - ); - } - - /// Mutating the raw node through the scoped mutator refreshes the cached - /// base, and mutations that strip `type` are rejected. - #[test] - fn with_raw_mut_refreshes_base_and_guards_type() { - let raw = json!({ - "type": "TSExportAssignment", - "start": 5, - "expression": { "type": "Identifier", "name": "x" } - }); - let Statement::Unknown(mut unknown) = serde_json::from_value(raw).unwrap() else { - panic!("expected Unknown"); - }; - - unknown - .with_raw_mut(|v| { - let mut parsed = v.parse_value(); - parsed["start"] = json!(9); - parsed["expression"]["name"] = json!("y"); - *v = RawNode::from_value(&parsed); - }) - .unwrap(); - assert_eq!(unknown.base().start, Some(9)); - assert_eq!( - unknown.raw().parse_value()["expression"]["name"], - json!("y") - ); - - let err = unknown.with_raw_mut(|v| { - let mut parsed = v.parse_value(); - parsed.as_object_mut().unwrap().remove("type"); - *v = RawNode::from_value(&parsed); - }); - assert!(err.is_err(), "type removal must be rejected"); - } -} diff --git a/compiler/crates/react_compiler_ast/src/visitor.rs b/compiler/crates/react_compiler_ast/src/visitor.rs deleted file mode 100644 index ed0b7dd5c184..000000000000 --- a/compiler/crates/react_compiler_ast/src/visitor.rs +++ /dev/null @@ -1,1488 +0,0 @@ -//! AST visitor with automatic scope tracking. -//! -//! Provides a [`Visitor`] trait with enter/leave hooks for specific node types, -//! and an [`AstWalker`] that traverses the AST while tracking the active scope -//! via the scope tree's `node_to_scope` map. - -use crate::Program; -use crate::declarations::*; -use crate::expressions::*; -use crate::jsx::*; -use crate::patterns::*; -use crate::scope::ScopeId; -use crate::scope::ScopeInfo; -use crate::statements::*; - -/// Trait for visiting Babel AST nodes. All methods default to no-ops. -/// Override specific methods to intercept nodes of interest. -/// -/// The `'ast` lifetime ties visitor hooks to the AST being walked, allowing -/// visitors to store references into the AST (e.g., for deferred processing). -/// -/// The `scope_stack` parameter provides the current scope context during traversal. -/// The active scope is `scope_stack.last()`. -pub trait Visitor<'ast> { - /// Controls whether the walker recurses into function/arrow/method bodies. - /// Returns `true` by default. Override to `false` to skip function bodies - /// (similar to Babel's `path.skip()` in traverse visitors). - /// - /// When `false`, the walker still calls `enter_*` / `leave_*` for functions - /// but does not walk their params or body. - fn traverse_function_bodies(&self) -> bool { - true - } - - fn enter_function_declaration( - &mut self, - _node: &'ast FunctionDeclaration, - _scope_stack: &[ScopeId], - ) { - } - fn leave_function_declaration( - &mut self, - _node: &'ast FunctionDeclaration, - _scope_stack: &[ScopeId], - ) { - } - fn enter_function_expression( - &mut self, - _node: &'ast FunctionExpression, - _scope_stack: &[ScopeId], - ) { - } - fn leave_function_expression( - &mut self, - _node: &'ast FunctionExpression, - _scope_stack: &[ScopeId], - ) { - } - fn enter_arrow_function_expression( - &mut self, - _node: &'ast ArrowFunctionExpression, - _scope_stack: &[ScopeId], - ) { - } - fn leave_arrow_function_expression( - &mut self, - _node: &'ast ArrowFunctionExpression, - _scope_stack: &[ScopeId], - ) { - } - fn enter_class_declaration( - &mut self, - _node: &'ast crate::statements::ClassDeclaration, - _scope_stack: &[ScopeId], - ) { - } - fn enter_class_expression(&mut self, _node: &'ast ClassExpression, _scope_stack: &[ScopeId]) {} - fn enter_object_method(&mut self, _node: &'ast ObjectMethod, _scope_stack: &[ScopeId]) {} - fn leave_object_method(&mut self, _node: &'ast ObjectMethod, _scope_stack: &[ScopeId]) {} - fn enter_assignment_expression( - &mut self, - _node: &'ast AssignmentExpression, - _scope_stack: &[ScopeId], - ) { - } - fn enter_update_expression(&mut self, _node: &'ast UpdateExpression, _scope_stack: &[ScopeId]) { - } - fn enter_identifier(&mut self, _node: &'ast Identifier, _scope_stack: &[ScopeId]) {} - fn enter_jsx_identifier(&mut self, _node: &'ast JSXIdentifier, _scope_stack: &[ScopeId]) {} - fn enter_jsx_opening_element( - &mut self, - _node: &'ast JSXOpeningElement, - _scope_stack: &[ScopeId], - ) { - } - fn leave_jsx_opening_element( - &mut self, - _node: &'ast JSXOpeningElement, - _scope_stack: &[ScopeId], - ) { - } - - fn enter_variable_declarator( - &mut self, - _node: &'ast VariableDeclarator, - _scope_stack: &[ScopeId], - ) { - } - fn leave_variable_declarator( - &mut self, - _node: &'ast VariableDeclarator, - _scope_stack: &[ScopeId], - ) { - } - - fn enter_call_expression(&mut self, _node: &'ast CallExpression, _scope_stack: &[ScopeId]) {} - fn leave_call_expression(&mut self, _node: &'ast CallExpression, _scope_stack: &[ScopeId]) {} - - /// Called when the walker enters a loop expression context (while.test, - /// do-while.test, for-in.right, for-of.right). Functions found in these - /// positions are treated as non-program-scope by Babel, even though the - /// walker doesn't push a scope for them. - fn enter_loop_expression(&mut self) {} - fn leave_loop_expression(&mut self) {} -} - -/// Walks the AST while tracking scope context via `node_to_scope`. -pub struct AstWalker<'a> { - scope_info: &'a ScopeInfo, - scope_stack: Vec<ScopeId>, - /// Depth counter for loop/iteration expression positions (while.test, - /// do-while.test, for-in.right, for-of.right). These positions are - /// NOT inside a scope in the walker's model, but Babel's scope analysis - /// treats them as non-program-scope. Visitors can check this via - /// `in_loop_expression_depth()` to implement Babel-compatible scope checks. - loop_expression_depth: usize, -} - -impl<'a> AstWalker<'a> { - pub fn new(scope_info: &'a ScopeInfo) -> Self { - AstWalker { - scope_info, - scope_stack: Vec::new(), - loop_expression_depth: 0, - } - } - - /// Create a walker with an initial scope already on the stack. - pub fn with_initial_scope(scope_info: &'a ScopeInfo, initial_scope: ScopeId) -> Self { - AstWalker { - scope_info, - scope_stack: vec![initial_scope], - loop_expression_depth: 0, - } - } - - pub fn scope_stack(&self) -> &[ScopeId] { - &self.scope_stack - } - - /// Returns the current loop-expression depth. Non-zero when the walker is - /// inside a loop's test/right expression (while.test, do-while.test, - /// for-in.right, for-of.right). Visitors can use this to implement - /// Babel-compatible scope checks in 'all' compilation mode. - pub fn loop_expression_depth(&self) -> usize { - self.loop_expression_depth - } - - /// Try to push a scope for a node. Returns true if a scope was pushed. - fn try_push_scope(&mut self, _start: Option<u32>, node_id: Option<u32>) -> bool { - let scope = self.scope_info.resolve_scope_for_node(node_id); - if let Some(scope_id) = scope { - self.scope_stack.push(scope_id); - return true; - } - false - } - - // ---- Public walk methods ---- - - pub fn walk_program<'ast>(&mut self, v: &mut impl Visitor<'ast>, node: &'ast Program) { - let pushed = self.try_push_scope(node.base.start, node.base.node_id); - for stmt in &node.body { - self.walk_statement(v, stmt); - } - if pushed { - self.scope_stack.pop(); - } - } - - pub fn walk_block_statement<'ast>( - &mut self, - v: &mut impl Visitor<'ast>, - node: &'ast BlockStatement, - ) { - let pushed = self.try_push_scope(node.base.start, node.base.node_id); - for stmt in &node.body { - self.walk_statement(v, stmt); - } - if pushed { - self.scope_stack.pop(); - } - } - - pub fn walk_statement<'ast>(&mut self, v: &mut impl Visitor<'ast>, stmt: &'ast Statement) { - match stmt { - Statement::BlockStatement(node) => self.walk_block_statement(v, node), - Statement::ReturnStatement(node) => { - if let Some(arg) = &node.argument { - self.walk_expression(v, arg); - } - } - Statement::ExpressionStatement(node) => { - self.walk_expression(v, &node.expression); - } - Statement::IfStatement(node) => { - self.walk_expression(v, &node.test); - self.walk_statement(v, &node.consequent); - if let Some(alt) = &node.alternate { - self.walk_statement(v, alt); - } - } - Statement::ForStatement(node) => { - let pushed = self.try_push_scope(node.base.start, node.base.node_id); - if let Some(init) = &node.init { - match init.as_ref() { - ForInit::VariableDeclaration(decl) => { - self.walk_variable_declaration(v, decl) - } - ForInit::Expression(expr) => self.walk_expression(v, expr), - } - } - if let Some(test) = &node.test { - self.walk_expression(v, test); - } - if let Some(update) = &node.update { - self.walk_expression(v, update); - } - self.walk_statement(v, &node.body); - if pushed { - self.scope_stack.pop(); - } - } - Statement::WhileStatement(node) => { - self.loop_expression_depth += 1; - v.enter_loop_expression(); - self.walk_expression(v, &node.test); - v.leave_loop_expression(); - self.loop_expression_depth -= 1; - self.walk_statement(v, &node.body); - } - Statement::DoWhileStatement(node) => { - self.walk_statement(v, &node.body); - self.loop_expression_depth += 1; - v.enter_loop_expression(); - self.walk_expression(v, &node.test); - v.leave_loop_expression(); - self.loop_expression_depth -= 1; - } - Statement::ForInStatement(node) => { - let pushed = self.try_push_scope(node.base.start, node.base.node_id); - self.walk_for_in_of_left(v, &node.left); - self.loop_expression_depth += 1; - v.enter_loop_expression(); - self.walk_expression(v, &node.right); - v.leave_loop_expression(); - self.loop_expression_depth -= 1; - self.walk_statement(v, &node.body); - if pushed { - self.scope_stack.pop(); - } - } - Statement::ForOfStatement(node) => { - let pushed = self.try_push_scope(node.base.start, node.base.node_id); - self.walk_for_in_of_left(v, &node.left); - self.loop_expression_depth += 1; - v.enter_loop_expression(); - self.walk_expression(v, &node.right); - v.leave_loop_expression(); - self.loop_expression_depth -= 1; - self.walk_statement(v, &node.body); - if pushed { - self.scope_stack.pop(); - } - } - Statement::SwitchStatement(node) => { - let pushed = self.try_push_scope(node.base.start, node.base.node_id); - self.walk_expression(v, &node.discriminant); - for case in &node.cases { - if let Some(test) = &case.test { - self.walk_expression(v, test); - } - for consequent in &case.consequent { - self.walk_statement(v, consequent); - } - } - if pushed { - self.scope_stack.pop(); - } - } - Statement::ThrowStatement(node) => { - self.walk_expression(v, &node.argument); - } - Statement::TryStatement(node) => { - self.walk_block_statement(v, &node.block); - if let Some(handler) = &node.handler { - let pushed = self.try_push_scope(handler.base.start, handler.base.node_id); - if let Some(param) = &handler.param { - self.walk_pattern(v, param); - } - self.walk_block_statement(v, &handler.body); - if pushed { - self.scope_stack.pop(); - } - } - if let Some(finalizer) = &node.finalizer { - self.walk_block_statement(v, finalizer); - } - } - Statement::LabeledStatement(node) => { - self.walk_statement(v, &node.body); - } - Statement::VariableDeclaration(node) => { - self.walk_variable_declaration(v, node); - } - Statement::FunctionDeclaration(node) => { - self.walk_function_declaration_inner(v, node); - } - Statement::ClassDeclaration(node) => { - // Call the visitor hook so consumers can index the class name, - // but skip walking the class body (no compilable functions inside) - v.enter_class_declaration(node, &self.scope_stack); - } - Statement::WithStatement(node) => { - self.walk_expression(v, &node.object); - self.walk_statement(v, &node.body); - } - Statement::ExportNamedDeclaration(node) => { - if let Some(decl) = &node.declaration { - self.walk_declaration(v, decl); - } - } - Statement::ExportDefaultDeclaration(node) => { - self.walk_export_default_decl(v, &node.declaration); - } - // No runtime expressions to traverse - Statement::BreakStatement(_) - | Statement::ContinueStatement(_) - | Statement::EmptyStatement(_) - | Statement::DebuggerStatement(_) - | Statement::ImportDeclaration(_) - | Statement::ExportAllDeclaration(_) - | Statement::TSTypeAliasDeclaration(_) - | Statement::TSInterfaceDeclaration(_) - | Statement::TSEnumDeclaration(_) - | Statement::TSModuleDeclaration(_) - | Statement::TSDeclareFunction(_) - | Statement::TypeAlias(_) - | Statement::OpaqueType(_) - | Statement::InterfaceDeclaration(_) - | Statement::DeclareVariable(_) - | Statement::DeclareFunction(_) - | Statement::DeclareClass(_) - | Statement::DeclareModule(_) - | Statement::DeclareModuleExports(_) - | Statement::DeclareExportDeclaration(_) - | Statement::DeclareExportAllDeclaration(_) - | Statement::DeclareInterface(_) - | Statement::DeclareTypeAlias(_) - | Statement::DeclareOpaqueType(_) - | Statement::EnumDeclaration(_) - // Unmodeled raw node: opaque, no compilable children to traverse. - | Statement::Unknown(_) => {} - } - } - - pub fn walk_expression<'ast>(&mut self, v: &mut impl Visitor<'ast>, expr: &'ast Expression) { - match expr { - Expression::Identifier(node) => { - v.enter_identifier(node, &self.scope_stack); - } - Expression::CallExpression(node) => { - v.enter_call_expression(node, &self.scope_stack); - self.walk_expression(v, &node.callee); - for arg in &node.arguments { - self.walk_expression(v, arg); - } - v.leave_call_expression(node, &self.scope_stack); - } - Expression::MemberExpression(node) => { - self.walk_expression(v, &node.object); - if node.computed { - self.walk_expression(v, &node.property); - } - } - Expression::OptionalCallExpression(node) => { - self.walk_expression(v, &node.callee); - for arg in &node.arguments { - self.walk_expression(v, arg); - } - } - Expression::OptionalMemberExpression(node) => { - self.walk_expression(v, &node.object); - if node.computed { - self.walk_expression(v, &node.property); - } - } - Expression::BinaryExpression(node) => { - self.walk_expression(v, &node.left); - self.walk_expression(v, &node.right); - } - Expression::LogicalExpression(node) => { - self.walk_expression(v, &node.left); - self.walk_expression(v, &node.right); - } - Expression::UnaryExpression(node) => { - self.walk_expression(v, &node.argument); - } - Expression::UpdateExpression(node) => { - v.enter_update_expression(node, &self.scope_stack); - self.walk_expression(v, &node.argument); - } - Expression::ConditionalExpression(node) => { - self.walk_expression(v, &node.test); - self.walk_expression(v, &node.consequent); - self.walk_expression(v, &node.alternate); - } - Expression::AssignmentExpression(node) => { - v.enter_assignment_expression(node, &self.scope_stack); - self.walk_pattern(v, &node.left); - self.walk_expression(v, &node.right); - } - Expression::SequenceExpression(node) => { - for expr in &node.expressions { - self.walk_expression(v, expr); - } - } - Expression::ArrowFunctionExpression(node) => { - let pushed = self.try_push_scope(node.base.start, node.base.node_id); - v.enter_arrow_function_expression(node, &self.scope_stack); - if v.traverse_function_bodies() { - for param in &node.params { - self.walk_pattern(v, param); - } - match node.body.as_ref() { - ArrowFunctionBody::BlockStatement(block) => { - self.walk_block_statement(v, block); - } - ArrowFunctionBody::Expression(expr) => { - self.walk_expression(v, expr); - } - } - } - v.leave_arrow_function_expression(node, &self.scope_stack); - if pushed { - self.scope_stack.pop(); - } - } - Expression::FunctionExpression(node) => { - let pushed = self.try_push_scope(node.base.start, node.base.node_id); - v.enter_function_expression(node, &self.scope_stack); - if v.traverse_function_bodies() { - for param in &node.params { - self.walk_pattern(v, param); - } - self.walk_block_statement(v, &node.body); - } - v.leave_function_expression(node, &self.scope_stack); - if pushed { - self.scope_stack.pop(); - } - } - Expression::ObjectExpression(node) => { - for prop in &node.properties { - self.walk_object_expression_property(v, prop); - } - } - Expression::ArrayExpression(node) => { - for element in &node.elements { - if let Some(el) = element { - self.walk_expression(v, el); - } - } - } - Expression::NewExpression(node) => { - self.walk_expression(v, &node.callee); - for arg in &node.arguments { - self.walk_expression(v, arg); - } - } - Expression::TemplateLiteral(node) => { - for expr in &node.expressions { - self.walk_expression(v, expr); - } - } - Expression::TaggedTemplateExpression(node) => { - self.walk_expression(v, &node.tag); - for expr in &node.quasi.expressions { - self.walk_expression(v, expr); - } - } - Expression::AwaitExpression(node) => { - self.walk_expression(v, &node.argument); - } - Expression::YieldExpression(node) => { - if let Some(arg) = &node.argument { - self.walk_expression(v, arg); - } - } - Expression::SpreadElement(node) => { - self.walk_expression(v, &node.argument); - } - Expression::ParenthesizedExpression(node) => { - self.walk_expression(v, &node.expression); - } - Expression::AssignmentPattern(node) => { - self.walk_pattern(v, &node.left); - self.walk_expression(v, &node.right); - } - Expression::ClassExpression(node) => { - // Call the visitor hook so consumers can index the class name, - // but skip walking the class body - v.enter_class_expression(node, &self.scope_stack); - } - // JSX - Expression::JSXElement(node) => self.walk_jsx_element(v, node), - Expression::JSXFragment(node) => self.walk_jsx_fragment(v, node), - // TS/Flow wrappers - traverse inner expression - Expression::TSAsExpression(node) => self.walk_expression(v, &node.expression), - Expression::TSSatisfiesExpression(node) => self.walk_expression(v, &node.expression), - Expression::TSNonNullExpression(node) => self.walk_expression(v, &node.expression), - Expression::TSTypeAssertion(node) => self.walk_expression(v, &node.expression), - Expression::TSInstantiationExpression(node) => { - self.walk_expression(v, &node.expression) - } - Expression::TypeCastExpression(node) => self.walk_expression(v, &node.expression), - // Leaf nodes - Expression::StringLiteral(_) - | Expression::NumericLiteral(_) - | Expression::BooleanLiteral(_) - | Expression::NullLiteral(_) - | Expression::BigIntLiteral(_) - | Expression::RegExpLiteral(_) - | Expression::MetaProperty(_) - | Expression::PrivateName(_) - | Expression::Super(_) - | Expression::Import(_) - | Expression::ThisExpression(_) => {} - } - } - - pub fn walk_pattern<'ast>(&mut self, v: &mut impl Visitor<'ast>, pat: &'ast PatternLike) { - match pat { - PatternLike::Identifier(node) => { - v.enter_identifier(node, &self.scope_stack); - } - PatternLike::ObjectPattern(node) => { - for prop in &node.properties { - match prop { - ObjectPatternProperty::ObjectProperty(p) => { - if p.computed { - self.walk_expression(v, &p.key); - } - self.walk_pattern(v, &p.value); - } - ObjectPatternProperty::RestElement(p) => { - self.walk_pattern(v, &p.argument); - } - } - } - } - PatternLike::ArrayPattern(node) => { - for element in &node.elements { - if let Some(el) = element { - self.walk_pattern(v, el); - } - } - } - PatternLike::AssignmentPattern(node) => { - self.walk_pattern(v, &node.left); - self.walk_expression(v, &node.right); - } - PatternLike::RestElement(node) => { - self.walk_pattern(v, &node.argument); - } - PatternLike::MemberExpression(node) => { - self.walk_expression(v, &node.object); - if node.computed { - self.walk_expression(v, &node.property); - } - } - PatternLike::TSAsExpression(node) => self.walk_expression(v, &node.expression), - PatternLike::TSSatisfiesExpression(node) => self.walk_expression(v, &node.expression), - PatternLike::TSNonNullExpression(node) => self.walk_expression(v, &node.expression), - PatternLike::TSTypeAssertion(node) => self.walk_expression(v, &node.expression), - PatternLike::TypeCastExpression(node) => self.walk_expression(v, &node.expression), - } - } - - // ---- Private helper walk methods ---- - - fn walk_for_in_of_left<'ast>(&mut self, v: &mut impl Visitor<'ast>, left: &'ast ForInOfLeft) { - match left { - ForInOfLeft::VariableDeclaration(decl) => self.walk_variable_declaration(v, decl), - ForInOfLeft::Pattern(pat) => self.walk_pattern(v, pat), - } - } - - fn walk_variable_declaration<'ast>( - &mut self, - v: &mut impl Visitor<'ast>, - decl: &'ast VariableDeclaration, - ) { - for declarator in &decl.declarations { - v.enter_variable_declarator(declarator, &self.scope_stack); - self.walk_pattern(v, &declarator.id); - if let Some(init) = &declarator.init { - self.walk_expression(v, init); - } - v.leave_variable_declarator(declarator, &self.scope_stack); - } - } - - fn walk_function_declaration_inner<'ast>( - &mut self, - v: &mut impl Visitor<'ast>, - node: &'ast FunctionDeclaration, - ) { - let pushed = self.try_push_scope(node.base.start, node.base.node_id); - v.enter_function_declaration(node, &self.scope_stack); - if v.traverse_function_bodies() { - for param in &node.params { - self.walk_pattern(v, param); - } - self.walk_block_statement(v, &node.body); - } - v.leave_function_declaration(node, &self.scope_stack); - if pushed { - self.scope_stack.pop(); - } - } - - fn walk_object_expression_property<'ast>( - &mut self, - v: &mut impl Visitor<'ast>, - prop: &'ast ObjectExpressionProperty, - ) { - match prop { - ObjectExpressionProperty::ObjectProperty(p) => { - if p.computed { - self.walk_expression(v, &p.key); - } - self.walk_expression(v, &p.value); - } - ObjectExpressionProperty::ObjectMethod(node) => { - let pushed = self.try_push_scope(node.base.start, node.base.node_id); - v.enter_object_method(node, &self.scope_stack); - if v.traverse_function_bodies() { - if node.computed { - self.walk_expression(v, &node.key); - } - for param in &node.params { - self.walk_pattern(v, param); - } - self.walk_block_statement(v, &node.body); - } - v.leave_object_method(node, &self.scope_stack); - if pushed { - self.scope_stack.pop(); - } - } - ObjectExpressionProperty::SpreadElement(p) => { - self.walk_expression(v, &p.argument); - } - } - } - - fn walk_declaration<'ast>(&mut self, v: &mut impl Visitor<'ast>, decl: &'ast Declaration) { - match decl { - Declaration::FunctionDeclaration(node) => { - self.walk_function_declaration_inner(v, node); - } - Declaration::VariableDeclaration(node) => { - self.walk_variable_declaration(v, node); - } - // TS/Flow declarations - no runtime expressions - _ => {} - } - } - - fn walk_export_default_decl<'ast>( - &mut self, - v: &mut impl Visitor<'ast>, - decl: &'ast ExportDefaultDecl, - ) { - match decl { - ExportDefaultDecl::FunctionDeclaration(node) => { - self.walk_function_declaration_inner(v, node); - } - ExportDefaultDecl::ClassDeclaration(node) => { - // Call the visitor hook, but skip the class body - v.enter_class_declaration(node, &self.scope_stack); - } - ExportDefaultDecl::EnumDeclaration(_) => { - // Flow enum declarations are opaque — no visitor hooks needed - } - ExportDefaultDecl::Expression(expr) => { - self.walk_expression(v, expr); - } - } - } - - fn walk_jsx_element<'ast>(&mut self, v: &mut impl Visitor<'ast>, node: &'ast JSXElement) { - v.enter_jsx_opening_element(&node.opening_element, &self.scope_stack); - self.walk_jsx_element_name(v, &node.opening_element.name); - v.leave_jsx_opening_element(&node.opening_element, &self.scope_stack); - for attr in &node.opening_element.attributes { - match attr { - JSXAttributeItem::JSXAttribute(a) => { - if let Some(value) = &a.value { - match value { - JSXAttributeValue::JSXExpressionContainer(c) => { - self.walk_jsx_expr_container(v, c); - } - JSXAttributeValue::JSXElement(el) => { - self.walk_jsx_element(v, el); - } - JSXAttributeValue::JSXFragment(f) => { - self.walk_jsx_fragment(v, f); - } - JSXAttributeValue::StringLiteral(_) => {} - } - } - } - JSXAttributeItem::JSXSpreadAttribute(a) => { - self.walk_expression(v, &a.argument); - } - } - } - for child in &node.children { - self.walk_jsx_child(v, child); - } - } - - fn walk_jsx_fragment<'ast>(&mut self, v: &mut impl Visitor<'ast>, node: &'ast JSXFragment) { - for child in &node.children { - self.walk_jsx_child(v, child); - } - } - - fn walk_jsx_child<'ast>(&mut self, v: &mut impl Visitor<'ast>, child: &'ast JSXChild) { - match child { - JSXChild::JSXElement(el) => self.walk_jsx_element(v, el), - JSXChild::JSXFragment(f) => self.walk_jsx_fragment(v, f), - JSXChild::JSXExpressionContainer(c) => self.walk_jsx_expr_container(v, c), - JSXChild::JSXSpreadChild(s) => self.walk_expression(v, &s.expression), - JSXChild::JSXText(_) => {} - } - } - - fn walk_jsx_expr_container<'ast>( - &mut self, - v: &mut impl Visitor<'ast>, - node: &'ast JSXExpressionContainer, - ) { - match &node.expression { - JSXExpressionContainerExpr::Expression(expr) => self.walk_expression(v, expr), - JSXExpressionContainerExpr::JSXEmptyExpression(_) => {} - } - } - - fn walk_jsx_element_name<'ast>( - &mut self, - v: &mut impl Visitor<'ast>, - name: &'ast JSXElementName, - ) { - match name { - JSXElementName::JSXIdentifier(id) => { - v.enter_jsx_identifier(id, &self.scope_stack); - } - JSXElementName::JSXMemberExpression(expr) => { - self.walk_jsx_member_expression(v, expr); - } - JSXElementName::JSXNamespacedName(_) => {} - } - } - - fn walk_jsx_member_expression<'ast>( - &mut self, - v: &mut impl Visitor<'ast>, - expr: &'ast JSXMemberExpression, - ) { - match &*expr.object { - JSXMemberExprObject::JSXIdentifier(id) => { - v.enter_jsx_identifier(id, &self.scope_stack); - } - JSXMemberExprObject::JSXMemberExpression(inner) => { - self.walk_jsx_member_expression(v, inner); - } - } - v.enter_jsx_identifier(&expr.property, &self.scope_stack); - } -} - -// ============================================================================= -// Mutable visitor -// ============================================================================= - -/// Result from a mutable visitor hook. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum VisitResult { - /// Continue traversal to children. - Continue, - /// Stop traversal immediately. - Stop, -} - -impl VisitResult { - pub fn is_stop(self) -> bool { - self == VisitResult::Stop - } -} - -/// Trait for mutating Babel AST nodes during traversal. -/// -/// Override hooks to intercept and mutate specific node types. -/// Return [`VisitResult::Stop`] from any hook to halt the walk. -/// Hooks are called *before* the walker recurses into children, -/// so returning `Stop` prevents child traversal. -pub trait MutVisitor { - /// Called for every statement before recursing into its children. - fn visit_statement(&mut self, _stmt: &mut Statement) -> VisitResult { - VisitResult::Continue - } - - /// Called for every expression before recursing into its children. - fn visit_expression(&mut self, _expr: &mut Expression) -> VisitResult { - VisitResult::Continue - } - - /// Called for identifiers in expression position. - fn visit_identifier(&mut self, _node: &mut Identifier) -> VisitResult { - VisitResult::Continue - } -} - -/// Walk a program's body mutably, calling visitor hooks for each node. -pub fn walk_program_mut(v: &mut impl MutVisitor, program: &mut Program) -> VisitResult { - for stmt in program.body.iter_mut() { - if walk_statement_mut(v, stmt).is_stop() { - return VisitResult::Stop; - } - } - VisitResult::Continue -} - -/// Walk a single statement mutably, calling visitor hooks and recursing into children. -pub fn walk_statement_mut(v: &mut impl MutVisitor, stmt: &mut Statement) -> VisitResult { - if v.visit_statement(stmt).is_stop() { - return VisitResult::Stop; - } - match stmt { - Statement::BlockStatement(node) => { - for s in node.body.iter_mut() { - if walk_statement_mut(v, s).is_stop() { - return VisitResult::Stop; - } - } - } - Statement::ReturnStatement(node) => { - if let Some(ref mut arg) = node.argument { - if walk_expression_mut(v, arg).is_stop() { - return VisitResult::Stop; - } - } - } - Statement::ExpressionStatement(node) => { - if walk_expression_mut(v, &mut node.expression).is_stop() { - return VisitResult::Stop; - } - } - Statement::IfStatement(node) => { - if walk_expression_mut(v, &mut node.test).is_stop() { - return VisitResult::Stop; - } - if walk_statement_mut(v, &mut node.consequent).is_stop() { - return VisitResult::Stop; - } - if let Some(ref mut alt) = node.alternate { - if walk_statement_mut(v, alt).is_stop() { - return VisitResult::Stop; - } - } - } - Statement::ForStatement(node) => { - if let Some(ref mut init) = node.init { - match init.as_mut() { - ForInit::VariableDeclaration(decl) => { - if walk_variable_declaration_mut(v, decl).is_stop() { - return VisitResult::Stop; - } - } - ForInit::Expression(expr) => { - if walk_expression_mut(v, expr).is_stop() { - return VisitResult::Stop; - } - } - } - } - if let Some(ref mut test) = node.test { - if walk_expression_mut(v, test).is_stop() { - return VisitResult::Stop; - } - } - if let Some(ref mut update) = node.update { - if walk_expression_mut(v, update).is_stop() { - return VisitResult::Stop; - } - } - if walk_statement_mut(v, &mut node.body).is_stop() { - return VisitResult::Stop; - } - } - Statement::WhileStatement(node) => { - if walk_expression_mut(v, &mut node.test).is_stop() { - return VisitResult::Stop; - } - if walk_statement_mut(v, &mut node.body).is_stop() { - return VisitResult::Stop; - } - } - Statement::DoWhileStatement(node) => { - if walk_statement_mut(v, &mut node.body).is_stop() { - return VisitResult::Stop; - } - if walk_expression_mut(v, &mut node.test).is_stop() { - return VisitResult::Stop; - } - } - Statement::ForInStatement(node) => { - if walk_expression_mut(v, &mut node.right).is_stop() { - return VisitResult::Stop; - } - if walk_statement_mut(v, &mut node.body).is_stop() { - return VisitResult::Stop; - } - } - Statement::ForOfStatement(node) => { - if walk_expression_mut(v, &mut node.right).is_stop() { - return VisitResult::Stop; - } - if walk_statement_mut(v, &mut node.body).is_stop() { - return VisitResult::Stop; - } - } - Statement::SwitchStatement(node) => { - if walk_expression_mut(v, &mut node.discriminant).is_stop() { - return VisitResult::Stop; - } - for case in node.cases.iter_mut() { - if let Some(ref mut test) = case.test { - if walk_expression_mut(v, test).is_stop() { - return VisitResult::Stop; - } - } - for s in case.consequent.iter_mut() { - if walk_statement_mut(v, s).is_stop() { - return VisitResult::Stop; - } - } - } - } - Statement::ThrowStatement(node) => { - if walk_expression_mut(v, &mut node.argument).is_stop() { - return VisitResult::Stop; - } - } - Statement::TryStatement(node) => { - for s in node.block.body.iter_mut() { - if walk_statement_mut(v, s).is_stop() { - return VisitResult::Stop; - } - } - if let Some(ref mut handler) = node.handler { - for s in handler.body.body.iter_mut() { - if walk_statement_mut(v, s).is_stop() { - return VisitResult::Stop; - } - } - } - if let Some(ref mut finalizer) = node.finalizer { - for s in finalizer.body.iter_mut() { - if walk_statement_mut(v, s).is_stop() { - return VisitResult::Stop; - } - } - } - } - Statement::LabeledStatement(node) => { - if walk_statement_mut(v, &mut node.body).is_stop() { - return VisitResult::Stop; - } - } - Statement::VariableDeclaration(node) => { - if walk_variable_declaration_mut(v, node).is_stop() { - return VisitResult::Stop; - } - } - Statement::FunctionDeclaration(node) => { - for s in node.body.body.iter_mut() { - if walk_statement_mut(v, s).is_stop() { - return VisitResult::Stop; - } - } - } - Statement::ClassDeclaration(node) => { - if let Some(ref mut sc) = node.super_class { - if walk_expression_mut(v, sc).is_stop() { - return VisitResult::Stop; - } - } - } - Statement::WithStatement(node) => { - if walk_expression_mut(v, &mut node.object).is_stop() { - return VisitResult::Stop; - } - if walk_statement_mut(v, &mut node.body).is_stop() { - return VisitResult::Stop; - } - } - Statement::ExportNamedDeclaration(node) => { - if let Some(ref mut decl) = node.declaration { - if walk_declaration_mut(v, decl).is_stop() { - return VisitResult::Stop; - } - } - } - Statement::ExportDefaultDeclaration(node) => { - if walk_export_default_decl_mut(v, &mut node.declaration).is_stop() { - return VisitResult::Stop; - } - } - // No runtime expressions to traverse - Statement::BreakStatement(_) - | Statement::ContinueStatement(_) - | Statement::EmptyStatement(_) - | Statement::DebuggerStatement(_) - | Statement::ImportDeclaration(_) - | Statement::ExportAllDeclaration(_) - | Statement::TSTypeAliasDeclaration(_) - | Statement::TSInterfaceDeclaration(_) - | Statement::TSEnumDeclaration(_) - | Statement::TSModuleDeclaration(_) - | Statement::TSDeclareFunction(_) - | Statement::TypeAlias(_) - | Statement::OpaqueType(_) - | Statement::InterfaceDeclaration(_) - | Statement::DeclareVariable(_) - | Statement::DeclareFunction(_) - | Statement::DeclareClass(_) - | Statement::DeclareModule(_) - | Statement::DeclareModuleExports(_) - | Statement::DeclareExportDeclaration(_) - | Statement::DeclareExportAllDeclaration(_) - | Statement::DeclareInterface(_) - | Statement::DeclareTypeAlias(_) - | Statement::DeclareOpaqueType(_) - | Statement::EnumDeclaration(_) - // Unmodeled raw node: opaque, no compilable children to traverse. - | Statement::Unknown(_) => {} - } - VisitResult::Continue -} - -/// Walk an expression mutably, calling visitor hooks and recursing into children. -pub fn walk_expression_mut(v: &mut impl MutVisitor, expr: &mut Expression) -> VisitResult { - if v.visit_expression(expr).is_stop() { - return VisitResult::Stop; - } - match expr { - Expression::Identifier(node) => { - if v.visit_identifier(node).is_stop() { - return VisitResult::Stop; - } - } - Expression::CallExpression(node) => { - if walk_expression_mut(v, &mut node.callee).is_stop() { - return VisitResult::Stop; - } - for arg in node.arguments.iter_mut() { - if walk_expression_mut(v, arg).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::MemberExpression(node) => { - if walk_expression_mut(v, &mut node.object).is_stop() { - return VisitResult::Stop; - } - if node.computed { - if walk_expression_mut(v, &mut node.property).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::OptionalCallExpression(node) => { - if walk_expression_mut(v, &mut node.callee).is_stop() { - return VisitResult::Stop; - } - for arg in node.arguments.iter_mut() { - if walk_expression_mut(v, arg).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::OptionalMemberExpression(node) => { - if walk_expression_mut(v, &mut node.object).is_stop() { - return VisitResult::Stop; - } - if node.computed { - if walk_expression_mut(v, &mut node.property).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::BinaryExpression(node) => { - if walk_expression_mut(v, &mut node.left).is_stop() { - return VisitResult::Stop; - } - if walk_expression_mut(v, &mut node.right).is_stop() { - return VisitResult::Stop; - } - } - Expression::LogicalExpression(node) => { - if walk_expression_mut(v, &mut node.left).is_stop() { - return VisitResult::Stop; - } - if walk_expression_mut(v, &mut node.right).is_stop() { - return VisitResult::Stop; - } - } - Expression::UnaryExpression(node) => { - if walk_expression_mut(v, &mut node.argument).is_stop() { - return VisitResult::Stop; - } - } - Expression::UpdateExpression(node) => { - if walk_expression_mut(v, &mut node.argument).is_stop() { - return VisitResult::Stop; - } - } - Expression::ConditionalExpression(node) => { - if walk_expression_mut(v, &mut node.test).is_stop() { - return VisitResult::Stop; - } - if walk_expression_mut(v, &mut node.consequent).is_stop() { - return VisitResult::Stop; - } - if walk_expression_mut(v, &mut node.alternate).is_stop() { - return VisitResult::Stop; - } - } - Expression::AssignmentExpression(node) => { - if walk_expression_mut(v, &mut node.right).is_stop() { - return VisitResult::Stop; - } - } - Expression::SequenceExpression(node) => { - for e in node.expressions.iter_mut() { - if walk_expression_mut(v, e).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::ArrowFunctionExpression(node) => match node.body.as_mut() { - ArrowFunctionBody::BlockStatement(block) => { - for s in block.body.iter_mut() { - if walk_statement_mut(v, s).is_stop() { - return VisitResult::Stop; - } - } - } - ArrowFunctionBody::Expression(e) => { - if walk_expression_mut(v, e).is_stop() { - return VisitResult::Stop; - } - } - }, - Expression::FunctionExpression(node) => { - for s in node.body.body.iter_mut() { - if walk_statement_mut(v, s).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::ObjectExpression(node) => { - for prop in node.properties.iter_mut() { - match prop { - ObjectExpressionProperty::ObjectProperty(p) => { - if p.computed { - if walk_expression_mut(v, &mut p.key).is_stop() { - return VisitResult::Stop; - } - } - if walk_expression_mut(v, &mut p.value).is_stop() { - return VisitResult::Stop; - } - } - ObjectExpressionProperty::ObjectMethod(m) => { - for s in m.body.body.iter_mut() { - if walk_statement_mut(v, s).is_stop() { - return VisitResult::Stop; - } - } - } - ObjectExpressionProperty::SpreadElement(s) => { - if walk_expression_mut(v, &mut s.argument).is_stop() { - return VisitResult::Stop; - } - } - } - } - } - Expression::ArrayExpression(node) => { - for elem in node.elements.iter_mut().flatten() { - if walk_expression_mut(v, elem).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::NewExpression(node) => { - if walk_expression_mut(v, &mut node.callee).is_stop() { - return VisitResult::Stop; - } - for arg in node.arguments.iter_mut() { - if walk_expression_mut(v, arg).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::TemplateLiteral(node) => { - for e in node.expressions.iter_mut() { - if walk_expression_mut(v, e).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::TaggedTemplateExpression(node) => { - if walk_expression_mut(v, &mut node.tag).is_stop() { - return VisitResult::Stop; - } - for e in node.quasi.expressions.iter_mut() { - if walk_expression_mut(v, e).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::AwaitExpression(node) => { - if walk_expression_mut(v, &mut node.argument).is_stop() { - return VisitResult::Stop; - } - } - Expression::YieldExpression(node) => { - if let Some(ref mut arg) = node.argument { - if walk_expression_mut(v, arg).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::SpreadElement(node) => { - if walk_expression_mut(v, &mut node.argument).is_stop() { - return VisitResult::Stop; - } - } - Expression::ParenthesizedExpression(node) => { - if walk_expression_mut(v, &mut node.expression).is_stop() { - return VisitResult::Stop; - } - } - Expression::AssignmentPattern(node) => { - if walk_expression_mut(v, &mut node.right).is_stop() { - return VisitResult::Stop; - } - } - Expression::ClassExpression(node) => { - if let Some(ref mut sc) = node.super_class { - if walk_expression_mut(v, sc).is_stop() { - return VisitResult::Stop; - } - } - } - Expression::JSXElement(node) => { - if walk_jsx_mut(v, &mut node.opening_element.attributes, &mut node.children).is_stop() { - return VisitResult::Stop; - } - } - Expression::JSXFragment(node) => { - if walk_jsx_children_mut(v, &mut node.children).is_stop() { - return VisitResult::Stop; - } - } - // TS/Flow wrappers — traverse inner expression - Expression::TSAsExpression(node) => { - if walk_expression_mut(v, &mut node.expression).is_stop() { - return VisitResult::Stop; - } - } - Expression::TSSatisfiesExpression(node) => { - if walk_expression_mut(v, &mut node.expression).is_stop() { - return VisitResult::Stop; - } - } - Expression::TSNonNullExpression(node) => { - if walk_expression_mut(v, &mut node.expression).is_stop() { - return VisitResult::Stop; - } - } - Expression::TSTypeAssertion(node) => { - if walk_expression_mut(v, &mut node.expression).is_stop() { - return VisitResult::Stop; - } - } - Expression::TSInstantiationExpression(node) => { - if walk_expression_mut(v, &mut node.expression).is_stop() { - return VisitResult::Stop; - } - } - Expression::TypeCastExpression(node) => { - if walk_expression_mut(v, &mut node.expression).is_stop() { - return VisitResult::Stop; - } - } - // Leaf nodes - Expression::StringLiteral(_) - | Expression::NumericLiteral(_) - | Expression::BooleanLiteral(_) - | Expression::NullLiteral(_) - | Expression::BigIntLiteral(_) - | Expression::RegExpLiteral(_) - | Expression::MetaProperty(_) - | Expression::PrivateName(_) - | Expression::Super(_) - | Expression::Import(_) - | Expression::ThisExpression(_) => {} - } - VisitResult::Continue -} - -fn walk_jsx_mut( - v: &mut impl MutVisitor, - attrs: &mut [crate::jsx::JSXAttributeItem], - children: &mut [crate::jsx::JSXChild], -) -> VisitResult { - for attr in attrs.iter_mut() { - match attr { - crate::jsx::JSXAttributeItem::JSXAttribute(a) => { - if let Some(ref mut val) = a.value { - match val { - crate::jsx::JSXAttributeValue::JSXExpressionContainer(c) => { - if let crate::jsx::JSXExpressionContainerExpr::Expression(ref mut e) = - c.expression - { - if walk_expression_mut(v, e).is_stop() { - return VisitResult::Stop; - } - } - } - _ => {} - } - } - } - crate::jsx::JSXAttributeItem::JSXSpreadAttribute(s) => { - if walk_expression_mut(v, &mut s.argument).is_stop() { - return VisitResult::Stop; - } - } - } - } - walk_jsx_children_mut(v, children) -} - -fn walk_jsx_children_mut( - v: &mut impl MutVisitor, - children: &mut [crate::jsx::JSXChild], -) -> VisitResult { - for child in children.iter_mut() { - match child { - crate::jsx::JSXChild::JSXElement(el) => { - if walk_jsx_mut(v, &mut el.opening_element.attributes, &mut el.children).is_stop() { - return VisitResult::Stop; - } - } - crate::jsx::JSXChild::JSXFragment(f) => { - if walk_jsx_children_mut(v, &mut f.children).is_stop() { - return VisitResult::Stop; - } - } - crate::jsx::JSXChild::JSXExpressionContainer(c) => { - if let crate::jsx::JSXExpressionContainerExpr::Expression(ref mut e) = c.expression - { - if walk_expression_mut(v, e).is_stop() { - return VisitResult::Stop; - } - } - } - crate::jsx::JSXChild::JSXSpreadChild(s) => { - if walk_expression_mut(v, &mut s.expression).is_stop() { - return VisitResult::Stop; - } - } - _ => {} - } - } - VisitResult::Continue -} - -// ---- Private helper walk-mut functions ---- - -fn walk_variable_declaration_mut( - v: &mut impl MutVisitor, - decl: &mut VariableDeclaration, -) -> VisitResult { - for declarator in decl.declarations.iter_mut() { - if let Some(ref mut init) = declarator.init { - if walk_expression_mut(v, init).is_stop() { - return VisitResult::Stop; - } - } - } - VisitResult::Continue -} - -fn walk_declaration_mut(v: &mut impl MutVisitor, decl: &mut Declaration) -> VisitResult { - match decl { - Declaration::FunctionDeclaration(node) => { - for s in node.body.body.iter_mut() { - if walk_statement_mut(v, s).is_stop() { - return VisitResult::Stop; - } - } - } - Declaration::VariableDeclaration(node) => { - if walk_variable_declaration_mut(v, node).is_stop() { - return VisitResult::Stop; - } - } - Declaration::ClassDeclaration(node) => { - if let Some(ref mut sc) = node.super_class { - if walk_expression_mut(v, sc).is_stop() { - return VisitResult::Stop; - } - } - } - _ => {} - } - VisitResult::Continue -} - -fn walk_export_default_decl_mut( - v: &mut impl MutVisitor, - decl: &mut ExportDefaultDecl, -) -> VisitResult { - match decl { - ExportDefaultDecl::FunctionDeclaration(node) => { - for s in node.body.body.iter_mut() { - if walk_statement_mut(v, s).is_stop() { - return VisitResult::Stop; - } - } - } - ExportDefaultDecl::Expression(expr) => { - if walk_expression_mut(v, expr).is_stop() { - return VisitResult::Stop; - } - } - ExportDefaultDecl::ClassDeclaration(node) => { - if let Some(ref mut sc) = node.super_class { - if walk_expression_mut(v, sc).is_stop() { - return VisitResult::Stop; - } - } - } - ExportDefaultDecl::EnumDeclaration(_) => { - // Flow enum declarations are opaque — nothing to walk - } - } - VisitResult::Continue -} diff --git a/compiler/crates/react_compiler_ast/tests/deep_nesting.rs b/compiler/crates/react_compiler_ast/tests/deep_nesting.rs deleted file mode 100644 index 8f4434c65dde..000000000000 --- a/compiler/crates/react_compiler_ast/tests/deep_nesting.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Deep ASTs must survive deserialization when the caller disables -//! serde_json's recursion limit, as the napi entrypoint does. The tolerant -//! statement deserializer reparses captured raw text internally; those -//! reparses must not reintroduce the default depth limit. - -use react_compiler_ast::File; -use serde::Deserialize; - -fn from_json_str_unbounded(s: &str) -> serde_json::Result<File> { - let mut deserializer = serde_json::Deserializer::from_str(s); - deserializer.disable_recursion_limit(); - File::deserialize(&mut deserializer) -} - -#[test] -fn statement_nested_beyond_default_recursion_limit_deserializes() { - let depth = 400; - let mut expr = r#"{"type":"Identifier","name":"x"}"#.to_string(); - for _ in 0..depth { - expr = format!(r#"{{"type":"CallExpression","callee":{expr},"arguments":[]}}"#); - } - let json = format!( - r#"{{"type":"File","program":{{"type":"Program","sourceType":"module","body":[{{"type":"ExpressionStatement","expression":{expr}}}],"directives":[]}}}}"# - ); - - // Parse on a large stack like the napi entrypoint does; without the limit, - // depth is bounded by stack, not by serde_json's counter. - let file = std::thread::Builder::new() - .stack_size(64 * 1024 * 1024) - .spawn(move || from_json_str_unbounded(&json).expect("deep statement must deserialize")) - .expect("spawn") - .join() - .expect("join"); - assert_eq!(file.program.body.len(), 1); -} diff --git a/compiler/crates/react_compiler_ast/tests/round_trip.rs b/compiler/crates/react_compiler_ast/tests/round_trip.rs deleted file mode 100644 index e39a113fdd2a..000000000000 --- a/compiler/crates/react_compiler_ast/tests/round_trip.rs +++ /dev/null @@ -1,151 +0,0 @@ -use std::path::PathBuf; - -fn get_fixture_json_dir() -> PathBuf { - if let Ok(dir) = std::env::var("FIXTURE_JSON_DIR") { - return PathBuf::from(dir); - } - // Default: fixtures checked in alongside the test - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures") -} - -/// Recursively sort all keys in a JSON value for order-independent comparison. -fn normalize_json(value: &serde_json::Value) -> serde_json::Value { - match value { - serde_json::Value::Object(map) => { - let mut sorted: Vec<(String, serde_json::Value)> = map - .iter() - .map(|(k, v)| (k.clone(), normalize_json(v))) - .collect(); - sorted.sort_by(|a, b| a.0.cmp(&b.0)); - serde_json::Value::Object(sorted.into_iter().collect()) - } - serde_json::Value::Array(arr) => { - serde_json::Value::Array(arr.iter().map(normalize_json).collect()) - } - // Normalize numbers: f64 values like 1.0 should compare equal to integer 1 - serde_json::Value::Number(n) => { - if let Some(f) = n.as_f64() { - if f.fract() == 0.0 && f.is_finite() && f.abs() < (i64::MAX as f64) { - serde_json::Value::Number(serde_json::Number::from(f as i64)) - } else { - value.clone() - } - } else { - value.clone() - } - } - other => other.clone(), - } -} - -fn compute_diff(original: &str, round_tripped: &str) -> String { - use similar::ChangeTag; - use similar::TextDiff; - - let diff = TextDiff::from_lines(original, round_tripped); - let mut output = String::new(); - let mut lines_written = 0; - const MAX_DIFF_LINES: usize = 50; - - for change in diff.iter_all_changes() { - if lines_written >= MAX_DIFF_LINES { - output.push_str("... (diff truncated)\n"); - break; - } - let sign = match change.tag() { - ChangeTag::Delete => "-", - ChangeTag::Insert => "+", - ChangeTag::Equal => continue, - }; - output.push_str(&format!("{sign} {change}")); - lines_written += 1; - } - - output -} - -#[test] -fn round_trip_all_fixtures() { - let json_dir = get_fixture_json_dir(); - - let mut failures: Vec<(String, String)> = Vec::new(); - let mut total = 0; - let mut passed = 0; - - // Fixtures with known issues that can't be fixed in the AST crate: - // - lone-surrogate-string-values: contains lone Unicode surrogates (\uD800) - // that serde_json rejects during deserialization. - let known_failures: &[&str] = &["lone-surrogate-string-values"]; - - for entry in walkdir::WalkDir::new(&json_dir) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| { - e.path().extension().is_some_and(|ext| ext == "json") - && !e.path().to_string_lossy().ends_with(".scope.json") - && !e.path().to_string_lossy().ends_with(".renamed.json") - }) - { - let fixture_name = entry - .path() - .strip_prefix(&json_dir) - .unwrap() - .display() - .to_string(); - let original_json = std::fs::read_to_string(entry.path()).unwrap(); - - if known_failures.iter().any(|kf| fixture_name.contains(kf)) { - continue; - } - - total += 1; - - // Deserialize into our Rust types - let ast: react_compiler_ast::File = match serde_json::from_str(&original_json) { - Ok(ast) => ast, - Err(e) => { - failures.push((fixture_name, format!("Deserialization error: {e}"))); - continue; - } - }; - - // Re-serialize back to JSON - let round_tripped = serde_json::to_string_pretty(&ast).unwrap(); - - // Normalize and compare - let original_value: serde_json::Value = serde_json::from_str(&original_json).unwrap(); - let round_tripped_value: serde_json::Value = serde_json::from_str(&round_tripped).unwrap(); - - let original_normalized = normalize_json(&original_value); - let round_tripped_normalized = normalize_json(&round_tripped_value); - - if original_normalized != round_tripped_normalized { - let orig_str = serde_json::to_string_pretty(&original_normalized).unwrap(); - let rt_str = serde_json::to_string_pretty(&round_tripped_normalized).unwrap(); - let diff = compute_diff(&orig_str, &rt_str); - failures.push((fixture_name, diff)); - } else { - passed += 1; - } - } - - println!("\n{passed}/{total} fixtures passed round-trip"); - - if !failures.is_empty() { - let show_count = failures.len().min(5); - let mut msg = format!( - "\n{} of {total} fixtures failed round-trip (showing first {show_count}):\n\n", - failures.len() - ); - for (name, diff) in failures.iter().take(show_count) { - msg.push_str(&format!("--- {name} ---\n{diff}\n\n")); - } - if failures.len() > show_count { - msg.push_str(&format!( - "... and {} more failures\n", - failures.len() - show_count - )); - } - panic!("{msg}"); - } -} diff --git a/compiler/crates/react_compiler_ast/tests/scope_resolution.rs b/compiler/crates/react_compiler_ast/tests/scope_resolution.rs deleted file mode 100644 index c152ca3b2055..000000000000 --- a/compiler/crates/react_compiler_ast/tests/scope_resolution.rs +++ /dev/null @@ -1,1121 +0,0 @@ -use std::path::PathBuf; - -use react_compiler_ast::declarations::*; -use react_compiler_ast::expressions::*; -use react_compiler_ast::jsx::*; -use react_compiler_ast::patterns::*; -use react_compiler_ast::scope::ScopeInfo; -use react_compiler_ast::statements::*; - -fn get_fixture_json_dir() -> PathBuf { - if let Ok(dir) = std::env::var("FIXTURE_JSON_DIR") { - return PathBuf::from(dir); - } - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures") -} - -/// Recursively sort all keys in a JSON value for order-independent comparison. -fn normalize_json(value: &serde_json::Value) -> serde_json::Value { - match value { - serde_json::Value::Object(map) => { - let mut sorted: Vec<(String, serde_json::Value)> = map - .iter() - .map(|(k, v)| (k.clone(), normalize_json(v))) - .collect(); - sorted.sort_by(|a, b| a.0.cmp(&b.0)); - serde_json::Value::Object(sorted.into_iter().collect()) - } - serde_json::Value::Array(arr) => { - serde_json::Value::Array(arr.iter().map(normalize_json).collect()) - } - serde_json::Value::Number(n) => { - if let Some(f) = n.as_f64() { - if f.fract() == 0.0 && f.is_finite() && f.abs() < (i64::MAX as f64) { - serde_json::Value::Number(serde_json::Number::from(f as i64)) - } else { - value.clone() - } - } else { - value.clone() - } - } - other => other.clone(), - } -} - -fn compute_diff(original: &str, round_tripped: &str) -> String { - use similar::ChangeTag; - use similar::TextDiff; - let diff = TextDiff::from_lines(original, round_tripped); - let mut output = String::new(); - let mut lines_written = 0; - const MAX_DIFF_LINES: usize = 50; - for change in diff.iter_all_changes() { - if lines_written >= MAX_DIFF_LINES { - output.push_str("... (diff truncated)\n"); - break; - } - let sign = match change.tag() { - ChangeTag::Delete => "-", - ChangeTag::Insert => "+", - ChangeTag::Equal => continue, - }; - output.push_str(&format!("{sign} {change}")); - lines_written += 1; - } - output -} - -#[test] -fn scope_info_round_trip() { - let json_dir = get_fixture_json_dir(); - let mut failures: Vec<(String, String)> = Vec::new(); - let mut total = 0; - let mut passed = 0; - let mut skipped = 0; - - for entry in walkdir::WalkDir::new(&json_dir) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| { - e.path().extension().is_some_and(|ext| ext == "json") - && !e.path().to_string_lossy().contains(".scope.") - && !e.path().to_string_lossy().contains(".renamed.") - }) - { - let ast_path_str = entry.path().to_string_lossy().to_string(); - let scope_path_str = ast_path_str.replace(".json", ".scope.json"); - let scope_path = std::path::Path::new(&scope_path_str); - - if !scope_path.exists() { - skipped += 1; - continue; - } - - let fixture_name = entry - .path() - .strip_prefix(&json_dir) - .unwrap() - .display() - .to_string(); - total += 1; - - let scope_json = std::fs::read_to_string(scope_path).unwrap(); - - let scope_info: react_compiler_ast::scope::ScopeInfo = - match serde_json::from_str(&scope_json) { - Ok(info) => info, - Err(e) => { - failures.push((fixture_name, format!("Scope deserialization error: {e}"))); - continue; - } - }; - - let round_tripped = serde_json::to_string_pretty(&scope_info).unwrap(); - let original_value: serde_json::Value = serde_json::from_str(&scope_json).unwrap(); - let round_tripped_value: serde_json::Value = serde_json::from_str(&round_tripped).unwrap(); - - let original_normalized = normalize_json(&original_value); - let round_tripped_normalized = normalize_json(&round_tripped_value); - - if original_normalized != round_tripped_normalized { - let orig_str = serde_json::to_string_pretty(&original_normalized).unwrap(); - let rt_str = serde_json::to_string_pretty(&round_tripped_normalized).unwrap(); - let diff = compute_diff(&orig_str, &rt_str); - failures.push((fixture_name, format!("Round-trip mismatch:\n{diff}"))); - continue; - } - - let mut consistency_error = None; - - for binding in &scope_info.bindings { - if binding.scope.0 as usize >= scope_info.scopes.len() { - consistency_error = Some(format!( - "Binding {} has scope {} but only {} scopes exist", - binding.name, - binding.scope.0, - scope_info.scopes.len() - )); - break; - } - } - - if consistency_error.is_none() { - for scope in &scope_info.scopes { - for (name, &bid) in &scope.bindings { - if bid.0 as usize >= scope_info.bindings.len() { - consistency_error = Some(format!( - "Scope {} has binding '{}' with id {} but only {} bindings exist", - scope.id.0, - name, - bid.0, - scope_info.bindings.len() - )); - break; - } - } - if consistency_error.is_some() { - break; - } - if let Some(parent) = scope.parent { - if parent.0 as usize >= scope_info.scopes.len() { - consistency_error = Some(format!( - "Scope {} has parent {} but only {} scopes exist", - scope.id.0, - parent.0, - scope_info.scopes.len() - )); - break; - } - } - } - } - - if consistency_error.is_none() { - for (&_offset, &bid) in &scope_info.reference_to_binding { - if bid.0 as usize >= scope_info.bindings.len() { - consistency_error = Some(format!( - "reference_to_binding has binding id {} but only {} bindings exist", - bid.0, - scope_info.bindings.len() - )); - break; - } - } - } - - if consistency_error.is_none() { - for (&_offset, &sid) in &scope_info.node_to_scope { - if sid.0 as usize >= scope_info.scopes.len() { - consistency_error = Some(format!( - "node_to_scope has scope id {} but only {} scopes exist", - sid.0, - scope_info.scopes.len() - )); - break; - } - } - } - - // Validate node-ID maps - if consistency_error.is_none() { - for (&_nid, &bid) in &scope_info.ref_node_id_to_binding { - if bid.0 as usize >= scope_info.bindings.len() { - consistency_error = Some(format!( - "ref_node_id_to_binding has binding id {} but only {} bindings exist", - bid.0, - scope_info.bindings.len() - )); - break; - } - } - } - - if consistency_error.is_none() { - for (&_nid, &sid) in &scope_info.node_id_to_scope { - if sid.0 as usize >= scope_info.scopes.len() { - consistency_error = Some(format!( - "node_id_to_scope has scope id {} but only {} scopes exist", - sid.0, - scope_info.scopes.len() - )); - break; - } - } - } - - if let Some(err) = consistency_error { - failures.push((fixture_name, format!("Consistency error: {err}"))); - continue; - } - - passed += 1; - } - - println!( - "\n{passed}/{total} fixtures passed scope info round-trip ({skipped} skipped - no scope.json)" - ); - - if !failures.is_empty() { - let show_count = failures.len().min(5); - let mut msg = format!( - "\n{} of {total} fixtures failed scope info test (showing first {show_count}):\n\n", - failures.len() - ); - for (name, err) in failures.iter().take(show_count) { - msg.push_str(&format!("--- {name} ---\n{err}\n\n")); - } - if failures.len() > show_count { - msg.push_str(&format!( - "... and {} more failures\n", - failures.len() - show_count - )); - } - panic!("{msg}"); - } -} - -// ============================================================================ -// Typed AST traversal for identifier renaming -// ============================================================================ - -/// Rename an Identifier if it has a binding in ref_node_id_to_binding. -/// Uses the declaring scope from the binding table — no scope stack needed. -fn rename_id(id: &mut Identifier, si: &ScopeInfo) { - if let Some(nid) = id.base.node_id { - if let Some(bid) = si.resolve_reference_by_node_id(nid) { - let scope = si.bindings[bid.0 as usize].scope.0; - id.name = format!("{}_{}", id.name, format_args!("{scope}_{}", bid.0)); - } - } - visit_json_opt(&mut id.type_annotation, si); - if let Some(decorators) = &mut id.decorators { - visit_json_vec(decorators, si); - } -} - -/// Fallback walker for serde_json::Value fields (class bodies, type annotations, decorators, etc.) -fn visit_json(val: &mut serde_json::Value, si: &ScopeInfo) { - match val { - serde_json::Value::Object(map) => { - if map.get("type").and_then(|v| v.as_str()) == Some("Identifier") { - if let Some(nid) = map.get("_nodeId").and_then(|v| v.as_u64()) { - if let Some(bid) = si.resolve_reference_by_node_id(nid as u32) { - let scope = si.bindings[bid.0 as usize].scope.0; - if let Some(name) = map - .get("name") - .and_then(|v| v.as_str()) - .map(|s| s.to_string()) - { - map.insert( - "name".to_string(), - serde_json::Value::String(format!("{name}_{scope}_{}", bid.0)), - ); - } - } - } - } - let keys: Vec<String> = map.keys().cloned().collect(); - for key in keys { - if let Some(child) = map.get_mut(&key) { - visit_json(child, si); - } - } - } - serde_json::Value::Array(arr) => { - for item in arr.iter_mut() { - visit_json(item, si); - } - } - _ => {} - } -} - -fn visit_raw(val: &mut react_compiler_ast::common::RawNode, si: &ScopeInfo) { - let mut v = val.parse_value(); - visit_json(&mut v, si); - *val = react_compiler_ast::common::RawNode::from_value(&v); -} - -fn visit_json_vec(vals: &mut [react_compiler_ast::common::RawNode], si: &ScopeInfo) { - for val in vals.iter_mut() { - visit_raw(val, si); - } -} - -fn visit_json_opt(val: &mut Option<react_compiler_ast::common::RawNode>, si: &ScopeInfo) { - if let Some(v) = val { - visit_raw(v, si); - } -} - -fn rename_identifiers(file: &mut react_compiler_ast::File, si: &ScopeInfo) { - visit_program(&mut file.program, si); -} - -fn visit_program(prog: &mut react_compiler_ast::Program, si: &ScopeInfo) { - for stmt in &mut prog.body { - visit_stmt(stmt, si); - } -} - -fn visit_block(block: &mut BlockStatement, si: &ScopeInfo) { - for stmt in &mut block.body { - visit_stmt(stmt, si); - } -} - -fn visit_stmt(stmt: &mut Statement, si: &ScopeInfo) { - match stmt { - Statement::BlockStatement(s) => visit_block(s, si), - Statement::ReturnStatement(s) => { - if let Some(arg) = &mut s.argument { - visit_expr(arg, si); - } - } - Statement::ExpressionStatement(s) => visit_expr(&mut s.expression, si), - Statement::IfStatement(s) => { - visit_expr(&mut s.test, si); - visit_stmt(&mut s.consequent, si); - if let Some(alt) = &mut s.alternate { - visit_stmt(alt, si); - } - } - Statement::ForStatement(s) => { - if let Some(init) = &mut s.init { - match init.as_mut() { - ForInit::VariableDeclaration(d) => visit_var_decl(d, si), - ForInit::Expression(e) => visit_expr(e, si), - } - } - if let Some(test) = &mut s.test { - visit_expr(test, si); - } - if let Some(update) = &mut s.update { - visit_expr(update, si); - } - visit_stmt(&mut s.body, si); - } - Statement::WhileStatement(s) => { - visit_expr(&mut s.test, si); - visit_stmt(&mut s.body, si); - } - Statement::DoWhileStatement(s) => { - visit_stmt(&mut s.body, si); - visit_expr(&mut s.test, si); - } - Statement::ForInStatement(s) => { - visit_for_left(&mut s.left, si); - visit_expr(&mut s.right, si); - visit_stmt(&mut s.body, si); - } - Statement::ForOfStatement(s) => { - visit_for_left(&mut s.left, si); - visit_expr(&mut s.right, si); - visit_stmt(&mut s.body, si); - } - Statement::SwitchStatement(s) => { - visit_expr(&mut s.discriminant, si); - for case in &mut s.cases { - if let Some(test) = &mut case.test { - visit_expr(test, si); - } - for child in &mut case.consequent { - visit_stmt(child, si); - } - } - } - Statement::ThrowStatement(s) => visit_expr(&mut s.argument, si), - Statement::TryStatement(s) => { - visit_block(&mut s.block, si); - if let Some(handler) = &mut s.handler { - if let Some(param) = &mut handler.param { - visit_pat(param, si); - } - visit_block(&mut handler.body, si); - } - if let Some(fin) = &mut s.finalizer { - visit_block(fin, si); - } - } - Statement::LabeledStatement(s) => visit_stmt(&mut s.body, si), - Statement::WithStatement(s) => { - visit_expr(&mut s.object, si); - visit_stmt(&mut s.body, si); - } - Statement::VariableDeclaration(d) => visit_var_decl(d, si), - Statement::FunctionDeclaration(f) => visit_func_decl(f, si), - Statement::ClassDeclaration(c) => visit_class_decl(c, si), - Statement::ImportDeclaration(d) => visit_import_decl(d, si), - Statement::ExportNamedDeclaration(d) => visit_export_named(d, si), - Statement::ExportDefaultDeclaration(d) => visit_export_default(d, si), - Statement::TSTypeAliasDeclaration(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.type_annotation, si); - visit_json_opt(&mut d.type_parameters, si); - } - Statement::TSInterfaceDeclaration(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.body, si); - visit_json_opt(&mut d.type_parameters, si); - if let Some(ext) = &mut d.extends { - visit_json_vec(ext, si); - } - } - Statement::TSEnumDeclaration(d) => { - rename_id(&mut d.id, si); - visit_json_vec(&mut d.members, si); - } - Statement::TSModuleDeclaration(d) => { - visit_raw(&mut d.id, si); - visit_raw(&mut d.body, si); - } - Statement::TSDeclareFunction(d) => { - if let Some(id) = &mut d.id { - rename_id(id, si); - } - visit_json_vec(&mut d.params, si); - visit_json_opt(&mut d.return_type, si); - visit_json_opt(&mut d.type_parameters, si); - } - Statement::TypeAlias(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.right, si); - visit_json_opt(&mut d.type_parameters, si); - } - Statement::OpaqueType(d) => { - rename_id(&mut d.id, si); - if let Some(st) = &mut d.supertype { - visit_raw(st, si); - } - visit_raw(&mut d.impltype, si); - visit_json_opt(&mut d.type_parameters, si); - } - Statement::InterfaceDeclaration(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.body, si); - visit_json_opt(&mut d.type_parameters, si); - if let Some(ext) = &mut d.extends { - visit_json_vec(ext, si); - } - } - Statement::DeclareVariable(d) => rename_id(&mut d.id, si), - Statement::DeclareFunction(d) => { - rename_id(&mut d.id, si); - if let Some(pred) = &mut d.predicate { - visit_raw(pred, si); - } - } - Statement::DeclareClass(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.body, si); - visit_json_opt(&mut d.type_parameters, si); - if let Some(ext) = &mut d.extends { - visit_json_vec(ext, si); - } - } - Statement::DeclareModule(d) => { - visit_raw(&mut d.id, si); - visit_raw(&mut d.body, si); - } - Statement::DeclareModuleExports(d) => visit_raw(&mut d.type_annotation, si), - Statement::DeclareExportDeclaration(d) => { - if let Some(decl) = &mut d.declaration { - visit_raw(decl, si); - } - if let Some(specs) = &mut d.specifiers { - visit_json_vec(specs, si); - } - } - Statement::DeclareInterface(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.body, si); - visit_json_opt(&mut d.type_parameters, si); - if let Some(ext) = &mut d.extends { - visit_json_vec(ext, si); - } - } - Statement::DeclareTypeAlias(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.right, si); - visit_json_opt(&mut d.type_parameters, si); - } - Statement::DeclareOpaqueType(d) => { - rename_id(&mut d.id, si); - if let Some(st) = &mut d.supertype { - visit_raw(st, si); - } - if let Some(impl_) = &mut d.impltype { - visit_raw(impl_, si); - } - visit_json_opt(&mut d.type_parameters, si); - } - Statement::EnumDeclaration(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.body, si); - } - Statement::Unknown(d) => { - d.with_raw_mut(|raw| visit_raw(raw, si)) - .expect("identifier rename preserves the node `type`"); - } - Statement::BreakStatement(_) - | Statement::ContinueStatement(_) - | Statement::EmptyStatement(_) - | Statement::DebuggerStatement(_) - | Statement::ExportAllDeclaration(_) - | Statement::DeclareExportAllDeclaration(_) => {} - } -} - -fn visit_expr(expr: &mut Expression, si: &ScopeInfo) { - match expr { - Expression::Identifier(id) => rename_id(id, si), - Expression::CallExpression(e) => { - visit_expr(&mut e.callee, si); - for arg in &mut e.arguments { - visit_expr(arg, si); - } - visit_json_opt(&mut e.type_parameters, si); - visit_json_opt(&mut e.type_arguments, si); - } - Expression::MemberExpression(e) => { - visit_expr(&mut e.object, si); - visit_expr(&mut e.property, si); - } - Expression::OptionalCallExpression(e) => { - visit_expr(&mut e.callee, si); - for arg in &mut e.arguments { - visit_expr(arg, si); - } - visit_json_opt(&mut e.type_parameters, si); - visit_json_opt(&mut e.type_arguments, si); - } - Expression::OptionalMemberExpression(e) => { - visit_expr(&mut e.object, si); - visit_expr(&mut e.property, si); - } - Expression::BinaryExpression(e) => { - visit_expr(&mut e.left, si); - visit_expr(&mut e.right, si); - } - Expression::LogicalExpression(e) => { - visit_expr(&mut e.left, si); - visit_expr(&mut e.right, si); - } - Expression::UnaryExpression(e) => visit_expr(&mut e.argument, si), - Expression::UpdateExpression(e) => visit_expr(&mut e.argument, si), - Expression::ConditionalExpression(e) => { - visit_expr(&mut e.test, si); - visit_expr(&mut e.consequent, si); - visit_expr(&mut e.alternate, si); - } - Expression::AssignmentExpression(e) => { - visit_pat(&mut e.left, si); - visit_expr(&mut e.right, si); - } - Expression::SequenceExpression(e) => { - for child in &mut e.expressions { - visit_expr(child, si); - } - } - Expression::ArrowFunctionExpression(e) => { - if let Some(id) = &mut e.id { - rename_id(id, si); - } - for param in &mut e.params { - visit_pat(param, si); - } - match e.body.as_mut() { - ArrowFunctionBody::BlockStatement(block) => visit_block(block, si), - ArrowFunctionBody::Expression(expr) => visit_expr(expr, si), - } - visit_json_opt(&mut e.return_type, si); - visit_json_opt(&mut e.type_parameters, si); - visit_json_opt(&mut e.predicate, si); - } - Expression::FunctionExpression(e) => { - if let Some(id) = &mut e.id { - rename_id(id, si); - } - for param in &mut e.params { - visit_pat(param, si); - } - visit_block(&mut e.body, si); - visit_json_opt(&mut e.return_type, si); - visit_json_opt(&mut e.type_parameters, si); - } - Expression::ObjectExpression(e) => { - for prop in &mut e.properties { - match prop { - ObjectExpressionProperty::ObjectProperty(p) => { - visit_expr(&mut p.key, si); - visit_expr(&mut p.value, si); - } - ObjectExpressionProperty::ObjectMethod(m) => { - visit_expr(&mut m.key, si); - for param in &mut m.params { - visit_pat(param, si); - } - visit_block(&mut m.body, si); - visit_json_opt(&mut m.return_type, si); - visit_json_opt(&mut m.type_parameters, si); - } - ObjectExpressionProperty::SpreadElement(s) => visit_expr(&mut s.argument, si), - } - } - } - Expression::ArrayExpression(e) => { - for elem in &mut e.elements { - if let Some(el) = elem { - visit_expr(el, si); - } - } - } - Expression::NewExpression(e) => { - visit_expr(&mut e.callee, si); - for arg in &mut e.arguments { - visit_expr(arg, si); - } - visit_json_opt(&mut e.type_parameters, si); - visit_json_opt(&mut e.type_arguments, si); - } - Expression::TemplateLiteral(e) => { - for child in &mut e.expressions { - visit_expr(child, si); - } - } - Expression::TaggedTemplateExpression(e) => { - visit_expr(&mut e.tag, si); - for child in &mut e.quasi.expressions { - visit_expr(child, si); - } - visit_json_opt(&mut e.type_parameters, si); - } - Expression::AwaitExpression(e) => visit_expr(&mut e.argument, si), - Expression::YieldExpression(e) => { - if let Some(arg) = &mut e.argument { - visit_expr(arg, si); - } - } - Expression::SpreadElement(e) => visit_expr(&mut e.argument, si), - Expression::MetaProperty(e) => { - rename_id(&mut e.meta, si); - rename_id(&mut e.property, si); - } - Expression::ClassExpression(e) => { - if let Some(id) = &mut e.id { - rename_id(id, si); - } - if let Some(sc) = &mut e.super_class { - visit_expr(sc, si); - } - visit_json_vec(&mut e.body.body, si); - if let Some(dec) = &mut e.decorators { - visit_json_vec(dec, si); - } - visit_json_opt(&mut e.super_type_parameters, si); - visit_json_opt(&mut e.type_parameters, si); - if let Some(imp) = &mut e.implements { - visit_json_vec(imp, si); - } - } - Expression::PrivateName(e) => rename_id(&mut e.id, si), - Expression::ParenthesizedExpression(e) => visit_expr(&mut e.expression, si), - Expression::AssignmentPattern(p) => { - visit_pat(&mut p.left, si); - visit_expr(&mut p.right, si); - } - Expression::TSAsExpression(e) => { - visit_expr(&mut e.expression, si); - visit_raw(&mut e.type_annotation, si); - } - Expression::TSSatisfiesExpression(e) => { - visit_expr(&mut e.expression, si); - visit_raw(&mut e.type_annotation, si); - } - Expression::TSNonNullExpression(e) => visit_expr(&mut e.expression, si), - Expression::TSTypeAssertion(e) => { - visit_expr(&mut e.expression, si); - visit_raw(&mut e.type_annotation, si); - } - Expression::TSInstantiationExpression(e) => { - visit_expr(&mut e.expression, si); - visit_raw(&mut e.type_parameters, si); - } - Expression::TypeCastExpression(e) => { - visit_expr(&mut e.expression, si); - visit_raw(&mut e.type_annotation, si); - } - Expression::JSXElement(e) => visit_jsx_element(e, si), - Expression::JSXFragment(f) => { - for child in &mut f.children { - visit_jsx_child(child, si); - } - } - Expression::StringLiteral(_) - | Expression::NumericLiteral(_) - | Expression::BooleanLiteral(_) - | Expression::NullLiteral(_) - | Expression::BigIntLiteral(_) - | Expression::RegExpLiteral(_) - | Expression::Super(_) - | Expression::Import(_) - | Expression::ThisExpression(_) => {} - } -} - -fn visit_pat(pat: &mut PatternLike, si: &ScopeInfo) { - match pat { - PatternLike::Identifier(id) => rename_id(id, si), - PatternLike::ObjectPattern(op) => { - for prop in &mut op.properties { - match prop { - ObjectPatternProperty::ObjectProperty(pp) => { - visit_expr(&mut pp.key, si); - visit_pat(&mut pp.value, si); - } - ObjectPatternProperty::RestElement(r) => { - visit_pat(&mut r.argument, si); - visit_json_opt(&mut r.type_annotation, si); - } - } - } - visit_json_opt(&mut op.type_annotation, si); - } - PatternLike::ArrayPattern(ap) => { - for elem in &mut ap.elements { - if let Some(el) = elem { - visit_pat(el, si); - } - } - visit_json_opt(&mut ap.type_annotation, si); - } - PatternLike::AssignmentPattern(ap) => { - visit_pat(&mut ap.left, si); - visit_expr(&mut ap.right, si); - visit_json_opt(&mut ap.type_annotation, si); - } - PatternLike::RestElement(re) => { - visit_pat(&mut re.argument, si); - visit_json_opt(&mut re.type_annotation, si); - } - PatternLike::MemberExpression(e) => { - visit_expr(&mut e.object, si); - visit_expr(&mut e.property, si); - } - PatternLike::TSAsExpression(e) => { - visit_expr(&mut e.expression, si); - visit_raw(&mut e.type_annotation, si); - } - PatternLike::TSSatisfiesExpression(e) => { - visit_expr(&mut e.expression, si); - visit_raw(&mut e.type_annotation, si); - } - PatternLike::TSNonNullExpression(e) => { - visit_expr(&mut e.expression, si); - } - PatternLike::TSTypeAssertion(e) => { - visit_expr(&mut e.expression, si); - visit_raw(&mut e.type_annotation, si); - } - PatternLike::TypeCastExpression(e) => { - visit_expr(&mut e.expression, si); - visit_raw(&mut e.type_annotation, si); - } - } -} - -fn visit_for_left(left: &mut Box<ForInOfLeft>, si: &ScopeInfo) { - match left.as_mut() { - ForInOfLeft::VariableDeclaration(d) => visit_var_decl(d, si), - ForInOfLeft::Pattern(p) => visit_pat(p, si), - } -} - -fn visit_var_decl(d: &mut VariableDeclaration, si: &ScopeInfo) { - for decl in &mut d.declarations { - visit_pat(&mut decl.id, si); - if let Some(init) = &mut decl.init { - visit_expr(init, si); - } - } -} - -fn visit_func_decl(f: &mut FunctionDeclaration, si: &ScopeInfo) { - if let Some(id) = &mut f.id { - rename_id(id, si); - } - for param in &mut f.params { - visit_pat(param, si); - } - visit_block(&mut f.body, si); - visit_json_opt(&mut f.return_type, si); - visit_json_opt(&mut f.type_parameters, si); - visit_json_opt(&mut f.predicate, si); -} - -fn visit_class_decl(c: &mut ClassDeclaration, si: &ScopeInfo) { - if let Some(id) = &mut c.id { - rename_id(id, si); - } - if let Some(sc) = &mut c.super_class { - visit_expr(sc, si); - } - visit_json_vec(&mut c.body.body, si); - if let Some(dec) = &mut c.decorators { - visit_json_vec(dec, si); - } - visit_json_opt(&mut c.super_type_parameters, si); - visit_json_opt(&mut c.type_parameters, si); - if let Some(imp) = &mut c.implements { - visit_json_vec(imp, si); - } -} - -fn visit_import_decl(d: &mut ImportDeclaration, si: &ScopeInfo) { - for spec in &mut d.specifiers { - match spec { - ImportSpecifier::ImportSpecifier(s) => { - rename_id(&mut s.local, si); - visit_module_export_name(&mut s.imported, si); - } - ImportSpecifier::ImportDefaultSpecifier(s) => rename_id(&mut s.local, si), - ImportSpecifier::ImportNamespaceSpecifier(s) => rename_id(&mut s.local, si), - } - } -} - -fn visit_export_named(d: &mut ExportNamedDeclaration, si: &ScopeInfo) { - if let Some(decl) = &mut d.declaration { - visit_declaration(decl, si); - } - for spec in &mut d.specifiers { - match spec { - ExportSpecifier::ExportSpecifier(s) => { - visit_module_export_name(&mut s.local, si); - visit_module_export_name(&mut s.exported, si); - } - ExportSpecifier::ExportDefaultSpecifier(s) => rename_id(&mut s.exported, si), - ExportSpecifier::ExportNamespaceSpecifier(s) => { - visit_module_export_name(&mut s.exported, si); - } - } - } -} - -fn visit_export_default(d: &mut ExportDefaultDeclaration, si: &ScopeInfo) { - match d.declaration.as_mut() { - ExportDefaultDecl::FunctionDeclaration(f) => visit_func_decl(f, si), - ExportDefaultDecl::ClassDeclaration(c) => visit_class_decl(c, si), - ExportDefaultDecl::EnumDeclaration(_) => {} // Flow enums are opaque - ExportDefaultDecl::Expression(e) => visit_expr(e, si), - } -} - -fn visit_declaration(d: &mut Declaration, si: &ScopeInfo) { - match d { - Declaration::FunctionDeclaration(f) => visit_func_decl(f, si), - Declaration::ClassDeclaration(c) => visit_class_decl(c, si), - Declaration::VariableDeclaration(v) => visit_var_decl(v, si), - Declaration::TSTypeAliasDeclaration(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.type_annotation, si); - visit_json_opt(&mut d.type_parameters, si); - } - Declaration::TSInterfaceDeclaration(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.body, si); - visit_json_opt(&mut d.type_parameters, si); - if let Some(ext) = &mut d.extends { - visit_json_vec(ext, si); - } - } - Declaration::TSEnumDeclaration(d) => { - rename_id(&mut d.id, si); - visit_json_vec(&mut d.members, si); - } - Declaration::TSModuleDeclaration(d) => { - visit_raw(&mut d.id, si); - visit_raw(&mut d.body, si); - } - Declaration::TSDeclareFunction(d) => { - if let Some(id) = &mut d.id { - rename_id(id, si); - } - visit_json_vec(&mut d.params, si); - visit_json_opt(&mut d.return_type, si); - visit_json_opt(&mut d.type_parameters, si); - } - Declaration::TypeAlias(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.right, si); - visit_json_opt(&mut d.type_parameters, si); - } - Declaration::OpaqueType(d) => { - rename_id(&mut d.id, si); - if let Some(st) = &mut d.supertype { - visit_raw(st, si); - } - visit_raw(&mut d.impltype, si); - visit_json_opt(&mut d.type_parameters, si); - } - Declaration::InterfaceDeclaration(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.body, si); - visit_json_opt(&mut d.type_parameters, si); - if let Some(ext) = &mut d.extends { - visit_json_vec(ext, si); - } - } - Declaration::EnumDeclaration(d) => { - rename_id(&mut d.id, si); - visit_raw(&mut d.body, si); - } - } -} - -fn visit_module_export_name(n: &mut ModuleExportName, si: &ScopeInfo) { - match n { - ModuleExportName::Identifier(id) => rename_id(id, si), - ModuleExportName::StringLiteral(_) => {} - } -} - -fn visit_jsx_element(el: &mut JSXElement, si: &ScopeInfo) { - for attr in &mut el.opening_element.attributes { - match attr { - JSXAttributeItem::JSXAttribute(a) => { - if let Some(val) = &mut a.value { - match val { - JSXAttributeValue::JSXExpressionContainer(c) => { - visit_jsx_expr(&mut c.expression, si); - } - JSXAttributeValue::JSXElement(e) => visit_jsx_element(e, si), - JSXAttributeValue::JSXFragment(f) => { - for child in &mut f.children { - visit_jsx_child(child, si); - } - } - JSXAttributeValue::StringLiteral(_) => {} - } - } - } - JSXAttributeItem::JSXSpreadAttribute(s) => visit_expr(&mut s.argument, si), - } - } - visit_json_opt(&mut el.opening_element.type_parameters, si); - for child in &mut el.children { - visit_jsx_child(child, si); - } -} - -fn visit_jsx_child(child: &mut JSXChild, si: &ScopeInfo) { - match child { - JSXChild::JSXElement(e) => visit_jsx_element(e, si), - JSXChild::JSXFragment(f) => { - for child in &mut f.children { - visit_jsx_child(child, si); - } - } - JSXChild::JSXExpressionContainer(c) => visit_jsx_expr(&mut c.expression, si), - JSXChild::JSXSpreadChild(s) => visit_expr(&mut s.expression, si), - JSXChild::JSXText(_) => {} - } -} - -fn visit_jsx_expr(expr: &mut JSXExpressionContainerExpr, si: &ScopeInfo) { - match expr { - JSXExpressionContainerExpr::Expression(e) => visit_expr(e, si), - JSXExpressionContainerExpr::JSXEmptyExpression(_) => {} - } -} - -#[test] -fn scope_resolution_rename() { - let json_dir = get_fixture_json_dir(); - let mut failures: Vec<(String, String)> = Vec::new(); - let mut total = 0; - let mut passed = 0; - let mut skipped = 0; - - let known_failures: &[&str] = &[ - "lone-surrogate-string-values", - "component-in-object-method-body.flow", - "error.todo-hoist-type-alias-before-declaration", - "error.todo-round2_severity_diff", - "error.todo-update-expression-context-variable-via-type-annotation", - ]; - - for entry in walkdir::WalkDir::new(&json_dir) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| { - e.path().extension().is_some_and(|ext| ext == "json") - && !e.path().to_string_lossy().contains(".scope.") - && !e.path().to_string_lossy().contains(".renamed.") - }) - { - let ast_path_str = entry.path().to_string_lossy().to_string(); - let scope_path_str = ast_path_str.replace(".json", ".scope.json"); - let renamed_path_str = ast_path_str.replace(".json", ".renamed.json"); - let scope_path = std::path::Path::new(&scope_path_str); - let renamed_path = std::path::Path::new(&renamed_path_str); - - if !scope_path.exists() || !renamed_path.exists() { - skipped += 1; - continue; - } - - let fixture_name = entry - .path() - .strip_prefix(&json_dir) - .unwrap() - .display() - .to_string(); - - if known_failures.iter().any(|kf| fixture_name.contains(kf)) { - continue; - } - - total += 1; - - let ast_json = std::fs::read_to_string(entry.path()).unwrap(); - let scope_json = std::fs::read_to_string(scope_path).unwrap(); - let babel_renamed_json = std::fs::read_to_string(renamed_path).unwrap(); - - let scope_info: react_compiler_ast::scope::ScopeInfo = - match serde_json::from_str(&scope_json) { - Ok(info) => info, - Err(e) => { - failures.push((fixture_name, format!("Scope deserialization error: {e}"))); - continue; - } - }; - - // Deserialize into typed AST, rename using scope info, re-serialize - let mut file: react_compiler_ast::File = match serde_json::from_str(&ast_json) { - Ok(f) => f, - Err(e) => { - failures.push((fixture_name, format!("AST deserialization error: {e}"))); - continue; - } - }; - rename_identifiers(&mut file, &scope_info); - let rust_renamed = serde_json::to_value(&file).unwrap(); - - let babel_renamed_value: serde_json::Value = - serde_json::from_str(&babel_renamed_json).unwrap(); - - let rust_normalized = normalize_json(&rust_renamed); - let babel_normalized = normalize_json(&babel_renamed_value); - - if rust_normalized != babel_normalized { - let rust_str = serde_json::to_string_pretty(&rust_normalized).unwrap(); - let babel_str = serde_json::to_string_pretty(&babel_normalized).unwrap(); - let diff = compute_diff(&babel_str, &rust_str); - failures.push((fixture_name, format!("Rename mismatch:\n{diff}"))); - } else { - passed += 1; - } - } - - println!("\n{passed}/{total} fixtures passed scope resolution rename ({skipped} skipped)"); - - if !failures.is_empty() { - let show_count = failures.len().min(5); - let mut msg = format!( - "\n{} of {total} fixtures failed scope resolution rename (showing first {show_count}):\n\n", - failures.len() - ); - for (name, err) in failures.iter().take(show_count) { - msg.push_str(&format!("--- {name} ---\n{err}\n\n")); - } - if failures.len() > show_count { - msg.push_str(&format!( - "... and {} more failures\n", - failures.len() - show_count - )); - } - panic!("{msg}"); - } -} diff --git a/compiler/crates/react_compiler_diagnostics/Cargo.toml b/compiler/crates/react_compiler_diagnostics/Cargo.toml deleted file mode 100644 index 873843c5ac34..000000000000 --- a/compiler/crates/react_compiler_diagnostics/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "react_compiler_diagnostics" -version = "0.1.0" -edition = "2024" - -[dependencies] -serde = { version = "1", features = ["derive"] } diff --git a/compiler/crates/react_compiler_diagnostics/src/code_frame.rs b/compiler/crates/react_compiler_diagnostics/src/code_frame.rs deleted file mode 100644 index 00bb2c495d46..000000000000 --- a/compiler/crates/react_compiler_diagnostics/src/code_frame.rs +++ /dev/null @@ -1,440 +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. - */ -use crate::{CompilerDiagnosticDetail, CompilerError, CompilerErrorOrDiagnostic}; - -const CODEFRAME_LINES_ABOVE: u32 = 2; -const CODEFRAME_LINES_BELOW: u32 = 3; -const CODEFRAME_MAX_LINES: u32 = 10; -const CODEFRAME_ABBREVIATED_SOURCE_LINES: usize = 5; - -/// Split source text on newlines, matching Babel's NEWLINE regex: /\r\n|[\n\r\u2028\u2029]/ -fn split_lines(source: &str) -> Vec<&str> { - let mut lines = Vec::new(); - let mut start = 0; - let bytes = source.as_bytes(); - let len = bytes.len(); - let mut i = 0; - while i < len { - let ch = bytes[i]; - if ch == b'\r' { - lines.push(&source[start..i]); - if i + 1 < len && bytes[i + 1] == b'\n' { - i += 2; - } else { - i += 1; - } - start = i; - } else if ch == b'\n' { - lines.push(&source[start..i]); - i += 1; - start = i; - } else { - // Check for Unicode line separators U+2028 and U+2029 - // These are encoded as E2 80 A8 and E2 80 A9 in UTF-8 - if ch == 0xE2 - && i + 2 < len - && bytes[i + 1] == 0x80 - && (bytes[i + 2] == 0xA8 || bytes[i + 2] == 0xA9) - { - lines.push(&source[start..i]); - i += 3; - start = i; - } else { - i += 1; - } - } - } - lines.push(&source[start..]); - lines -} - -/// Represents a marker line entry: either mark the whole line (true) or a [column, length] range. -#[derive(Clone, Debug)] -enum MarkerEntry { - WholeLine, - Range(usize, usize), // (start_column_1based, length) -} - -/// Compute marker lines matching Babel's getMarkerLines(). -/// All column values here are 1-based (Babel convention). -fn get_marker_lines( - start_line: u32, - start_column: u32, // 1-based - end_line: u32, - end_column: u32, // 1-based - source_line_count: usize, - lines_above: u32, - lines_below: u32, -) -> (usize, usize, Vec<(usize, MarkerEntry)>) { - let start_line = start_line as usize; - let end_line = end_line as usize; - let start_column = start_column as usize; - let end_column = end_column as usize; - - // Compute display range - let start = if start_line > (lines_above as usize + 1) { - start_line - (lines_above as usize + 1) - } else { - 0 - }; - let end = std::cmp::min(source_line_count, end_line + lines_below as usize); - - let line_diff = end_line - start_line; - let mut marker_lines: Vec<(usize, MarkerEntry)> = Vec::new(); - - if line_diff > 0 { - // Multi-line error - for i in 0..=line_diff { - let line_number = i + start_line; - if start_column == 0 { - marker_lines.push((line_number, MarkerEntry::WholeLine)); - } else if i == 0 { - // First line: from start_column to end of source line - // source[lineNumber - 1] gives us the source line (0-indexed array, 1-indexed line numbers) - // But we don't have access to source lines here, so we pass the length through. - // Actually, Babel accesses source[lineNumber - 1].length. We need to thread source lines. - // For now, this is handled in code_frame_columns where we have access to source lines. - // We use a placeholder that will be filled in later. - marker_lines.push((line_number, MarkerEntry::Range(start_column, 0))); // 0 = placeholder - } else if i == line_diff { - marker_lines.push((line_number, MarkerEntry::Range(0, end_column))); - } else { - marker_lines.push((line_number, MarkerEntry::Range(0, 0))); // 0 = placeholder for full line - } - } - } else { - // Single-line error - if start_column == end_column { - if start_column != 0 { - marker_lines.push((start_line, MarkerEntry::Range(start_column, 0))); - } else { - marker_lines.push((start_line, MarkerEntry::WholeLine)); - } - } else { - marker_lines.push(( - start_line, - MarkerEntry::Range(start_column, end_column - start_column), - )); - } - } - - (start, end, marker_lines) -} - -/// Produce a code frame matching @babel/code-frame's codeFrameColumns() in non-highlighted mode. -/// -/// Columns are 0-based (matching the Rust/AST convention). They are converted to 1-based -/// internally to match Babel's convention (the JS caller already does column + 1). -pub fn code_frame_columns( - source: &str, - start_line: u32, - start_col: u32, - end_line: u32, - end_col: u32, - message: &str, -) -> String { - // Convert 0-based columns to 1-based (Babel convention) - let start_column_1 = start_col + 1; - let end_column_1 = end_col + 1; - - let lines = split_lines(source); - let source_line_count = lines.len(); - - let (start, end, marker_lines_raw) = get_marker_lines( - start_line, - start_column_1, - end_line, - end_column_1, - source_line_count, - CODEFRAME_LINES_ABOVE, - CODEFRAME_LINES_BELOW, - ); - - let has_columns = start_column_1 > 0; - let number_max_width = format!("{}", end).len(); - - // Build a lookup map for marker lines - let mut marker_map: std::collections::HashMap<usize, MarkerEntry> = - std::collections::HashMap::new(); - let line_diff = end_line as usize - start_line as usize; - for (line_number, entry) in marker_lines_raw { - // Resolve placeholder lengths using actual source lines - let resolved = match &entry { - MarkerEntry::Range(col, len) => { - if line_diff > 0 { - let i = line_number - start_line as usize; - if i == 0 && *len == 0 { - // First line of multi-line: from start_column to end of line - let source_length = if line_number >= 1 && line_number <= lines.len() { - lines[line_number - 1].len() - } else { - 0 - }; - MarkerEntry::Range(*col, source_length.saturating_sub(*col) + 1) - } else if i > 0 && i < line_diff && *col == 0 && *len == 0 { - // Middle line of multi-line: Babel uses source[lineNumber - i].length - // which evaluates to source[startLine] (0-indexed array, 1-indexed line number). - // This means all middle lines use the length of source[startLine], - // which is the line at 0-indexed position startLine in the source array. - let source_length = if (start_line as usize) < lines.len() { - lines[start_line as usize].len() - } else { - 0 - }; - MarkerEntry::Range(0, source_length) - } else { - entry - } - } else { - entry - } - } - _ => entry, - }; - marker_map.insert(line_number, resolved); - } - - // Build frame lines - let mut frame_parts: Vec<String> = Vec::new(); - let display_lines = &lines[start..end]; - - for (index, line) in display_lines.iter().enumerate() { - let number = start + 1 + index; - // Right-align the line number: ` ${number}`.slice(-numberMaxWidth) - let number_str = format!("{}", number); - let padded_number = if number_str.len() >= number_max_width { - number_str - } else { - let padding = " ".repeat(number_max_width - number_str.len()); - format!("{}{}", padding, number_str) - }; - let gutter = format!(" {} |", padded_number); - - let has_marker = marker_map.get(&number); - let has_next_marker = marker_map.contains_key(&(number + 1)); - let last_marker_line = has_marker.is_some() && !has_next_marker; - - if let Some(marker_entry) = has_marker { - // This is a marked line - let line_content = if line.is_empty() { - String::new() - } else { - format!(" {}", line) - }; - - let marker_line_str = match marker_entry { - MarkerEntry::Range(col, len) => { - // Build marker spacing: replace non-tab chars with spaces - let max_col = if *col > 0 { col - 1 } else { 0 }; - let byte_end = std::cmp::min(max_col, line.len()); - // Ensure we don't slice in the middle of a multi-byte UTF-8 character - let safe_end = if byte_end < line.len() && !line.is_char_boundary(byte_end) { - line.floor_char_boundary(byte_end) - } else { - byte_end - }; - let prefix = &line[..safe_end]; - let marker_spacing: String = prefix - .chars() - .map(|c| if c == '\t' { '\t' } else { ' ' }) - .collect(); - let number_of_markers = if *len == 0 { 1 } else { *len }; - let carets = "^".repeat(number_of_markers); - let gutter_spaces = gutter.replace(|c: char| c.is_ascii_digit(), " "); - let mut marker_str = - format!("\n {} {}{}", gutter_spaces, marker_spacing, carets); - if last_marker_line && !message.is_empty() { - marker_str.push(' '); - marker_str.push_str(message); - } - marker_str - } - MarkerEntry::WholeLine => String::new(), - }; - - frame_parts.push(format!(">{}{}{}", gutter, line_content, marker_line_str)); - } else { - // Non-marked line - let line_content = if line.is_empty() { - String::new() - } else { - format!(" {}", line) - }; - frame_parts.push(format!(" {}{}", gutter, line_content)); - } - } - - let mut frame = frame_parts.join("\n"); - - // If message is set but no columns, prepend the message - if !message.is_empty() && !has_columns { - frame = format!("{}{}\n{}", " ".repeat(number_max_width + 1), message, frame); - } - - frame -} - -/// Format a code frame with abbreviation for long spans, -/// matching the JS printCodeFrame() function. -pub fn print_code_frame( - source: &str, - start_line: u32, - start_col: u32, - end_line: u32, - end_col: u32, - message: &str, -) -> String { - let printed = code_frame_columns(source, start_line, start_col, end_line, end_col, message); - - if end_line - start_line < CODEFRAME_MAX_LINES { - return printed; - } - - // Abbreviate: truncate middle - let lines: Vec<&str> = printed.split('\n').collect(); - let head_count = CODEFRAME_LINES_ABOVE as usize + CODEFRAME_ABBREVIATED_SOURCE_LINES; - let tail_count = CODEFRAME_LINES_BELOW as usize + CODEFRAME_ABBREVIATED_SOURCE_LINES; - - if lines.len() <= head_count + tail_count { - return printed; - } - - // Find the pipe index from the first line - let pipe_index = lines[0].find('|').unwrap_or(0); - let tail_start = lines.len() - tail_count; - - let mut parts: Vec<String> = Vec::new(); - for line in &lines[..head_count] { - parts.push(line.to_string()); - } - parts.push(format!("{}\u{2026}", " ".repeat(pipe_index))); - for line in &lines[tail_start..] { - parts.push(line.to_string()); - } - parts.join("\n") -} - -use crate::format_category_heading; - -/// Format a CompilerError into a message string matching the TS compiler's -/// CompilerError.printErrorMessage() / formatCompilerError() format. -/// -/// The source parameter is the full source code of the file being compiled. -/// The filename parameter is the source filename (e.g., "foo.ts") used in -/// location displays. -pub fn format_compiler_error(err: &CompilerError, source: &str, filename: Option<&str>) -> String { - let detail_messages: Vec<String> = err - .details - .iter() - .map(|d| format_error_detail(d, source, filename)) - .collect(); - - let count = err.details.len(); - let plural = if count == 1 { "" } else { "s" }; - let header = format!("Found {} error{}:\n\n", count, plural); - - let trimmed: Vec<String> = detail_messages - .iter() - .map(|m| m.trim().to_string()) - .collect(); - format!("{}{}", header, trimmed.join("\n\n")) -} - -/// Format a single error detail (either Diagnostic or ErrorDetail). -fn format_error_detail( - detail: &CompilerErrorOrDiagnostic, - source: &str, - filename: Option<&str>, -) -> String { - match detail { - CompilerErrorOrDiagnostic::Diagnostic(d) => { - let heading = format_category_heading(d.category); - let mut buffer = vec![format!("{}: {}", heading, d.reason)]; - - if let Some(ref description) = d.description { - buffer.push(format!("\n\n{}.", description)); - } - for item in &d.details { - match item { - CompilerDiagnosticDetail::Error { loc, message, .. } => { - if let Some(loc) = loc { - let frame = print_code_frame( - source, - loc.start.line, - loc.start.column, - loc.end.line, - loc.end.column, - message.as_deref().unwrap_or(""), - ); - buffer.push("\n\n".to_string()); - if let Some(fname) = filename { - buffer.push(format!( - "{}:{}:{}\n", - fname, loc.start.line, loc.start.column - )); - } - buffer.push(frame); - } - } - CompilerDiagnosticDetail::Hint { message } => { - buffer.push("\n\n".to_string()); - buffer.push(message.clone()); - } - } - } - - buffer.join("") - } - CompilerErrorOrDiagnostic::ErrorDetail(d) => { - let heading = format_category_heading(d.category); - let mut buffer = vec![format!("{}: {}", heading, d.reason)]; - - if let Some(ref description) = d.description { - buffer.push(format!("\n\n{}.", description)); - if let Some(ref loc) = d.loc { - let frame = print_code_frame( - source, - loc.start.line, - loc.start.column, - loc.end.line, - loc.end.column, - &d.reason, - ); - buffer.push("\n\n".to_string()); - if let Some(fname) = filename { - buffer.push(format!( - "{}:{}:{}\n", - fname, loc.start.line, loc.start.column - )); - } - buffer.push(frame); - buffer.push("\n\n".to_string()); - } - } else if let Some(ref loc) = d.loc { - let frame = print_code_frame( - source, - loc.start.line, - loc.start.column, - loc.end.line, - loc.end.column, - &d.reason, - ); - buffer.push("\n\n".to_string()); - if let Some(fname) = filename { - buffer.push(format!( - "{}:{}:{}\n", - fname, loc.start.line, loc.start.column - )); - } - buffer.push(frame); - buffer.push("\n\n".to_string()); - } - - buffer.join("") - } - } -} diff --git a/compiler/crates/react_compiler_diagnostics/src/lib.rs b/compiler/crates/react_compiler_diagnostics/src/lib.rs deleted file mode 100644 index 16debb24ec7c..000000000000 --- a/compiler/crates/react_compiler_diagnostics/src/lib.rs +++ /dev/null @@ -1,455 +0,0 @@ -pub mod code_frame; - -use serde::{Deserialize, Serialize}; - -/// Error categories matching the TS ErrorCategory enum -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum ErrorCategory { - Hooks, - CapitalizedCalls, - StaticComponents, - UseMemo, - VoidUseMemo, - PreserveManualMemo, - MemoDependencies, - IncompatibleLibrary, - Immutability, - Globals, - Refs, - EffectDependencies, - EffectExhaustiveDependencies, - EffectSetState, - EffectDerivationsOfState, - ErrorBoundaries, - Purity, - RenderSetState, - Invariant, - Todo, - Syntax, - UnsupportedSyntax, - Config, - Gating, - Suppression, - FBT, -} - -/// Error severity levels -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum ErrorSeverity { - Error, - Warning, - Hint, - Off, -} - -impl ErrorCategory { - pub fn severity(&self) -> ErrorSeverity { - match self { - // These map to "Compilation Skipped" (Warning severity) - ErrorCategory::EffectDependencies - | ErrorCategory::IncompatibleLibrary - | ErrorCategory::PreserveManualMemo - | ErrorCategory::UnsupportedSyntax => ErrorSeverity::Warning, - - // Todo is Hint - ErrorCategory::Todo => ErrorSeverity::Hint, - - // Invariant and all others are Error severity - _ => ErrorSeverity::Error, - } - } - - /// The severity to use in logged output, matching the TS compiler's - /// `getRuleForCategory()`. This may differ from the internal `severity()` - /// used for panicThreshold logic. In particular, `PreserveManualMemo` is - /// `Warning` internally (so it doesn't trigger panicThreshold throws) but - /// `Error` in logged output (matching TS behavior). - pub fn logged_severity(&self) -> ErrorSeverity { - match self { - ErrorCategory::PreserveManualMemo => ErrorSeverity::Error, - _ => self.severity(), - } - } -} - -/// Suggestion operations for auto-fixes -#[derive(Debug, Clone, Serialize)] -pub enum CompilerSuggestionOperation { - InsertBefore, - InsertAfter, - Remove, - Replace, -} - -/// A compiler suggestion for fixing an error -#[derive(Debug, Clone, Serialize)] -pub struct CompilerSuggestion { - pub op: CompilerSuggestionOperation, - pub range: (usize, usize), - pub description: String, - pub text: Option<String>, // None for Remove operations -} - -/// Source location (matches Babel's SourceLocation format) -/// This is the HIR source location, separate from AST's BaseNode location. -/// GeneratedSource is represented as None. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct SourceLocation { - pub start: Position, - pub end: Position, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct Position { - pub line: u32, - pub column: u32, - /// Byte offset in the source file. Preserved for logger event serialization. - #[serde(default, skip_serializing)] - pub index: Option<u32>, -} - -/// Sentinel value for generated/synthetic source locations -pub const GENERATED_SOURCE: Option<SourceLocation> = None; - -/// Detail for a diagnostic -#[derive(Debug, Clone, Serialize)] -pub enum CompilerDiagnosticDetail { - Error { - loc: Option<SourceLocation>, - message: Option<String>, - /// The identifier name from the AST source location, if this error - /// points to an identifier node. Preserved for logger event serialization - /// to match Babel's SourceLocation.identifierName field. - #[serde(skip)] - identifier_name: Option<String>, - }, - Hint { - message: String, - }, -} - -/// A single compiler diagnostic (new-style) -#[derive(Debug, Clone)] -pub struct CompilerDiagnostic { - pub category: ErrorCategory, - pub reason: String, - pub description: Option<String>, - pub details: Vec<CompilerDiagnosticDetail>, - pub suggestions: Option<Vec<CompilerSuggestion>>, -} - -impl CompilerDiagnostic { - pub fn new( - category: ErrorCategory, - reason: impl Into<String>, - description: Option<String>, - ) -> Self { - Self { - category, - reason: reason.into(), - description, - details: Vec::new(), - suggestions: None, - } - } - - pub fn severity(&self) -> ErrorSeverity { - self.category.severity() - } - - pub fn logged_severity(&self) -> ErrorSeverity { - self.category.logged_severity() - } - - pub fn with_detail(mut self, detail: CompilerDiagnosticDetail) -> Self { - self.details.push(detail); - self - } - - /// Create a Todo diagnostic (matches TS `CompilerError.throwTodo()`). - pub fn todo(reason: impl Into<String>, loc: Option<SourceLocation>) -> Self { - let reason = reason.into(); - let mut diag = Self::new(ErrorCategory::Todo, reason.clone(), None); - diag.details.push(CompilerDiagnosticDetail::Error { - loc, - message: Some(reason), - identifier_name: None, - }); - diag - } - - /// Create a diagnostic from a CompilerErrorDetail. - pub fn from_detail(detail: CompilerErrorDetail) -> Self { - Self::new( - detail.category, - detail.reason.clone(), - detail.description.clone(), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: detail.loc, - message: Some(detail.reason), - identifier_name: None, - }) - } - - pub fn primary_location(&self) -> Option<&SourceLocation> { - self.details.iter().find_map(|d| match d { - CompilerDiagnosticDetail::Error { loc, .. } => loc.as_ref(), // identifier_name covered by .. - _ => None, - }) - } -} - -/// Legacy-style error detail (matches CompilerErrorDetail in TS) -#[derive(Debug, Clone, Serialize)] -pub struct CompilerErrorDetail { - pub category: ErrorCategory, - pub reason: String, - pub description: Option<String>, - pub loc: Option<SourceLocation>, - pub suggestions: Option<Vec<CompilerSuggestion>>, -} - -impl CompilerErrorDetail { - pub fn new(category: ErrorCategory, reason: impl Into<String>) -> Self { - Self { - category, - reason: reason.into(), - description: None, - loc: None, - suggestions: None, - } - } - - pub fn with_description(mut self, description: impl Into<String>) -> Self { - self.description = Some(description.into()); - self - } - - pub fn with_loc(mut self, loc: Option<SourceLocation>) -> Self { - self.loc = loc; - self - } - - pub fn severity(&self) -> ErrorSeverity { - self.category.severity() - } - - pub fn logged_severity(&self) -> ErrorSeverity { - self.category.logged_severity() - } -} - -/// Aggregate compiler error - can contain multiple diagnostics. -/// This is the main error type thrown/returned by the compiler. -#[derive(Debug, Clone)] -pub struct CompilerError { - pub details: Vec<CompilerErrorOrDiagnostic>, - /// When false, this error was accumulated on the Environment via - /// `record_error()` / `record_diagnostic()` and returned at the end - /// of the pipeline. In TS, `CompileUnexpectedThrow` is only emitted - /// for errors that are **thrown** (not accumulated). Defaults to `true` - /// because errors created directly (e.g., via `?` from a pass) are - /// analogous to thrown errors in the TS code. - pub is_thrown: bool, -} - -/// Either a new-style diagnostic or legacy error detail -#[derive(Debug, Clone)] -pub enum CompilerErrorOrDiagnostic { - Diagnostic(CompilerDiagnostic), - ErrorDetail(CompilerErrorDetail), -} - -impl CompilerErrorOrDiagnostic { - pub fn severity(&self) -> ErrorSeverity { - match self { - Self::Diagnostic(d) => d.severity(), - Self::ErrorDetail(d) => d.severity(), - } - } - - pub fn logged_severity(&self) -> ErrorSeverity { - match self { - Self::Diagnostic(d) => d.logged_severity(), - Self::ErrorDetail(d) => d.logged_severity(), - } - } -} - -impl CompilerError { - pub fn new() -> Self { - Self { - details: Vec::new(), - is_thrown: true, - } - } - - pub fn push_diagnostic(&mut self, diagnostic: CompilerDiagnostic) { - if diagnostic.severity() != ErrorSeverity::Off { - self.details - .push(CompilerErrorOrDiagnostic::Diagnostic(diagnostic)); - } - } - - pub fn push_error_detail(&mut self, detail: CompilerErrorDetail) { - if detail.severity() != ErrorSeverity::Off { - self.details - .push(CompilerErrorOrDiagnostic::ErrorDetail(detail)); - } - } - - pub fn has_errors(&self) -> bool { - self.details - .iter() - .any(|d| d.severity() == ErrorSeverity::Error) - } - - pub fn has_any_errors(&self) -> bool { - !self.details.is_empty() - } - - /// Check if any error detail has Invariant category. - pub fn has_invariant_errors(&self) -> bool { - self.details.iter().any(|d| { - let cat = match d { - CompilerErrorOrDiagnostic::Diagnostic(d) => d.category, - CompilerErrorOrDiagnostic::ErrorDetail(d) => d.category, - }; - cat == ErrorCategory::Invariant - }) - } - - pub fn merge(&mut self, other: CompilerError) { - self.details.extend(other.details); - } - - /// Check if all error details are non-invariant. - /// In TS, this is used to determine if an error thrown during compilation - /// should be logged as CompileUnexpectedThrow. - pub fn is_all_non_invariant(&self) -> bool { - self.details.iter().all(|d| { - let cat = match d { - CompilerErrorOrDiagnostic::Diagnostic(d) => d.category, - CompilerErrorOrDiagnostic::ErrorDetail(d) => d.category, - }; - cat != ErrorCategory::Invariant - }) - } - - /// Format as a string matching the TS `CompilerError.toString()` output. - /// Used for the `data` field of `CompileUnexpectedThrow` events. - /// - /// Format per detail: `"Category: reason. Description. (line:column)"` - /// Multiple details are joined with `"\n\n"`. - pub fn to_string_for_event(&self) -> String { - self.details - .iter() - .map(|d| { - let (category, reason, description, loc) = match d { - CompilerErrorOrDiagnostic::Diagnostic(d) => { - let loc = d.primary_location().cloned(); - (d.category, &d.reason, &d.description, loc) - } - CompilerErrorOrDiagnostic::ErrorDetail(d) => { - (d.category, &d.reason, &d.description, d.loc) - } - }; - let mut buf = format!("{}: {}", format_category_heading(category), reason); - if let Some(desc) = description { - buf.push_str(&format!(". {}.", desc)); - } - if let Some(loc) = loc { - buf.push_str(&format!(" ({}:{})", loc.start.line, loc.start.column)); - } - buf - }) - .collect::<Vec<_>>() - .join("\n\n") - } -} - -impl Default for CompilerError { - fn default() -> Self { - Self::new() - } -} - -/// Allow `?` to convert a `CompilerError` into a `CompilerDiagnostic` -/// when the enclosing function returns `Result<T, CompilerDiagnostic>`. -/// -/// This typically happens when `record_error()` returns `Err(CompilerError)` -/// for an Invariant error, and the calling function already returns -/// `Result<T, CompilerDiagnostic>`. The conversion extracts the first -/// error detail from the aggregate error. -impl From<CompilerError> for CompilerDiagnostic { - fn from(err: CompilerError) -> Self { - if let Some(first) = err.details.into_iter().next() { - match first { - CompilerErrorOrDiagnostic::Diagnostic(d) => d, - CompilerErrorOrDiagnostic::ErrorDetail(d) => CompilerDiagnostic::from_detail(d), - } - } else { - CompilerDiagnostic::new(ErrorCategory::Invariant, "Unknown compiler error", None) - } - } -} - -impl From<CompilerDiagnostic> for CompilerError { - fn from(diagnostic: CompilerDiagnostic) -> Self { - let mut error = CompilerError::new(); - // Todo diagnostics should produce ErrorDetail (flat loc format), matching - // the TS behavior where CompilerError.throwTodo() creates a CompilerErrorDetail - // with loc directly on it, not a CompilerDiagnostic with sub-details. - if diagnostic.category == ErrorCategory::Todo { - let loc = diagnostic.primary_location().cloned(); - error.push_error_detail(CompilerErrorDetail { - category: diagnostic.category, - reason: diagnostic.reason, - description: diagnostic.description, - loc, - suggestions: diagnostic.suggestions, - }); - } else { - error.push_diagnostic(diagnostic); - } - error - } -} - -impl std::fmt::Display for CompilerError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for detail in &self.details { - match detail { - CompilerErrorOrDiagnostic::Diagnostic(d) => { - write!(f, "{}: {}", format_category_heading(d.category), d.reason)?; - if let Some(desc) = &d.description { - write!(f, ". {}.", desc)?; - } - } - CompilerErrorOrDiagnostic::ErrorDetail(d) => { - write!(f, "{}: {}", format_category_heading(d.category), d.reason)?; - if let Some(desc) = &d.description { - write!(f, ". {}.", desc)?; - } - } - } - writeln!(f)?; - } - Ok(()) - } -} - -impl std::error::Error for CompilerError {} - -pub fn format_category_heading(category: ErrorCategory) -> &'static str { - match category { - ErrorCategory::EffectDependencies - | ErrorCategory::IncompatibleLibrary - | ErrorCategory::PreserveManualMemo - | ErrorCategory::UnsupportedSyntax => "Compilation Skipped", - ErrorCategory::Invariant => "Invariant", - ErrorCategory::Todo => "Todo", - _ => "Error", - } -} diff --git a/compiler/crates/react_compiler_hir/Cargo.toml b/compiler/crates/react_compiler_hir/Cargo.toml deleted file mode 100644 index b410995f125e..000000000000 --- a/compiler/crates/react_compiler_hir/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "react_compiler_hir" -version = "0.1.0" -edition = "2024" - -[dependencies] -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" } -indexmap = { version = "2", features = ["serde"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" diff --git a/compiler/crates/react_compiler_hir/src/default_module_type_provider.rs b/compiler/crates/react_compiler_hir/src/default_module_type_provider.rs deleted file mode 100644 index 282daa177244..000000000000 --- a/compiler/crates/react_compiler_hir/src/default_module_type_provider.rs +++ /dev/null @@ -1,100 +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. - -//! Default module type provider, ported from DefaultModuleTypeProvider.ts. -//! -//! Provides hardcoded type overrides for known-incompatible third-party libraries. - -use indexmap::IndexMap; - -use crate::Effect; -use crate::type_config::{ - BuiltInTypeRef, FunctionTypeConfig, HookTypeConfig, ObjectTypeConfig, TypeConfig, - TypeReferenceConfig, ValueKind, -}; - -/// Returns type configuration for known third-party modules that are -/// incompatible with memoization. Ported from TS `defaultModuleTypeProvider`. -pub fn default_module_type_provider(module_name: &str) -> Option<TypeConfig> { - match module_name { - "react-hook-form" => Some(TypeConfig::Object(ObjectTypeConfig { - properties: Some(IndexMap::from([( - "useForm".to_string(), - TypeConfig::Hook(HookTypeConfig { - return_type: Box::new(TypeConfig::Object(ObjectTypeConfig { - properties: Some(IndexMap::from([( - "watch".to_string(), - TypeConfig::Function(FunctionTypeConfig { - positional_params: Vec::new(), - rest_param: Some(Effect::Read), - callee_effect: Effect::Read, - return_type: Box::new(TypeConfig::TypeReference( - TypeReferenceConfig { - name: BuiltInTypeRef::Any, - }, - )), - return_value_kind: ValueKind::Mutable, - no_alias: None, - mutable_only_if_operands_are_mutable: None, - impure: None, - canonical_name: None, - aliasing: None, - known_incompatible: Some( - "React Hook Form's `useForm()` API returns a `watch()` function which cannot be memoized safely.".to_string(), - ), - }), - )])), - })), - positional_params: None, - rest_param: None, - return_value_kind: None, - no_alias: None, - aliasing: None, - known_incompatible: None, - }), - )])), - })), - - "@tanstack/react-table" => Some(TypeConfig::Object(ObjectTypeConfig { - properties: Some(IndexMap::from([( - "useReactTable".to_string(), - TypeConfig::Hook(HookTypeConfig { - positional_params: Some(Vec::new()), - rest_param: Some(Effect::Read), - return_type: Box::new(TypeConfig::TypeReference(TypeReferenceConfig { - name: BuiltInTypeRef::Any, - })), - return_value_kind: None, - no_alias: None, - aliasing: None, - known_incompatible: Some( - "TanStack Table's `useReactTable()` API returns functions that cannot be memoized safely".to_string(), - ), - }), - )])), - })), - - "@tanstack/react-virtual" => Some(TypeConfig::Object(ObjectTypeConfig { - properties: Some(IndexMap::from([( - "useVirtualizer".to_string(), - TypeConfig::Hook(HookTypeConfig { - positional_params: Some(Vec::new()), - rest_param: Some(Effect::Read), - return_type: Box::new(TypeConfig::TypeReference(TypeReferenceConfig { - name: BuiltInTypeRef::Any, - })), - return_value_kind: None, - no_alias: None, - aliasing: None, - known_incompatible: Some( - "TanStack Virtual's `useVirtualizer()` API returns functions that cannot be memoized safely".to_string(), - ), - }), - )])), - })), - - _ => None, - } -} diff --git a/compiler/crates/react_compiler_hir/src/dominator.rs b/compiler/crates/react_compiler_hir/src/dominator.rs deleted file mode 100644 index 9aaf6e9d5170..000000000000 --- a/compiler/crates/react_compiler_hir/src/dominator.rs +++ /dev/null @@ -1,361 +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. - -//! Dominator and post-dominator tree computation. -//! -//! Port of Dominator.ts and ComputeUnconditionalBlocks.ts. -//! Uses the Cooper/Harvey/Kennedy algorithm from -//! https://www.cs.rice.edu/~keith/Embed/dom.pdf - -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory}; - -use crate::visitors::each_terminal_successor; -use crate::{BlockId, HirFunction, Terminal}; - -// ============================================================================= -// Public types -// ============================================================================= - -/// Stores the immediate post-dominator for each block. -pub struct PostDominator { - /// The exit node (synthetic node representing function exit). - pub exit: BlockId, - nodes: HashMap<BlockId, BlockId>, -} - -impl PostDominator { - /// Returns the immediate post-dominator of the given block, or None if - /// the block post-dominates itself (i.e., it is the exit node). - pub fn get(&self, id: BlockId) -> Option<BlockId> { - let dominator = self - .nodes - .get(&id) - .expect("Unknown node in post-dominator tree"); - if *dominator == id { - None - } else { - Some(*dominator) - } - } -} - -// ============================================================================= -// Graph representation -// ============================================================================= - -struct Node { - id: BlockId, - index: usize, - preds: HashSet<BlockId>, - succs: HashSet<BlockId>, -} - -struct Graph { - entry: BlockId, - /// Nodes stored in iteration order (RPO for reverse graph). - nodes: Vec<Node>, - /// Map from BlockId to index in the nodes vec. - node_index: HashMap<BlockId, usize>, -} - -impl Graph { - fn get_node(&self, id: BlockId) -> &Node { - let idx = self.node_index[&id]; - &self.nodes[idx] - } -} - -// ============================================================================= -// Post-dominator tree computation -// ============================================================================= - -/// Compute the post-dominator tree for a function. -/// -/// If `include_throws_as_exit_node` is true, throw terminals are treated as -/// exit nodes (like return). Otherwise, only return terminals feed into exit. -pub fn compute_post_dominator_tree( - func: &HirFunction, - next_block_id_counter: u32, - include_throws_as_exit_node: bool, -) -> Result<PostDominator, CompilerDiagnostic> { - let graph = build_reverse_graph(func, next_block_id_counter, include_throws_as_exit_node); - let mut nodes = compute_immediate_dominators(&graph)?; - - // When include_throws_as_exit_node is false, nodes that flow into a throw - // terminal and don't reach the exit won't be in the node map. Add them - // with themselves as dominator. - if !include_throws_as_exit_node { - for (id, _) in &func.body.blocks { - nodes.entry(*id).or_insert(*id); - } - } - - Ok(PostDominator { - exit: graph.entry, - nodes, - }) -} - -/// Build the reverse graph from the HIR function. -/// -/// Reverses all edges and adds a synthetic exit node that receives edges from -/// return (and optionally throw) terminals. The result is put into RPO order. -fn build_reverse_graph( - func: &HirFunction, - next_block_id_counter: u32, - include_throws_as_exit_node: bool, -) -> Graph { - let exit_id = BlockId(next_block_id_counter); - - // Build initial nodes with reversed edges - let mut raw_nodes: HashMap<BlockId, Node> = HashMap::new(); - - // Create exit node - raw_nodes.insert( - exit_id, - Node { - id: exit_id, - index: 0, - preds: HashSet::new(), - succs: HashSet::new(), - }, - ); - - for (id, block) in &func.body.blocks { - let successors = each_terminal_successor(&block.terminal); - let mut preds_set: HashSet<BlockId> = successors.into_iter().collect(); - let succs_set: HashSet<BlockId> = block.preds.iter().copied().collect(); - - let is_return = matches!(&block.terminal, Terminal::Return { .. }); - let is_throw = matches!(&block.terminal, Terminal::Throw { .. }); - - if is_return || (is_throw && include_throws_as_exit_node) { - preds_set.insert(exit_id); - raw_nodes.get_mut(&exit_id).unwrap().succs.insert(*id); - } - - raw_nodes.insert( - *id, - Node { - id: *id, - index: 0, - preds: preds_set, - succs: succs_set, - }, - ); - } - - // DFS from exit to compute RPO - let mut visited = HashSet::new(); - let mut postorder = Vec::new(); - dfs_postorder(exit_id, &raw_nodes, &mut visited, &mut postorder); - - // Reverse postorder - postorder.reverse(); - - let mut nodes = Vec::with_capacity(postorder.len()); - let mut node_index = HashMap::new(); - for (idx, id) in postorder.into_iter().enumerate() { - let mut node = raw_nodes.remove(&id).unwrap(); - node.index = idx; - node_index.insert(id, idx); - nodes.push(node); - } - - Graph { - entry: exit_id, - nodes, - node_index, - } -} - -fn dfs_postorder( - id: BlockId, - nodes: &HashMap<BlockId, Node>, - visited: &mut HashSet<BlockId>, - postorder: &mut Vec<BlockId>, -) { - if !visited.insert(id) { - return; - } - if let Some(node) = nodes.get(&id) { - for &succ in &node.succs { - dfs_postorder(succ, nodes, visited, postorder); - } - } - postorder.push(id); -} - -// ============================================================================= -// Dominator fixpoint (Cooper/Harvey/Kennedy) -// ============================================================================= - -fn compute_immediate_dominators( - graph: &Graph, -) -> Result<HashMap<BlockId, BlockId>, CompilerDiagnostic> { - let mut doms: HashMap<BlockId, BlockId> = HashMap::new(); - doms.insert(graph.entry, graph.entry); - - let mut changed = true; - while changed { - changed = false; - for node in &graph.nodes { - if node.id == graph.entry { - continue; - } - - // Find first processed predecessor - let mut new_idom: Option<BlockId> = None; - for &pred in &node.preds { - if doms.contains_key(&pred) { - new_idom = Some(pred); - break; - } - } - let mut new_idom = match new_idom { - Some(idom) => idom, - None => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!( - "At least one predecessor must have been visited for block {:?}", - node.id - ), - None, - )); - } - }; - - // Intersect with other processed predecessors - for &pred in &node.preds { - if pred == new_idom { - continue; - } - if doms.contains_key(&pred) { - new_idom = intersect(pred, new_idom, graph, &doms); - } - } - - if doms.get(&node.id) != Some(&new_idom) { - doms.insert(node.id, new_idom); - changed = true; - } - } - } - Ok(doms) -} - -fn intersect(a: BlockId, b: BlockId, graph: &Graph, doms: &HashMap<BlockId, BlockId>) -> BlockId { - let mut block1 = graph.get_node(a); - let mut block2 = graph.get_node(b); - while block1.id != block2.id { - while block1.index > block2.index { - let dom = doms[&block1.id]; - block1 = graph.get_node(dom); - } - while block2.index > block1.index { - let dom = doms[&block2.id]; - block2 = graph.get_node(dom); - } - } - block1.id -} - -// ============================================================================= -// Post-dominator frontier -// ============================================================================= - -/// Computes the post-dominator frontier of `target_id`. These are immediate -/// predecessors of nodes that post-dominate `target_id` from which execution may -/// not reach `target_id`. Intuitively, these are the earliest blocks from which -/// execution branches such that it may or may not reach the target block. -pub fn post_dominator_frontier( - func: &HirFunction, - post_dominators: &PostDominator, - target_id: BlockId, -) -> HashSet<BlockId> { - let target_post_dominators = post_dominators_of(func, post_dominators, target_id); - let mut visited = HashSet::new(); - let mut frontier = HashSet::new(); - - let mut to_visit: Vec<BlockId> = target_post_dominators.iter().copied().collect(); - to_visit.push(target_id); - - for block_id in to_visit { - if !visited.insert(block_id) { - continue; - } - if let Some(block) = func.body.blocks.get(&block_id) { - for &pred in &block.preds { - if !target_post_dominators.contains(&pred) { - frontier.insert(pred); - } - } - } - } - frontier -} - -/// Walks up the post-dominator tree to collect all blocks that post-dominate `target_id`. -pub fn post_dominators_of( - func: &HirFunction, - post_dominators: &PostDominator, - target_id: BlockId, -) -> HashSet<BlockId> { - let mut result = HashSet::new(); - let mut visited = HashSet::new(); - let mut queue = vec![target_id]; - - while let Some(current_id) = queue.pop() { - if !visited.insert(current_id) { - continue; - } - if let Some(block) = func.body.blocks.get(¤t_id) { - for &pred in &block.preds { - let pred_post_dom = post_dominators.get(pred).unwrap_or(pred); - if pred_post_dom == target_id || result.contains(&pred_post_dom) { - result.insert(pred); - } - queue.push(pred); - } - } - } - result -} - -// ============================================================================= -// Unconditional blocks -// ============================================================================= - -/// Compute the set of blocks that are unconditionally executed from the entry. -/// -/// Port of ComputeUnconditionalBlocks.ts. Walks the immediate post-dominator -/// chain starting from the function entry. A block is unconditional if it lies -/// on this chain (meaning every path through the function must pass through it). -pub fn compute_unconditional_blocks( - func: &HirFunction, - next_block_id_counter: u32, -) -> Result<HashSet<BlockId>, CompilerDiagnostic> { - let mut unconditional = HashSet::new(); - let dominators = compute_post_dominator_tree(func, next_block_id_counter, false)?; - let exit = dominators.exit; - let mut current: Option<BlockId> = Some(func.body.entry); - - while let Some(block_id) = current { - if block_id == exit { - break; - } - assert!( - !unconditional.contains(&block_id), - "Internal error: non-terminating loop in ComputeUnconditionalBlocks" - ); - unconditional.insert(block_id); - current = dominators.get(block_id); - } - - Ok(unconditional) -} diff --git a/compiler/crates/react_compiler_hir/src/environment.rs b/compiler/crates/react_compiler_hir/src/environment.rs deleted file mode 100644 index e1257b65fe53..000000000000 --- a/compiler/crates/react_compiler_hir/src/environment.rs +++ /dev/null @@ -1,1155 +0,0 @@ -use std::collections::HashMap; -use std::collections::HashSet; - -use react_compiler_diagnostics::CompilerDiagnostic; -use react_compiler_diagnostics::CompilerError; -use react_compiler_diagnostics::CompilerErrorDetail; -use react_compiler_diagnostics::ErrorCategory; - -use crate::default_module_type_provider::default_module_type_provider; -use crate::environment_config::EnvironmentConfig; -use crate::globals::Global; -use crate::globals::GlobalRegistry; -use crate::globals::{self}; -use crate::object_shape::BUILT_IN_MIXED_READONLY_ID; -use crate::object_shape::FunctionSignature; -use crate::object_shape::HookKind; -use crate::object_shape::HookSignatureBuilder; -use crate::object_shape::ShapeRegistry; -use crate::object_shape::add_hook; -use crate::object_shape::default_mutating_hook; -use crate::object_shape::default_nonmutating_hook; -use crate::*; - -/// A variable rename from lowering: the binding at `declaration_start` position -/// was renamed from `original` to `renamed`. -#[derive(Debug, Clone)] -pub struct BindingRename { - pub original: String, - pub renamed: String, - pub declaration_start: u32, -} - -/// Output mode for the compiler, mirrored from the entrypoint's CompilerOutputMode. -/// Stored on Environment so pipeline passes can access it. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum OutputMode { - Ssr, - Client, - Lint, -} - -pub struct Environment { - // Counters - pub next_block_id_counter: u32, - pub next_scope_id_counter: u32, - next_mutable_range_id_counter: u32, - - // Arenas (use direct field access for sliced borrows) - pub identifiers: Vec<Identifier>, - pub types: Vec<Type>, - pub scopes: Vec<ReactiveScope>, - pub functions: Vec<HirFunction>, - - // Error accumulation - pub errors: CompilerError, - - // Function type classification (Component, Hook, Other) - pub fn_type: ReactFunctionType, - - // Output mode (Client, Ssr, Lint) - pub output_mode: OutputMode, - - // Source file code (for fast refresh hash computation) - pub code: Option<String>, - - // Source file name (for instrumentation) - pub filename: Option<String>, - - // Pre-resolved import local names for instrumentation/hook guards. - // Set by the program-level code before compilation. - pub instrument_fn_name: Option<String>, - pub instrument_gating_name: Option<String>, - pub hook_guard_name: Option<String>, - - // Renames: tracks variable renames from lowering (original_name → new_name) - // keyed by binding declaration position, for applying back to the Babel AST. - pub renames: Vec<BindingRename>, - - // Node IDs of identifiers that are actual references to bindings. - // Used by codegen to filter type annotation renames — only rename identifiers - // whose node_id is in this set (type labels like ObjectTypeIndexer params - // are NOT in this set and should keep their original names). - pub reference_node_ids: HashSet<u32>, - - // Hoisted identifiers: tracks which bindings have already been hoisted - // via DeclareContext to avoid duplicate hoisting. - // Uses u32 to avoid depending on react_compiler_ast types. - hoisted_identifiers: HashSet<u32>, - - // Config flags for validation passes (kept for backwards compat with existing pipeline code) - pub validate_preserve_existing_memoization_guarantees: bool, - pub validate_no_set_state_in_render: bool, - pub enable_preserve_existing_memoization_guarantees: bool, - - // Type system registries - globals: GlobalRegistry, - pub shapes: ShapeRegistry, - module_types: HashMap<String, Option<Global>>, - module_type_errors: HashMap<String, Vec<String>>, - - // Environment configuration (feature flags, custom hooks, etc.) - pub config: EnvironmentConfig, - - // Cached default hook types (lazily initialized) - default_nonmutating_hook: Option<Global>, - default_mutating_hook: Option<Global>, - - // Outlined functions: functions extracted from the component during outlining passes - outlined_functions: Vec<OutlinedFunctionEntry>, - - // Known names for collision-aware UID generation. Lazily populated from - // identifiers on first use, then updated with each generated name. - // Matches Babel's generateUid behavior of checking hasBinding/hasReference. - uid_known_names: Option<HashSet<String>>, -} - -/// An outlined function entry, stored on Environment during compilation. -/// Corresponds to TS `{ fn: HIRFunction, type: ReactFunctionType | null }`. -#[derive(Debug, Clone)] -pub struct OutlinedFunctionEntry { - pub func: HirFunction, - pub fn_type: Option<ReactFunctionType>, -} - -impl Environment { - pub fn new() -> Self { - Self::with_config(EnvironmentConfig::default()) - } - - /// Create a new Environment with the given configuration. - /// - /// Initializes the shape and global registries, registers custom hooks, - /// and sets up the module type cache. - pub fn with_config(config: EnvironmentConfig) -> Self { - let mut shapes = ShapeRegistry::with_base(globals::base_shapes()); - let mut global_registry = GlobalRegistry::with_base(globals::base_globals()); - - // Register custom hooks from config - for (hook_name, hook) in &config.custom_hooks { - // Don't overwrite existing globals (matches TS invariant) - if global_registry.contains_key(hook_name) { - continue; - } - let return_type = if hook.transitive_mixed_data { - Type::Object { - shape_id: Some(BUILT_IN_MIXED_READONLY_ID.to_string()), - } - } else { - Type::Poly - }; - let hook_type = add_hook( - &mut shapes, - HookSignatureBuilder { - rest_param: Some(hook.effect_kind), - return_type, - return_value_kind: hook.value_kind, - hook_kind: HookKind::Custom, - no_alias: hook.no_alias, - ..Default::default() - }, - None, - ); - global_registry.insert(hook_name.clone(), hook_type); - } - - // Register reanimated module type when enabled - let mut module_types: HashMap<String, Option<Global>> = HashMap::new(); - if config.enable_custom_type_definition_for_reanimated { - let reanimated_module_type = globals::get_reanimated_module_type(&mut shapes); - module_types.insert( - "react-native-reanimated".to_string(), - Some(reanimated_module_type), - ); - } - - Self { - next_block_id_counter: 0, - next_scope_id_counter: 0, - next_mutable_range_id_counter: 0, - identifiers: Vec::new(), - types: Vec::new(), - scopes: Vec::new(), - functions: Vec::new(), - errors: CompilerError::new(), - fn_type: ReactFunctionType::Other, - output_mode: OutputMode::Client, - code: None, - filename: None, - instrument_fn_name: None, - instrument_gating_name: None, - hook_guard_name: None, - renames: Vec::new(), - reference_node_ids: HashSet::new(), - hoisted_identifiers: HashSet::new(), - validate_preserve_existing_memoization_guarantees: config - .validate_preserve_existing_memoization_guarantees, - validate_no_set_state_in_render: config.validate_no_set_state_in_render, - enable_preserve_existing_memoization_guarantees: config - .enable_preserve_existing_memoization_guarantees, - globals: global_registry, - shapes, - module_types, - module_type_errors: HashMap::new(), - default_nonmutating_hook: None, - default_mutating_hook: None, - outlined_functions: Vec::new(), - uid_known_names: None, - config, - } - } - - /// Create a child Environment for compiling an outlined function. - /// - /// The child shares the same config, globals, and shapes, and receives copies of - /// all arenas (identifiers, types, scopes, functions) so that references from - /// the outlined HIR remain valid. Block/scope counters start past the cloned - /// data to avoid ID conflicts. - pub fn for_outlined_fn(&self, fn_type: ReactFunctionType) -> Self { - Self { - // Start block counter past any existing blocks in the outlined function. - // The outlined function has BlockId(0), parent may have more. Use parent's - // counter which is guaranteed to be > any block ID in the outlined function. - next_block_id_counter: self.next_block_id_counter, - // Scope counter must be consistent with scopes vec length - next_scope_id_counter: self.scopes.len() as u32, - next_mutable_range_id_counter: self.next_mutable_range_id_counter, - identifiers: self.identifiers.clone(), - types: self.types.clone(), - scopes: self.scopes.clone(), - functions: self.functions.clone(), - errors: CompilerError::new(), - fn_type, - output_mode: self.output_mode, - code: self.code.clone(), - filename: self.filename.clone(), - instrument_fn_name: self.instrument_fn_name.clone(), - instrument_gating_name: self.instrument_gating_name.clone(), - hook_guard_name: self.hook_guard_name.clone(), - renames: Vec::new(), - reference_node_ids: HashSet::new(), - hoisted_identifiers: HashSet::new(), - validate_preserve_existing_memoization_guarantees: self - .validate_preserve_existing_memoization_guarantees, - validate_no_set_state_in_render: self.validate_no_set_state_in_render, - enable_preserve_existing_memoization_guarantees: self - .enable_preserve_existing_memoization_guarantees, - globals: self.globals.clone(), - shapes: self.shapes.clone(), - module_types: self.module_types.clone(), - module_type_errors: self.module_type_errors.clone(), - config: self.config.clone(), - default_nonmutating_hook: self.default_nonmutating_hook.clone(), - default_mutating_hook: self.default_mutating_hook.clone(), - outlined_functions: Vec::new(), - uid_known_names: self.uid_known_names.clone(), - } - } - - pub fn next_block_id(&mut self) -> BlockId { - let id = BlockId(self.next_block_id_counter); - self.next_block_id_counter += 1; - id - } - - /// Create a new MutableRange with a unique ID. - /// Use this when creating a logically new range (not copying an existing one). - /// To copy a range preserving its identity, use `.clone()` instead. - pub fn new_mutable_range( - &mut self, - start: EvaluationOrder, - end: EvaluationOrder, - ) -> MutableRange { - let id = MutableRangeId(self.next_mutable_range_id_counter); - self.next_mutable_range_id_counter += 1; - MutableRange { id, start, end } - } - - /// Allocate a new Identifier in the arena with default values, - /// returns its IdentifierId. - pub fn next_identifier_id(&mut self) -> IdentifierId { - let id = IdentifierId(self.identifiers.len() as u32); - let type_id = self.make_type(); - let mutable_range = self.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0)); - self.identifiers.push(Identifier { - id, - declaration_id: DeclarationId(id.0), - name: None, - mutable_range, - scope: None, - type_: type_id, - loc: None, - }); - id - } - - /// Allocate a new ReactiveScope in the arena, returns its ScopeId. - pub fn next_scope_id(&mut self) -> ScopeId { - let id = ScopeId(self.next_scope_id_counter); - self.next_scope_id_counter += 1; - let range = self.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0)); - self.scopes.push(ReactiveScope { - id, - range, - dependencies: Vec::new(), - declarations: Vec::new(), - reassignments: Vec::new(), - early_return_value: None, - merged: Vec::new(), - loc: None, - }); - id - } - - /// Allocate a new Type in the arena, returns its TypeId. - pub fn next_type_id(&mut self) -> TypeId { - let id = TypeId(self.types.len() as u32); - self.types.push(Type::TypeVar { id }); - id - } - - /// Allocate a new Type (TypeVar) in the arena, returns its TypeId. - pub fn make_type(&mut self) -> TypeId { - self.next_type_id() - } - - pub fn add_function(&mut self, func: HirFunction) -> FunctionId { - let id = FunctionId(self.functions.len() as u32); - self.functions.push(func); - id - } - - pub fn record_error(&mut self, detail: CompilerErrorDetail) -> Result<(), CompilerError> { - if detail.category == ErrorCategory::Invariant { - let detail_clone = detail.clone(); - self.errors.push_error_detail(detail); - let mut err = CompilerError::new(); - err.push_error_detail(detail_clone); - return Err(err); - } - self.errors.push_error_detail(detail); - Ok(()) - } - - pub fn record_diagnostic(&mut self, diagnostic: CompilerDiagnostic) { - self.errors.push_diagnostic(diagnostic); - } - - pub fn has_errors(&self) -> bool { - self.errors.has_any_errors() - } - - pub fn error_count(&self) -> usize { - self.errors.details.len() - } - - /// Check if any recorded errors have Invariant category. - /// In TS, Invariant errors throw immediately from recordError(), - /// which aborts the current operation. - pub fn has_invariant_errors(&self) -> bool { - self.errors.has_invariant_errors() - } - - pub fn errors(&self) -> &CompilerError { - &self.errors - } - - pub fn take_errors(&mut self) -> CompilerError { - let mut errors = std::mem::take(&mut self.errors); - // Mark as not thrown — these are accumulated errors returned at the end - // of the pipeline, not errors thrown by a pass. - errors.is_thrown = false; - errors - } - - /// Take errors added after position `since_count`, leaving earlier errors in place. - /// Used to detect new errors added by a specific pass. - pub fn take_errors_since(&mut self, since_count: usize) -> CompilerError { - let mut taken = CompilerError::new(); - if self.errors.details.len() > since_count { - taken.details = self.errors.details.split_off(since_count); - } - taken - } - - /// Take only the Invariant errors, leaving non-Invariant errors in place. - /// In TS, Invariant errors throw as a separate CompilerError, so only - /// the Invariant error is surfaced. - pub fn take_invariant_errors(&mut self) -> CompilerError { - let mut invariant = CompilerError::new(); - let mut remaining = CompilerError::new(); - let old = std::mem::take(&mut self.errors); - for detail in old.details { - let is_invariant = match &detail { - react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => { - d.category == react_compiler_diagnostics::ErrorCategory::Invariant - } - react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => { - d.category == react_compiler_diagnostics::ErrorCategory::Invariant - } - }; - if is_invariant { - invariant.details.push(detail); - } else { - remaining.details.push(detail); - } - } - self.errors = remaining; - invariant - } - - /// Check if any recorded errors have Todo category. - /// In TS, Todo errors throw immediately via CompilerError.throwTodo(). - pub fn has_todo_errors(&self) -> bool { - self.errors.details.iter().any(|d| match d { - react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => { - d.category == react_compiler_diagnostics::ErrorCategory::Todo - } - react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => { - d.category == react_compiler_diagnostics::ErrorCategory::Todo - } - }) - } - - /// Take errors that would have been thrown in TS (Invariant and Todo), - /// leaving other accumulated errors in place. - pub fn take_thrown_errors(&mut self) -> CompilerError { - let mut thrown = CompilerError::new(); - let mut remaining = CompilerError::new(); - let old = std::mem::take(&mut self.errors); - for detail in old.details { - let is_thrown = match &detail { - react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => { - d.category == react_compiler_diagnostics::ErrorCategory::Invariant - || d.category == react_compiler_diagnostics::ErrorCategory::Todo - } - react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => { - d.category == react_compiler_diagnostics::ErrorCategory::Invariant - || d.category == react_compiler_diagnostics::ErrorCategory::Todo - } - }; - if is_thrown { - thrown.details.push(detail); - } else { - remaining.details.push(detail); - } - } - self.errors = remaining; - thrown - } - - /// Check if a binding has been hoisted (via DeclareContext) already. - pub fn is_hoisted_identifier(&self, binding_id: u32) -> bool { - self.hoisted_identifiers.contains(&binding_id) - } - - /// Mark a binding as hoisted. - pub fn add_hoisted_identifier(&mut self, binding_id: u32) { - self.hoisted_identifiers.insert(binding_id); - } - - // ========================================================================= - // Type resolution methods (ported from Environment.ts) - // ========================================================================= - - /// Resolve a non-local binding to its type. Ported from TS `getGlobalDeclaration`. - /// - /// The `loc` parameter is used for error diagnostics when validating module type - /// configurations. Pass `None` if no source location is available. - pub fn get_global_declaration( - &mut self, - binding: &NonLocalBinding, - loc: Option<SourceLocation>, - ) -> Result<Option<Global>, CompilerError> { - match binding { - NonLocalBinding::ModuleLocal { name, .. } => { - if is_hook_name(name) { - Ok(Some(self.get_custom_hook_type())) - } else { - Ok(None) - } - } - NonLocalBinding::Global { name, .. } => { - if let Some(ty) = self.globals.get(name) { - return Ok(Some(ty.clone())); - } - if is_hook_name(name) { - Ok(Some(self.get_custom_hook_type())) - } else { - Ok(None) - } - } - NonLocalBinding::ImportSpecifier { - name, - module, - imported, - } => { - if self.is_known_react_module(module) { - if let Some(ty) = self.globals.get(imported) { - return Ok(Some(ty.clone())); - } - if is_hook_name(imported) || is_hook_name(name) { - return Ok(Some(self.get_custom_hook_type())); - } - return Ok(None); - } - - // Try module type provider. We resolve first, then do property - // lookup on the cloned result to avoid double-borrow of self. - let module_type = self.resolve_module_type(module); - - // Check for module type validation errors (hook-name vs hook-type mismatches) - if let Some(errors) = self.module_type_errors.remove(module.as_str()) { - if let Some(first_error) = errors.into_iter().next() { - self.record_error( - CompilerErrorDetail::new( - ErrorCategory::Config, - "Invalid type configuration for module", - ) - .with_description(format!("{}", first_error)) - .with_loc(loc), - )?; - } - } - - if let Some(module_type) = module_type { - if let Some(imported_type) = - Self::get_property_type_from_shapes(&self.shapes, &module_type, imported) - { - return Ok(Some(imported_type)); - } - } - - if is_hook_name(imported) || is_hook_name(name) { - Ok(Some(self.get_custom_hook_type())) - } else { - Ok(None) - } - } - NonLocalBinding::ImportDefault { name, module } - | NonLocalBinding::ImportNamespace { name, module } => { - let is_default = matches!(binding, NonLocalBinding::ImportDefault { .. }); - - if self.is_known_react_module(module) { - if let Some(ty) = self.globals.get(name) { - return Ok(Some(ty.clone())); - } - if is_hook_name(name) { - return Ok(Some(self.get_custom_hook_type())); - } - return Ok(None); - } - - let module_type = self.resolve_module_type(module); - - // Check for module type validation errors (hook-name vs hook-type mismatches) - if let Some(errors) = self.module_type_errors.remove(module.as_str()) { - if let Some(first_error) = errors.into_iter().next() { - self.record_error( - CompilerErrorDetail::new( - ErrorCategory::Config, - "Invalid type configuration for module", - ) - .with_description(format!("{}", first_error)) - .with_loc(loc), - )?; - } - } - - if let Some(module_type) = module_type { - let imported_type = if is_default { - Self::get_property_type_from_shapes(&self.shapes, &module_type, "default") - } else { - Some(module_type) - }; - if let Some(imported_type) = imported_type { - // Validate hook-name vs hook-type consistency for module name - let expect_hook = is_hook_name(module); - let is_hook = self - .get_hook_kind_for_type(&imported_type) - .ok() - .flatten() - .is_some(); - if expect_hook != is_hook { - self.record_error( - CompilerErrorDetail::new( - ErrorCategory::Config, - "Invalid type configuration for module", - ) - .with_description(format!( - "Expected type for `import ... from '{}'` {} based on the module name", - module, - if expect_hook { "to be a hook" } else { "not to be a hook" } - )) - .with_loc(loc), - )?; - } - return Ok(Some(imported_type)); - } - } - - if is_hook_name(name) { - Ok(Some(self.get_custom_hook_type())) - } else { - Ok(None) - } - } - } - } - - /// Static helper: resolve a property type using only the shapes registry. - /// Used internally to avoid double-borrow of `self`. Includes hook-name - /// fallback matching TS `getPropertyType`. - fn get_property_type_from_shapes( - shapes: &ShapeRegistry, - receiver: &Type, - property: &str, - ) -> Option<Type> { - let shape_id = match receiver { - Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(), - _ => None, - }; - if let Some(shape_id) = shape_id { - let shape = shapes.get(shape_id)?; - if let Some(ty) = shape.properties.get(property) { - return Some(ty.clone()); - } - if let Some(ty) = shape.properties.get("*") { - return Some(ty.clone()); - } - // Hook-name fallback: callers that need the custom hook type - // check is_hook_name after this returns None, which produces - // the same result as the TS getPropertyType hook-name fallback. - } - None - } - - /// Get the type of a named property on a receiver type. - /// Ported from TS `getPropertyType`. - pub fn get_property_type( - &mut self, - receiver: &Type, - property: &str, - ) -> Result<Option<Type>, CompilerDiagnostic> { - let shape_id = match receiver { - Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(), - _ => None, - }; - if let Some(shape_id) = shape_id { - let shape = self.shapes.get(shape_id).ok_or_else(|| { - CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!( - "[HIR] Forget internal error: cannot resolve shape {}", - shape_id - ), - None, - ) - })?; - if let Some(ty) = shape.properties.get(property) { - return Ok(Some(ty.clone())); - } - // Fall through to wildcard - if let Some(ty) = shape.properties.get("*") { - return Ok(Some(ty.clone())); - } - // If property name looks like a hook, return custom hook type - if is_hook_name(property) { - return Ok(Some(self.get_custom_hook_type())); - } - return Ok(None); - } - // No shape ID — if property looks like a hook, return custom hook type - if is_hook_name(property) { - return Ok(Some(self.get_custom_hook_type())); - } - Ok(None) - } - - /// Get the type of a numeric property on a receiver type. - /// Ported from the numeric branch of TS `getPropertyType`. - pub fn get_property_type_numeric( - &self, - receiver: &Type, - ) -> Result<Option<Type>, CompilerDiagnostic> { - let shape_id = match receiver { - Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(), - _ => None, - }; - if let Some(shape_id) = shape_id { - let shape = self.shapes.get(shape_id).ok_or_else(|| { - CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!( - "[HIR] Forget internal error: cannot resolve shape {}", - shape_id - ), - None, - ) - })?; - return Ok(shape.properties.get("*").cloned()); - } - Ok(None) - } - - /// Get the fallthrough (wildcard `*`) property type for computed property access. - /// Ported from TS `getFallthroughPropertyType`. - pub fn get_fallthrough_property_type( - &self, - receiver: &Type, - ) -> Result<Option<Type>, CompilerDiagnostic> { - let shape_id = match receiver { - Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(), - _ => None, - }; - if let Some(shape_id) = shape_id { - let shape = self.shapes.get(shape_id).ok_or_else(|| { - CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!( - "[HIR] Forget internal error: cannot resolve shape {}", - shape_id - ), - None, - ) - })?; - return Ok(shape.properties.get("*").cloned()); - } - Ok(None) - } - - /// Get the function signature for a function type. - /// Ported from TS `getFunctionSignature`. - pub fn get_function_signature( - &self, - ty: &Type, - ) -> Result<Option<&FunctionSignature>, CompilerDiagnostic> { - let shape_id = match ty { - Type::Function { shape_id, .. } => shape_id.as_deref(), - _ => return Ok(None), - }; - if let Some(shape_id) = shape_id { - let shape = self.shapes.get(shape_id).ok_or_else(|| { - CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!( - "[HIR] Forget internal error: cannot resolve shape {}", - shape_id - ), - None, - ) - })?; - return Ok(shape.function_type.as_ref()); - } - Ok(None) - } - - /// Get the hook kind for a type, if it represents a hook. - /// Ported from TS `getHookKindForType` in HIR.ts. - pub fn get_hook_kind_for_type( - &self, - ty: &Type, - ) -> Result<Option<&HookKind>, CompilerDiagnostic> { - Ok(self - .get_function_signature(ty)? - .and_then(|sig| sig.hook_kind.as_ref())) - } - - /// Resolve the module type provider for a given module name. - /// Caches results. Checks pre-resolved provider results first, then falls - /// back to `defaultModuleTypeProvider` (hardcoded). - fn resolve_module_type(&mut self, module_name: &str) -> Option<Global> { - if let Some(cached) = self.module_types.get(module_name) { - return cached.clone(); - } - - // Check pre-resolved provider results first, then fall back to default - let module_config = self - .config - .module_type_provider - .as_ref() - .and_then(|map| map.get(module_name).cloned()) - .or_else(|| default_module_type_provider(module_name)); - - let module_type = module_config.map(|config| { - let mut type_errors: Vec<String> = Vec::new(); - let ty = globals::install_type_config_with_errors( - &mut self.globals, - &mut self.shapes, - &config, - module_name, - (), - &mut type_errors, - ); - // Store errors for later reporting when the import is actually used - for err in type_errors { - self.module_type_errors - .entry(module_name.to_string()) - .or_default() - .push(err); - } - ty - }); - self.module_types - .insert(module_name.to_string(), module_type.clone()); - module_type - } - - fn is_known_react_module(&self, module_name: &str) -> bool { - let lower = module_name.to_lowercase(); - lower == "react" || lower == "react-dom" - } - - fn get_custom_hook_type(&mut self) -> Global { - if self.config.enable_assume_hooks_follow_rules_of_react { - if self.default_nonmutating_hook.is_none() { - self.default_nonmutating_hook = Some(default_nonmutating_hook(&mut self.shapes)); - } - self.default_nonmutating_hook.clone().unwrap() - } else { - if self.default_mutating_hook.is_none() { - self.default_mutating_hook = Some(default_mutating_hook(&mut self.shapes)); - } - self.default_mutating_hook.clone().unwrap() - } - } - - /// Public accessor for the custom hook type, used by InferTypes for - /// property resolution fallback when a property name looks like a hook. - pub fn get_custom_hook_type_opt(&mut self) -> Option<Global> { - Some(self.get_custom_hook_type()) - } - - /// Get a reference to the shapes registry. - pub fn shapes(&self) -> &ShapeRegistry { - &self.shapes - } - - /// Get a reference to the globals registry. - pub fn globals(&self) -> &GlobalRegistry { - &self.globals - } - - /// Generate a globally unique identifier name, analogous to TS - /// `generateGloballyUniqueIdentifierName` which delegates to Babel's - /// `scope.generateUidIdentifier`. Matches Babel's naming convention: - /// first name is `_<name>`, subsequent are `_<name>2`, `_<name>3`, etc. - /// Also applies Babel's `toIdentifier` sanitization on the input name. - /// - /// Like Babel's `generateUid`, checks for collisions against existing - /// bindings (source-level identifier names) and previously generated UIDs, - /// rather than using a blind counter. - pub fn generate_globally_unique_identifier_name(&mut self, name: Option<&str>) -> String { - let base = name.unwrap_or("temp"); - // Apply Babel's toIdentifier sanitization: - // 1. Replace non-identifier chars with '-' - // 2. Strip leading '-' and digits - // 3. CamelCase: replace '-' sequences + optional following char with uppercase of that char - let mut dashed = String::new(); - for c in base.chars() { - if c.is_ascii_alphanumeric() || c == '_' || c == '$' { - dashed.push(c); - } else { - dashed.push('-'); - } - } - // Strip leading dashes and digits - let trimmed = dashed.trim_start_matches(|c: char| c == '-' || c.is_ascii_digit()); - // CamelCase conversion: replace sequences of '-' followed by optional char with uppercase - let mut camel = String::new(); - let mut chars = trimmed.chars().peekable(); - while let Some(c) = chars.next() { - if c == '-' { - while chars.peek() == Some(&'-') { - chars.next(); - } - if let Some(next) = chars.next() { - for uc in next.to_uppercase() { - camel.push(uc); - } - } - } else { - camel.push(c); - } - } - if camel.is_empty() { - camel = "temp".to_string(); - } - // Strip leading '_' and trailing digits (Babel's generateUid behavior) - let stripped = camel.trim_start_matches('_'); - let stripped = stripped.trim_end_matches(|c: char| c.is_ascii_digit()); - let uid_base = if stripped.is_empty() { - "temp" - } else { - stripped - }; - - // Lazily build the set of known names from existing identifiers. - // This approximates Babel's hasBinding/hasGlobal/hasReference checks. - if self.uid_known_names.is_none() { - let mut known = HashSet::new(); - for id in &self.identifiers { - if let Some(name) = &id.name { - known.insert(name.value().to_string()); - } - } - self.uid_known_names = Some(known); - } - - // Find a name that doesn't collide, matching Babel's generateUid loop - let mut i = 1u32; - let uid = loop { - let candidate = if i == 1 { - format!("_{}", uid_base) - } else { - format!("_{}{}", uid_base, i) - }; - i += 1; - if !self.uid_known_names.as_ref().unwrap().contains(&candidate) { - break candidate; - } - }; - - // Register the generated name so subsequent calls see it - self.uid_known_names.as_mut().unwrap().insert(uid.clone()); - - uid - } - - /// Seed the UID known names set with external names (e.g. from ProgramContext). - /// This ensures UID generation avoids names generated by previous function compilations, - /// matching Babel's behavior where the program scope accumulates all generated UIDs. - pub fn seed_uid_known_names(&mut self, names: &HashSet<String>) { - match &mut self.uid_known_names { - Some(existing) => existing.extend(names.iter().cloned()), - None => self.uid_known_names = Some(names.clone()), - } - } - - /// Return the UID known names accumulated during this compilation. - pub fn take_uid_known_names(&mut self) -> Option<HashSet<String>> { - self.uid_known_names.take() - } - - /// Record an outlined function (extracted during outlineFunctions or outlineJSX). - /// Corresponds to TS `env.outlineFunction(fn, type)`. - pub fn outline_function(&mut self, func: HirFunction, fn_type: Option<ReactFunctionType>) { - self.outlined_functions - .push(OutlinedFunctionEntry { func, fn_type }); - } - - /// Get the outlined functions accumulated during compilation. - pub fn get_outlined_functions(&self) -> &[OutlinedFunctionEntry] { - &self.outlined_functions - } - - /// Take the outlined functions, leaving the vec empty. - pub fn take_outlined_functions(&mut self) -> Vec<OutlinedFunctionEntry> { - std::mem::take(&mut self.outlined_functions) - } - - /// Whether memoization is enabled for this compilation. - /// Ported from TS `get enableMemoization()` in Environment.ts. - /// Returns true for client/lint modes, false for SSR. - pub fn enable_memoization(&self) -> bool { - match self.output_mode { - OutputMode::Client | OutputMode::Lint => true, - OutputMode::Ssr => false, - } - } - - /// Whether validations are enabled for this compilation. - /// Ported from TS `get enableValidations()` in Environment.ts. - pub fn enable_validations(&self) -> bool { - match self.output_mode { - OutputMode::Client | OutputMode::Lint | OutputMode::Ssr => true, - } - } - - // ========================================================================= - // Name resolution helpers - // ========================================================================= - - /// Get the user-visible name for an identifier. - /// - /// First checks the identifier's own name. If None, looks for another - /// identifier with the same `declaration_id` that has a name. This handles - /// SSA identifiers that don't carry names but share a declaration_id with - /// the original named identifier from lowering. - /// - /// This is analogous to `identifierName` on Babel's SourceLocation, - /// which the parser sets on every identifier node. - pub fn identifier_name_for_id(&self, id: IdentifierId) -> Option<String> { - let ident = &self.identifiers[id.0 as usize]; - if let Some(name) = &ident.name { - return Some(name.value().to_string()); - } - // Fall back: find another identifier with the same declaration_id that has a Named name - let decl_id = ident.declaration_id; - for other in &self.identifiers { - if other.declaration_id == decl_id { - if let Some(IdentifierName::Named(name)) = &other.name { - return Some(name.clone()); - } - } - } - None - } - - // ========================================================================= - // ID-based type helper methods - // ========================================================================= - - /// Check whether the function type for an identifier has a noAlias signature. - /// Looks up the identifier's type and checks its function signature. - pub fn has_no_alias_signature(&self, identifier_id: IdentifierId) -> bool { - let ty = &self.types[self.identifiers[identifier_id.0 as usize].type_.0 as usize]; - self.get_function_signature(ty) - .ok() - .flatten() - .map_or(false, |sig| sig.no_alias) - } - - /// Get the hook kind for an identifier, if its type represents a hook. - /// Looks up the identifier's type and delegates to `get_hook_kind_for_type`. - pub fn get_hook_kind_for_id( - &self, - identifier_id: IdentifierId, - ) -> Result<Option<&HookKind>, CompilerDiagnostic> { - let ty = &self.types[self.identifiers[identifier_id.0 as usize].type_.0 as usize]; - self.get_hook_kind_for_type(ty) - } -} - -impl Default for Environment { - fn default() -> Self { - Self::new() - } -} - -/// Check if a name matches the React hook naming convention: `use[A-Z0-9]`. -/// Ported from TS `isHookName` in Environment.ts. -pub fn is_hook_name(name: &str) -> bool { - if name.len() < 4 { - return false; - } - if !name.starts_with("use") { - return false; - } - let fourth_char = name.as_bytes()[3]; - fourth_char.is_ascii_uppercase() || fourth_char.is_ascii_digit() -} - -/// Returns true if the name follows React naming conventions (component or hook). -/// Components start with an uppercase letter; hooks match `use[A-Z0-9]`. -pub fn is_react_like_name(name: &str) -> bool { - if name.is_empty() { - return false; - } - let first_char = name.as_bytes()[0]; - if first_char.is_ascii_uppercase() { - return true; - } - is_hook_name(name) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_is_hook_name() { - assert!(is_hook_name("useState")); - assert!(is_hook_name("useEffect")); - assert!(is_hook_name("useMyHook")); - assert!(is_hook_name("use3rdParty")); - assert!(!is_hook_name("use")); - assert!(!is_hook_name("used")); - assert!(!is_hook_name("useless")); - assert!(!is_hook_name("User")); - assert!(!is_hook_name("foo")); - } - - #[test] - fn test_environment_has_globals() { - let env = Environment::new(); - assert!(env.globals().contains_key("useState")); - assert!(env.globals().contains_key("useEffect")); - assert!(env.globals().contains_key("useRef")); - assert!(env.globals().contains_key("Math")); - assert!(env.globals().contains_key("console")); - assert!(env.globals().contains_key("Array")); - assert!(env.globals().contains_key("Object")); - } - - #[test] - fn test_get_property_type_array() { - let mut env = Environment::new(); - let array_type = Type::Object { - shape_id: Some("BuiltInArray".to_string()), - }; - let map_type = env.get_property_type(&array_type, "map").unwrap(); - assert!(map_type.is_some()); - let push_type = env.get_property_type(&array_type, "push").unwrap(); - assert!(push_type.is_some()); - let nonexistent = env - .get_property_type(&array_type, "nonExistentMethod") - .unwrap(); - assert!(nonexistent.is_none()); - } - - #[test] - fn test_get_function_signature() { - let env = Environment::new(); - let use_state_type = env.globals().get("useState").unwrap(); - let sig = env.get_function_signature(use_state_type).unwrap(); - assert!(sig.is_some()); - let sig = sig.unwrap(); - assert!(sig.hook_kind.is_some()); - assert_eq!(sig.hook_kind.as_ref().unwrap(), &HookKind::UseState); - } - - #[test] - fn test_get_global_declaration() { - let mut env = Environment::new(); - // Global binding - let binding = NonLocalBinding::Global { - name: "Math".to_string(), - }; - let result = env.get_global_declaration(&binding, None).unwrap(); - assert!(result.is_some()); - - // Import from react - let binding = NonLocalBinding::ImportSpecifier { - name: "useState".to_string(), - module: "react".to_string(), - imported: "useState".to_string(), - }; - let result = env.get_global_declaration(&binding, None).unwrap(); - assert!(result.is_some()); - - // Unknown global - let binding = NonLocalBinding::Global { - name: "unknownThing".to_string(), - }; - let result = env.get_global_declaration(&binding, None).unwrap(); - assert!(result.is_none()); - - // Hook-like name gets default hook type - let binding = NonLocalBinding::Global { - name: "useCustom".to_string(), - }; - let result = env.get_global_declaration(&binding, None).unwrap(); - assert!(result.is_some()); - } -} diff --git a/compiler/crates/react_compiler_hir/src/environment_config.rs b/compiler/crates/react_compiler_hir/src/environment_config.rs deleted file mode 100644 index 042802ac7650..000000000000 --- a/compiler/crates/react_compiler_hir/src/environment_config.rs +++ /dev/null @@ -1,229 +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. - -//! Environment configuration, ported from EnvironmentConfigSchema in Environment.ts. -//! -//! Contains feature flags and custom hook definitions that control compiler behavior. - -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; - -use crate::Effect; -use crate::type_config::{TypeConfig, ValueKind}; - -/// External function reference (source module + import name). -/// Corresponds to TS `ExternalFunction`. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ExternalFunctionConfig { - pub source: String, - pub import_specifier_name: String, -} - -/// Instrumentation configuration. -/// Corresponds to TS `InstrumentationSchema`. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct InstrumentationConfig { - #[serde(rename = "fn")] - pub fn_: ExternalFunctionConfig, - #[serde(default)] - pub gating: Option<ExternalFunctionConfig>, - #[serde(default)] - pub global_gating: Option<String>, -} - -/// Custom hook configuration, ported from TS `HookSchema`. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct HookConfig { - pub effect_kind: Effect, - pub value_kind: ValueKind, - #[serde(default)] - pub no_alias: bool, - #[serde(default)] - pub transitive_mixed_data: bool, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExhaustiveEffectDepsMode { - #[serde(rename = "off")] - Off, - #[serde(rename = "all")] - All, - #[serde(rename = "missing-only")] - MissingOnly, - #[serde(rename = "extra-only")] - ExtraOnly, -} - -impl Default for ExhaustiveEffectDepsMode { - fn default() -> Self { - Self::Off - } -} - -fn default_true() -> bool { - true -} - -/// Compiler environment configuration. Contains feature flags and settings. -/// -/// Fields that would require passing JS functions across the JS/Rust boundary -/// are omitted with TODO comments. The Rust port uses hardcoded defaults for -/// these (e.g., `defaultModuleTypeProvider`). -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EnvironmentConfig { - /// Custom hook type definitions, keyed by hook name. - #[serde(default)] - pub custom_hooks: HashMap<String, HookConfig>, - - /// Pre-resolved module type provider results. - /// Map from module name to TypeConfig, computed by the JS shim. - #[serde(default)] - pub module_type_provider: Option<indexmap::IndexMap<String, TypeConfig>>, - - /// Custom macro-like function names that should have their operands - /// memoized in the same scope (similar to fbt). - #[serde(default)] - pub custom_macros: Option<Vec<String>>, - - /// If true, emit code to reset the memo cache on source file changes (HMR/fast refresh). - /// If null (None), HMR detection is conditionally enabled based on NODE_ENV/__DEV__. - #[serde(default)] - pub enable_reset_cache_on_source_file_changes: Option<bool>, - - #[serde(default = "default_true")] - pub enable_preserve_existing_memoization_guarantees: bool, - #[serde(default = "default_true")] - pub validate_preserve_existing_memoization_guarantees: bool, - #[serde(default = "default_true")] - pub validate_exhaustive_memoization_dependencies: bool, - #[serde(default)] - pub validate_exhaustive_effect_dependencies: ExhaustiveEffectDepsMode, - - // TODO: flowTypeProvider — requires JS function callback. - #[serde(default = "default_true")] - pub enable_optional_dependencies: bool, - #[serde(default)] - pub enable_name_anonymous_functions: bool, - #[serde(default = "default_true")] - pub validate_hooks_usage: bool, - #[serde(default = "default_true")] - pub validate_ref_access_during_render: bool, - #[serde(default = "default_true")] - pub validate_no_set_state_in_render: bool, - #[serde(default)] - pub enable_use_keyed_state: bool, - #[serde(default)] - pub validate_no_set_state_in_effects: bool, - #[serde(default)] - pub validate_no_derived_computations_in_effects: bool, - #[serde(default)] - #[serde(alias = "validateNoDerivedComputationsInEffects_exp")] - pub validate_no_derived_computations_in_effects_exp: bool, - #[serde(default)] - #[serde(alias = "validateNoJSXInTryStatements")] - pub validate_no_jsx_in_try_statements: bool, - #[serde(default)] - pub validate_static_components: bool, - #[serde(default)] - pub validate_no_capitalized_calls: Option<Vec<String>>, - #[serde(default)] - #[serde(alias = "restrictedImports")] - pub validate_blocklisted_imports: Option<Vec<String>>, - #[serde(default)] - pub validate_source_locations: bool, - #[serde(default)] - pub validate_no_impure_functions_in_render: bool, - #[serde(default)] - pub validate_no_freezing_known_mutable_functions: bool, - #[serde(default = "default_true")] - pub enable_assume_hooks_follow_rules_of_react: bool, - #[serde(default = "default_true")] - pub enable_transitively_freeze_function_expressions: bool, - - /// Hook guard configuration. When set, wraps hook calls with dispatcher guard calls. - #[serde(default)] - pub enable_emit_hook_guards: Option<ExternalFunctionConfig>, - - /// Instrumentation configuration. When set, emits calls to instrument functions. - #[serde(default)] - pub enable_emit_instrument_forget: Option<InstrumentationConfig>, - - #[serde(default = "default_true")] - pub enable_function_outlining: bool, - #[serde(default)] - pub enable_jsx_outlining: bool, - #[serde(default)] - pub assert_valid_mutable_ranges: bool, - #[serde(default)] - #[serde(alias = "throwUnknownException__testonly")] - pub throw_unknown_exception_testonly: bool, - #[serde(default)] - pub enable_custom_type_definition_for_reanimated: bool, - #[serde(default = "default_true")] - pub enable_treat_ref_like_identifiers_as_refs: bool, - #[serde(default)] - pub enable_treat_set_identifiers_as_state_setters: bool, - #[serde(default = "default_true")] - pub validate_no_void_use_memo: bool, - #[serde(default = "default_true")] - pub enable_allow_set_state_from_refs_in_effects: bool, - #[serde(default)] - pub enable_verbose_no_set_state_in_effect: bool, - - // 🌲 - #[serde(default)] - pub enable_forest: bool, -} - -impl Default for EnvironmentConfig { - fn default() -> Self { - Self { - custom_hooks: HashMap::new(), - enable_reset_cache_on_source_file_changes: None, - module_type_provider: None, - enable_preserve_existing_memoization_guarantees: true, - validate_preserve_existing_memoization_guarantees: true, - validate_exhaustive_memoization_dependencies: true, - validate_exhaustive_effect_dependencies: ExhaustiveEffectDepsMode::Off, - enable_optional_dependencies: true, - enable_name_anonymous_functions: false, - validate_hooks_usage: true, - validate_ref_access_during_render: true, - validate_no_set_state_in_render: true, - enable_use_keyed_state: false, - validate_no_set_state_in_effects: false, - validate_no_derived_computations_in_effects: false, - validate_no_derived_computations_in_effects_exp: false, - validate_no_jsx_in_try_statements: false, - validate_static_components: false, - validate_no_capitalized_calls: None, - validate_blocklisted_imports: None, - validate_source_locations: false, - validate_no_impure_functions_in_render: false, - validate_no_freezing_known_mutable_functions: false, - enable_assume_hooks_follow_rules_of_react: true, - enable_transitively_freeze_function_expressions: true, - enable_emit_hook_guards: None, - enable_emit_instrument_forget: None, - enable_function_outlining: true, - enable_jsx_outlining: false, - assert_valid_mutable_ranges: false, - throw_unknown_exception_testonly: false, - enable_custom_type_definition_for_reanimated: false, - enable_treat_ref_like_identifiers_as_refs: true, - enable_treat_set_identifiers_as_state_setters: false, - validate_no_void_use_memo: true, - enable_allow_set_state_from_refs_in_effects: true, - enable_verbose_no_set_state_in_effect: false, - enable_forest: false, - custom_macros: None, - } - } -} diff --git a/compiler/crates/react_compiler_hir/src/globals.rs b/compiler/crates/react_compiler_hir/src/globals.rs deleted file mode 100644 index ae93255ff434..000000000000 --- a/compiler/crates/react_compiler_hir/src/globals.rs +++ /dev/null @@ -1,2381 +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. - -//! Global type registry and built-in shape definitions, ported from Globals.ts. -//! -//! Provides `DEFAULT_SHAPES` (built-in object shapes) and `DEFAULT_GLOBALS` -//! (global variable types including React hooks and JS built-ins). - -use std::collections::HashMap; -use std::sync::LazyLock; - -use crate::Effect; -use crate::Type; -use crate::object_shape::*; -use crate::type_config::AliasingEffectConfig; -use crate::type_config::AliasingSignatureConfig; -use crate::type_config::ApplyArgConfig; -use crate::type_config::ApplyArgHoleKind; -use crate::type_config::BuiltInTypeRef; -use crate::type_config::TypeConfig; -use crate::type_config::TypeReferenceConfig; -use crate::type_config::ValueKind; -use crate::type_config::ValueReason; - -/// Type alias matching TS `Global = BuiltInType | PolyType`. -/// In the Rust port, both map to our `Type` enum. -pub type Global = Type; - -/// Registry mapping global names to their types. -/// -/// Supports two modes: -/// - **Builder mode** (`base=None`): wraps a single HashMap, used during -/// `build_default_globals` to construct the static base. -/// - **Overlay mode** (`base=Some`): holds a `&'static HashMap` base plus a small -/// extras HashMap. Lookups check extras first, then base. Inserts go into extras. -/// Cloning only copies the extras map (the base pointer is shared). -pub struct GlobalRegistry { - base: Option<&'static HashMap<String, Global>>, - entries: HashMap<String, Global>, -} - -impl GlobalRegistry { - /// Create an empty builder-mode registry. - pub fn new() -> Self { - Self { - base: None, - entries: HashMap::new(), - } - } - - /// Create an overlay-mode registry backed by a static base. - pub fn with_base(base: &'static HashMap<String, Global>) -> Self { - Self { - base: Some(base), - entries: HashMap::new(), - } - } - - pub fn get(&self, key: &str) -> Option<&Global> { - self.entries - .get(key) - .or_else(|| self.base.and_then(|b| b.get(key))) - } - - pub fn insert(&mut self, key: String, value: Global) { - self.entries.insert(key, value); - } - - pub fn contains_key(&self, key: &str) -> bool { - self.entries.contains_key(key) || self.base.map_or(false, |b| b.contains_key(key)) - } - - /// Iterate over all keys in the registry (base + extras). - /// Keys in extras that shadow base keys appear only once. - pub fn keys(&self) -> impl Iterator<Item = &String> { - let base_keys = self - .base - .into_iter() - .flat_map(|b| b.keys()) - .filter(|k| !self.entries.contains_key(k.as_str())); - self.entries.keys().chain(base_keys) - } - - /// Consume the registry and return the inner HashMap. - /// Only valid in builder mode (no base). - pub fn into_inner(self) -> HashMap<String, Global> { - debug_assert!( - self.base.is_none(), - "into_inner() called on overlay-mode GlobalRegistry" - ); - self.entries - } -} - -impl Clone for GlobalRegistry { - fn clone(&self) -> Self { - Self { - base: self.base, - entries: self.entries.clone(), - } - } -} - -// ============================================================================= -// Static base registries (initialized once, shared across all Environments) -// ============================================================================= - -struct BaseRegistries { - shapes: HashMap<String, ObjectShape>, - globals: HashMap<String, Global>, -} - -static BASE: LazyLock<BaseRegistries> = LazyLock::new(|| { - let mut shapes = build_builtin_shapes(); - let globals = build_default_globals(&mut shapes); - BaseRegistries { - shapes: shapes.into_inner(), - globals: globals.into_inner(), - } -}); - -/// Get a reference to the static base shapes registry. -pub fn base_shapes() -> &'static HashMap<String, ObjectShape> { - &BASE.shapes -} - -/// Get a reference to the static base globals registry. -pub fn base_globals() -> &'static HashMap<String, Global> { - &BASE.globals -} - -// ============================================================================= -// installTypeConfig — converts TypeConfig to internal Type -// ============================================================================= - -/// Convert a user-provided TypeConfig into an internal Type, registering shapes -/// as needed. Ported from TS `installTypeConfig` in Globals.ts. -/// If `errors` is provided, hook-name vs hook-type consistency validation -/// errors are collected there. -pub fn install_type_config( - _globals: &mut GlobalRegistry, - shapes: &mut ShapeRegistry, - type_config: &TypeConfig, - module_name: &str, - _loc: (), -) -> Global { - install_type_config_inner(_globals, shapes, type_config, module_name, _loc, &mut None) -} - -/// Like `install_type_config` but collects validation errors. -pub fn install_type_config_with_errors( - _globals: &mut GlobalRegistry, - shapes: &mut ShapeRegistry, - type_config: &TypeConfig, - module_name: &str, - _loc: (), - errors: &mut Vec<String>, -) -> Global { - install_type_config_inner( - _globals, - shapes, - type_config, - module_name, - _loc, - &mut Some(errors), - ) -} - -fn install_type_config_inner( - _globals: &mut GlobalRegistry, - shapes: &mut ShapeRegistry, - type_config: &TypeConfig, - module_name: &str, - _loc: (), - errors: &mut Option<&mut Vec<String>>, -) -> Global { - match type_config { - TypeConfig::TypeReference(TypeReferenceConfig { name }) => match name { - BuiltInTypeRef::Array => Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - BuiltInTypeRef::MixedReadonly => Type::Object { - shape_id: Some(BUILT_IN_MIXED_READONLY_ID.to_string()), - }, - BuiltInTypeRef::Primitive => Type::Primitive, - BuiltInTypeRef::Ref => Type::Object { - shape_id: Some(BUILT_IN_USE_REF_ID.to_string()), - }, - BuiltInTypeRef::Any => Type::Poly, - }, - TypeConfig::Function(func_config) => { - // Compute return type first to avoid double-borrow of shapes - let return_type = install_type_config_inner( - _globals, - shapes, - &func_config.return_type, - module_name, - (), - errors, - ); - add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: func_config.positional_params.clone(), - rest_param: func_config.rest_param, - callee_effect: func_config.callee_effect, - return_type, - return_value_kind: func_config.return_value_kind, - no_alias: func_config.no_alias.unwrap_or(false), - mutable_only_if_operands_are_mutable: func_config - .mutable_only_if_operands_are_mutable - .unwrap_or(false), - impure: func_config.impure.unwrap_or(false), - canonical_name: func_config.canonical_name.clone(), - aliasing: func_config.aliasing.clone(), - known_incompatible: func_config.known_incompatible.clone(), - ..Default::default() - }, - None, - false, - ) - } - TypeConfig::Hook(hook_config) => { - // Compute return type first to avoid double-borrow of shapes - let return_type = install_type_config_inner( - _globals, - shapes, - &hook_config.return_type, - module_name, - (), - errors, - ); - add_hook( - shapes, - HookSignatureBuilder { - hook_kind: HookKind::Custom, - positional_params: hook_config.positional_params.clone().unwrap_or_default(), - rest_param: hook_config.rest_param.or(Some(Effect::Freeze)), - callee_effect: Effect::Read, - return_type, - return_value_kind: hook_config.return_value_kind.unwrap_or(ValueKind::Frozen), - no_alias: hook_config.no_alias.unwrap_or(false), - aliasing: hook_config.aliasing.clone(), - known_incompatible: hook_config.known_incompatible.clone(), - ..Default::default() - }, - None, - ) - } - TypeConfig::Object(obj_config) => { - let properties: Vec<(String, Type)> = obj_config - .properties - .as_ref() - .map(|props| { - props - .iter() - .map(|(key, value)| { - let ty = install_type_config_inner( - _globals, - shapes, - value, - module_name, - (), - errors, - ); - // Validate hook-name vs hook-type consistency (matching TS installTypeConfig) - if let Some(errs) = errors { - let expect_hook = crate::environment::is_hook_name(key); - let is_hook = match &ty { - Type::Function { shape_id: Some(id), .. } => { - shapes.get(id) - .and_then(|shape| shape.function_type.as_ref()) - .and_then(|ft| ft.hook_kind.as_ref()) - .is_some() - } - _ => false, - }; - if expect_hook != is_hook { - errs.push(format!( - "Expected type for object property '{}' from module '{}' {} based on the property name", - key, - module_name, - if expect_hook { "to be a hook" } else { "not to be a hook" } - )); - } - } - (key.clone(), ty) - }) - .collect() - }) - .unwrap_or_default(); - add_object(shapes, None, properties) - } - } -} - -// ============================================================================= -// Build built-in shapes (BUILTIN_SHAPES from ObjectShape.ts) -// ============================================================================= - -/// Build the built-in shapes registry. This corresponds to TS `BUILTIN_SHAPES` -/// defined at module level in ObjectShape.ts. -pub fn build_builtin_shapes() -> ShapeRegistry { - let mut shapes = ShapeRegistry::new(); - - // BuiltInProps: { ref: UseRefType } - add_object( - &mut shapes, - Some(BUILT_IN_PROPS_ID), - vec![( - "ref".to_string(), - Type::Object { - shape_id: Some(BUILT_IN_USE_REF_ID.to_string()), - }, - )], - ); - - build_array_shape(&mut shapes); - build_set_shape(&mut shapes); - build_map_shape(&mut shapes); - build_weak_set_shape(&mut shapes); - build_weak_map_shape(&mut shapes); - build_object_shape(&mut shapes); - build_ref_shapes(&mut shapes); - build_state_shapes(&mut shapes); - build_hook_shapes(&mut shapes); - build_misc_shapes(&mut shapes); - - shapes -} - -fn simple_function( - shapes: &mut ShapeRegistry, - positional_params: Vec<Effect>, - rest_param: Option<Effect>, - return_type: Type, - return_value_kind: ValueKind, -) -> Type { - add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params, - rest_param, - return_type, - return_value_kind, - ..Default::default() - }, - None, - false, - ) -} - -/// Shorthand for a pure function returning Primitive. -fn pure_primitive_fn(shapes: &mut ShapeRegistry) -> Type { - simple_function( - shapes, - Vec::new(), - Some(Effect::Read), - Type::Primitive, - ValueKind::Primitive, - ) -} - -fn build_array_shape(shapes: &mut ShapeRegistry) { - let index_of = pure_primitive_fn(shapes); - let includes = pure_primitive_fn(shapes); - let pop = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - callee_effect: Effect::Store, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let at = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - callee_effect: Effect::Capture, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let concat = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Capture), - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - callee_effect: Effect::Capture, - ..Default::default() - }, - None, - false, - ); - let join = pure_primitive_fn(shapes); - let slice = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Read), - callee_effect: Effect::Capture, - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let map = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - callee_effect: Effect::ConditionallyMutate, - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - aliasing: Some(AliasingSignatureConfig { - receiver: "@receiver".to_string(), - params: vec!["@callback".to_string()], - rest: None, - returns: "@returns".to_string(), - temporaries: vec![ - "@item".to_string(), - "@callbackReturn".to_string(), - "@thisArg".to_string(), - ], - effects: vec![ - // Map creates a new mutable array - AliasingEffectConfig::Create { - into: "@returns".to_string(), - value: ValueKind::Mutable, - reason: ValueReason::KnownReturnSignature, - }, - // The first arg to the callback is an item extracted from the receiver array - AliasingEffectConfig::CreateFrom { - from: "@receiver".to_string(), - into: "@item".to_string(), - }, - // The undefined this for the callback - AliasingEffectConfig::Create { - into: "@thisArg".to_string(), - value: ValueKind::Primitive, - reason: ValueReason::KnownReturnSignature, - }, - // Calls the callback, returning the result into a temporary - AliasingEffectConfig::Apply { - receiver: "@thisArg".to_string(), - function: "@callback".to_string(), - mutates_function: false, - args: vec![ - ApplyArgConfig::Place("@item".to_string()), - ApplyArgConfig::Hole { - kind: ApplyArgHoleKind::Hole, - }, - ApplyArgConfig::Place("@receiver".to_string()), - ], - into: "@callbackReturn".to_string(), - }, - // Captures the result of the callback into the return array - AliasingEffectConfig::Capture { - from: "@callbackReturn".to_string(), - into: "@returns".to_string(), - }, - ], - }), - ..Default::default() - }, - None, - false, - ); - let filter = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - callee_effect: Effect::ConditionallyMutate, - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let find = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - callee_effect: Effect::ConditionallyMutate, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let find_index = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - callee_effect: Effect::ConditionallyMutate, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let every = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - callee_effect: Effect::ConditionallyMutate, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let some = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - callee_effect: Effect::ConditionallyMutate, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let flat_map = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - callee_effect: Effect::ConditionallyMutate, - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let length = Type::Primitive; - let push = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Capture), - callee_effect: Effect::Store, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - aliasing: Some(AliasingSignatureConfig { - receiver: "@receiver".to_string(), - params: Vec::new(), - rest: Some("@rest".to_string()), - returns: "@returns".to_string(), - temporaries: Vec::new(), - effects: vec![ - // Push directly mutates the array itself - AliasingEffectConfig::Mutate { - value: "@receiver".to_string(), - }, - // The arguments are captured into the array - AliasingEffectConfig::Capture { - from: "@rest".to_string(), - into: "@receiver".to_string(), - }, - // Returns the new length, a primitive - AliasingEffectConfig::Create { - into: "@returns".to_string(), - value: ValueKind::Primitive, - reason: ValueReason::KnownReturnSignature, - }, - ], - }), - ..Default::default() - }, - None, - false, - ); - - add_object( - shapes, - Some(BUILT_IN_ARRAY_ID), - vec![ - ("indexOf".to_string(), index_of), - ("includes".to_string(), includes), - ("pop".to_string(), pop), - ("at".to_string(), at), - ("concat".to_string(), concat), - ("length".to_string(), length), - ("push".to_string(), push), - ("slice".to_string(), slice), - ("map".to_string(), map), - ("flatMap".to_string(), flat_map), - ("filter".to_string(), filter), - ("every".to_string(), every), - ("some".to_string(), some), - ("find".to_string(), find), - ("findIndex".to_string(), find_index), - ("join".to_string(), join), - // TODO: rest of Array properties - ], - ); -} - -fn build_set_shape(shapes: &mut ShapeRegistry) { - let has = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let add = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Capture], - callee_effect: Effect::Store, - return_type: Type::Object { - shape_id: Some(BUILT_IN_SET_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - aliasing: Some(AliasingSignatureConfig { - receiver: "@receiver".to_string(), - params: Vec::new(), - rest: Some("@rest".to_string()), - returns: "@returns".to_string(), - temporaries: Vec::new(), - effects: vec![ - // Set.add returns the receiver Set - AliasingEffectConfig::Assign { - from: "@receiver".to_string(), - into: "@returns".to_string(), - }, - // Set.add mutates the set itself - AliasingEffectConfig::Mutate { - value: "@receiver".to_string(), - }, - // Captures the rest params into the set - AliasingEffectConfig::Capture { - from: "@rest".to_string(), - into: "@receiver".to_string(), - }, - ], - }), - ..Default::default() - }, - None, - false, - ); - let clear = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - callee_effect: Effect::Store, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let delete = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - callee_effect: Effect::Store, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let size = Type::Primitive; - let difference = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Capture], - callee_effect: Effect::Capture, - return_type: Type::Object { - shape_id: Some(BUILT_IN_SET_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let union = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Capture], - callee_effect: Effect::Capture, - return_type: Type::Object { - shape_id: Some(BUILT_IN_SET_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let symmetrical_difference = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Capture], - callee_effect: Effect::Capture, - return_type: Type::Object { - shape_id: Some(BUILT_IN_SET_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let is_subset_of = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - callee_effect: Effect::Read, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let is_superset_of = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - callee_effect: Effect::Read, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let for_each = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - callee_effect: Effect::ConditionallyMutate, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let values = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - callee_effect: Effect::Capture, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let keys = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - callee_effect: Effect::Capture, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let entries = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - callee_effect: Effect::Capture, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - - add_object( - shapes, - Some(BUILT_IN_SET_ID), - vec![ - ("add".to_string(), add), - ("clear".to_string(), clear), - ("delete".to_string(), delete), - ("has".to_string(), has), - ("size".to_string(), size), - ("difference".to_string(), difference), - ("union".to_string(), union), - ("symmetricalDifference".to_string(), symmetrical_difference), - ("isSubsetOf".to_string(), is_subset_of), - ("isSupersetOf".to_string(), is_superset_of), - ("forEach".to_string(), for_each), - ("values".to_string(), values), - ("keys".to_string(), keys), - ("entries".to_string(), entries), - ], - ); -} - -fn build_map_shape(shapes: &mut ShapeRegistry) { - let has = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let get = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - callee_effect: Effect::Capture, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let clear = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - callee_effect: Effect::Store, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let set = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Capture, Effect::Capture], - callee_effect: Effect::Store, - return_type: Type::Object { - shape_id: Some(BUILT_IN_MAP_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let delete = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - callee_effect: Effect::Store, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let size = Type::Primitive; - let for_each = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - callee_effect: Effect::ConditionallyMutate, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let values = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - callee_effect: Effect::Capture, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let keys = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - callee_effect: Effect::Capture, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let entries = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - callee_effect: Effect::Capture, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - - add_object( - shapes, - Some(BUILT_IN_MAP_ID), - vec![ - ("has".to_string(), has), - ("get".to_string(), get), - ("set".to_string(), set), - ("clear".to_string(), clear), - ("delete".to_string(), delete), - ("size".to_string(), size), - ("forEach".to_string(), for_each), - ("values".to_string(), values), - ("keys".to_string(), keys), - ("entries".to_string(), entries), - ], - ); -} - -fn build_weak_set_shape(shapes: &mut ShapeRegistry) { - let has = pure_primitive_fn(shapes); - let add = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Capture], - callee_effect: Effect::Store, - return_type: Type::Object { - shape_id: Some(BUILT_IN_WEAK_SET_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let delete = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - callee_effect: Effect::Store, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - - add_object( - shapes, - Some(BUILT_IN_WEAK_SET_ID), - vec![ - ("has".to_string(), has), - ("add".to_string(), add), - ("delete".to_string(), delete), - ], - ); -} - -fn build_weak_map_shape(shapes: &mut ShapeRegistry) { - let has = pure_primitive_fn(shapes); - let get = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - callee_effect: Effect::Capture, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let set = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Capture, Effect::Capture], - callee_effect: Effect::Store, - return_type: Type::Object { - shape_id: Some(BUILT_IN_WEAK_MAP_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let delete = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - callee_effect: Effect::Store, - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - - add_object( - shapes, - Some(BUILT_IN_WEAK_MAP_ID), - vec![ - ("has".to_string(), has), - ("get".to_string(), get), - ("set".to_string(), set), - ("delete".to_string(), delete), - ], - ); -} - -fn build_object_shape(shapes: &mut ShapeRegistry) { - // BuiltInObject: has toString() returning Primitive (matches TS BuiltInObjectId shape) - let to_string = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - add_object( - shapes, - Some(BUILT_IN_OBJECT_ID), - vec![("toString".to_string(), to_string)], - ); - // BuiltInFunction: empty shape - add_object(shapes, Some(BUILT_IN_FUNCTION_ID), Vec::new()); - // BuiltInJsx: empty shape - add_object(shapes, Some(BUILT_IN_JSX_ID), Vec::new()); - // BuiltInMixedReadonly: has explicit method types + wildcard returning MixedReadonly - // (matches TS BuiltInMixedReadonlyId shape) - let mixed_to_string = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Read), - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let mixed_index_of = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Read), - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let mixed_includes = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Read), - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let mixed_at = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - return_type: Type::Object { - shape_id: Some(BUILT_IN_MIXED_READONLY_ID.to_string()), - }, - callee_effect: Effect::Capture, - return_value_kind: ValueKind::Frozen, - ..Default::default() - }, - None, - false, - ); - let mixed_map = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - callee_effect: Effect::ConditionallyMutate, - return_value_kind: ValueKind::Mutable, - no_alias: true, - ..Default::default() - }, - None, - false, - ); - let mixed_flat_map = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - callee_effect: Effect::ConditionallyMutate, - return_value_kind: ValueKind::Mutable, - no_alias: true, - ..Default::default() - }, - None, - false, - ); - let mixed_filter = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - callee_effect: Effect::ConditionallyMutate, - return_value_kind: ValueKind::Mutable, - no_alias: true, - ..Default::default() - }, - None, - false, - ); - let mixed_concat = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Capture), - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - callee_effect: Effect::Capture, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let mixed_slice = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Read), - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - callee_effect: Effect::Capture, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let mixed_every = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - return_type: Type::Primitive, - callee_effect: Effect::ConditionallyMutate, - return_value_kind: ValueKind::Primitive, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let mixed_some = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - return_type: Type::Primitive, - callee_effect: Effect::ConditionallyMutate, - return_value_kind: ValueKind::Primitive, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let mixed_find = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - return_type: Type::Object { - shape_id: Some(BUILT_IN_MIXED_READONLY_ID.to_string()), - }, - callee_effect: Effect::ConditionallyMutate, - return_value_kind: ValueKind::Frozen, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let mixed_find_index = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - return_type: Type::Primitive, - callee_effect: Effect::ConditionallyMutate, - return_value_kind: ValueKind::Primitive, - no_alias: true, - mutable_only_if_operands_are_mutable: true, - ..Default::default() - }, - None, - false, - ); - let mixed_join = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Read), - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let mut mixed_props = HashMap::new(); - mixed_props.insert("toString".to_string(), mixed_to_string); - mixed_props.insert("indexOf".to_string(), mixed_index_of); - mixed_props.insert("includes".to_string(), mixed_includes); - mixed_props.insert("at".to_string(), mixed_at); - mixed_props.insert("map".to_string(), mixed_map); - mixed_props.insert("flatMap".to_string(), mixed_flat_map); - mixed_props.insert("filter".to_string(), mixed_filter); - mixed_props.insert("concat".to_string(), mixed_concat); - mixed_props.insert("slice".to_string(), mixed_slice); - mixed_props.insert("every".to_string(), mixed_every); - mixed_props.insert("some".to_string(), mixed_some); - mixed_props.insert("find".to_string(), mixed_find); - mixed_props.insert("findIndex".to_string(), mixed_find_index); - mixed_props.insert("join".to_string(), mixed_join); - mixed_props.insert( - "*".to_string(), - Type::Object { - shape_id: Some(BUILT_IN_MIXED_READONLY_ID.to_string()), - }, - ); - shapes.insert( - BUILT_IN_MIXED_READONLY_ID.to_string(), - ObjectShape { - properties: mixed_props, - function_type: None, - }, - ); -} - -fn build_ref_shapes(shapes: &mut ShapeRegistry) { - // BuiltInUseRefId: { current: Object { shapeId: BuiltInRefValue } } - add_object( - shapes, - Some(BUILT_IN_USE_REF_ID), - vec![( - "current".to_string(), - Type::Object { - shape_id: Some(BUILT_IN_REF_VALUE_ID.to_string()), - }, - )], - ); - // BuiltInRefValue: { *: Object { shapeId: BuiltInRefValue } } (self-referencing) - add_object( - shapes, - Some(BUILT_IN_REF_VALUE_ID), - vec![( - "*".to_string(), - Type::Object { - shape_id: Some(BUILT_IN_REF_VALUE_ID.to_string()), - }, - )], - ); -} - -fn build_state_shapes(shapes: &mut ShapeRegistry) { - // BuiltInSetState: function that freezes its argument - let set_state = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - Some(BUILT_IN_SET_STATE_ID), - false, - ); - - // BuiltInUseState: object with [0] = Poly (state), [1] = setState function - add_object( - shapes, - Some(BUILT_IN_USE_STATE_ID), - vec![("0".to_string(), Type::Poly), ("1".to_string(), set_state)], - ); - - // BuiltInSetActionState - let set_action_state = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - Some(BUILT_IN_SET_ACTION_STATE_ID), - false, - ); - - // BuiltInUseActionState: [0] = Poly, [1] = setActionState function - add_object( - shapes, - Some(BUILT_IN_USE_ACTION_STATE_ID), - vec![ - ("0".to_string(), Type::Poly), - ("1".to_string(), set_action_state), - ], - ); - - // BuiltInDispatch - let dispatch = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - Some(BUILT_IN_DISPATCH_ID), - false, - ); - - // BuiltInUseReducer: [0] = Poly, [1] = dispatch function - add_object( - shapes, - Some(BUILT_IN_USE_REDUCER_ID), - vec![("0".to_string(), Type::Poly), ("1".to_string(), dispatch)], - ); - - // BuiltInStartTransition - let start_transition = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - // Note: TS uses restParam: null for startTransition - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - Some(BUILT_IN_START_TRANSITION_ID), - false, - ); - - // BuiltInUseTransition: [0] = Primitive (isPending), [1] = startTransition function - add_object( - shapes, - Some(BUILT_IN_USE_TRANSITION_ID), - vec![ - ("0".to_string(), Type::Primitive), - ("1".to_string(), start_transition), - ], - ); - - // BuiltInSetOptimistic - let set_optimistic = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - Some(BUILT_IN_SET_OPTIMISTIC_ID), - false, - ); - - // BuiltInUseOptimistic: [0] = Poly, [1] = setOptimistic function - add_object( - shapes, - Some(BUILT_IN_USE_OPTIMISTIC_ID), - vec![ - ("0".to_string(), Type::Poly), - ("1".to_string(), set_optimistic), - ], - ); -} - -fn build_hook_shapes(shapes: &mut ShapeRegistry) { - // BuiltInEffectEvent function shape (the return value of useEffectEvent) - add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - callee_effect: Effect::ConditionallyMutate, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - Some(BUILT_IN_EFFECT_EVENT_ID), - false, - ); -} - -fn build_misc_shapes(shapes: &mut ShapeRegistry) { - // ReanimatedSharedValue: empty properties (matching TS) - add_object(shapes, Some(REANIMATED_SHARED_VALUE_ID), Vec::new()); -} - -/// Build the reanimated module type. Ported from TS `getReanimatedModuleType`. -pub fn get_reanimated_module_type(shapes: &mut ShapeRegistry) -> Type { - let mut reanimated_type: Vec<(String, Type)> = Vec::new(); - - // hooks that freeze args and return frozen value - let frozen_hooks = [ - "useFrameCallback", - "useAnimatedStyle", - "useAnimatedProps", - "useAnimatedScrollHandler", - "useAnimatedReaction", - "useWorkletCallback", - ]; - for hook in &frozen_hooks { - let hook_type = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - no_alias: true, - hook_kind: HookKind::Custom, - ..Default::default() - }, - None, - ); - reanimated_type.push((hook.to_string(), hook_type)); - } - - // hooks that return a mutable value (modelled as shared value) - let mutable_hooks = ["useSharedValue", "useDerivedValue"]; - for hook in &mutable_hooks { - let hook_type = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Object { - shape_id: Some(REANIMATED_SHARED_VALUE_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - no_alias: true, - hook_kind: HookKind::Custom, - ..Default::default() - }, - None, - ); - reanimated_type.push((hook.to_string(), hook_type)); - } - - // functions that return mutable value - let funcs = [ - "withTiming", - "withSpring", - "createAnimatedPropAdapter", - "withDecay", - "withRepeat", - "runOnUI", - "executeOnUIRuntimeSync", - ]; - for func_name in &funcs { - let func_type = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Read), - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - no_alias: true, - ..Default::default() - }, - None, - false, - ); - reanimated_type.push((func_name.to_string(), func_type)); - } - - add_object(shapes, None, reanimated_type) -} - -// ============================================================================= -// Build default globals (DEFAULT_GLOBALS from Globals.ts) -// ============================================================================= - -/// Build the default globals registry. This corresponds to TS `DEFAULT_GLOBALS`. -/// -/// Requires a mutable reference to the shapes registry because some globals -/// (like Object.keys, Array.isArray) register new shapes. -pub fn build_default_globals(shapes: &mut ShapeRegistry) -> GlobalRegistry { - let mut globals = GlobalRegistry::new(); - - // React APIs — returns the list so we can reuse them for the React namespace - let react_apis = build_react_apis(shapes, &mut globals); - - // Untyped globals (treated as Poly) — must come before typed globals - // so typed definitions take priority (matching TS ordering) - for name in UNTYPED_GLOBALS { - globals.insert(name.to_string(), Type::Poly); - } - - // Typed JS globals (overwrites Poly entries from UNTYPED_GLOBALS). - // Returns the list of typed globals for use as globalThis/global properties. - let typed_globals = build_typed_globals(shapes, &mut globals, react_apis); - - // globalThis and global — populated with all typed globals as properties - // (matching TS: `addObject(DEFAULT_SHAPES, 'globalThis', TYPED_GLOBALS)`) - globals.insert( - "globalThis".to_string(), - add_object(shapes, Some("globalThis"), typed_globals.clone()), - ); - globals.insert( - "global".to_string(), - add_object(shapes, Some("global"), typed_globals), - ); - - globals -} - -const UNTYPED_GLOBALS: &[&str] = &[ - "Object", - "Function", - "RegExp", - "Date", - "Error", - "TypeError", - "RangeError", - "ReferenceError", - "SyntaxError", - "URIError", - "EvalError", - "DataView", - "Float32Array", - "Float64Array", - "Int8Array", - "Int16Array", - "Int32Array", - "WeakMap", - "Uint8Array", - "Uint8ClampedArray", - "Uint16Array", - "Uint32Array", - "ArrayBuffer", - "JSON", - "console", - "eval", -]; - -/// Build the React API types (REACT_APIS from TS). Returns the list of (name, type) pairs -/// so they can be reused as properties of the React namespace object (matching TS behavior -/// where the SAME type objects are used in both DEFAULT_GLOBALS and the React namespace). -fn build_react_apis( - shapes: &mut ShapeRegistry, - globals: &mut GlobalRegistry, -) -> Vec<(String, Type)> { - let mut react_apis: Vec<(String, Type)> = Vec::new(); - - // useContext - let use_context = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Read), - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - return_value_reason: Some(ValueReason::Context), - hook_kind: HookKind::UseContext, - ..Default::default() - }, - Some(BUILT_IN_USE_CONTEXT_HOOK_ID), - ); - react_apis.push(("useContext".to_string(), use_context)); - - // useState - let use_state = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Object { - shape_id: Some(BUILT_IN_USE_STATE_ID.to_string()), - }, - return_value_kind: ValueKind::Frozen, - return_value_reason: Some(ValueReason::State), - hook_kind: HookKind::UseState, - ..Default::default() - }, - None, - ); - react_apis.push(("useState".to_string(), use_state)); - - // useActionState - let use_action_state = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Object { - shape_id: Some(BUILT_IN_USE_ACTION_STATE_ID.to_string()), - }, - return_value_kind: ValueKind::Frozen, - return_value_reason: Some(ValueReason::State), - hook_kind: HookKind::UseActionState, - ..Default::default() - }, - None, - ); - react_apis.push(("useActionState".to_string(), use_action_state)); - - // useReducer - let use_reducer = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Object { - shape_id: Some(BUILT_IN_USE_REDUCER_ID.to_string()), - }, - return_value_kind: ValueKind::Frozen, - return_value_reason: Some(ValueReason::ReducerState), - hook_kind: HookKind::UseReducer, - ..Default::default() - }, - None, - ); - react_apis.push(("useReducer".to_string(), use_reducer)); - - // useRef - let use_ref = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Capture), - return_type: Type::Object { - shape_id: Some(BUILT_IN_USE_REF_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - hook_kind: HookKind::UseRef, - ..Default::default() - }, - None, - ); - react_apis.push(("useRef".to_string(), use_ref)); - - // useImperativeHandle - let use_imperative_handle = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Primitive, - return_value_kind: ValueKind::Frozen, - hook_kind: HookKind::UseImperativeHandle, - ..Default::default() - }, - None, - ); - react_apis.push(("useImperativeHandle".to_string(), use_imperative_handle)); - - // useMemo - let use_memo = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - hook_kind: HookKind::UseMemo, - ..Default::default() - }, - None, - ); - react_apis.push(("useMemo".to_string(), use_memo)); - - // useCallback - let use_callback = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - hook_kind: HookKind::UseCallback, - ..Default::default() - }, - None, - ); - react_apis.push(("useCallback".to_string(), use_callback)); - - // useEffect (with aliasing signature) - let use_effect = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Primitive, - return_value_kind: ValueKind::Frozen, - hook_kind: HookKind::UseEffect, - aliasing: Some(AliasingSignatureConfig { - receiver: "@receiver".to_string(), - params: Vec::new(), - rest: Some("@rest".to_string()), - returns: "@returns".to_string(), - temporaries: vec!["@effect".to_string()], - effects: vec![ - AliasingEffectConfig::Freeze { - value: "@rest".to_string(), - reason: ValueReason::Effect, - }, - AliasingEffectConfig::Create { - into: "@effect".to_string(), - value: ValueKind::Frozen, - reason: ValueReason::KnownReturnSignature, - }, - AliasingEffectConfig::Capture { - from: "@rest".to_string(), - into: "@effect".to_string(), - }, - AliasingEffectConfig::Create { - into: "@returns".to_string(), - value: ValueKind::Primitive, - reason: ValueReason::KnownReturnSignature, - }, - ], - }), - ..Default::default() - }, - Some(BUILT_IN_USE_EFFECT_HOOK_ID), - ); - react_apis.push(("useEffect".to_string(), use_effect)); - - // useLayoutEffect - let use_layout_effect = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - hook_kind: HookKind::UseLayoutEffect, - ..Default::default() - }, - Some(BUILT_IN_USE_LAYOUT_EFFECT_HOOK_ID), - ); - react_apis.push(("useLayoutEffect".to_string(), use_layout_effect)); - - // useInsertionEffect - let use_insertion_effect = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - hook_kind: HookKind::UseInsertionEffect, - ..Default::default() - }, - Some(BUILT_IN_USE_INSERTION_EFFECT_HOOK_ID), - ); - react_apis.push(("useInsertionEffect".to_string(), use_insertion_effect)); - - // useTransition - let use_transition = add_hook( - shapes, - HookSignatureBuilder { - rest_param: None, - return_type: Type::Object { - shape_id: Some(BUILT_IN_USE_TRANSITION_ID.to_string()), - }, - return_value_kind: ValueKind::Frozen, - hook_kind: HookKind::UseTransition, - ..Default::default() - }, - None, - ); - react_apis.push(("useTransition".to_string(), use_transition)); - - // useOptimistic - let use_optimistic = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Object { - shape_id: Some(BUILT_IN_USE_OPTIMISTIC_ID.to_string()), - }, - return_value_kind: ValueKind::Frozen, - return_value_reason: Some(ValueReason::State), - hook_kind: HookKind::UseOptimistic, - ..Default::default() - }, - None, - ); - react_apis.push(("useOptimistic".to_string(), use_optimistic)); - - // use (not a hook, it's a function) - let use_fn = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - ..Default::default() - }, - Some(BUILT_IN_USE_OPERATOR_ID), - false, - ); - react_apis.push(("use".to_string(), use_fn)); - - // useEffectEvent - let use_effect_event = add_hook( - shapes, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Function { - shape_id: Some(BUILT_IN_EFFECT_EVENT_ID.to_string()), - return_type: Box::new(Type::Poly), - is_constructor: false, - }, - return_value_kind: ValueKind::Frozen, - hook_kind: HookKind::UseEffectEvent, - ..Default::default() - }, - Some(BUILT_IN_USE_EFFECT_EVENT_ID), - ); - react_apis.push(("useEffectEvent".to_string(), use_effect_event)); - - // Insert all React APIs as standalone globals - for (name, ty) in &react_apis { - globals.insert(name.clone(), ty.clone()); - } - - react_apis -} - -/// Build typed globals and return them as a list for use as globalThis/global properties. -fn build_typed_globals( - shapes: &mut ShapeRegistry, - globals: &mut GlobalRegistry, - react_apis: Vec<(String, Type)>, -) -> Vec<(String, Type)> { - let mut typed_globals: Vec<(String, Type)> = Vec::new(); - // Object - let obj_keys = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - aliasing: Some(AliasingSignatureConfig { - receiver: "@receiver".to_string(), - params: vec!["@object".to_string()], - rest: None, - returns: "@returns".to_string(), - temporaries: Vec::new(), - effects: vec![ - AliasingEffectConfig::Create { - into: "@returns".to_string(), - value: ValueKind::Mutable, - reason: ValueReason::KnownReturnSignature, - }, - // Only keys are captured, and keys are immutable - AliasingEffectConfig::ImmutableCapture { - from: "@object".to_string(), - into: "@returns".to_string(), - }, - ], - }), - ..Default::default() - }, - None, - false, - ); - let obj_from_entries = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::ConditionallyMutate], - return_type: Type::Object { - shape_id: Some(BUILT_IN_OBJECT_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let obj_entries = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Capture], - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - aliasing: Some(AliasingSignatureConfig { - receiver: "@receiver".to_string(), - params: vec!["@object".to_string()], - rest: None, - returns: "@returns".to_string(), - temporaries: Vec::new(), - effects: vec![ - AliasingEffectConfig::Create { - into: "@returns".to_string(), - value: ValueKind::Mutable, - reason: ValueReason::KnownReturnSignature, - }, - // Object values are captured into the return - AliasingEffectConfig::Capture { - from: "@object".to_string(), - into: "@returns".to_string(), - }, - ], - }), - ..Default::default() - }, - None, - false, - ); - let obj_values = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Capture], - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - aliasing: Some(AliasingSignatureConfig { - receiver: "@receiver".to_string(), - params: vec!["@object".to_string()], - rest: None, - returns: "@returns".to_string(), - temporaries: Vec::new(), - effects: vec![ - AliasingEffectConfig::Create { - into: "@returns".to_string(), - value: ValueKind::Mutable, - reason: ValueReason::KnownReturnSignature, - }, - // Object values are captured into the return - AliasingEffectConfig::Capture { - from: "@object".to_string(), - into: "@returns".to_string(), - }, - ], - }), - ..Default::default() - }, - None, - false, - ); - let object_global = add_object( - shapes, - Some("Object"), - vec![ - ("keys".to_string(), obj_keys), - ("fromEntries".to_string(), obj_from_entries), - ("entries".to_string(), obj_entries), - ("values".to_string(), obj_values), - ], - ); - typed_globals.push(("Object".to_string(), object_global.clone())); - globals.insert("Object".to_string(), object_global); - - // Array - let array_is_array = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::Read], - return_type: Type::Primitive, - return_value_kind: ValueKind::Primitive, - ..Default::default() - }, - None, - false, - ); - let array_from = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![ - Effect::ConditionallyMutateIterator, - Effect::ConditionallyMutate, - Effect::ConditionallyMutate, - ], - rest_param: Some(Effect::Read), - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let array_of = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Read), - return_type: Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - let array_global = add_object( - shapes, - Some("Array"), - vec![ - ("isArray".to_string(), array_is_array), - ("from".to_string(), array_from), - ("of".to_string(), array_of), - ], - ); - typed_globals.push(("Array".to_string(), array_global.clone())); - globals.insert("Array".to_string(), array_global); - - // Math - let math_fns: Vec<(String, Type)> = ["max", "min", "trunc", "ceil", "floor", "pow"] - .iter() - .map(|name| (name.to_string(), pure_primitive_fn(shapes))) - .collect(); - let mut math_props = math_fns; - math_props.push(("PI".to_string(), Type::Primitive)); - // Math.random is impure - let math_random = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - impure: true, - canonical_name: Some("Math.random".to_string()), - ..Default::default() - }, - None, - false, - ); - math_props.push(("random".to_string(), math_random)); - let math_global = add_object(shapes, Some("Math"), math_props); - typed_globals.push(("Math".to_string(), math_global.clone())); - globals.insert("Math".to_string(), math_global); - - // performance - let perf_now = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Read), - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - impure: true, - canonical_name: Some("performance.now".to_string()), - ..Default::default() - }, - None, - false, - ); - let perf_global = add_object( - shapes, - Some("performance"), - vec![("now".to_string(), perf_now)], - ); - typed_globals.push(("performance".to_string(), perf_global.clone())); - globals.insert("performance".to_string(), perf_global); - - // Date - let date_now = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Read), - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - impure: true, - canonical_name: Some("Date.now".to_string()), - ..Default::default() - }, - None, - false, - ); - let date_global = add_object(shapes, Some("Date"), vec![("now".to_string(), date_now)]); - typed_globals.push(("Date".to_string(), date_global.clone())); - globals.insert("Date".to_string(), date_global); - - // console - let console_methods: Vec<(String, Type)> = ["error", "info", "log", "table", "trace", "warn"] - .iter() - .map(|name| (name.to_string(), pure_primitive_fn(shapes))) - .collect(); - let console_global = add_object(shapes, Some("console"), console_methods); - typed_globals.push(("console".to_string(), console_global.clone())); - globals.insert("console".to_string(), console_global); - - // Simple global functions returning Primitive - for name in &[ - "Boolean", - "Number", - "String", - "parseInt", - "parseFloat", - "isNaN", - "isFinite", - "encodeURI", - "encodeURIComponent", - "decodeURI", - "decodeURIComponent", - ] { - let f = pure_primitive_fn(shapes); - typed_globals.push((name.to_string(), f.clone())); - globals.insert(name.to_string(), f); - } - - // Primitive globals - typed_globals.push(("Infinity".to_string(), Type::Primitive)); - globals.insert("Infinity".to_string(), Type::Primitive); - typed_globals.push(("NaN".to_string(), Type::Primitive)); - globals.insert("NaN".to_string(), Type::Primitive); - - // Map, Set, WeakMap, WeakSet constructors - let map_ctor = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::ConditionallyMutateIterator], - return_type: Type::Object { - shape_id: Some(BUILT_IN_MAP_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - true, - ); - typed_globals.push(("Map".to_string(), map_ctor.clone())); - globals.insert("Map".to_string(), map_ctor); - - let set_ctor = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::ConditionallyMutateIterator], - return_type: Type::Object { - shape_id: Some(BUILT_IN_SET_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - true, - ); - typed_globals.push(("Set".to_string(), set_ctor.clone())); - globals.insert("Set".to_string(), set_ctor); - - let weak_map_ctor = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::ConditionallyMutateIterator], - return_type: Type::Object { - shape_id: Some(BUILT_IN_WEAK_MAP_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - true, - ); - typed_globals.push(("WeakMap".to_string(), weak_map_ctor.clone())); - globals.insert("WeakMap".to_string(), weak_map_ctor); - - let weak_set_ctor = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - positional_params: vec![Effect::ConditionallyMutateIterator], - return_type: Type::Object { - shape_id: Some(BUILT_IN_WEAK_SET_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - true, - ); - typed_globals.push(("WeakSet".to_string(), weak_set_ctor.clone())); - globals.insert("WeakSet".to_string(), weak_set_ctor); - - // React global object — reuses the same REACT_APIS types (matching TS behavior - // where the same type objects are used as both standalone globals and React.* properties) - let react_create_element = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - ..Default::default() - }, - None, - false, - ); - let react_clone_element = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - ..Default::default() - }, - None, - false, - ); - let react_create_ref = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Capture), - return_type: Type::Object { - shape_id: Some(BUILT_IN_USE_REF_ID.to_string()), - }, - return_value_kind: ValueKind::Mutable, - ..Default::default() - }, - None, - false, - ); - - // Build React namespace properties from react_apis + React-specific functions - let mut react_props: Vec<(String, Type)> = react_apis; - react_props.push(("createElement".to_string(), react_create_element)); - react_props.push(("cloneElement".to_string(), react_clone_element)); - react_props.push(("createRef".to_string(), react_create_ref)); - - let react_global = add_object(shapes, None, react_props); - typed_globals.push(("React".to_string(), react_global.clone())); - globals.insert("React".to_string(), react_global); - - // _jsx (used by JSX transform) - let jsx_fn = add_function( - shapes, - Vec::new(), - FunctionSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - ..Default::default() - }, - None, - false, - ); - typed_globals.push(("_jsx".to_string(), jsx_fn.clone())); - globals.insert("_jsx".to_string(), jsx_fn); - - typed_globals -} diff --git a/compiler/crates/react_compiler_hir/src/lib.rs b/compiler/crates/react_compiler_hir/src/lib.rs deleted file mode 100644 index a23296c79e9a..000000000000 --- a/compiler/crates/react_compiler_hir/src/lib.rs +++ /dev/null @@ -1,1633 +0,0 @@ -pub mod default_module_type_provider; -pub mod dominator; -pub mod environment; -pub mod environment_config; -pub mod globals; -pub mod object_shape; -pub mod print; -pub mod reactive; -pub mod type_config; -pub mod visitors; - -use indexmap::IndexMap; -use indexmap::IndexSet; -pub use react_compiler_diagnostics::CompilerDiagnostic; -pub use react_compiler_diagnostics::ErrorCategory; -pub use react_compiler_diagnostics::GENERATED_SOURCE; -pub use react_compiler_diagnostics::Position; -pub use react_compiler_diagnostics::SourceLocation; -pub use reactive::*; - -// ============================================================================= -// ID newtypes -// ============================================================================= - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct BlockId(pub u32); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct IdentifierId(pub u32); - -/// Index into the flat instruction table on HirFunction. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct InstructionId(pub u32); - -/// Evaluation order assigned to instructions and terminals during numbering. -/// This was previously called InstructionId in the TypeScript compiler. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct EvaluationOrder(pub u32); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct DeclarationId(pub u32); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct ScopeId(pub u32); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct TypeId(pub u32); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct FunctionId(pub u32); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct MutableRangeId(pub u32); - -// ============================================================================= -// FloatValue wrapper -// ============================================================================= - -/// Wrapper around f64 that stores raw bytes for deterministic equality and hashing. -/// This allows use in HashMap keys and ensures NaN == NaN (bitwise comparison). -#[derive(Debug, Clone, Copy)] -pub struct FloatValue(u64); - -impl FloatValue { - pub fn new(value: f64) -> Self { - FloatValue(value.to_bits()) - } - - pub fn value(self) -> f64 { - f64::from_bits(self.0) - } -} - -impl From<f64> for FloatValue { - fn from(value: f64) -> Self { - FloatValue::new(value) - } -} - -impl From<FloatValue> for f64 { - fn from(value: FloatValue) -> Self { - value.value() - } -} - -impl PartialEq for FloatValue { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl Eq for FloatValue {} - -impl std::hash::Hash for FloatValue { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.0.hash(state); - } -} - -impl std::fmt::Display for FloatValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", format_js_number(self.value())) - } -} - -/// Format an f64 the way JavaScript's `Number.prototype.toString()` does. -/// -/// Key differences from Rust's default `Display`: -/// - Uses scientific notation for |x| >= 1e21 (e.g. `1e+21`, `2.18739127891275e+22`) -/// - Uses scientific notation for 0 < |x| < 1e-6 (e.g. `1e-7`, `1.5e-8`) -/// - Uses minimal significant digits that round-trip to the same f64 -/// - Formats -0 as "0" -pub fn format_js_number(n: f64) -> String { - if n.is_nan() { - return "NaN".to_string(); - } - if n.is_infinite() { - return if n > 0.0 { - "Infinity".to_string() - } else { - "-Infinity".to_string() - }; - } - if n == 0.0 { - return "0".to_string(); - } - - let abs = n.abs(); - let sign = if n < 0.0 { "-" } else { "" }; - - if abs >= 1e21 || (abs > 0.0 && abs < 1e-6) { - // Use scientific notation matching JS format: coefficient + "e+" or "e-" + exponent - // Rust's {:e} uses "e" (lowercase) like JS, but formats as e.g. "1.5e21" not "1.5e+21" - let formatted = format!("{:e}", abs); - // Split into coefficient and exponent parts - let (coeff, exp_str) = formatted.split_once('e').unwrap(); - let exp: i32 = exp_str.parse().unwrap(); - // JS uses e+N for positive exponents, e-N for negative - if exp >= 0 { - format!("{}{}e+{}", sign, coeff, exp) - } else { - format!("{}{}e-{}", sign, coeff, exp.unsigned_abs()) - } - } else if abs.fract() == 0.0 && abs < (i64::MAX as f64) { - // Integer that fits in i64 — format without decimal point - format!("{}{}", sign, abs as i64) - } else { - // Regular float: Rust's default Display gives us the right digits - format!("{}", n) - } -} - -// ============================================================================= -// Core HIR types -// ============================================================================= - -/// A function lowered to HIR form -#[derive(Debug, Clone)] -pub struct HirFunction { - pub loc: Option<SourceLocation>, - pub id: Option<String>, - pub name_hint: Option<String>, - pub fn_type: ReactFunctionType, - pub params: Vec<ParamPattern>, - pub return_type_annotation: Option<String>, - pub returns: Place, - pub context: Vec<Place>, - pub body: HIR, - pub instructions: Vec<Instruction>, - pub generator: bool, - pub is_async: bool, - pub directives: Vec<String>, - pub aliasing_effects: Option<Vec<AliasingEffect>>, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ReactFunctionType { - Component, - Hook, - Other, -} - -#[derive(Debug, Clone)] -pub enum ParamPattern { - Place(Place), - Spread(SpreadPattern), -} - -/// The HIR control-flow graph -#[derive(Debug, Clone)] -pub struct HIR { - pub entry: BlockId, - pub blocks: IndexMap<BlockId, BasicBlock>, -} - -/// Block kinds -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BlockKind { - Block, - Value, - Loop, - Sequence, - Catch, -} - -impl std::fmt::Display for BlockKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BlockKind::Block => write!(f, "block"), - BlockKind::Value => write!(f, "value"), - BlockKind::Loop => write!(f, "loop"), - BlockKind::Sequence => write!(f, "sequence"), - BlockKind::Catch => write!(f, "catch"), - } - } -} - -/// A basic block in the CFG -#[derive(Debug, Clone)] -pub struct BasicBlock { - pub kind: BlockKind, - pub id: BlockId, - pub instructions: Vec<InstructionId>, - pub terminal: Terminal, - pub preds: IndexSet<BlockId>, - pub phis: Vec<Phi>, -} - -/// Phi node for SSA -#[derive(Debug, Clone)] -pub struct Phi { - pub place: Place, - pub operands: IndexMap<BlockId, Place>, -} - -// ============================================================================= -// Terminal enum -// ============================================================================= - -#[derive(Debug, Clone)] -pub enum Terminal { - Unsupported { - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Unreachable { - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Throw { - value: Place, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Return { - value: Place, - return_variant: ReturnVariant, - id: EvaluationOrder, - loc: Option<SourceLocation>, - effects: Option<Vec<AliasingEffect>>, - }, - Goto { - block: BlockId, - variant: GotoVariant, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - If { - test: Place, - consequent: BlockId, - alternate: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Branch { - test: Place, - consequent: BlockId, - alternate: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Switch { - test: Place, - cases: Vec<Case>, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - DoWhile { - loop_block: BlockId, - test: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - While { - test: BlockId, - loop_block: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - For { - init: BlockId, - test: BlockId, - update: Option<BlockId>, - loop_block: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - ForOf { - init: BlockId, - test: BlockId, - loop_block: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - ForIn { - init: BlockId, - loop_block: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Logical { - operator: LogicalOperator, - test: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Ternary { - test: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Optional { - optional: bool, - test: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Label { - block: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Sequence { - block: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - MaybeThrow { - continuation: BlockId, - handler: Option<BlockId>, - id: EvaluationOrder, - loc: Option<SourceLocation>, - effects: Option<Vec<AliasingEffect>>, - }, - Try { - block: BlockId, - handler_binding: Option<Place>, - handler: BlockId, - fallthrough: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Scope { - fallthrough: BlockId, - block: BlockId, - scope: ScopeId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - PrunedScope { - fallthrough: BlockId, - block: BlockId, - scope: ScopeId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, -} - -impl Terminal { - /// Get the evaluation order of this terminal - pub fn evaluation_order(&self) -> EvaluationOrder { - match self { - Terminal::Unsupported { id, .. } - | Terminal::Unreachable { id, .. } - | Terminal::Throw { id, .. } - | Terminal::Return { id, .. } - | Terminal::Goto { id, .. } - | Terminal::If { id, .. } - | Terminal::Branch { id, .. } - | Terminal::Switch { id, .. } - | Terminal::DoWhile { id, .. } - | Terminal::While { id, .. } - | Terminal::For { id, .. } - | Terminal::ForOf { id, .. } - | Terminal::ForIn { id, .. } - | Terminal::Logical { id, .. } - | Terminal::Ternary { id, .. } - | Terminal::Optional { id, .. } - | Terminal::Label { id, .. } - | Terminal::Sequence { id, .. } - | Terminal::MaybeThrow { id, .. } - | Terminal::Try { id, .. } - | Terminal::Scope { id, .. } - | Terminal::PrunedScope { id, .. } => *id, - } - } - - /// Get the source location of this terminal - pub fn loc(&self) -> Option<&SourceLocation> { - match self { - Terminal::Unsupported { loc, .. } - | Terminal::Unreachable { loc, .. } - | Terminal::Throw { loc, .. } - | Terminal::Return { loc, .. } - | Terminal::Goto { loc, .. } - | Terminal::If { loc, .. } - | Terminal::Branch { loc, .. } - | Terminal::Switch { loc, .. } - | Terminal::DoWhile { loc, .. } - | Terminal::While { loc, .. } - | Terminal::For { loc, .. } - | Terminal::ForOf { loc, .. } - | Terminal::ForIn { loc, .. } - | Terminal::Logical { loc, .. } - | Terminal::Ternary { loc, .. } - | Terminal::Optional { loc, .. } - | Terminal::Label { loc, .. } - | Terminal::Sequence { loc, .. } - | Terminal::MaybeThrow { loc, .. } - | Terminal::Try { loc, .. } - | Terminal::Scope { loc, .. } - | Terminal::PrunedScope { loc, .. } => loc.as_ref(), - } - } - - /// Set the evaluation order of this terminal - pub fn set_evaluation_order(&mut self, new_id: EvaluationOrder) { - match self { - Terminal::Unsupported { id, .. } - | Terminal::Unreachable { id, .. } - | Terminal::Throw { id, .. } - | Terminal::Return { id, .. } - | Terminal::Goto { id, .. } - | Terminal::If { id, .. } - | Terminal::Branch { id, .. } - | Terminal::Switch { id, .. } - | Terminal::DoWhile { id, .. } - | Terminal::While { id, .. } - | Terminal::For { id, .. } - | Terminal::ForOf { id, .. } - | Terminal::ForIn { id, .. } - | Terminal::Logical { id, .. } - | Terminal::Ternary { id, .. } - | Terminal::Optional { id, .. } - | Terminal::Label { id, .. } - | Terminal::Sequence { id, .. } - | Terminal::MaybeThrow { id, .. } - | Terminal::Try { id, .. } - | Terminal::Scope { id, .. } - | Terminal::PrunedScope { id, .. } => *id = new_id, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ReturnVariant { - Void, - Implicit, - Explicit, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum GotoVariant { - Break, - Continue, - Try, -} - -#[derive(Debug, Clone)] -pub struct Case { - pub test: Option<Place>, - pub block: BlockId, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum LogicalOperator { - And, - Or, - NullishCoalescing, -} - -impl std::fmt::Display for LogicalOperator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LogicalOperator::And => write!(f, "&&"), - LogicalOperator::Or => write!(f, "||"), - LogicalOperator::NullishCoalescing => write!(f, "??"), - } - } -} - -// ============================================================================= -// Instruction types -// ============================================================================= - -#[derive(Debug, Clone)] -pub struct Instruction { - pub id: EvaluationOrder, - pub lvalue: Place, - pub value: InstructionValue, - pub loc: Option<SourceLocation>, - pub effects: Option<Vec<AliasingEffect>>, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum InstructionKind { - Const, - Let, - Reassign, - Catch, - HoistedConst, - HoistedLet, - HoistedFunction, - Function, -} - -#[derive(Debug, Clone)] -pub struct LValue { - pub place: Place, - pub kind: InstructionKind, -} - -#[derive(Debug, Clone)] -pub struct LValuePattern { - pub pattern: Pattern, - pub kind: InstructionKind, -} - -#[derive(Debug, Clone)] -pub enum Pattern { - Array(ArrayPattern), - Object(ObjectPattern), -} - -// ============================================================================= -// InstructionValue enum -// ============================================================================= - -#[derive(Debug, Clone)] -pub enum InstructionValue { - LoadLocal { - place: Place, - loc: Option<SourceLocation>, - }, - LoadContext { - place: Place, - loc: Option<SourceLocation>, - }, - DeclareLocal { - lvalue: LValue, - type_annotation: Option<String>, - loc: Option<SourceLocation>, - }, - DeclareContext { - lvalue: LValue, - loc: Option<SourceLocation>, - }, - StoreLocal { - lvalue: LValue, - value: Place, - type_annotation: Option<String>, - loc: Option<SourceLocation>, - }, - StoreContext { - lvalue: LValue, - value: Place, - loc: Option<SourceLocation>, - }, - Destructure { - lvalue: LValuePattern, - value: Place, - loc: Option<SourceLocation>, - }, - Primitive { - value: PrimitiveValue, - loc: Option<SourceLocation>, - }, - JSXText { - value: String, - loc: Option<SourceLocation>, - }, - BinaryExpression { - operator: BinaryOperator, - left: Place, - right: Place, - loc: Option<SourceLocation>, - }, - NewExpression { - callee: Place, - args: Vec<PlaceOrSpread>, - loc: Option<SourceLocation>, - }, - CallExpression { - callee: Place, - args: Vec<PlaceOrSpread>, - loc: Option<SourceLocation>, - }, - MethodCall { - receiver: Place, - property: Place, - args: Vec<PlaceOrSpread>, - loc: Option<SourceLocation>, - }, - UnaryExpression { - operator: UnaryOperator, - value: Place, - loc: Option<SourceLocation>, - }, - TypeCastExpression { - value: Place, - type_: Type, - type_annotation_name: Option<String>, - type_annotation_kind: Option<String>, - /// The original AST type annotation node, preserved for codegen. - /// For Flow: the inner type from TypeAnnotation.typeAnnotation - /// For TS: the TSType node from TSAsExpression/TSSatisfiesExpression - type_annotation: Option<Box<serde_json::Value>>, - loc: Option<SourceLocation>, - }, - JsxExpression { - tag: JsxTag, - props: Vec<JsxAttribute>, - children: Option<Vec<Place>>, - loc: Option<SourceLocation>, - opening_loc: Option<SourceLocation>, - closing_loc: Option<SourceLocation>, - }, - ObjectExpression { - properties: Vec<ObjectPropertyOrSpread>, - loc: Option<SourceLocation>, - }, - ObjectMethod { - loc: Option<SourceLocation>, - lowered_func: LoweredFunction, - }, - ArrayExpression { - elements: Vec<ArrayElement>, - loc: Option<SourceLocation>, - }, - JsxFragment { - children: Vec<Place>, - loc: Option<SourceLocation>, - }, - RegExpLiteral { - pattern: String, - flags: String, - loc: Option<SourceLocation>, - }, - MetaProperty { - meta: String, - property: String, - loc: Option<SourceLocation>, - }, - PropertyStore { - object: Place, - property: PropertyLiteral, - value: Place, - loc: Option<SourceLocation>, - }, - PropertyLoad { - object: Place, - property: PropertyLiteral, - loc: Option<SourceLocation>, - }, - PropertyDelete { - object: Place, - property: PropertyLiteral, - loc: Option<SourceLocation>, - }, - ComputedStore { - object: Place, - property: Place, - value: Place, - loc: Option<SourceLocation>, - }, - ComputedLoad { - object: Place, - property: Place, - loc: Option<SourceLocation>, - }, - ComputedDelete { - object: Place, - property: Place, - loc: Option<SourceLocation>, - }, - LoadGlobal { - binding: NonLocalBinding, - loc: Option<SourceLocation>, - }, - StoreGlobal { - name: String, - value: Place, - loc: Option<SourceLocation>, - }, - FunctionExpression { - name: Option<String>, - name_hint: Option<String>, - lowered_func: LoweredFunction, - expr_type: FunctionExpressionType, - loc: Option<SourceLocation>, - }, - TaggedTemplateExpression { - tag: Place, - value: TemplateQuasi, - loc: Option<SourceLocation>, - }, - TemplateLiteral { - subexprs: Vec<Place>, - quasis: Vec<TemplateQuasi>, - loc: Option<SourceLocation>, - }, - Await { - value: Place, - loc: Option<SourceLocation>, - }, - GetIterator { - collection: Place, - loc: Option<SourceLocation>, - }, - IteratorNext { - iterator: Place, - collection: Place, - loc: Option<SourceLocation>, - }, - NextPropertyOf { - value: Place, - loc: Option<SourceLocation>, - }, - PrefixUpdate { - lvalue: Place, - operation: UpdateOperator, - value: Place, - loc: Option<SourceLocation>, - }, - PostfixUpdate { - lvalue: Place, - operation: UpdateOperator, - value: Place, - loc: Option<SourceLocation>, - }, - Debugger { - loc: Option<SourceLocation>, - }, - StartMemoize { - manual_memo_id: u32, - deps: Option<Vec<ManualMemoDependency>>, - deps_loc: Option<Option<SourceLocation>>, - has_invalid_deps: bool, - loc: Option<SourceLocation>, - }, - FinishMemoize { - manual_memo_id: u32, - decl: Place, - pruned: bool, - loc: Option<SourceLocation>, - }, - UnsupportedNode { - node_type: Option<String>, - /// The original AST node serialized as JSON, so codegen can emit it verbatim. - original_node: Option<serde_json::Value>, - loc: Option<SourceLocation>, - }, -} - -impl InstructionValue { - pub fn loc(&self) -> Option<&SourceLocation> { - match self { - InstructionValue::LoadLocal { loc, .. } - | InstructionValue::LoadContext { loc, .. } - | InstructionValue::DeclareLocal { loc, .. } - | InstructionValue::DeclareContext { loc, .. } - | InstructionValue::StoreLocal { loc, .. } - | InstructionValue::StoreContext { loc, .. } - | InstructionValue::Destructure { loc, .. } - | InstructionValue::Primitive { loc, .. } - | InstructionValue::JSXText { loc, .. } - | InstructionValue::BinaryExpression { loc, .. } - | InstructionValue::NewExpression { loc, .. } - | InstructionValue::CallExpression { loc, .. } - | InstructionValue::MethodCall { loc, .. } - | InstructionValue::UnaryExpression { loc, .. } - | InstructionValue::TypeCastExpression { loc, .. } - | InstructionValue::JsxExpression { loc, .. } - | InstructionValue::ObjectExpression { loc, .. } - | InstructionValue::ObjectMethod { loc, .. } - | InstructionValue::ArrayExpression { loc, .. } - | InstructionValue::JsxFragment { loc, .. } - | InstructionValue::RegExpLiteral { loc, .. } - | InstructionValue::MetaProperty { loc, .. } - | InstructionValue::PropertyStore { loc, .. } - | InstructionValue::PropertyLoad { loc, .. } - | InstructionValue::PropertyDelete { loc, .. } - | InstructionValue::ComputedStore { loc, .. } - | InstructionValue::ComputedLoad { loc, .. } - | InstructionValue::ComputedDelete { loc, .. } - | InstructionValue::LoadGlobal { loc, .. } - | InstructionValue::StoreGlobal { loc, .. } - | InstructionValue::FunctionExpression { loc, .. } - | InstructionValue::TaggedTemplateExpression { loc, .. } - | InstructionValue::TemplateLiteral { loc, .. } - | InstructionValue::Await { loc, .. } - | InstructionValue::GetIterator { loc, .. } - | InstructionValue::IteratorNext { loc, .. } - | InstructionValue::NextPropertyOf { loc, .. } - | InstructionValue::PrefixUpdate { loc, .. } - | InstructionValue::PostfixUpdate { loc, .. } - | InstructionValue::Debugger { loc, .. } - | InstructionValue::StartMemoize { loc, .. } - | InstructionValue::FinishMemoize { loc, .. } - | InstructionValue::UnsupportedNode { loc, .. } => loc.as_ref(), - } - } -} - -// ============================================================================= -// Supporting types -// ============================================================================= - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum PrimitiveValue { - Null, - Undefined, - Boolean(bool), - Number(FloatValue), - String(String), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BinaryOperator { - Equal, - NotEqual, - StrictEqual, - StrictNotEqual, - LessThan, - LessEqual, - GreaterThan, - GreaterEqual, - ShiftLeft, - ShiftRight, - UnsignedShiftRight, - Add, - Subtract, - Multiply, - Divide, - Modulo, - Exponent, - BitwiseOr, - BitwiseXor, - BitwiseAnd, - In, - InstanceOf, -} - -impl std::fmt::Display for BinaryOperator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BinaryOperator::Equal => write!(f, "=="), - BinaryOperator::NotEqual => write!(f, "!="), - BinaryOperator::StrictEqual => write!(f, "==="), - BinaryOperator::StrictNotEqual => write!(f, "!=="), - BinaryOperator::LessThan => write!(f, "<"), - BinaryOperator::LessEqual => write!(f, "<="), - BinaryOperator::GreaterThan => write!(f, ">"), - BinaryOperator::GreaterEqual => write!(f, ">="), - BinaryOperator::ShiftLeft => write!(f, "<<"), - BinaryOperator::ShiftRight => write!(f, ">>"), - BinaryOperator::UnsignedShiftRight => write!(f, ">>>"), - BinaryOperator::Add => write!(f, "+"), - BinaryOperator::Subtract => write!(f, "-"), - BinaryOperator::Multiply => write!(f, "*"), - BinaryOperator::Divide => write!(f, "/"), - BinaryOperator::Modulo => write!(f, "%"), - BinaryOperator::Exponent => write!(f, "**"), - BinaryOperator::BitwiseOr => write!(f, "|"), - BinaryOperator::BitwiseXor => write!(f, "^"), - BinaryOperator::BitwiseAnd => write!(f, "&"), - BinaryOperator::In => write!(f, "in"), - BinaryOperator::InstanceOf => write!(f, "instanceof"), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum UnaryOperator { - Minus, - Plus, - Not, - BitwiseNot, - TypeOf, - Void, -} - -impl std::fmt::Display for UnaryOperator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - UnaryOperator::Minus => write!(f, "-"), - UnaryOperator::Plus => write!(f, "+"), - UnaryOperator::Not => write!(f, "!"), - UnaryOperator::BitwiseNot => write!(f, "~"), - UnaryOperator::TypeOf => write!(f, "typeof"), - UnaryOperator::Void => write!(f, "void"), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum UpdateOperator { - Increment, - Decrement, -} - -impl std::fmt::Display for UpdateOperator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - UpdateOperator::Increment => write!(f, "++"), - UpdateOperator::Decrement => write!(f, "--"), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FunctionExpressionType { - ArrowFunctionExpression, - FunctionExpression, - FunctionDeclaration, -} - -#[derive(Debug, Clone)] -pub struct TemplateQuasi { - pub raw: String, - pub cooked: Option<String>, -} - -#[derive(Debug, Clone)] -pub struct ManualMemoDependency { - pub root: ManualMemoDependencyRoot, - pub path: Vec<DependencyPathEntry>, - pub loc: Option<SourceLocation>, -} - -#[derive(Debug, Clone)] -pub enum ManualMemoDependencyRoot { - NamedLocal { value: Place, constant: bool }, - Global { identifier_name: String }, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DependencyPathEntry { - pub property: PropertyLiteral, - pub optional: bool, - pub loc: Option<SourceLocation>, -} - -// ============================================================================= -// Place, Identifier, and related types -// ============================================================================= - -#[derive(Debug, Clone)] -pub struct Place { - pub identifier: IdentifierId, - pub effect: Effect, - pub reactive: bool, - pub loc: Option<SourceLocation>, -} - -#[derive(Debug, Clone)] -pub struct Identifier { - pub id: IdentifierId, - pub declaration_id: DeclarationId, - pub name: Option<IdentifierName>, - pub mutable_range: MutableRange, - pub scope: Option<ScopeId>, - pub type_: TypeId, - pub loc: Option<SourceLocation>, -} - -#[derive(Debug, Clone)] -pub struct MutableRange { - /// Unique identity for this logical range. Cloning preserves the ID - /// (same logical range); use `Environment::new_mutable_range()` to create - /// a range with a fresh ID. - pub id: MutableRangeId, - pub start: EvaluationOrder, - pub end: EvaluationOrder, -} - -impl MutableRange { - /// Returns true if the given evaluation order falls within this mutable range. - /// Corresponds to TS `inRange({id}, range)` / `isMutable(instr, place)`. - pub fn contains(&self, eval_order: EvaluationOrder) -> bool { - eval_order >= self.start && eval_order < self.end - } - - /// Returns true if this range has the same identity as `other`. - /// In the TS compiler, this corresponds to checking whether two mutableRange - /// references point to the same JS object (=== identity). - pub fn same_range(&self, other: &MutableRange) -> bool { - self.id == other.id - } -} - -#[derive(Debug, Clone)] -pub enum IdentifierName { - Named(String), - Promoted(String), -} - -impl IdentifierName { - pub fn value(&self) -> &str { - match self { - IdentifierName::Named(v) | IdentifierName::Promoted(v) => v, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub enum Effect { - #[serde(rename = "<unknown>")] - Unknown, - #[serde(rename = "freeze")] - Freeze, - #[serde(rename = "read")] - Read, - #[serde(rename = "capture")] - Capture, - #[serde(rename = "mutate-iterator?")] - ConditionallyMutateIterator, - #[serde(rename = "mutate?")] - ConditionallyMutate, - #[serde(rename = "mutate")] - Mutate, - #[serde(rename = "store")] - Store, -} - -impl Effect { - /// Returns true if this effect represents a mutable operation. - /// Mutable effects are: Capture, Store, ConditionallyMutate, - /// ConditionallyMutateIterator, and Mutate. - pub fn is_mutable(&self) -> bool { - matches!( - self, - Effect::Capture - | Effect::Store - | Effect::ConditionallyMutate - | Effect::ConditionallyMutateIterator - | Effect::Mutate - ) - } -} - -impl std::fmt::Display for Effect { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Effect::Unknown => write!(f, "<unknown>"), - Effect::Freeze => write!(f, "freeze"), - Effect::Read => write!(f, "read"), - Effect::Capture => write!(f, "capture"), - Effect::ConditionallyMutateIterator => write!(f, "mutate-iterator?"), - Effect::ConditionallyMutate => write!(f, "mutate?"), - Effect::Mutate => write!(f, "mutate"), - Effect::Store => write!(f, "store"), - } - } -} - -#[derive(Debug, Clone)] -pub struct SpreadPattern { - pub place: Place, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Hole { - Hole, -} - -#[derive(Debug, Clone)] -pub struct ArrayPattern { - pub items: Vec<ArrayPatternElement>, - pub loc: Option<SourceLocation>, -} - -#[derive(Debug, Clone)] -pub enum ArrayPatternElement { - Place(Place), - Spread(SpreadPattern), - Hole, -} - -#[derive(Debug, Clone)] -pub struct ObjectPattern { - pub properties: Vec<ObjectPropertyOrSpread>, - pub loc: Option<SourceLocation>, -} - -#[derive(Debug, Clone)] -pub enum ObjectPropertyOrSpread { - Property(ObjectProperty), - Spread(SpreadPattern), -} - -#[derive(Debug, Clone)] -pub struct ObjectProperty { - pub key: ObjectPropertyKey, - pub property_type: ObjectPropertyType, - pub place: Place, -} - -#[derive(Debug, Clone)] -pub enum ObjectPropertyKey { - String { name: String }, - Identifier { name: String }, - Computed { name: Place }, - Number { name: FloatValue }, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ObjectPropertyType { - Property, - Method, -} - -impl std::fmt::Display for ObjectPropertyType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ObjectPropertyType::Property => write!(f, "property"), - ObjectPropertyType::Method => write!(f, "method"), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum PropertyLiteral { - String(String), - Number(FloatValue), -} - -impl std::fmt::Display for PropertyLiteral { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - PropertyLiteral::String(s) => write!(f, "{}", s), - PropertyLiteral::Number(n) => write!(f, "{}", n), - } - } -} - -#[derive(Debug, Clone)] -pub enum PlaceOrSpread { - Place(Place), - Spread(SpreadPattern), -} - -#[derive(Debug, Clone)] -pub enum ArrayElement { - Place(Place), - Spread(SpreadPattern), - Hole, -} - -#[derive(Debug, Clone)] -pub struct LoweredFunction { - pub func: FunctionId, -} - -#[derive(Debug, Clone)] -pub struct BuiltinTag { - pub name: String, - pub loc: Option<SourceLocation>, -} - -#[derive(Debug, Clone)] -pub enum JsxTag { - Place(Place), - Builtin(BuiltinTag), -} - -#[derive(Debug, Clone)] -pub enum JsxAttribute { - SpreadAttribute { argument: Place }, - Attribute { name: String, place: Place }, -} - -// ============================================================================= -// Variable Binding types -// ============================================================================= - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BindingKind { - Var, - Let, - Const, - Param, - Module, - Hoisted, - Local, - Unknown, -} - -#[derive(Debug, Clone)] -pub enum VariableBinding { - Identifier { - identifier: IdentifierId, - binding_kind: BindingKind, - }, - Global { - name: String, - }, - ImportDefault { - name: String, - module: String, - }, - ImportSpecifier { - name: String, - module: String, - imported: String, - }, - ImportNamespace { - name: String, - module: String, - }, - ModuleLocal { - name: String, - }, -} - -#[derive(Debug, Clone)] -pub enum NonLocalBinding { - ImportDefault { - name: String, - module: String, - }, - ImportSpecifier { - name: String, - module: String, - imported: String, - }, - ImportNamespace { - name: String, - module: String, - }, - ModuleLocal { - name: String, - }, - Global { - name: String, - }, -} - -impl NonLocalBinding { - /// Returns the `name` field common to all variants. - pub fn name(&self) -> &str { - match self { - NonLocalBinding::ImportDefault { name, .. } - | NonLocalBinding::ImportSpecifier { name, .. } - | NonLocalBinding::ImportNamespace { name, .. } - | NonLocalBinding::ModuleLocal { name, .. } - | NonLocalBinding::Global { name, .. } => name, - } - } -} - -// ============================================================================= -// Type system (from Types.ts) -// ============================================================================= - -#[derive(Debug, Clone)] -pub enum Type { - Primitive, - Function { - shape_id: Option<String>, - return_type: Box<Type>, - is_constructor: bool, - }, - Object { - shape_id: Option<String>, - }, - TypeVar { - id: TypeId, - }, - Poly, - Phi { - operands: Vec<Type>, - }, - Property { - object_type: Box<Type>, - object_name: String, - property_name: PropertyNameKind, - }, - ObjectMethod, -} - -#[derive(Debug, Clone)] -pub enum PropertyNameKind { - Literal { value: PropertyLiteral }, - Computed { value: Box<Type> }, -} - -// ============================================================================= -// ReactiveScope -// ============================================================================= - -#[derive(Debug, Clone)] -pub struct ReactiveScope { - pub id: ScopeId, - pub range: MutableRange, - - /// The inputs to this reactive scope (populated by later passes) - pub dependencies: Vec<ReactiveScopeDependency>, - - /// The set of values produced by this scope (populated by later passes) - pub declarations: Vec<(IdentifierId, ReactiveScopeDeclaration)>, - - /// Identifiers which are reassigned by this scope (populated by later passes) - pub reassignments: Vec<IdentifierId>, - - /// If the scope contains an early return, this stores info about it (populated by later passes) - pub early_return_value: Option<ReactiveScopeEarlyReturn>, - - /// Scopes that were merged into this one (populated by later passes) - pub merged: Vec<ScopeId>, - - /// Source location spanning the scope - pub loc: Option<SourceLocation>, -} - -/// A dependency of a reactive scope. -#[derive(Debug, Clone)] -pub struct ReactiveScopeDependency { - pub identifier: IdentifierId, - pub reactive: bool, - pub path: Vec<DependencyPathEntry>, - pub loc: Option<SourceLocation>, -} - -/// A declaration produced by a reactive scope. -#[derive(Debug, Clone)] -pub struct ReactiveScopeDeclaration { - pub identifier: IdentifierId, - pub scope: ScopeId, -} - -/// Early return value info for a reactive scope. -#[derive(Debug, Clone)] -pub struct ReactiveScopeEarlyReturn { - pub value: IdentifierId, - pub loc: Option<SourceLocation>, - pub label: BlockId, -} - -// ============================================================================= -// Aliasing effects (runtime types, from AliasingEffects.ts) -// ============================================================================= - -use crate::object_shape::FunctionSignature; -use crate::type_config::ValueKind; -use crate::type_config::ValueReason; - -/// Reason for a mutation, used for generating hints (e.g. rename to "Ref"). -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum MutationReason { - AssignCurrentProperty, -} - -/// Describes the aliasing/mutation/data-flow effects of an instruction or terminal. -/// Ported from TS `AliasingEffect` in `AliasingEffects.ts`. -#[derive(Debug, Clone)] -pub enum AliasingEffect { - /// Marks the given value and its direct aliases as frozen. - Freeze { value: Place, reason: ValueReason }, - /// Mutate the value and any direct aliases. - Mutate { - value: Place, - reason: Option<MutationReason>, - }, - /// Mutate the value conditionally (only if mutable). - MutateConditionally { value: Place }, - /// Mutate the value and transitive captures. - MutateTransitive { value: Place }, - /// Mutate the value and transitive captures conditionally. - MutateTransitiveConditionally { value: Place }, - /// Information flow from `from` to `into` (non-aliasing capture). - Capture { from: Place, into: Place }, - /// Direct aliasing: mutation of `into` implies mutation of `from`. - Alias { from: Place, into: Place }, - /// Potential aliasing relationship. - MaybeAlias { from: Place, into: Place }, - /// Direct assignment: `into = from`. - Assign { from: Place, into: Place }, - /// Creates a value of the given kind at the given place. - Create { - into: Place, - value: ValueKind, - reason: ValueReason, - }, - /// Creates a new value with the same kind as the source. - CreateFrom { from: Place, into: Place }, - /// Immutable data flow (escape analysis only, no mutable range influence). - ImmutableCapture { from: Place, into: Place }, - /// Function call application. - Apply { - receiver: Place, - function: Place, - mutates_function: bool, - args: Vec<PlaceOrSpreadOrHole>, - into: Place, - signature: Option<FunctionSignature>, - loc: Option<SourceLocation>, - }, - /// Function expression creation with captures. - CreateFunction { - captures: Vec<Place>, - function_id: FunctionId, - into: Place, - }, - /// Mutation of a value known to be frozen (error). - MutateFrozen { - place: Place, - error: CompilerDiagnostic, - }, - /// Mutation of a global value (error). - MutateGlobal { - place: Place, - error: CompilerDiagnostic, - }, - /// Side-effect not safe during render. - Impure { - place: Place, - error: CompilerDiagnostic, - }, - /// Value is accessed during render. - Render { place: Place }, -} - -/// Combined Place/Spread/Hole for Apply args. -#[derive(Debug, Clone)] -pub enum PlaceOrSpreadOrHole { - Place(Place), - Spread(SpreadPattern), - Hole, -} - -/// Aliasing signature for function calls. -/// Ported from TS `AliasingSignature` in `AliasingEffects.ts`. -#[derive(Debug, Clone)] -pub struct AliasingSignature { - pub receiver: IdentifierId, - pub params: Vec<IdentifierId>, - pub rest: Option<IdentifierId>, - pub returns: IdentifierId, - pub effects: Vec<AliasingEffect>, - pub temporaries: Vec<Place>, -} - -// ============================================================================= -// Type helper functions (ported from HIR.ts) -// ============================================================================= - -use crate::object_shape::BUILT_IN_ARRAY_ID; -use crate::object_shape::BUILT_IN_JSX_ID; -use crate::object_shape::BUILT_IN_MAP_ID; -use crate::object_shape::BUILT_IN_PROPS_ID; -use crate::object_shape::BUILT_IN_REF_VALUE_ID; -use crate::object_shape::BUILT_IN_SET_ID; -use crate::object_shape::BUILT_IN_USE_OPERATOR_ID; -use crate::object_shape::BUILT_IN_USE_REF_ID; - -/// Returns true if the type (looked up via identifier) is primitive. -pub fn is_primitive_type(ty: &Type) -> bool { - matches!(ty, Type::Primitive) -} - -/// Returns true if the type is the props object. -pub fn is_props_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } if id == BUILT_IN_PROPS_ID) -} - -/// Returns true if the type is an array. -pub fn is_array_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } if id == BUILT_IN_ARRAY_ID) -} - -/// Returns true if the type is a Set. -pub fn is_set_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } if id == BUILT_IN_SET_ID) -} - -/// Returns true if the type is a Map. -pub fn is_map_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } if id == BUILT_IN_MAP_ID) -} - -/// Returns true if the type is JSX. -pub fn is_jsx_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } if id == BUILT_IN_JSX_ID) -} - -/// Returns true if the identifier type is a ref value. -pub fn is_ref_value_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } if id == BUILT_IN_REF_VALUE_ID) -} - -/// Returns true if the identifier type is useRef. -pub fn is_use_ref_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } if id == BUILT_IN_USE_REF_ID) -} - -/// Returns true if the type is a ref or ref value. -pub fn is_ref_or_ref_value(ty: &Type) -> bool { - is_use_ref_type(ty) || is_ref_value_type(ty) -} - -/// Returns true if the type is a useState result (BuiltInUseState). -pub fn is_use_state_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } if id == object_shape::BUILT_IN_USE_STATE_ID) -} - -/// Returns true if the type is a setState function (BuiltInSetState). -pub fn is_set_state_type(ty: &Type) -> bool { - matches!(ty, Type::Function { shape_id: Some(id), .. } if id == object_shape::BUILT_IN_SET_STATE_ID) -} - -/// Returns true if the type is a useEffect hook. -pub fn is_use_effect_hook_type(ty: &Type) -> bool { - matches!(ty, Type::Function { shape_id: Some(id), .. } if id == object_shape::BUILT_IN_USE_EFFECT_HOOK_ID) -} - -/// Returns true if the type is a useLayoutEffect hook. -pub fn is_use_layout_effect_hook_type(ty: &Type) -> bool { - matches!(ty, Type::Function { shape_id: Some(id), .. } if id == object_shape::BUILT_IN_USE_LAYOUT_EFFECT_HOOK_ID) -} - -/// Returns true if the type is a useInsertionEffect hook. -pub fn is_use_insertion_effect_hook_type(ty: &Type) -> bool { - matches!(ty, Type::Function { shape_id: Some(id), .. } if id == object_shape::BUILT_IN_USE_INSERTION_EFFECT_HOOK_ID) -} - -/// Returns true if the type is a useEffectEvent function. -pub fn is_use_effect_event_type(ty: &Type) -> bool { - matches!(ty, Type::Function { shape_id: Some(id), .. } if id == object_shape::BUILT_IN_USE_EFFECT_EVENT_ID) -} - -/// Returns true if the type is a ref or ref-like mutable type (e.g. Reanimated shared values). -pub fn is_ref_or_ref_like_mutable_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } - if id == object_shape::BUILT_IN_USE_REF_ID || id == object_shape::REANIMATED_SHARED_VALUE_ID) -} - -/// Returns true if the type is the `use()` operator (React.use). -pub fn is_use_operator_type(ty: &Type) -> bool { - matches!( - ty, - Type::Function { shape_id: Some(id), .. } - if id == BUILT_IN_USE_OPERATOR_ID - ) -} - -/// Returns true if the type is a plain object (BuiltInObject). -pub fn is_plain_object_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } if id == object_shape::BUILT_IN_OBJECT_ID) -} - -/// Returns true if the type is a startTransition function (BuiltInStartTransition). -pub fn is_start_transition_type(ty: &Type) -> bool { - matches!(ty, Type::Function { shape_id: Some(id), .. } if id == object_shape::BUILT_IN_START_TRANSITION_ID) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_format_js_number() { - // Scientific notation for large numbers (>= 1e21) - assert_eq!(format_js_number(1e21), "1e+21"); - assert_eq!(format_js_number(1.5e21), "1.5e+21"); - assert_eq!( - format_js_number(2.18739127891275e22), - "2.18739127891275e+22" - ); - assert_eq!(format_js_number(1e100), "1e+100"); - assert_eq!(format_js_number(-1e21), "-1e+21"); - assert_eq!(format_js_number(-1e100), "-1e+100"); - - // Scientific notation for small numbers (< 1e-6) - assert_eq!(format_js_number(1e-7), "1e-7"); - assert_eq!(format_js_number(5e-7), "5e-7"); - assert_eq!(format_js_number(1.5e-8), "1.5e-8"); - assert_eq!(format_js_number(-1.5e-8), "-1.5e-8"); - - // Non-scientific large numbers (< 1e21) - assert_eq!(format_js_number(1e20), "100000000000000000000"); - assert_eq!(format_js_number(1e-6), "0.000001"); - - // Integers - assert_eq!(format_js_number(0.0), "0"); - assert_eq!(format_js_number(-0.0), "0"); - assert_eq!(format_js_number(1.0), "1"); - assert_eq!(format_js_number(100.0), "100"); - - // Regular floats - assert_eq!(format_js_number(1.5), "1.5"); - assert_eq!(format_js_number(0.5), "0.5"); - assert_eq!(format_js_number(0.1), "0.1"); - - // Special values - assert_eq!(format_js_number(f64::NAN), "NaN"); - assert_eq!(format_js_number(f64::INFINITY), "Infinity"); - assert_eq!(format_js_number(f64::NEG_INFINITY), "-Infinity"); - } -} diff --git a/compiler/crates/react_compiler_hir/src/object_shape.rs b/compiler/crates/react_compiler_hir/src/object_shape.rs deleted file mode 100644 index 3ef536190f75..000000000000 --- a/compiler/crates/react_compiler_hir/src/object_shape.rs +++ /dev/null @@ -1,432 +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. - -//! Object shapes and function signatures, ported from ObjectShape.ts. -//! -//! Defines the shape registry used by Environment to resolve property types -//! and function call signatures for built-in objects, hooks, and user-defined types. - -use std::collections::HashMap; - -use crate::Effect; -use crate::Type; -use crate::type_config::{AliasingEffectConfig, AliasingSignatureConfig, ValueKind, ValueReason}; - -// ============================================================================= -// Shape ID constants (matching TS ObjectShape.ts) -// ============================================================================= - -pub const BUILT_IN_PROPS_ID: &str = "BuiltInProps"; -pub const BUILT_IN_ARRAY_ID: &str = "BuiltInArray"; -pub const BUILT_IN_SET_ID: &str = "BuiltInSet"; -pub const BUILT_IN_MAP_ID: &str = "BuiltInMap"; -pub const BUILT_IN_WEAK_SET_ID: &str = "BuiltInWeakSet"; -pub const BUILT_IN_WEAK_MAP_ID: &str = "BuiltInWeakMap"; -pub const BUILT_IN_FUNCTION_ID: &str = "BuiltInFunction"; -pub const BUILT_IN_JSX_ID: &str = "BuiltInJsx"; -pub const BUILT_IN_OBJECT_ID: &str = "BuiltInObject"; -pub const BUILT_IN_USE_STATE_ID: &str = "BuiltInUseState"; -pub const BUILT_IN_SET_STATE_ID: &str = "BuiltInSetState"; -pub const BUILT_IN_USE_ACTION_STATE_ID: &str = "BuiltInUseActionState"; -pub const BUILT_IN_SET_ACTION_STATE_ID: &str = "BuiltInSetActionState"; -pub const BUILT_IN_USE_REF_ID: &str = "BuiltInUseRefId"; -pub const BUILT_IN_REF_VALUE_ID: &str = "BuiltInRefValue"; -pub const BUILT_IN_MIXED_READONLY_ID: &str = "BuiltInMixedReadonly"; -pub const BUILT_IN_USE_EFFECT_HOOK_ID: &str = "BuiltInUseEffectHook"; -pub const BUILT_IN_USE_LAYOUT_EFFECT_HOOK_ID: &str = "BuiltInUseLayoutEffectHook"; -pub const BUILT_IN_USE_INSERTION_EFFECT_HOOK_ID: &str = "BuiltInUseInsertionEffectHook"; -pub const BUILT_IN_USE_OPERATOR_ID: &str = "BuiltInUseOperator"; -pub const BUILT_IN_USE_REDUCER_ID: &str = "BuiltInUseReducer"; -pub const BUILT_IN_DISPATCH_ID: &str = "BuiltInDispatch"; -pub const BUILT_IN_USE_CONTEXT_HOOK_ID: &str = "BuiltInUseContextHook"; -pub const BUILT_IN_USE_TRANSITION_ID: &str = "BuiltInUseTransition"; -pub const BUILT_IN_USE_OPTIMISTIC_ID: &str = "BuiltInUseOptimistic"; -pub const BUILT_IN_SET_OPTIMISTIC_ID: &str = "BuiltInSetOptimistic"; -pub const BUILT_IN_START_TRANSITION_ID: &str = "BuiltInStartTransition"; -pub const BUILT_IN_USE_EFFECT_EVENT_ID: &str = "BuiltInUseEffectEvent"; -pub const BUILT_IN_EFFECT_EVENT_ID: &str = "BuiltInEffectEventFunction"; -pub const REANIMATED_SHARED_VALUE_ID: &str = "ReanimatedSharedValueId"; - -// ============================================================================= -// Core types -// ============================================================================= - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum HookKind { - UseContext, - UseState, - UseActionState, - UseReducer, - UseRef, - UseEffect, - UseLayoutEffect, - UseInsertionEffect, - UseMemo, - UseCallback, - UseTransition, - UseImperativeHandle, - UseEffectEvent, - UseOptimistic, - Custom, -} - -impl std::fmt::Display for HookKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - HookKind::UseContext => write!(f, "useContext"), - HookKind::UseState => write!(f, "useState"), - HookKind::UseActionState => write!(f, "useActionState"), - HookKind::UseReducer => write!(f, "useReducer"), - HookKind::UseRef => write!(f, "useRef"), - HookKind::UseEffect => write!(f, "useEffect"), - HookKind::UseLayoutEffect => write!(f, "useLayoutEffect"), - HookKind::UseInsertionEffect => write!(f, "useInsertionEffect"), - HookKind::UseMemo => write!(f, "useMemo"), - HookKind::UseCallback => write!(f, "useCallback"), - HookKind::UseTransition => write!(f, "useTransition"), - HookKind::UseImperativeHandle => write!(f, "useImperativeHandle"), - HookKind::UseEffectEvent => write!(f, "useEffectEvent"), - HookKind::UseOptimistic => write!(f, "useOptimistic"), - HookKind::Custom => write!(f, "Custom"), - } - } -} - -/// Call signature of a function, used for type and effect inference. -/// Ported from TS `FunctionSignature`. -#[derive(Debug, Clone)] -pub struct FunctionSignature { - pub positional_params: Vec<Effect>, - pub rest_param: Option<Effect>, - pub return_type: Type, - pub return_value_kind: ValueKind, - pub return_value_reason: Option<ValueReason>, - pub callee_effect: Effect, - pub hook_kind: Option<HookKind>, - pub no_alias: bool, - pub mutable_only_if_operands_are_mutable: bool, - pub impure: bool, - pub known_incompatible: Option<String>, - pub canonical_name: Option<String>, - /// Aliasing signature in config form. Full parsing into AliasingSignature - /// with Place values is deferred until the aliasing effects system is ported. - pub aliasing: Option<AliasingSignatureConfig>, -} - -/// Shape of an object or function type. -/// Ported from TS `ObjectShape`. -#[derive(Debug, Clone)] -pub struct ObjectShape { - pub properties: HashMap<String, Type>, - pub function_type: Option<FunctionSignature>, -} - -/// Registry mapping shape IDs to their ObjectShape definitions. -/// -/// Supports two modes: -/// - **Builder mode** (`base=None`): wraps a single HashMap, used during -/// `build_builtin_shapes` / `build_default_globals` to construct the static base. -/// - **Overlay mode** (`base=Some`): holds a `&'static HashMap` base plus a small -/// extras HashMap. Lookups check extras first, then base. Inserts go into extras. -/// Cloning only copies the extras map (the base pointer is shared). -pub struct ShapeRegistry { - base: Option<&'static HashMap<String, ObjectShape>>, - entries: HashMap<String, ObjectShape>, -} - -impl ShapeRegistry { - /// Create an empty builder-mode registry. - pub fn new() -> Self { - Self { - base: None, - entries: HashMap::new(), - } - } - - /// Create an overlay-mode registry backed by a static base. - pub fn with_base(base: &'static HashMap<String, ObjectShape>) -> Self { - Self { - base: Some(base), - entries: HashMap::new(), - } - } - - pub fn get(&self, key: &str) -> Option<&ObjectShape> { - self.entries - .get(key) - .or_else(|| self.base.and_then(|b| b.get(key))) - } - - pub fn insert(&mut self, key: String, value: ObjectShape) { - self.entries.insert(key, value); - } - - /// Consume the registry and return the inner HashMap. - /// Only valid in builder mode (no base). - pub fn into_inner(self) -> HashMap<String, ObjectShape> { - debug_assert!( - self.base.is_none(), - "into_inner() called on overlay-mode ShapeRegistry" - ); - self.entries - } -} - -impl Clone for ShapeRegistry { - fn clone(&self) -> Self { - Self { - base: self.base, - entries: self.entries.clone(), - } - } -} - -// ============================================================================= -// Counter for anonymous shape IDs -// ============================================================================= - -/// Thread-local counter for generating unique anonymous shape IDs. -/// Mirrors TS `nextAnonId` in ObjectShape.ts. -fn next_anon_id() -> String { - use std::sync::atomic::{AtomicU32, Ordering}; - static COUNTER: AtomicU32 = AtomicU32::new(0); - let id = COUNTER.fetch_add(1, Ordering::Relaxed); - format!("<generated_{}>", id) -} - -// ============================================================================= -// Builder functions (matching TS addFunction, addHook, addObject) -// ============================================================================= - -/// Add a non-hook function to a ShapeRegistry. -/// Returns a `Type::Function` representing the added function. -pub fn add_function( - registry: &mut ShapeRegistry, - properties: Vec<(String, Type)>, - sig: FunctionSignatureBuilder, - id: Option<&str>, - is_constructor: bool, -) -> Type { - let shape_id = id.map(|s| s.to_string()).unwrap_or_else(next_anon_id); - let return_type = sig.return_type.clone(); - add_shape( - registry, - &shape_id, - properties, - Some(FunctionSignature { - positional_params: sig.positional_params, - rest_param: sig.rest_param, - return_type: sig.return_type, - return_value_kind: sig.return_value_kind, - return_value_reason: sig.return_value_reason, - callee_effect: sig.callee_effect, - hook_kind: None, - no_alias: sig.no_alias, - mutable_only_if_operands_are_mutable: sig.mutable_only_if_operands_are_mutable, - impure: sig.impure, - known_incompatible: sig.known_incompatible, - canonical_name: sig.canonical_name, - aliasing: sig.aliasing, - }), - ); - Type::Function { - shape_id: Some(shape_id), - return_type: Box::new(return_type), - is_constructor, - } -} - -/// Add a hook to a ShapeRegistry. -/// Returns a `Type::Function` representing the added hook. -pub fn add_hook(registry: &mut ShapeRegistry, sig: HookSignatureBuilder, id: Option<&str>) -> Type { - let shape_id = id.map(|s| s.to_string()).unwrap_or_else(next_anon_id); - let return_type = sig.return_type.clone(); - add_shape( - registry, - &shape_id, - Vec::new(), - Some(FunctionSignature { - positional_params: sig.positional_params, - rest_param: sig.rest_param, - return_type: sig.return_type, - return_value_kind: sig.return_value_kind, - return_value_reason: sig.return_value_reason, - callee_effect: sig.callee_effect, - hook_kind: Some(sig.hook_kind), - no_alias: sig.no_alias, - mutable_only_if_operands_are_mutable: false, - impure: false, - known_incompatible: sig.known_incompatible, - canonical_name: None, - aliasing: sig.aliasing, - }), - ); - Type::Function { - shape_id: Some(shape_id), - return_type: Box::new(return_type), - is_constructor: false, - } -} - -/// Add an object to a ShapeRegistry. -/// Returns a `Type::Object` representing the added object. -pub fn add_object( - registry: &mut ShapeRegistry, - id: Option<&str>, - properties: Vec<(String, Type)>, -) -> Type { - let shape_id = id.map(|s| s.to_string()).unwrap_or_else(next_anon_id); - add_shape(registry, &shape_id, properties, None); - Type::Object { - shape_id: Some(shape_id), - } -} - -fn add_shape( - registry: &mut ShapeRegistry, - id: &str, - properties: Vec<(String, Type)>, - function_type: Option<FunctionSignature>, -) { - let shape = ObjectShape { - properties: properties.into_iter().collect(), - function_type, - }; - // Note: TS has an invariant that the id doesn't already exist. We use - // insert which overwrites. In practice duplicates don't occur for built-in - // shapes, and for user configs we want last-write-wins behavior. - registry.insert(id.to_string(), shape); -} - -// ============================================================================= -// Builder structs (to avoid large parameter lists) -// ============================================================================= - -/// Builder for non-hook function signatures. -pub struct FunctionSignatureBuilder { - pub positional_params: Vec<Effect>, - pub rest_param: Option<Effect>, - pub return_type: Type, - pub return_value_kind: ValueKind, - pub return_value_reason: Option<ValueReason>, - pub callee_effect: Effect, - pub no_alias: bool, - pub mutable_only_if_operands_are_mutable: bool, - pub impure: bool, - pub known_incompatible: Option<String>, - pub canonical_name: Option<String>, - pub aliasing: Option<AliasingSignatureConfig>, -} - -impl Default for FunctionSignatureBuilder { - fn default() -> Self { - Self { - positional_params: Vec::new(), - rest_param: None, - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - return_value_reason: None, - callee_effect: Effect::Read, - no_alias: false, - mutable_only_if_operands_are_mutable: false, - impure: false, - known_incompatible: None, - canonical_name: None, - aliasing: None, - } - } -} - -/// Builder for hook signatures. -pub struct HookSignatureBuilder { - pub positional_params: Vec<Effect>, - pub rest_param: Option<Effect>, - pub return_type: Type, - pub return_value_kind: ValueKind, - pub return_value_reason: Option<ValueReason>, - pub callee_effect: Effect, - pub hook_kind: HookKind, - pub no_alias: bool, - pub known_incompatible: Option<String>, - pub aliasing: Option<AliasingSignatureConfig>, -} - -impl Default for HookSignatureBuilder { - fn default() -> Self { - Self { - positional_params: Vec::new(), - rest_param: None, - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - return_value_reason: None, - callee_effect: Effect::Read, - hook_kind: HookKind::Custom, - no_alias: false, - known_incompatible: None, - aliasing: None, - } - } -} - -// ============================================================================= -// Default hook types used for unknown hooks -// ============================================================================= - -/// Default type for hooks when enableAssumeHooksFollowRulesOfReact is true. -/// Matches TS `DefaultNonmutatingHook`. -pub fn default_nonmutating_hook(registry: &mut ShapeRegistry) -> Type { - add_hook( - registry, - HookSignatureBuilder { - rest_param: Some(Effect::Freeze), - return_type: Type::Poly, - return_value_kind: ValueKind::Frozen, - hook_kind: HookKind::Custom, - aliasing: Some(AliasingSignatureConfig { - receiver: "@receiver".to_string(), - params: Vec::new(), - rest: Some("@rest".to_string()), - returns: "@returns".to_string(), - temporaries: Vec::new(), - effects: vec![ - // Freeze the arguments - AliasingEffectConfig::Freeze { - value: "@rest".to_string(), - reason: ValueReason::HookCaptured, - }, - // Returns a frozen value - AliasingEffectConfig::Create { - into: "@returns".to_string(), - value: ValueKind::Frozen, - reason: ValueReason::HookReturn, - }, - // May alias any arguments into the return - AliasingEffectConfig::Alias { - from: "@rest".to_string(), - into: "@returns".to_string(), - }, - ], - }), - ..Default::default() - }, - Some("DefaultNonmutatingHook"), - ) -} - -/// Default type for hooks when enableAssumeHooksFollowRulesOfReact is false. -/// Matches TS `DefaultMutatingHook`. -pub fn default_mutating_hook(registry: &mut ShapeRegistry) -> Type { - add_hook( - registry, - HookSignatureBuilder { - rest_param: Some(Effect::ConditionallyMutate), - return_type: Type::Poly, - return_value_kind: ValueKind::Mutable, - hook_kind: HookKind::Custom, - ..Default::default() - }, - Some("DefaultMutatingHook"), - ) -} diff --git a/compiler/crates/react_compiler_hir/src/print.rs b/compiler/crates/react_compiler_hir/src/print.rs deleted file mode 100644 index 8589391dc7b6..000000000000 --- a/compiler/crates/react_compiler_hir/src/print.rs +++ /dev/null @@ -1,1496 +0,0 @@ -//! Shared formatting utilities for HIR debug printing. -//! -//! This module provides `PrintFormatter` — a stateful formatter that both -//! `react_compiler::debug_print` (HIR printer) and -//! `react_compiler_reactive_scopes::print_reactive_function` (reactive printer) -//! delegate to for shared formatting logic. -//! -//! It also exports standalone formatting functions (format_loc, format_primitive, etc.) -//! that require no state. - -use std::collections::HashSet; - -use react_compiler_diagnostics::CompilerError; -use react_compiler_diagnostics::CompilerErrorOrDiagnostic; -use react_compiler_diagnostics::SourceLocation; - -use crate::AliasingEffect; -use crate::HirFunction; -use crate::IdentifierId; -use crate::IdentifierName; -use crate::InstructionValue; -use crate::LValue; -use crate::MutationReason; -use crate::Pattern; -use crate::Place; -use crate::PlaceOrSpreadOrHole; -use crate::ScopeId; -use crate::Type; -use crate::environment::Environment; -use crate::type_config::ValueKind; -use crate::type_config::ValueReason; - -// ============================================================================= -// Standalone formatting functions (no state needed) -// ============================================================================= - -pub fn format_loc(loc: &Option<SourceLocation>) -> String { - match loc { - Some(l) => format_loc_value(l), - None => "generated".to_string(), - } -} - -pub fn format_loc_value(loc: &SourceLocation) -> String { - format!( - "{}:{}-{}:{}", - loc.start.line, loc.start.column, loc.end.line, loc.end.column - ) -} - -/// Format a string like JS `JSON.stringify`: escape control chars and quotes -/// but preserve non-ASCII unicode (e.g. U+00A0 nbsp) as literal characters. -pub fn format_js_string(s: &str) -> String { - let mut result = String::with_capacity(s.len() + 2); - result.push('"'); - for c in s.chars() { - match c { - '"' => result.push_str("\\\""), - '\\' => result.push_str("\\\\"), - '\n' => result.push_str("\\n"), - '\r' => result.push_str("\\r"), - '\t' => result.push_str("\\t"), - '\u{0008}' => result.push_str("\\b"), - '\u{000c}' => result.push_str("\\f"), - // Only escape C0 control chars (U+0000–U+001F), matching JS JSON.stringify. - // Do NOT escape C1 controls (U+0080–U+009F) — JS outputs those as literal chars. - c if (c as u32) <= 0x1F => { - result.push_str(&format!("\\u{:04x}", c as u32)); - } - c => result.push(c), - } - } - result.push('"'); - result -} - -pub fn format_primitive(prim: &crate::PrimitiveValue) -> String { - match prim { - crate::PrimitiveValue::Null => "null".to_string(), - crate::PrimitiveValue::Undefined => "undefined".to_string(), - crate::PrimitiveValue::Boolean(b) => format!("{}", b), - crate::PrimitiveValue::Number(n) => crate::format_js_number(n.value()), - crate::PrimitiveValue::String(s) => format_js_string(s), - } -} - -pub fn format_property_literal(prop: &crate::PropertyLiteral) -> String { - match prop { - crate::PropertyLiteral::String(s) => s.clone(), - crate::PropertyLiteral::Number(n) => crate::format_js_number(n.value()), - } -} - -pub fn format_object_property_key(key: &crate::ObjectPropertyKey) -> String { - match key { - crate::ObjectPropertyKey::String { name } => format!("String(\"{}\")", name), - crate::ObjectPropertyKey::Identifier { name } => { - format!("Identifier(\"{}\")", name) - } - crate::ObjectPropertyKey::Computed { name } => { - format!("Computed({})", name.identifier.0) - } - crate::ObjectPropertyKey::Number { name } => { - format!("Number({})", crate::format_js_number(name.value())) - } - } -} - -pub fn format_non_local_binding(binding: &crate::NonLocalBinding) -> String { - match binding { - crate::NonLocalBinding::Global { name } => { - format!("Global {{ name: \"{}\" }}", name) - } - crate::NonLocalBinding::ModuleLocal { name } => { - format!("ModuleLocal {{ name: \"{}\" }}", name) - } - crate::NonLocalBinding::ImportDefault { name, module } => { - format!( - "ImportDefault {{ name: \"{}\", module: \"{}\" }}", - name, module - ) - } - crate::NonLocalBinding::ImportNamespace { name, module } => { - format!( - "ImportNamespace {{ name: \"{}\", module: \"{}\" }}", - name, module - ) - } - crate::NonLocalBinding::ImportSpecifier { - name, - module, - imported, - } => { - format!( - "ImportSpecifier {{ name: \"{}\", module: \"{}\", imported: \"{}\" }}", - name, module, imported - ) - } - } -} - -pub fn format_value_kind(kind: ValueKind) -> &'static str { - match kind { - ValueKind::Mutable => "mutable", - ValueKind::Frozen => "frozen", - ValueKind::Primitive => "primitive", - ValueKind::MaybeFrozen => "maybe-frozen", - ValueKind::Global => "global", - ValueKind::Context => "context", - } -} - -pub fn format_value_reason(reason: ValueReason) -> &'static str { - match reason { - ValueReason::KnownReturnSignature => "known-return-signature", - ValueReason::State => "state", - ValueReason::ReducerState => "reducer-state", - ValueReason::Context => "context", - ValueReason::Effect => "effect", - ValueReason::HookCaptured => "hook-captured", - ValueReason::HookReturn => "hook-return", - ValueReason::Global => "global", - ValueReason::JsxCaptured => "jsx-captured", - ValueReason::StoreLocal => "store-local", - ValueReason::ReactiveFunctionArgument => "reactive-function-argument", - ValueReason::Other => "other", - } -} - -// ============================================================================= -// PrintFormatter — shared stateful formatter -// ============================================================================= - -/// Shared formatter state used by both HIR and reactive printers. -/// -/// Both `DebugPrinter` structs delegate to this for formatting shared constructs -/// like Places, Identifiers, Scopes, Types, InstructionValues, etc. -pub struct PrintFormatter<'a> { - pub env: &'a Environment, - pub seen_identifiers: HashSet<IdentifierId>, - pub seen_scopes: HashSet<ScopeId>, - pub output: Vec<String>, - pub indent_level: usize, -} - -impl<'a> PrintFormatter<'a> { - pub fn new(env: &'a Environment) -> Self { - Self { - env, - seen_identifiers: HashSet::new(), - seen_scopes: HashSet::new(), - output: Vec::new(), - indent_level: 0, - } - } - - pub fn line(&mut self, text: &str) { - let indent = " ".repeat(self.indent_level); - self.output.push(format!("{}{}", indent, text)); - } - - /// Write a line without adding indentation (used when copying pre-formatted output) - pub fn line_raw(&mut self, text: &str) { - self.output.push(text.to_string()); - } - - pub fn indent(&mut self) { - self.indent_level += 1; - } - - pub fn dedent(&mut self) { - self.indent_level -= 1; - } - - pub fn to_string_output(&self) -> String { - self.output.join("\n") - } - - // ========================================================================= - // AliasingEffect - // ========================================================================= - - pub fn format_effect(&self, effect: &AliasingEffect) -> String { - match effect { - AliasingEffect::Freeze { value, reason } => { - format!( - "Freeze {{ value: {}, reason: {} }}", - value.identifier.0, - format_value_reason(*reason) - ) - } - AliasingEffect::Mutate { value, reason } => match reason { - Some(MutationReason::AssignCurrentProperty) => { - format!( - "Mutate {{ value: {}, reason: AssignCurrentProperty }}", - value.identifier.0 - ) - } - None => format!("Mutate {{ value: {} }}", value.identifier.0), - }, - AliasingEffect::MutateConditionally { value } => { - format!("MutateConditionally {{ value: {} }}", value.identifier.0) - } - AliasingEffect::MutateTransitive { value } => { - format!("MutateTransitive {{ value: {} }}", value.identifier.0) - } - AliasingEffect::MutateTransitiveConditionally { value } => { - format!( - "MutateTransitiveConditionally {{ value: {} }}", - value.identifier.0 - ) - } - AliasingEffect::Capture { from, into } => { - format!( - "Capture {{ into: {}, from: {} }}", - into.identifier.0, from.identifier.0 - ) - } - AliasingEffect::Alias { from, into } => { - format!( - "Alias {{ into: {}, from: {} }}", - into.identifier.0, from.identifier.0 - ) - } - AliasingEffect::MaybeAlias { from, into } => { - format!( - "MaybeAlias {{ into: {}, from: {} }}", - into.identifier.0, from.identifier.0 - ) - } - AliasingEffect::Assign { from, into } => { - format!( - "Assign {{ into: {}, from: {} }}", - into.identifier.0, from.identifier.0 - ) - } - AliasingEffect::Create { - into, - value, - reason, - } => { - format!( - "Create {{ into: {}, value: {}, reason: {} }}", - into.identifier.0, - format_value_kind(*value), - format_value_reason(*reason) - ) - } - AliasingEffect::CreateFrom { from, into } => { - format!( - "CreateFrom {{ into: {}, from: {} }}", - into.identifier.0, from.identifier.0 - ) - } - AliasingEffect::ImmutableCapture { from, into } => { - format!( - "ImmutableCapture {{ into: {}, from: {} }}", - into.identifier.0, from.identifier.0 - ) - } - AliasingEffect::Apply { - receiver, - function, - mutates_function, - args, - into, - .. - } => { - let args_str: Vec<String> = args - .iter() - .map(|a| match a { - PlaceOrSpreadOrHole::Hole => "hole".to_string(), - PlaceOrSpreadOrHole::Place(p) => p.identifier.0.to_string(), - PlaceOrSpreadOrHole::Spread(s) => format!("...{}", s.place.identifier.0), - }) - .collect(); - format!( - "Apply {{ into: {}, receiver: {}, function: {}, mutatesFunction: {}, args: [{}] }}", - into.identifier.0, - receiver.identifier.0, - function.identifier.0, - mutates_function, - args_str.join(", ") - ) - } - AliasingEffect::CreateFunction { - captures, - function_id: _, - into, - } => { - let cap_str: Vec<String> = captures - .iter() - .map(|p| p.identifier.0.to_string()) - .collect(); - format!( - "CreateFunction {{ into: {}, captures: [{}] }}", - into.identifier.0, - cap_str.join(", ") - ) - } - AliasingEffect::MutateFrozen { place, error } => { - format!( - "MutateFrozen {{ place: {}, reason: {:?} }}", - place.identifier.0, error.reason - ) - } - AliasingEffect::MutateGlobal { place, error } => { - format!( - "MutateGlobal {{ place: {}, reason: {:?} }}", - place.identifier.0, error.reason - ) - } - AliasingEffect::Impure { place, error } => { - format!( - "Impure {{ place: {}, reason: {:?} }}", - place.identifier.0, error.reason - ) - } - AliasingEffect::Render { place } => { - format!("Render {{ place: {} }}", place.identifier.0) - } - } - } - - // ========================================================================= - // Place (with identifier deduplication) - // ========================================================================= - - pub fn format_place_field(&mut self, field_name: &str, place: &Place) { - let is_seen = self.seen_identifiers.contains(&place.identifier); - if is_seen { - self.line(&format!( - "{}: Place {{ identifier: Identifier({}), effect: {}, reactive: {}, loc: {} }}", - field_name, - place.identifier.0, - place.effect, - place.reactive, - format_loc(&place.loc) - )); - } else { - self.line(&format!("{}: Place {{", field_name)); - self.indent(); - self.line("identifier:"); - self.indent(); - self.format_identifier(place.identifier); - self.dedent(); - self.line(&format!("effect: {}", place.effect)); - self.line(&format!("reactive: {}", place.reactive)); - self.line(&format!("loc: {}", format_loc(&place.loc))); - self.dedent(); - self.line("}"); - } - } - - // ========================================================================= - // Identifier (first-seen expansion) - // ========================================================================= - - pub fn format_identifier(&mut self, id: IdentifierId) { - self.seen_identifiers.insert(id); - let ident = &self.env.identifiers[id.0 as usize]; - self.line("Identifier {"); - self.indent(); - self.line(&format!("id: {}", ident.id.0)); - self.line(&format!("declarationId: {}", ident.declaration_id.0)); - match &ident.name { - Some(name) => { - let (kind, value) = match name { - IdentifierName::Named(n) => ("named", n.as_str()), - IdentifierName::Promoted(n) => ("promoted", n.as_str()), - }; - self.line(&format!( - "name: {{ kind: \"{}\", value: \"{}\" }}", - kind, value - )); - } - None => self.line("name: null"), - } - // Print the identifier's mutable_range directly, matching the TS - // DebugPrintHIR which prints `identifier.mutableRange`. In TS, - // InferReactiveScopeVariables sets identifier.mutableRange = scope.range - // (shared reference), and AlignReactiveScopesToBlockScopesHIR syncs them. - // After MergeOverlappingReactiveScopesHIR repoints scopes, the TS - // identifier.mutableRange still references the OLD scope's range (stale), - // so we match by using ident.mutable_range directly (which is synced - // at the AlignReactiveScopesToBlockScopesHIR step but not re-synced - // after scope repointing in merge passes). - self.line(&format!( - "mutableRange: [{}:{}]", - ident.mutable_range.start.0, ident.mutable_range.end.0 - )); - match ident.scope { - Some(scope_id) => self.format_scope_field("scope", scope_id), - None => self.line("scope: null"), - } - self.line(&format!("type: {}", self.format_type(ident.type_))); - self.line(&format!("loc: {}", format_loc(&ident.loc))); - self.dedent(); - self.line("}"); - } - - // ========================================================================= - // Scope (with deduplication) - // ========================================================================= - - pub fn format_scope_field(&mut self, field_name: &str, scope_id: ScopeId) { - let is_seen = self.seen_scopes.contains(&scope_id); - if is_seen { - self.line(&format!("{}: Scope({})", field_name, scope_id.0)); - } else { - self.seen_scopes.insert(scope_id); - if let Some(scope) = self.env.scopes.iter().find(|s| s.id == scope_id) { - let range_start = scope.range.start.0; - let range_end = scope.range.end.0; - let dependencies = scope.dependencies.clone(); - let declarations = scope.declarations.clone(); - let reassignments = scope.reassignments.clone(); - let early_return_value = scope.early_return_value.clone(); - let merged = scope.merged.clone(); - let loc = scope.loc; - - self.line(&format!("{}: Scope {{", field_name)); - self.indent(); - self.line(&format!("id: {}", scope_id.0)); - self.line(&format!("range: [{}:{}]", range_start, range_end)); - - // dependencies - self.line("dependencies:"); - self.indent(); - for (i, dep) in dependencies.iter().enumerate() { - let path_str: String = dep - .path - .iter() - .map(|p| { - let prop = match &p.property { - crate::PropertyLiteral::String(s) => s.clone(), - crate::PropertyLiteral::Number(n) => { - crate::format_js_number(n.value()) - } - }; - format!("{}{}", if p.optional { "?." } else { "." }, prop) - }) - .collect(); - self.line(&format!( - "[{}] {{ identifier: {}, reactive: {}, path: \"{}\" }}", - i, dep.identifier.0, dep.reactive, path_str - )); - } - self.dedent(); - - // declarations - self.line("declarations:"); - self.indent(); - for (ident_id, decl) in &declarations { - self.line(&format!( - "{}: {{ identifier: {}, scope: {} }}", - ident_id.0, decl.identifier.0, decl.scope.0 - )); - } - self.dedent(); - - // reassignments - self.line("reassignments:"); - self.indent(); - for ident_id in &reassignments { - self.line(&format!("{}", ident_id.0)); - } - self.dedent(); - - // earlyReturnValue - if let Some(early_return) = &early_return_value { - self.line("earlyReturnValue:"); - self.indent(); - self.line(&format!("value: {}", early_return.value.0)); - self.line(&format!("loc: {}", format_loc(&early_return.loc))); - self.line(&format!("label: bb{}", early_return.label.0)); - self.dedent(); - } else { - self.line("earlyReturnValue: null"); - } - - // merged - let merged_str: Vec<String> = merged.iter().map(|s| s.0.to_string()).collect(); - self.line(&format!("merged: [{}]", merged_str.join(", "))); - - // loc - self.line(&format!("loc: {}", format_loc(&loc))); - - self.dedent(); - self.line("}"); - } else { - self.line(&format!("{}: Scope({})", field_name, scope_id.0)); - } - } - } - - // ========================================================================= - // Type - // ========================================================================= - - pub fn format_type(&self, type_id: crate::TypeId) -> String { - if let Some(ty) = self.env.types.get(type_id.0 as usize) { - self.format_type_value(ty) - } else { - format!("Type({})", type_id.0) - } - } - - pub fn format_type_value(&self, ty: &Type) -> String { - match ty { - Type::Primitive => "Primitive".to_string(), - Type::Function { - shape_id, - return_type, - is_constructor, - } => { - format!( - "Function {{ shapeId: {}, return: {}, isConstructor: {} }}", - match shape_id { - Some(s) => format!("\"{}\"", s), - None => "null".to_string(), - }, - self.format_type_value(return_type), - is_constructor - ) - } - Type::Object { shape_id } => { - format!( - "Object {{ shapeId: {} }}", - match shape_id { - Some(s) => format!("\"{}\"", s), - None => "null".to_string(), - } - ) - } - Type::TypeVar { id } => format!("Type({})", id.0), - Type::Poly => "Poly".to_string(), - Type::Phi { operands } => { - let ops: Vec<String> = operands - .iter() - .map(|op| self.format_type_value(op)) - .collect(); - format!("Phi {{ operands: [{}] }}", ops.join(", ")) - } - Type::Property { - object_type, - object_name, - property_name, - } => { - let prop_str = match property_name { - crate::PropertyNameKind::Literal { value } => { - format!("\"{}\"", format_property_literal(value)) - } - crate::PropertyNameKind::Computed { value } => { - format!("computed({})", self.format_type_value(value)) - } - }; - format!( - "Property {{ objectType: {}, objectName: \"{}\", propertyName: {} }}", - self.format_type_value(object_type), - object_name, - prop_str - ) - } - Type::ObjectMethod => "ObjectMethod".to_string(), - } - } - - // ========================================================================= - // LValue - // ========================================================================= - - pub fn format_lvalue(&mut self, field_name: &str, lv: &LValue) { - self.line(&format!("{}:", field_name)); - self.indent(); - self.line(&format!("kind: {:?}", lv.kind)); - self.format_place_field("place", &lv.place); - self.dedent(); - } - - // ========================================================================= - // Pattern - // ========================================================================= - - pub fn format_pattern(&mut self, pattern: &Pattern) { - match pattern { - Pattern::Array(arr) => { - self.line("pattern: ArrayPattern {"); - self.indent(); - self.line("items:"); - self.indent(); - for (i, item) in arr.items.iter().enumerate() { - match item { - crate::ArrayPatternElement::Hole => { - self.line(&format!("[{}] Hole", i)); - } - crate::ArrayPatternElement::Place(p) => { - self.format_place_field(&format!("[{}]", i), p); - } - crate::ArrayPatternElement::Spread(s) => { - self.line(&format!("[{}] Spread:", i)); - self.indent(); - self.format_place_field("place", &s.place); - self.dedent(); - } - } - } - self.dedent(); - self.line(&format!("loc: {}", format_loc(&arr.loc))); - self.dedent(); - self.line("}"); - } - Pattern::Object(obj) => { - self.line("pattern: ObjectPattern {"); - self.indent(); - self.line("properties:"); - self.indent(); - for (i, prop) in obj.properties.iter().enumerate() { - match prop { - crate::ObjectPropertyOrSpread::Property(p) => { - self.line(&format!("[{}] ObjectProperty {{", i)); - self.indent(); - self.line(&format!("key: {}", format_object_property_key(&p.key))); - self.line(&format!("type: \"{}\"", p.property_type)); - self.format_place_field("place", &p.place); - self.dedent(); - self.line("}"); - } - crate::ObjectPropertyOrSpread::Spread(s) => { - self.line(&format!("[{}] Spread:", i)); - self.indent(); - self.format_place_field("place", &s.place); - self.dedent(); - } - } - } - self.dedent(); - self.line(&format!("loc: {}", format_loc(&obj.loc))); - self.dedent(); - self.line("}"); - } - } - } - - // ========================================================================= - // Arguments - // ========================================================================= - - pub fn format_argument(&mut self, arg: &crate::PlaceOrSpread, index: usize) { - match arg { - crate::PlaceOrSpread::Place(p) => { - self.format_place_field(&format!("[{}]", index), p); - } - crate::PlaceOrSpread::Spread(s) => { - self.line(&format!("[{}] Spread:", index)); - self.indent(); - self.format_place_field("place", &s.place); - self.dedent(); - } - } - } - - // ========================================================================= - // InstructionValue - // ========================================================================= - - /// Format an InstructionValue. The `inner_func_formatter` callback is invoked - /// for FunctionExpression/ObjectMethod to format the inner HirFunction. If None, - /// a placeholder is printed instead. - pub fn format_instruction_value( - &mut self, - value: &InstructionValue, - inner_func_formatter: Option<&dyn Fn(&mut PrintFormatter, &HirFunction)>, - ) { - match value { - InstructionValue::ArrayExpression { elements, loc } => { - self.line("ArrayExpression {"); - self.indent(); - self.line("elements:"); - self.indent(); - for (i, elem) in elements.iter().enumerate() { - match elem { - crate::ArrayElement::Place(p) => { - self.format_place_field(&format!("[{}]", i), p); - } - crate::ArrayElement::Hole => { - self.line(&format!("[{}] Hole", i)); - } - crate::ArrayElement::Spread(s) => { - self.line(&format!("[{}] Spread:", i)); - self.indent(); - self.format_place_field("place", &s.place); - self.dedent(); - } - } - } - self.dedent(); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::ObjectExpression { properties, loc } => { - self.line("ObjectExpression {"); - self.indent(); - self.line("properties:"); - self.indent(); - for (i, prop) in properties.iter().enumerate() { - match prop { - crate::ObjectPropertyOrSpread::Property(p) => { - self.line(&format!("[{}] ObjectProperty {{", i)); - self.indent(); - self.line(&format!("key: {}", format_object_property_key(&p.key))); - self.line(&format!("type: \"{}\"", p.property_type)); - self.format_place_field("place", &p.place); - self.dedent(); - self.line("}"); - } - crate::ObjectPropertyOrSpread::Spread(s) => { - self.line(&format!("[{}] Spread:", i)); - self.indent(); - self.format_place_field("place", &s.place); - self.dedent(); - } - } - } - self.dedent(); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::UnaryExpression { - operator, - value: val, - loc, - } => { - self.line("UnaryExpression {"); - self.indent(); - self.line(&format!("operator: \"{}\"", operator)); - self.format_place_field("value", val); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::BinaryExpression { - operator, - left, - right, - loc, - } => { - self.line("BinaryExpression {"); - self.indent(); - self.line(&format!("operator: \"{}\"", operator)); - self.format_place_field("left", left); - self.format_place_field("right", right); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::NewExpression { callee, args, loc } => { - self.line("NewExpression {"); - self.indent(); - self.format_place_field("callee", callee); - self.line("args:"); - self.indent(); - for (i, arg) in args.iter().enumerate() { - self.format_argument(arg, i); - } - self.dedent(); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::CallExpression { callee, args, loc } => { - self.line("CallExpression {"); - self.indent(); - self.format_place_field("callee", callee); - self.line("args:"); - self.indent(); - for (i, arg) in args.iter().enumerate() { - self.format_argument(arg, i); - } - self.dedent(); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::MethodCall { - receiver, - property, - args, - loc, - } => { - self.line("MethodCall {"); - self.indent(); - self.format_place_field("receiver", receiver); - self.format_place_field("property", property); - self.line("args:"); - self.indent(); - for (i, arg) in args.iter().enumerate() { - self.format_argument(arg, i); - } - self.dedent(); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::JSXText { value: val, loc } => { - self.line(&format!( - "JSXText {{ value: {}, loc: {} }}", - format_js_string(val), - format_loc(loc) - )); - } - InstructionValue::Primitive { value: prim, loc } => { - self.line(&format!( - "Primitive {{ value: {}, loc: {} }}", - format_primitive(prim), - format_loc(loc) - )); - } - InstructionValue::TypeCastExpression { - value: val, - type_, - type_annotation_name, - type_annotation_kind, - type_annotation: _, - loc, - } => { - self.line("TypeCastExpression {"); - self.indent(); - self.format_place_field("value", val); - self.line(&format!("type: {}", self.format_type_value(type_))); - if let Some(annotation_name) = type_annotation_name { - self.line(&format!("typeAnnotation: {}", annotation_name)); - } - if let Some(annotation_kind) = type_annotation_kind { - self.line(&format!("typeAnnotationKind: \"{}\"", annotation_kind)); - } - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::JsxExpression { - tag, - props, - children, - loc, - opening_loc, - closing_loc, - } => { - self.line("JsxExpression {"); - self.indent(); - match tag { - crate::JsxTag::Place(p) => { - self.format_place_field("tag", p); - } - crate::JsxTag::Builtin(b) => { - self.line(&format!("tag: BuiltinTag(\"{}\")", b.name)); - } - } - self.line("props:"); - self.indent(); - for (i, prop) in props.iter().enumerate() { - match prop { - crate::JsxAttribute::Attribute { name, place } => { - self.line(&format!("[{}] JsxAttribute {{", i)); - self.indent(); - self.line(&format!("name: \"{}\"", name)); - self.format_place_field("place", place); - self.dedent(); - self.line("}"); - } - crate::JsxAttribute::SpreadAttribute { argument } => { - self.line(&format!("[{}] JsxSpreadAttribute:", i)); - self.indent(); - self.format_place_field("argument", argument); - self.dedent(); - } - } - } - self.dedent(); - match children { - Some(c) => { - self.line("children:"); - self.indent(); - for (i, child) in c.iter().enumerate() { - self.format_place_field(&format!("[{}]", i), child); - } - self.dedent(); - } - None => self.line("children: null"), - } - self.line(&format!("openingLoc: {}", format_loc(opening_loc))); - self.line(&format!("closingLoc: {}", format_loc(closing_loc))); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::JsxFragment { children, loc } => { - self.line("JsxFragment {"); - self.indent(); - self.line("children:"); - self.indent(); - for (i, child) in children.iter().enumerate() { - self.format_place_field(&format!("[{}]", i), child); - } - self.dedent(); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::UnsupportedNode { node_type, loc, .. } => match node_type { - Some(t) => self.line(&format!( - "UnsupportedNode {{ type: {:?}, loc: {} }}", - t, - format_loc(loc) - )), - None => self.line(&format!("UnsupportedNode {{ loc: {} }}", format_loc(loc))), - }, - InstructionValue::LoadLocal { place, loc } => { - self.line("LoadLocal {"); - self.indent(); - self.format_place_field("place", place); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::DeclareLocal { - lvalue, - type_annotation, - loc, - } => { - self.line("DeclareLocal {"); - self.indent(); - self.format_lvalue("lvalue", lvalue); - self.line(&format!( - "type: {}", - match type_annotation { - Some(t) => t.clone(), - None => "null".to_string(), - } - )); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::DeclareContext { lvalue, loc } => { - self.line("DeclareContext {"); - self.indent(); - self.line("lvalue:"); - self.indent(); - self.line(&format!("kind: {:?}", lvalue.kind)); - self.format_place_field("place", &lvalue.place); - self.dedent(); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::StoreLocal { - lvalue, - value: val, - type_annotation, - loc, - } => { - self.line("StoreLocal {"); - self.indent(); - self.format_lvalue("lvalue", lvalue); - self.format_place_field("value", val); - self.line(&format!( - "type: {}", - match type_annotation { - Some(t) => t.clone(), - None => "null".to_string(), - } - )); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::LoadContext { place, loc } => { - self.line("LoadContext {"); - self.indent(); - self.format_place_field("place", place); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::StoreContext { - lvalue, - value: val, - loc, - } => { - self.line("StoreContext {"); - self.indent(); - self.line("lvalue:"); - self.indent(); - self.line(&format!("kind: {:?}", lvalue.kind)); - self.format_place_field("place", &lvalue.place); - self.dedent(); - self.format_place_field("value", val); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::Destructure { - lvalue, - value: val, - loc, - } => { - self.line("Destructure {"); - self.indent(); - self.line("lvalue:"); - self.indent(); - self.line(&format!("kind: {:?}", lvalue.kind)); - self.format_pattern(&lvalue.pattern); - self.dedent(); - self.format_place_field("value", val); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::PropertyLoad { - object, - property, - loc, - } => { - self.line("PropertyLoad {"); - self.indent(); - self.format_place_field("object", object); - self.line(&format!( - "property: \"{}\"", - format_property_literal(property) - )); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::PropertyStore { - object, - property, - value: val, - loc, - } => { - self.line("PropertyStore {"); - self.indent(); - self.format_place_field("object", object); - self.line(&format!( - "property: \"{}\"", - format_property_literal(property) - )); - self.format_place_field("value", val); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::PropertyDelete { - object, - property, - loc, - } => { - self.line("PropertyDelete {"); - self.indent(); - self.format_place_field("object", object); - self.line(&format!( - "property: \"{}\"", - format_property_literal(property) - )); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::ComputedLoad { - object, - property, - loc, - } => { - self.line("ComputedLoad {"); - self.indent(); - self.format_place_field("object", object); - self.format_place_field("property", property); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::ComputedStore { - object, - property, - value: val, - loc, - } => { - self.line("ComputedStore {"); - self.indent(); - self.format_place_field("object", object); - self.format_place_field("property", property); - self.format_place_field("value", val); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::ComputedDelete { - object, - property, - loc, - } => { - self.line("ComputedDelete {"); - self.indent(); - self.format_place_field("object", object); - self.format_place_field("property", property); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::LoadGlobal { binding, loc } => { - self.line("LoadGlobal {"); - self.indent(); - self.line(&format!("binding: {}", format_non_local_binding(binding))); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::StoreGlobal { - name, - value: val, - loc, - } => { - self.line("StoreGlobal {"); - self.indent(); - self.line(&format!("name: \"{}\"", name)); - self.format_place_field("value", val); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::FunctionExpression { - name, - name_hint, - lowered_func, - expr_type, - loc, - } => { - self.line("FunctionExpression {"); - self.indent(); - self.line(&format!( - "name: {}", - match name { - Some(n) => format!("\"{}\"", n), - None => "null".to_string(), - } - )); - self.line(&format!( - "nameHint: {}", - match name_hint { - Some(h) => format!("\"{}\"", h), - None => "null".to_string(), - } - )); - self.line(&format!("type: \"{:?}\"", expr_type)); - self.line("loweredFunc:"); - let inner_func = &self.env.functions[lowered_func.func.0 as usize]; - if let Some(formatter) = inner_func_formatter { - formatter(self, inner_func); - } else { - self.line(&format!(" <function {}>", lowered_func.func.0)); - } - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::ObjectMethod { loc, lowered_func } => { - self.line("ObjectMethod {"); - self.indent(); - self.line("loweredFunc:"); - let inner_func = &self.env.functions[lowered_func.func.0 as usize]; - if let Some(formatter) = inner_func_formatter { - formatter(self, inner_func); - } else { - self.line(&format!(" <function {}>", lowered_func.func.0)); - } - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::TaggedTemplateExpression { - tag, - value: val, - loc, - } => { - self.line("TaggedTemplateExpression {"); - self.indent(); - self.format_place_field("tag", tag); - self.line(&format!("raw: {}", format_js_string(&val.raw))); - self.line(&format!( - "cooked: {}", - match &val.cooked { - Some(c) => format_js_string(c), - None => "undefined".to_string(), - } - )); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::TemplateLiteral { - subexprs, - quasis, - loc, - } => { - self.line("TemplateLiteral {"); - self.indent(); - self.line("subexprs:"); - self.indent(); - for (i, sub) in subexprs.iter().enumerate() { - self.format_place_field(&format!("[{}]", i), sub); - } - self.dedent(); - self.line("quasis:"); - self.indent(); - for (i, q) in quasis.iter().enumerate() { - self.line(&format!( - "[{}] {{ raw: {}, cooked: {} }}", - i, - format_js_string(&q.raw), - match &q.cooked { - Some(c) => format_js_string(c), - None => "undefined".to_string(), - } - )); - } - self.dedent(); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::RegExpLiteral { - pattern, - flags, - loc, - } => { - self.line(&format!( - "RegExpLiteral {{ pattern: \"{}\", flags: \"{}\", loc: {} }}", - pattern, - flags, - format_loc(loc) - )); - } - InstructionValue::MetaProperty { - meta, - property, - loc, - } => { - self.line(&format!( - "MetaProperty {{ meta: \"{}\", property: \"{}\", loc: {} }}", - meta, - property, - format_loc(loc) - )); - } - InstructionValue::Await { value: val, loc } => { - self.line("Await {"); - self.indent(); - self.format_place_field("value", val); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::GetIterator { collection, loc } => { - self.line("GetIterator {"); - self.indent(); - self.format_place_field("collection", collection); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::IteratorNext { - iterator, - collection, - loc, - } => { - self.line("IteratorNext {"); - self.indent(); - self.format_place_field("iterator", iterator); - self.format_place_field("collection", collection); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::NextPropertyOf { value: val, loc } => { - self.line("NextPropertyOf {"); - self.indent(); - self.format_place_field("value", val); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::Debugger { loc } => { - self.line(&format!("Debugger {{ loc: {} }}", format_loc(loc))); - } - InstructionValue::PostfixUpdate { - lvalue, - operation, - value: val, - loc, - } => { - self.line("PostfixUpdate {"); - self.indent(); - self.format_place_field("lvalue", lvalue); - self.line(&format!("operation: \"{}\"", operation)); - self.format_place_field("value", val); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::PrefixUpdate { - lvalue, - operation, - value: val, - loc, - } => { - self.line("PrefixUpdate {"); - self.indent(); - self.format_place_field("lvalue", lvalue); - self.line(&format!("operation: \"{}\"", operation)); - self.format_place_field("value", val); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::StartMemoize { - manual_memo_id, - deps, - deps_loc: _, - has_invalid_deps: _, - loc, - } => { - self.line("StartMemoize {"); - self.indent(); - self.line(&format!("manualMemoId: {}", manual_memo_id)); - match deps { - Some(d) => { - self.line("deps:"); - self.indent(); - for (i, dep) in d.iter().enumerate() { - let root_str = match &dep.root { - crate::ManualMemoDependencyRoot::Global { identifier_name } => { - format!("Global(\"{}\")", identifier_name) - } - crate::ManualMemoDependencyRoot::NamedLocal { - value: val, - constant, - } => { - format!( - "NamedLocal({}, constant={})", - val.identifier.0, constant - ) - } - }; - let path_str: String = dep - .path - .iter() - .map(|p| { - format!( - "{}.{}", - if p.optional { "?" } else { "" }, - format_property_literal(&p.property) - ) - }) - .collect(); - self.line(&format!("[{}] {}{}", i, root_str, path_str)); - } - self.dedent(); - } - None => self.line("deps: null"), - } - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - InstructionValue::FinishMemoize { - manual_memo_id, - decl, - pruned, - loc, - } => { - self.line("FinishMemoize {"); - self.indent(); - self.line(&format!("manualMemoId: {}", manual_memo_id)); - self.format_place_field("decl", decl); - self.line(&format!("pruned: {}", pruned)); - self.line(&format!("loc: {}", format_loc(loc))); - self.dedent(); - self.line("}"); - } - } - } - - // ========================================================================= - // Errors - // ========================================================================= - - pub fn format_errors(&mut self, error: &CompilerError) { - if error.details.is_empty() { - self.line("Errors: []"); - return; - } - self.line("Errors:"); - self.indent(); - for (i, detail) in error.details.iter().enumerate() { - self.line(&format!("[{}] {{", i)); - self.indent(); - match detail { - CompilerErrorOrDiagnostic::Diagnostic(d) => { - self.line(&format!("severity: {:?}", d.severity())); - self.line(&format!("reason: {:?}", d.reason)); - self.line(&format!( - "description: {}", - match &d.description { - Some(desc) => format!("{:?}", desc), - None => "null".to_string(), - } - )); - self.line(&format!("category: {:?}", d.category)); - let loc = d.primary_location(); - self.line(&format!( - "loc: {}", - match loc { - Some(l) => format_loc_value(l), - None => "null".to_string(), - } - )); - } - CompilerErrorOrDiagnostic::ErrorDetail(d) => { - self.line(&format!("severity: {:?}", d.severity())); - self.line(&format!("reason: {:?}", d.reason)); - self.line(&format!( - "description: {}", - match &d.description { - Some(desc) => format!("{:?}", desc), - None => "null".to_string(), - } - )); - self.line(&format!("category: {:?}", d.category)); - self.line(&format!( - "loc: {}", - match &d.loc { - Some(l) => format_loc_value(l), - None => "null".to_string(), - } - )); - } - } - self.dedent(); - self.line("}"); - } - self.dedent(); - } -} diff --git a/compiler/crates/react_compiler_hir/src/reactive.rs b/compiler/crates/react_compiler_hir/src/reactive.rs deleted file mode 100644 index fd736d55717b..000000000000 --- a/compiler/crates/react_compiler_hir/src/reactive.rs +++ /dev/null @@ -1,248 +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. - -//! Reactive function types — tree representation of a compiled function. -//! -//! `ReactiveFunction` is derived from the HIR CFG by `BuildReactiveFunction`. -//! Control flow constructs (if/switch/loops/try) and reactive scopes become -//! nested blocks rather than block references. -//! -//! Corresponds to the reactive types in `HIR.ts`. - -use react_compiler_diagnostics::SourceLocation; - -use crate::{ - AliasingEffect, BlockId, EvaluationOrder, InstructionValue, LogicalOperator, ParamPattern, - Place, ScopeId, -}; - -// ============================================================================= -// ReactiveFunction -// ============================================================================= - -/// Tree representation of a compiled function, converted from the CFG-based HIR. -/// TS: ReactiveFunction in HIR.ts -#[derive(Debug, Clone)] -pub struct ReactiveFunction { - pub loc: Option<SourceLocation>, - pub id: Option<String>, - pub name_hint: Option<String>, - pub params: Vec<ParamPattern>, - pub generator: bool, - pub is_async: bool, - pub body: ReactiveBlock, - pub directives: Vec<String>, - // No env field — passed separately per established Rust convention -} - -// ============================================================================= -// ReactiveBlock and ReactiveStatement -// ============================================================================= - -/// TS: ReactiveBlock = Array<ReactiveStatement> -pub type ReactiveBlock = Vec<ReactiveStatement>; - -/// TS: ReactiveStatement (discriminated union with 'kind' field) -#[derive(Debug, Clone)] -pub enum ReactiveStatement { - Instruction(ReactiveInstruction), - Terminal(ReactiveTerminalStatement), - Scope(ReactiveScopeBlock), - PrunedScope(PrunedReactiveScopeBlock), -} - -// ============================================================================= -// ReactiveInstruction and ReactiveValue -// ============================================================================= - -/// TS: ReactiveInstruction -#[derive(Debug, Clone)] -pub struct ReactiveInstruction { - pub id: EvaluationOrder, - pub lvalue: Option<Place>, - pub value: ReactiveValue, - pub effects: Option<Vec<AliasingEffect>>, - pub loc: Option<SourceLocation>, -} - -/// Extends InstructionValue with compound expression types that were -/// separate blocks+terminals in HIR but become nested expressions here. -/// TS: ReactiveValue = InstructionValue | ReactiveLogicalValue | ... -#[derive(Debug, Clone)] -pub enum ReactiveValue { - /// All ~35 base instruction value kinds - Instruction(InstructionValue), - - /// TS: ReactiveLogicalValue - LogicalExpression { - operator: LogicalOperator, - left: Box<ReactiveValue>, - right: Box<ReactiveValue>, - loc: Option<SourceLocation>, - }, - - /// TS: ReactiveTernaryValue - ConditionalExpression { - test: Box<ReactiveValue>, - consequent: Box<ReactiveValue>, - alternate: Box<ReactiveValue>, - loc: Option<SourceLocation>, - }, - - /// TS: ReactiveSequenceValue - SequenceExpression { - instructions: Vec<ReactiveInstruction>, - id: EvaluationOrder, - value: Box<ReactiveValue>, - loc: Option<SourceLocation>, - }, - - /// TS: ReactiveOptionalCallValue - OptionalExpression { - id: EvaluationOrder, - value: Box<ReactiveValue>, - optional: bool, - loc: Option<SourceLocation>, - }, -} - -// ============================================================================= -// Terminals -// ============================================================================= - -#[derive(Debug, Clone)] -pub struct ReactiveTerminalStatement { - pub terminal: ReactiveTerminal, - pub label: Option<ReactiveLabel>, -} - -#[derive(Debug, Clone)] -pub struct ReactiveLabel { - pub id: BlockId, - pub implicit: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ReactiveTerminalTargetKind { - Implicit, - Labeled, - Unlabeled, -} - -impl std::fmt::Display for ReactiveTerminalTargetKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ReactiveTerminalTargetKind::Implicit => write!(f, "implicit"), - ReactiveTerminalTargetKind::Labeled => write!(f, "labeled"), - ReactiveTerminalTargetKind::Unlabeled => write!(f, "unlabeled"), - } - } -} - -#[derive(Debug, Clone)] -pub enum ReactiveTerminal { - Break { - target: BlockId, - id: EvaluationOrder, - target_kind: ReactiveTerminalTargetKind, - loc: Option<SourceLocation>, - }, - Continue { - target: BlockId, - id: EvaluationOrder, - target_kind: ReactiveTerminalTargetKind, - loc: Option<SourceLocation>, - }, - Return { - value: Place, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Throw { - value: Place, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Switch { - test: Place, - cases: Vec<ReactiveSwitchCase>, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - DoWhile { - loop_block: ReactiveBlock, - test: ReactiveValue, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - While { - test: ReactiveValue, - loop_block: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - For { - init: ReactiveValue, - test: ReactiveValue, - update: Option<ReactiveValue>, - loop_block: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - ForOf { - init: ReactiveValue, - test: ReactiveValue, - loop_block: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - ForIn { - init: ReactiveValue, - loop_block: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - If { - test: Place, - consequent: ReactiveBlock, - alternate: Option<ReactiveBlock>, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Label { - block: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Try { - block: ReactiveBlock, - handler_binding: Option<Place>, - handler: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, -} - -#[derive(Debug, Clone)] -pub struct ReactiveSwitchCase { - pub test: Option<Place>, - pub block: Option<ReactiveBlock>, -} - -// ============================================================================= -// Scope Blocks -// ============================================================================= - -#[derive(Debug, Clone)] -pub struct ReactiveScopeBlock { - pub scope: ScopeId, - pub instructions: ReactiveBlock, -} - -#[derive(Debug, Clone)] -pub struct PrunedReactiveScopeBlock { - pub scope: ScopeId, - pub instructions: ReactiveBlock, -} diff --git a/compiler/crates/react_compiler_hir/src/type_config.rs b/compiler/crates/react_compiler_hir/src/type_config.rs deleted file mode 100644 index 06554b82ff57..000000000000 --- a/compiler/crates/react_compiler_hir/src/type_config.rs +++ /dev/null @@ -1,212 +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. - -//! Type configuration types, ported from TypeSchema.ts. -//! -//! These are the JSON-serializable config types used by `moduleTypeProvider` -//! and `installTypeConfig` to describe module/function/hook types. - -use indexmap::IndexMap; - -use crate::Effect; - -/// Mirrors TS `ValueKind` enum for use in config. -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ValueKind { - Mutable, - Frozen, - Primitive, - #[serde(rename = "maybefrozen")] - MaybeFrozen, - Global, - Context, -} - -/// Mirrors TS `ValueReason` enum for use in config. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] -pub enum ValueReason { - #[serde(rename = "known-return-signature")] - KnownReturnSignature, - #[serde(rename = "state")] - State, - #[serde(rename = "reducer-state")] - ReducerState, - #[serde(rename = "context")] - Context, - #[serde(rename = "effect")] - Effect, - #[serde(rename = "hook-captured")] - HookCaptured, - #[serde(rename = "hook-return")] - HookReturn, - #[serde(rename = "global")] - Global, - #[serde(rename = "jsx-captured")] - JsxCaptured, - #[serde(rename = "store-local")] - StoreLocal, - #[serde(rename = "reactive-function-argument")] - ReactiveFunctionArgument, - #[serde(rename = "other")] - Other, -} - -// ============================================================================= -// Aliasing effect config types (from TypeSchema.ts) -// ============================================================================= - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(tag = "kind")] -pub enum AliasingEffectConfig { - Freeze { - value: String, - reason: ValueReason, - }, - Create { - into: String, - value: ValueKind, - reason: ValueReason, - }, - CreateFrom { - from: String, - into: String, - }, - Assign { - from: String, - into: String, - }, - Alias { - from: String, - into: String, - }, - Capture { - from: String, - into: String, - }, - ImmutableCapture { - from: String, - into: String, - }, - Impure { - place: String, - }, - Mutate { - value: String, - }, - MutateTransitiveConditionally { - value: String, - }, - Apply { - receiver: String, - function: String, - #[serde(rename = "mutatesFunction")] - mutates_function: bool, - args: Vec<ApplyArgConfig>, - into: String, - }, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(untagged)] -pub enum ApplyArgConfig { - Place(String), - Spread { - #[allow(dead_code)] - kind: ApplyArgSpreadKind, - place: String, - }, - Hole { - #[allow(dead_code)] - kind: ApplyArgHoleKind, - }, -} - -/// Helper enum for tagged serde of `ApplyArgConfig::Spread`. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub enum ApplyArgSpreadKind { - Spread, -} - -/// Helper enum for tagged serde of `ApplyArgConfig::Hole`. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub enum ApplyArgHoleKind { - Hole, -} - -/// Aliasing signature config, the JSON-serializable form. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AliasingSignatureConfig { - pub receiver: String, - pub params: Vec<String>, - pub rest: Option<String>, - pub returns: String, - pub temporaries: Vec<String>, - pub effects: Vec<AliasingEffectConfig>, -} - -// ============================================================================= -// Type config (from TypeSchema.ts) -// ============================================================================= - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(tag = "kind")] -pub enum TypeConfig { - #[serde(rename = "object")] - Object(ObjectTypeConfig), - #[serde(rename = "function")] - Function(FunctionTypeConfig), - #[serde(rename = "hook")] - Hook(HookTypeConfig), - #[serde(rename = "type")] - TypeReference(TypeReferenceConfig), -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct ObjectTypeConfig { - pub properties: Option<IndexMap<String, TypeConfig>>, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FunctionTypeConfig { - pub positional_params: Vec<Effect>, - pub rest_param: Option<Effect>, - pub callee_effect: Effect, - pub return_type: Box<TypeConfig>, - pub return_value_kind: ValueKind, - pub no_alias: Option<bool>, - pub mutable_only_if_operands_are_mutable: Option<bool>, - pub impure: Option<bool>, - pub canonical_name: Option<String>, - pub aliasing: Option<AliasingSignatureConfig>, - pub known_incompatible: Option<String>, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct HookTypeConfig { - pub positional_params: Option<Vec<Effect>>, - pub rest_param: Option<Effect>, - pub return_type: Box<TypeConfig>, - pub return_value_kind: Option<ValueKind>, - pub no_alias: Option<bool>, - pub aliasing: Option<AliasingSignatureConfig>, - pub known_incompatible: Option<String>, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub enum BuiltInTypeRef { - Any, - Ref, - Array, - Primitive, - MixedReadonly, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct TypeReferenceConfig { - pub name: BuiltInTypeRef, -} diff --git a/compiler/crates/react_compiler_hir/src/visitors.rs b/compiler/crates/react_compiler_hir/src/visitors.rs deleted file mode 100644 index 993710b198c7..000000000000 --- a/compiler/crates/react_compiler_hir/src/visitors.rs +++ /dev/null @@ -1,1773 +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. - */ -use std::collections::HashMap; - -use crate::environment::Environment; -use crate::{ - ArrayElement, ArrayPatternElement, BasicBlock, BlockId, HirFunction, IdentifierId, Instruction, - InstructionKind, InstructionValue, JsxAttribute, JsxTag, ManualMemoDependencyRoot, - ObjectPropertyKey, ObjectPropertyOrSpread, Pattern, Place, PlaceOrSpread, ScopeId, Terminal, -}; - -// ============================================================================= -// Iterator functions (return Vec instead of generators) -// ============================================================================= - -/// Yields `instr.lvalue` plus the value's lvalues. -/// Equivalent to TS `eachInstructionLValue`. -pub fn each_instruction_lvalue(instr: &Instruction) -> Vec<Place> { - let mut result = Vec::new(); - result.push(instr.lvalue.clone()); - result.extend(each_instruction_value_lvalue(&instr.value)); - result -} - -/// Yields lvalues from DeclareLocal/StoreLocal/DeclareContext/StoreContext/Destructure/PostfixUpdate/PrefixUpdate. -/// Equivalent to TS `eachInstructionValueLValue`. -pub fn each_instruction_value_lvalue(value: &InstructionValue) -> Vec<Place> { - let mut result = Vec::new(); - match value { - InstructionValue::DeclareContext { lvalue, .. } - | InstructionValue::StoreContext { lvalue, .. } - | InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::StoreLocal { lvalue, .. } => { - result.push(lvalue.place.clone()); - } - InstructionValue::Destructure { lvalue, .. } => { - result.extend(each_pattern_operand(&lvalue.pattern)); - } - InstructionValue::PostfixUpdate { lvalue, .. } - | InstructionValue::PrefixUpdate { lvalue, .. } => { - result.push(lvalue.clone()); - } - // All other variants have no lvalues - InstructionValue::LoadLocal { .. } - | InstructionValue::LoadContext { .. } - | InstructionValue::Primitive { .. } - | InstructionValue::JSXText { .. } - | InstructionValue::BinaryExpression { .. } - | InstructionValue::NewExpression { .. } - | InstructionValue::CallExpression { .. } - | InstructionValue::MethodCall { .. } - | InstructionValue::UnaryExpression { .. } - | InstructionValue::TypeCastExpression { .. } - | InstructionValue::JsxExpression { .. } - | InstructionValue::ObjectExpression { .. } - | InstructionValue::ObjectMethod { .. } - | InstructionValue::ArrayExpression { .. } - | InstructionValue::JsxFragment { .. } - | InstructionValue::RegExpLiteral { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::PropertyStore { .. } - | InstructionValue::PropertyLoad { .. } - | InstructionValue::PropertyDelete { .. } - | InstructionValue::ComputedStore { .. } - | InstructionValue::ComputedLoad { .. } - | InstructionValue::ComputedDelete { .. } - | InstructionValue::LoadGlobal { .. } - | InstructionValue::StoreGlobal { .. } - | InstructionValue::FunctionExpression { .. } - | InstructionValue::TaggedTemplateExpression { .. } - | InstructionValue::TemplateLiteral { .. } - | InstructionValue::Await { .. } - | InstructionValue::GetIterator { .. } - | InstructionValue::IteratorNext { .. } - | InstructionValue::NextPropertyOf { .. } - | InstructionValue::Debugger { .. } - | InstructionValue::StartMemoize { .. } - | InstructionValue::FinishMemoize { .. } - | InstructionValue::UnsupportedNode { .. } => {} - } - result -} - -/// Yields lvalues with their InstructionKind. -/// Equivalent to TS `eachInstructionLValueWithKind`. -pub fn each_instruction_lvalue_with_kind( - value: &InstructionValue, -) -> Vec<(Place, InstructionKind)> { - let mut result = Vec::new(); - match value { - InstructionValue::DeclareContext { lvalue, .. } - | InstructionValue::StoreContext { lvalue, .. } - | InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::StoreLocal { lvalue, .. } => { - result.push((lvalue.place.clone(), lvalue.kind)); - } - InstructionValue::Destructure { lvalue, .. } => { - let kind = lvalue.kind; - for place in each_pattern_operand(&lvalue.pattern) { - result.push((place, kind)); - } - } - InstructionValue::PostfixUpdate { lvalue, .. } - | InstructionValue::PrefixUpdate { lvalue, .. } => { - result.push((lvalue.clone(), InstructionKind::Reassign)); - } - // All other variants have no lvalues with kind - InstructionValue::LoadLocal { .. } - | InstructionValue::LoadContext { .. } - | InstructionValue::Primitive { .. } - | InstructionValue::JSXText { .. } - | InstructionValue::BinaryExpression { .. } - | InstructionValue::NewExpression { .. } - | InstructionValue::CallExpression { .. } - | InstructionValue::MethodCall { .. } - | InstructionValue::UnaryExpression { .. } - | InstructionValue::TypeCastExpression { .. } - | InstructionValue::JsxExpression { .. } - | InstructionValue::ObjectExpression { .. } - | InstructionValue::ObjectMethod { .. } - | InstructionValue::ArrayExpression { .. } - | InstructionValue::JsxFragment { .. } - | InstructionValue::RegExpLiteral { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::PropertyStore { .. } - | InstructionValue::PropertyLoad { .. } - | InstructionValue::PropertyDelete { .. } - | InstructionValue::ComputedStore { .. } - | InstructionValue::ComputedLoad { .. } - | InstructionValue::ComputedDelete { .. } - | InstructionValue::LoadGlobal { .. } - | InstructionValue::StoreGlobal { .. } - | InstructionValue::FunctionExpression { .. } - | InstructionValue::TaggedTemplateExpression { .. } - | InstructionValue::TemplateLiteral { .. } - | InstructionValue::Await { .. } - | InstructionValue::GetIterator { .. } - | InstructionValue::IteratorNext { .. } - | InstructionValue::NextPropertyOf { .. } - | InstructionValue::Debugger { .. } - | InstructionValue::StartMemoize { .. } - | InstructionValue::FinishMemoize { .. } - | InstructionValue::UnsupportedNode { .. } => {} - } - result -} - -/// Delegates to each_instruction_value_operand. -/// Equivalent to TS `eachInstructionOperand`. -pub fn each_instruction_operand(instr: &Instruction, env: &Environment) -> Vec<Place> { - each_instruction_value_operand(&instr.value, env) -} - -/// Like `each_instruction_operand` but takes `functions` directly instead of `env`. -/// Useful when borrow splitting prevents passing the full `Environment`. -pub fn each_instruction_operand_with_functions( - instr: &Instruction, - functions: &[HirFunction], -) -> Vec<Place> { - each_instruction_value_operand_with_functions(&instr.value, functions) -} - -/// Yields operand places from an InstructionValue. -/// Equivalent to TS `eachInstructionValueOperand`. -pub fn each_instruction_value_operand(value: &InstructionValue, env: &Environment) -> Vec<Place> { - each_instruction_value_operand_with_functions(value, &env.functions) -} - -/// Like `each_instruction_value_operand` but takes `functions` directly instead of `env`. -/// Useful when borrow splitting prevents passing the full `Environment`. -pub fn each_instruction_value_operand_with_functions( - value: &InstructionValue, - functions: &[HirFunction], -) -> Vec<Place> { - let mut result = Vec::new(); - match value { - InstructionValue::NewExpression { callee, args, .. } - | InstructionValue::CallExpression { callee, args, .. } => { - result.push(callee.clone()); - result.extend(each_call_argument(args)); - } - InstructionValue::BinaryExpression { left, right, .. } => { - result.push(left.clone()); - result.push(right.clone()); - } - InstructionValue::MethodCall { - receiver, - property, - args, - .. - } => { - result.push(receiver.clone()); - result.push(property.clone()); - result.extend(each_call_argument(args)); - } - InstructionValue::DeclareContext { .. } | InstructionValue::DeclareLocal { .. } => { - // no operands - } - InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => { - result.push(place.clone()); - } - InstructionValue::StoreLocal { value: val, .. } => { - result.push(val.clone()); - } - InstructionValue::StoreContext { - lvalue, value: val, .. - } => { - result.push(lvalue.place.clone()); - result.push(val.clone()); - } - InstructionValue::StoreGlobal { value: val, .. } => { - result.push(val.clone()); - } - InstructionValue::Destructure { value: val, .. } => { - result.push(val.clone()); - } - InstructionValue::PropertyLoad { object, .. } => { - result.push(object.clone()); - } - InstructionValue::PropertyDelete { object, .. } => { - result.push(object.clone()); - } - InstructionValue::PropertyStore { - object, value: val, .. - } => { - result.push(object.clone()); - result.push(val.clone()); - } - InstructionValue::ComputedLoad { - object, property, .. - } => { - result.push(object.clone()); - result.push(property.clone()); - } - InstructionValue::ComputedDelete { - object, property, .. - } => { - result.push(object.clone()); - result.push(property.clone()); - } - InstructionValue::ComputedStore { - object, - property, - value: val, - .. - } => { - result.push(object.clone()); - result.push(property.clone()); - result.push(val.clone()); - } - InstructionValue::UnaryExpression { value: val, .. } => { - result.push(val.clone()); - } - InstructionValue::JsxExpression { - tag, - props, - children, - .. - } => { - if let JsxTag::Place(place) = tag { - result.push(place.clone()); - } - for attribute in props { - match attribute { - JsxAttribute::Attribute { place, .. } => { - result.push(place.clone()); - } - JsxAttribute::SpreadAttribute { argument, .. } => { - result.push(argument.clone()); - } - } - } - if let Some(children) = children { - for child in children { - result.push(child.clone()); - } - } - } - InstructionValue::JsxFragment { children, .. } => { - for child in children { - result.push(child.clone()); - } - } - InstructionValue::ObjectExpression { properties, .. } => { - for property in properties { - match property { - ObjectPropertyOrSpread::Property(prop) => { - if let ObjectPropertyKey::Computed { name } = &prop.key { - result.push(name.clone()); - } - result.push(prop.place.clone()); - } - ObjectPropertyOrSpread::Spread(spread) => { - result.push(spread.place.clone()); - } - } - } - } - InstructionValue::ArrayExpression { elements, .. } => { - for element in elements { - match element { - ArrayElement::Place(place) => { - result.push(place.clone()); - } - ArrayElement::Spread(spread) => { - result.push(spread.place.clone()); - } - ArrayElement::Hole => {} - } - } - } - InstructionValue::ObjectMethod { lowered_func, .. } - | InstructionValue::FunctionExpression { lowered_func, .. } => { - let func = &functions[lowered_func.func.0 as usize]; - for ctx_place in &func.context { - result.push(ctx_place.clone()); - } - } - InstructionValue::TaggedTemplateExpression { tag, .. } => { - result.push(tag.clone()); - } - InstructionValue::TypeCastExpression { value: val, .. } => { - result.push(val.clone()); - } - InstructionValue::TemplateLiteral { subexprs, .. } => { - for subexpr in subexprs { - result.push(subexpr.clone()); - } - } - InstructionValue::Await { value: val, .. } => { - result.push(val.clone()); - } - InstructionValue::GetIterator { collection, .. } => { - result.push(collection.clone()); - } - InstructionValue::IteratorNext { - iterator, - collection, - .. - } => { - result.push(iterator.clone()); - result.push(collection.clone()); - } - InstructionValue::NextPropertyOf { value: val, .. } => { - result.push(val.clone()); - } - InstructionValue::PostfixUpdate { value: val, .. } - | InstructionValue::PrefixUpdate { value: val, .. } => { - result.push(val.clone()); - } - InstructionValue::StartMemoize { deps, .. } => { - if let Some(deps) = deps { - for dep in deps { - if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &dep.root { - result.push(value.clone()); - } - } - } - } - InstructionValue::FinishMemoize { decl, .. } => { - result.push(decl.clone()); - } - InstructionValue::Debugger { .. } - | InstructionValue::RegExpLiteral { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::LoadGlobal { .. } - | InstructionValue::UnsupportedNode { .. } - | InstructionValue::Primitive { .. } - | InstructionValue::JSXText { .. } => { - // no operands - } - } - result -} - -/// Yields each arg's place. -/// Equivalent to TS `eachCallArgument`. -pub fn each_call_argument(args: &[PlaceOrSpread]) -> Vec<Place> { - let mut result = Vec::new(); - for arg in args { - match arg { - PlaceOrSpread::Place(place) => { - result.push(place.clone()); - } - PlaceOrSpread::Spread(spread) => { - result.push(spread.place.clone()); - } - } - } - result -} - -/// Yields places from array/object patterns. -/// Equivalent to TS `eachPatternOperand`. -pub fn each_pattern_operand(pattern: &Pattern) -> Vec<Place> { - let mut result = Vec::new(); - match pattern { - Pattern::Array(arr) => { - for item in &arr.items { - match item { - ArrayPatternElement::Place(place) => { - result.push(place.clone()); - } - ArrayPatternElement::Spread(spread) => { - result.push(spread.place.clone()); - } - ArrayPatternElement::Hole => {} - } - } - } - Pattern::Object(obj) => { - for property in &obj.properties { - match property { - ObjectPropertyOrSpread::Property(prop) => { - result.push(prop.place.clone()); - } - ObjectPropertyOrSpread::Spread(spread) => { - result.push(spread.place.clone()); - } - } - } - } - } - result -} - -/// Returns true if the pattern contains a spread element. -/// Equivalent to TS `doesPatternContainSpreadElement`. -pub fn does_pattern_contain_spread_element(pattern: &Pattern) -> bool { - match pattern { - Pattern::Array(arr) => { - for item in &arr.items { - if matches!(item, ArrayPatternElement::Spread(_)) { - return true; - } - } - } - Pattern::Object(obj) => { - for property in &obj.properties { - if matches!(property, ObjectPropertyOrSpread::Spread(_)) { - return true; - } - } - } - } - false -} - -/// Yields successor block IDs (NOT fallthroughs, this is intentional). -/// Equivalent to TS `eachTerminalSuccessor`. -pub fn each_terminal_successor(terminal: &Terminal) -> Vec<BlockId> { - let mut result = Vec::new(); - match terminal { - Terminal::Goto { block, .. } => { - result.push(*block); - } - Terminal::If { - consequent, - alternate, - .. - } => { - result.push(*consequent); - result.push(*alternate); - } - Terminal::Branch { - consequent, - alternate, - .. - } => { - result.push(*consequent); - result.push(*alternate); - } - Terminal::Switch { cases, .. } => { - for case in cases { - result.push(case.block); - } - } - Terminal::Optional { test, .. } - | Terminal::Ternary { test, .. } - | Terminal::Logical { test, .. } => { - result.push(*test); - } - Terminal::Return { .. } => {} - Terminal::Throw { .. } => {} - Terminal::DoWhile { loop_block, .. } => { - result.push(*loop_block); - } - Terminal::While { test, .. } => { - result.push(*test); - } - Terminal::For { init, .. } => { - result.push(*init); - } - Terminal::ForOf { init, .. } => { - result.push(*init); - } - Terminal::ForIn { init, .. } => { - result.push(*init); - } - Terminal::Label { block, .. } => { - result.push(*block); - } - Terminal::Sequence { block, .. } => { - result.push(*block); - } - Terminal::MaybeThrow { - continuation, - handler, - .. - } => { - result.push(*continuation); - if let Some(handler) = handler { - result.push(*handler); - } - } - Terminal::Try { block, .. } => { - result.push(*block); - } - Terminal::Scope { block, .. } | Terminal::PrunedScope { block, .. } => { - result.push(*block); - } - Terminal::Unreachable { .. } | Terminal::Unsupported { .. } => {} - } - result -} - -/// Yields places used by terminal. -/// Equivalent to TS `eachTerminalOperand`. -pub fn each_terminal_operand(terminal: &Terminal) -> Vec<Place> { - let mut result = Vec::new(); - match terminal { - Terminal::If { test, .. } => { - result.push(test.clone()); - } - Terminal::Branch { test, .. } => { - result.push(test.clone()); - } - Terminal::Switch { test, cases, .. } => { - result.push(test.clone()); - for case in cases { - if let Some(test) = &case.test { - result.push(test.clone()); - } - } - } - Terminal::Return { value, .. } | Terminal::Throw { value, .. } => { - result.push(value.clone()); - } - Terminal::Try { - handler_binding, .. - } => { - if let Some(binding) = handler_binding { - result.push(binding.clone()); - } - } - Terminal::MaybeThrow { .. } - | Terminal::Sequence { .. } - | Terminal::Label { .. } - | Terminal::Optional { .. } - | Terminal::Ternary { .. } - | Terminal::Logical { .. } - | Terminal::DoWhile { .. } - | Terminal::While { .. } - | Terminal::For { .. } - | Terminal::ForOf { .. } - | Terminal::ForIn { .. } - | Terminal::Goto { .. } - | Terminal::Unreachable { .. } - | Terminal::Unsupported { .. } - | Terminal::Scope { .. } - | Terminal::PrunedScope { .. } => { - // no-op - } - } - result -} - -// ============================================================================= -// Mapping functions (mutate in place) -// ============================================================================= - -/// Maps the instruction's lvalue and value's lvalues. -/// Equivalent to TS `mapInstructionLValues`. -pub fn map_instruction_lvalues(instr: &mut Instruction, f: &mut impl FnMut(Place) -> Place) { - match &mut instr.value { - InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::StoreLocal { lvalue, .. } - | InstructionValue::DeclareContext { lvalue, .. } - | InstructionValue::StoreContext { lvalue, .. } => { - lvalue.place = f(lvalue.place.clone()); - } - InstructionValue::Destructure { lvalue, .. } => { - map_pattern_operands(&mut lvalue.pattern, f); - } - InstructionValue::PostfixUpdate { lvalue, .. } - | InstructionValue::PrefixUpdate { lvalue, .. } => { - *lvalue = f(lvalue.clone()); - } - _ => {} - } - instr.lvalue = f(instr.lvalue.clone()); -} - -/// Maps operands of an instruction. -/// Equivalent to TS `mapInstructionOperands`. -pub fn map_instruction_operands( - instr: &mut Instruction, - env: &mut Environment, - f: &mut impl FnMut(Place) -> Place, -) { - map_instruction_value_operands(&mut instr.value, env, f); -} - -/// Maps operand places in an InstructionValue. -/// Equivalent to TS `mapInstructionValueOperands`. -pub fn map_instruction_value_operands( - value: &mut InstructionValue, - env: &mut Environment, - f: &mut impl FnMut(Place) -> Place, -) { - match value { - InstructionValue::BinaryExpression { left, right, .. } => { - *left = f(left.clone()); - *right = f(right.clone()); - } - InstructionValue::PropertyLoad { object, .. } => { - *object = f(object.clone()); - } - InstructionValue::PropertyDelete { object, .. } => { - *object = f(object.clone()); - } - InstructionValue::PropertyStore { - object, value: val, .. - } => { - *object = f(object.clone()); - *val = f(val.clone()); - } - InstructionValue::ComputedLoad { - object, property, .. - } => { - *object = f(object.clone()); - *property = f(property.clone()); - } - InstructionValue::ComputedDelete { - object, property, .. - } => { - *object = f(object.clone()); - *property = f(property.clone()); - } - InstructionValue::ComputedStore { - object, - property, - value: val, - .. - } => { - *object = f(object.clone()); - *property = f(property.clone()); - *val = f(val.clone()); - } - InstructionValue::DeclareContext { .. } | InstructionValue::DeclareLocal { .. } => { - // no operands - } - InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => { - *place = f(place.clone()); - } - InstructionValue::StoreLocal { value: val, .. } => { - *val = f(val.clone()); - } - InstructionValue::StoreContext { - lvalue, value: val, .. - } => { - lvalue.place = f(lvalue.place.clone()); - *val = f(val.clone()); - } - InstructionValue::StoreGlobal { value: val, .. } => { - *val = f(val.clone()); - } - InstructionValue::Destructure { value: val, .. } => { - *val = f(val.clone()); - } - InstructionValue::NewExpression { callee, args, .. } - | InstructionValue::CallExpression { callee, args, .. } => { - *callee = f(callee.clone()); - map_call_arguments(args, f); - } - InstructionValue::MethodCall { - receiver, - property, - args, - .. - } => { - *receiver = f(receiver.clone()); - *property = f(property.clone()); - map_call_arguments(args, f); - } - InstructionValue::UnaryExpression { value: val, .. } => { - *val = f(val.clone()); - } - InstructionValue::JsxExpression { - tag, - props, - children, - .. - } => { - if let JsxTag::Place(place) = tag { - *place = f(place.clone()); - } - for attribute in props.iter_mut() { - match attribute { - JsxAttribute::Attribute { place, .. } => { - *place = f(place.clone()); - } - JsxAttribute::SpreadAttribute { argument, .. } => { - *argument = f(argument.clone()); - } - } - } - if let Some(children) = children { - *children = children.iter().map(|p| f(p.clone())).collect(); - } - } - InstructionValue::ObjectExpression { properties, .. } => { - for property in properties.iter_mut() { - match property { - ObjectPropertyOrSpread::Property(prop) => { - if let ObjectPropertyKey::Computed { name } = &mut prop.key { - *name = f(name.clone()); - } - prop.place = f(prop.place.clone()); - } - ObjectPropertyOrSpread::Spread(spread) => { - spread.place = f(spread.place.clone()); - } - } - } - } - InstructionValue::ArrayExpression { elements, .. } => { - *elements = elements - .iter() - .map(|element| match element { - ArrayElement::Place(place) => ArrayElement::Place(f(place.clone())), - ArrayElement::Spread(spread) => { - let mut spread = spread.clone(); - spread.place = f(spread.place.clone()); - ArrayElement::Spread(spread) - } - ArrayElement::Hole => ArrayElement::Hole, - }) - .collect(); - } - InstructionValue::JsxFragment { children, .. } => { - *children = children.iter().map(|e| f(e.clone())).collect(); - } - InstructionValue::ObjectMethod { lowered_func, .. } - | InstructionValue::FunctionExpression { lowered_func, .. } => { - let func = &mut env.functions[lowered_func.func.0 as usize]; - func.context = func.context.iter().map(|d| f(d.clone())).collect(); - } - InstructionValue::TaggedTemplateExpression { tag, .. } => { - *tag = f(tag.clone()); - } - InstructionValue::TypeCastExpression { value: val, .. } => { - *val = f(val.clone()); - } - InstructionValue::TemplateLiteral { subexprs, .. } => { - *subexprs = subexprs.iter().map(|s| f(s.clone())).collect(); - } - InstructionValue::Await { value: val, .. } => { - *val = f(val.clone()); - } - InstructionValue::GetIterator { collection, .. } => { - *collection = f(collection.clone()); - } - InstructionValue::IteratorNext { - iterator, - collection, - .. - } => { - *iterator = f(iterator.clone()); - *collection = f(collection.clone()); - } - InstructionValue::NextPropertyOf { value: val, .. } => { - *val = f(val.clone()); - } - InstructionValue::PostfixUpdate { value: val, .. } - | InstructionValue::PrefixUpdate { value: val, .. } => { - *val = f(val.clone()); - } - InstructionValue::StartMemoize { deps, .. } => { - if let Some(deps) = deps { - for dep in deps.iter_mut() { - if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &mut dep.root { - *value = f(value.clone()); - } - } - } - } - InstructionValue::FinishMemoize { decl, .. } => { - *decl = f(decl.clone()); - } - InstructionValue::Debugger { .. } - | InstructionValue::RegExpLiteral { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::LoadGlobal { .. } - | InstructionValue::UnsupportedNode { .. } - | InstructionValue::Primitive { .. } - | InstructionValue::JSXText { .. } => { - // no operands - } - } -} - -/// Maps call arguments in place. -/// Equivalent to TS `mapCallArguments`. -pub fn map_call_arguments(args: &mut Vec<PlaceOrSpread>, f: &mut impl FnMut(Place) -> Place) { - for arg in args.iter_mut() { - match arg { - PlaceOrSpread::Place(place) => { - *place = f(place.clone()); - } - PlaceOrSpread::Spread(spread) => { - spread.place = f(spread.place.clone()); - } - } - } -} - -/// Maps pattern operands in place. -/// Equivalent to TS `mapPatternOperands`. -pub fn map_pattern_operands(pattern: &mut Pattern, f: &mut impl FnMut(Place) -> Place) { - match pattern { - Pattern::Array(arr) => { - arr.items = arr - .items - .iter() - .map(|item| match item { - ArrayPatternElement::Place(place) => { - ArrayPatternElement::Place(f(place.clone())) - } - ArrayPatternElement::Spread(spread) => { - let mut spread = spread.clone(); - spread.place = f(spread.place.clone()); - ArrayPatternElement::Spread(spread) - } - ArrayPatternElement::Hole => ArrayPatternElement::Hole, - }) - .collect(); - } - Pattern::Object(obj) => { - for property in obj.properties.iter_mut() { - match property { - ObjectPropertyOrSpread::Property(prop) => { - prop.place = f(prop.place.clone()); - } - ObjectPropertyOrSpread::Spread(spread) => { - spread.place = f(spread.place.clone()); - } - } - } - } - } -} - -/// Maps a terminal node's block assignments in place. -/// Equivalent to TS `mapTerminalSuccessors` — but mutates in place instead of returning a new terminal. -pub fn map_terminal_successors(terminal: &mut Terminal, f: &mut impl FnMut(BlockId) -> BlockId) { - match terminal { - Terminal::Goto { block, .. } => { - *block = f(*block); - } - Terminal::If { - consequent, - alternate, - fallthrough, - .. - } => { - *consequent = f(*consequent); - *alternate = f(*alternate); - *fallthrough = f(*fallthrough); - } - Terminal::Branch { - consequent, - alternate, - fallthrough, - .. - } => { - *consequent = f(*consequent); - *alternate = f(*alternate); - *fallthrough = f(*fallthrough); - } - Terminal::Switch { - cases, fallthrough, .. - } => { - for case in cases.iter_mut() { - case.block = f(case.block); - } - *fallthrough = f(*fallthrough); - } - Terminal::Logical { - test, fallthrough, .. - } => { - *test = f(*test); - *fallthrough = f(*fallthrough); - } - Terminal::Ternary { - test, fallthrough, .. - } => { - *test = f(*test); - *fallthrough = f(*fallthrough); - } - Terminal::Optional { - test, fallthrough, .. - } => { - *test = f(*test); - *fallthrough = f(*fallthrough); - } - Terminal::Return { .. } => {} - Terminal::Throw { .. } => {} - Terminal::DoWhile { - loop_block, - test, - fallthrough, - .. - } => { - *loop_block = f(*loop_block); - *test = f(*test); - *fallthrough = f(*fallthrough); - } - Terminal::While { - test, - loop_block, - fallthrough, - .. - } => { - *test = f(*test); - *loop_block = f(*loop_block); - *fallthrough = f(*fallthrough); - } - Terminal::For { - init, - test, - update, - loop_block, - fallthrough, - .. - } => { - *init = f(*init); - *test = f(*test); - if let Some(update) = update { - *update = f(*update); - } - *loop_block = f(*loop_block); - *fallthrough = f(*fallthrough); - } - Terminal::ForOf { - init, - test, - loop_block, - fallthrough, - .. - } => { - *init = f(*init); - *test = f(*test); - *loop_block = f(*loop_block); - *fallthrough = f(*fallthrough); - } - Terminal::ForIn { - init, - loop_block, - fallthrough, - .. - } => { - *init = f(*init); - *loop_block = f(*loop_block); - *fallthrough = f(*fallthrough); - } - Terminal::Label { - block, fallthrough, .. - } => { - *block = f(*block); - *fallthrough = f(*fallthrough); - } - Terminal::Sequence { - block, fallthrough, .. - } => { - *block = f(*block); - *fallthrough = f(*fallthrough); - } - Terminal::MaybeThrow { - continuation, - handler, - .. - } => { - *continuation = f(*continuation); - if let Some(handler) = handler { - *handler = f(*handler); - } - } - Terminal::Try { - block, - handler, - fallthrough, - .. - } => { - *block = f(*block); - *handler = f(*handler); - *fallthrough = f(*fallthrough); - } - Terminal::Scope { - block, fallthrough, .. - } - | Terminal::PrunedScope { - block, fallthrough, .. - } => { - *block = f(*block); - *fallthrough = f(*fallthrough); - } - Terminal::Unreachable { .. } | Terminal::Unsupported { .. } => {} - } -} - -/// Maps a terminal node's operand places in place. -/// Equivalent to TS `mapTerminalOperands`. -pub fn map_terminal_operands(terminal: &mut Terminal, f: &mut impl FnMut(Place) -> Place) { - match terminal { - Terminal::If { test, .. } => { - *test = f(test.clone()); - } - Terminal::Branch { test, .. } => { - *test = f(test.clone()); - } - Terminal::Switch { test, cases, .. } => { - *test = f(test.clone()); - for case in cases.iter_mut() { - if let Some(t) = &mut case.test { - *t = f(t.clone()); - } - } - } - Terminal::Return { value, .. } | Terminal::Throw { value, .. } => { - *value = f(value.clone()); - } - Terminal::Try { - handler_binding, .. - } => { - if let Some(binding) = handler_binding { - *binding = f(binding.clone()); - } - } - Terminal::MaybeThrow { .. } - | Terminal::Sequence { .. } - | Terminal::Label { .. } - | Terminal::Optional { .. } - | Terminal::Ternary { .. } - | Terminal::Logical { .. } - | Terminal::DoWhile { .. } - | Terminal::While { .. } - | Terminal::For { .. } - | Terminal::ForOf { .. } - | Terminal::ForIn { .. } - | Terminal::Goto { .. } - | Terminal::Unreachable { .. } - | Terminal::Unsupported { .. } - | Terminal::Scope { .. } - | Terminal::PrunedScope { .. } => { - // no-op - } - } -} - -/// Yields ALL block IDs referenced by a terminal (successors + fallthroughs + internal blocks). -/// Unlike `each_terminal_successor` which yields only standard control flow successors, -/// this function yields every block ID that `map_terminal_successors` would visit. -pub fn each_terminal_all_successors(terminal: &Terminal) -> Vec<BlockId> { - let mut result = Vec::new(); - match terminal { - Terminal::Goto { block, .. } => { - result.push(*block); - } - Terminal::If { - consequent, - alternate, - fallthrough, - .. - } => { - result.push(*consequent); - result.push(*alternate); - result.push(*fallthrough); - } - Terminal::Branch { - consequent, - alternate, - fallthrough, - .. - } => { - result.push(*consequent); - result.push(*alternate); - result.push(*fallthrough); - } - Terminal::Switch { - cases, fallthrough, .. - } => { - for case in cases { - result.push(case.block); - } - result.push(*fallthrough); - } - Terminal::Logical { - test, fallthrough, .. - } - | Terminal::Ternary { - test, fallthrough, .. - } - | Terminal::Optional { - test, fallthrough, .. - } => { - result.push(*test); - result.push(*fallthrough); - } - Terminal::Return { .. } | Terminal::Throw { .. } => {} - Terminal::DoWhile { - loop_block, - test, - fallthrough, - .. - } => { - result.push(*loop_block); - result.push(*test); - result.push(*fallthrough); - } - Terminal::While { - test, - loop_block, - fallthrough, - .. - } => { - result.push(*test); - result.push(*loop_block); - result.push(*fallthrough); - } - Terminal::For { - init, - test, - update, - loop_block, - fallthrough, - .. - } => { - result.push(*init); - result.push(*test); - if let Some(update) = update { - result.push(*update); - } - result.push(*loop_block); - result.push(*fallthrough); - } - Terminal::ForOf { - init, - test, - loop_block, - fallthrough, - .. - } => { - result.push(*init); - result.push(*test); - result.push(*loop_block); - result.push(*fallthrough); - } - Terminal::ForIn { - init, - loop_block, - fallthrough, - .. - } => { - result.push(*init); - result.push(*loop_block); - result.push(*fallthrough); - } - Terminal::Label { - block, fallthrough, .. - } - | Terminal::Sequence { - block, fallthrough, .. - } => { - result.push(*block); - result.push(*fallthrough); - } - Terminal::MaybeThrow { - continuation, - handler, - .. - } => { - result.push(*continuation); - if let Some(handler) = handler { - result.push(*handler); - } - } - Terminal::Try { - block, - handler, - fallthrough, - .. - } => { - result.push(*block); - result.push(*handler); - result.push(*fallthrough); - } - Terminal::Scope { - block, fallthrough, .. - } - | Terminal::PrunedScope { - block, fallthrough, .. - } => { - result.push(*block); - result.push(*fallthrough); - } - Terminal::Unreachable { .. } | Terminal::Unsupported { .. } => {} - } - result -} - -// ============================================================================= -// Terminal fallthrough functions -// ============================================================================= - -/// Returns the fallthrough block ID for terminals that have one. -/// Equivalent to TS `terminalFallthrough`. -pub fn terminal_fallthrough(terminal: &Terminal) -> Option<BlockId> { - match terminal { - // These terminals do NOT have a fallthrough - Terminal::MaybeThrow { .. } - | Terminal::Goto { .. } - | Terminal::Return { .. } - | Terminal::Throw { .. } - | Terminal::Unreachable { .. } - | Terminal::Unsupported { .. } => None, - - // These terminals DO have a fallthrough - Terminal::Branch { fallthrough, .. } - | Terminal::Try { fallthrough, .. } - | Terminal::DoWhile { fallthrough, .. } - | Terminal::ForOf { fallthrough, .. } - | Terminal::ForIn { fallthrough, .. } - | Terminal::For { fallthrough, .. } - | Terminal::If { fallthrough, .. } - | Terminal::Label { fallthrough, .. } - | Terminal::Logical { fallthrough, .. } - | Terminal::Optional { fallthrough, .. } - | Terminal::Sequence { fallthrough, .. } - | Terminal::Switch { fallthrough, .. } - | Terminal::Ternary { fallthrough, .. } - | Terminal::While { fallthrough, .. } - | Terminal::Scope { fallthrough, .. } - | Terminal::PrunedScope { fallthrough, .. } => Some(*fallthrough), - } -} - -/// Returns true if the terminal has a fallthrough block. -/// Equivalent to TS `terminalHasFallthrough`. -pub fn terminal_has_fallthrough(terminal: &Terminal) -> bool { - terminal_fallthrough(terminal).is_some() -} - -// ============================================================================= -// ScopeBlockTraversal -// ============================================================================= - -/// Block info entry for ScopeBlockTraversal. -#[derive(Debug, Clone)] -pub enum ScopeBlockInfo { - Begin { - scope: ScopeId, - pruned: bool, - fallthrough: BlockId, - }, - End { - scope: ScopeId, - pruned: bool, - }, -} - -/// Helper struct for traversing scope blocks in HIR-form. -/// Equivalent to TS `ScopeBlockTraversal` class. -pub struct ScopeBlockTraversal { - /// Live stack of active scopes - active_scopes: Vec<ScopeId>, - /// Map from block ID to scope block info - pub block_infos: HashMap<BlockId, ScopeBlockInfo>, -} - -impl ScopeBlockTraversal { - pub fn new() -> Self { - ScopeBlockTraversal { - active_scopes: Vec::new(), - block_infos: HashMap::new(), - } - } - - /// Record scope information for a block's terminal. - /// Equivalent to TS `recordScopes`. - pub fn record_scopes(&mut self, block: &BasicBlock) { - if let Some(block_info) = self.block_infos.get(&block.id) { - match block_info { - ScopeBlockInfo::Begin { scope, .. } => { - self.active_scopes.push(*scope); - } - ScopeBlockInfo::End { scope, .. } => { - let top = self.active_scopes.last(); - assert_eq!( - Some(scope), - top, - "Expected traversed block fallthrough to match top-most active scope" - ); - self.active_scopes.pop(); - } - } - } - - match &block.terminal { - Terminal::Scope { - block: scope_block, - fallthrough, - scope, - .. - } => { - assert!( - !self.block_infos.contains_key(scope_block) - && !self.block_infos.contains_key(fallthrough), - "Expected unique scope blocks and fallthroughs" - ); - self.block_infos.insert( - *scope_block, - ScopeBlockInfo::Begin { - scope: *scope, - pruned: false, - fallthrough: *fallthrough, - }, - ); - self.block_infos.insert( - *fallthrough, - ScopeBlockInfo::End { - scope: *scope, - pruned: false, - }, - ); - } - Terminal::PrunedScope { - block: scope_block, - fallthrough, - scope, - .. - } => { - assert!( - !self.block_infos.contains_key(scope_block) - && !self.block_infos.contains_key(fallthrough), - "Expected unique scope blocks and fallthroughs" - ); - self.block_infos.insert( - *scope_block, - ScopeBlockInfo::Begin { - scope: *scope, - pruned: true, - fallthrough: *fallthrough, - }, - ); - self.block_infos.insert( - *fallthrough, - ScopeBlockInfo::End { - scope: *scope, - pruned: true, - }, - ); - } - _ => {} - } - } - - /// Returns true if the given scope is currently 'active', i.e. if the scope start - /// block but not the scope fallthrough has been recorded. - pub fn is_scope_active(&self, scope_id: ScopeId) -> bool { - self.active_scopes.contains(&scope_id) - } - - /// The current, innermost active scope. - pub fn current_scope(&self) -> Option<ScopeId> { - self.active_scopes.last().copied() - } -} - -impl Default for ScopeBlockTraversal { - fn default() -> Self { - Self::new() - } -} - -// ============================================================================= -// Convenience wrappers: extract IdentifierIds from Places -// ============================================================================= - -/// Collect all lvalue IdentifierIds from an instruction. -/// Convenience wrapper around `each_instruction_lvalue` that maps to ids. -pub fn each_instruction_lvalue_ids(instr: &Instruction) -> Vec<IdentifierId> { - each_instruction_lvalue(instr) - .into_iter() - .map(|p| p.identifier) - .collect() -} - -/// Collect all operand IdentifierIds from an instruction. -/// Convenience wrapper around `each_instruction_operand` that maps to ids. -pub fn each_instruction_operand_ids(instr: &Instruction, env: &Environment) -> Vec<IdentifierId> { - each_instruction_operand(instr, env) - .into_iter() - .map(|p| p.identifier) - .collect() -} - -/// Collect all operand IdentifierIds from an instruction value. -/// Convenience wrapper around `each_instruction_value_operand` that maps to ids. -pub fn each_instruction_value_operand_ids( - value: &InstructionValue, - env: &Environment, -) -> Vec<IdentifierId> { - each_instruction_value_operand(value, env) - .into_iter() - .map(|p| p.identifier) - .collect() -} - -/// Collect all operand IdentifierIds from a terminal. -/// Convenience wrapper around `each_terminal_operand` that maps to ids. -pub fn each_terminal_operand_ids(terminal: &Terminal) -> Vec<IdentifierId> { - each_terminal_operand(terminal) - .into_iter() - .map(|p| p.identifier) - .collect() -} - -/// Collect all IdentifierIds from a pattern. -/// Convenience wrapper around `each_pattern_operand` that maps to ids. -pub fn each_pattern_operand_ids(pattern: &Pattern) -> Vec<IdentifierId> { - each_pattern_operand(pattern) - .into_iter() - .map(|p| p.identifier) - .collect() -} - -// ============================================================================= -// In-place mutation variants (f(&mut Place) callbacks) -// ============================================================================= -// -// These variants use `f(&mut Place)` instead of `f(Place) -> Place`, which is -// more natural for Rust in-place mutation patterns. They do NOT handle -// FunctionExpression/ObjectMethod context (since that requires env access). -// Callers that need to process inner function context should handle it -// separately, e.g.: -// -// for_each_instruction_value_operand_mut(&mut instr.value, &mut |place| { ... }); -// if let InstructionValue::FunctionExpression { lowered_func, .. } -// | InstructionValue::ObjectMethod { lowered_func, .. } = &mut instr.value { -// let func = &mut env.functions[lowered_func.func.0 as usize]; -// for ctx in func.context.iter_mut() { ... } -// } -// - -/// In-place mutation of all operand places in an InstructionValue. -/// Does NOT handle FunctionExpression/ObjectMethod context — callers handle those separately. -pub fn for_each_instruction_value_operand_mut( - value: &mut InstructionValue, - f: &mut impl FnMut(&mut Place), -) { - match value { - InstructionValue::BinaryExpression { left, right, .. } => { - f(left); - f(right); - } - InstructionValue::PropertyLoad { object, .. } - | InstructionValue::PropertyDelete { object, .. } => { - f(object); - } - InstructionValue::PropertyStore { - object, value: val, .. - } => { - f(object); - f(val); - } - InstructionValue::ComputedLoad { - object, property, .. - } - | InstructionValue::ComputedDelete { - object, property, .. - } => { - f(object); - f(property); - } - InstructionValue::ComputedStore { - object, - property, - value: val, - .. - } => { - f(object); - f(property); - f(val); - } - InstructionValue::DeclareContext { .. } | InstructionValue::DeclareLocal { .. } => {} - InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => { - f(place); - } - InstructionValue::StoreLocal { value: val, .. } => { - f(val); - } - InstructionValue::StoreContext { - lvalue, value: val, .. - } => { - f(&mut lvalue.place); - f(val); - } - InstructionValue::StoreGlobal { value: val, .. } => { - f(val); - } - InstructionValue::Destructure { value: val, .. } => { - f(val); - } - InstructionValue::NewExpression { callee, args, .. } - | InstructionValue::CallExpression { callee, args, .. } => { - f(callee); - for_each_call_argument_mut(args, f); - } - InstructionValue::MethodCall { - receiver, - property, - args, - .. - } => { - f(receiver); - f(property); - for_each_call_argument_mut(args, f); - } - InstructionValue::UnaryExpression { value: val, .. } => { - f(val); - } - InstructionValue::JsxExpression { - tag, - props, - children, - .. - } => { - if let JsxTag::Place(place) = tag { - f(place); - } - for attribute in props.iter_mut() { - match attribute { - JsxAttribute::Attribute { place, .. } => f(place), - JsxAttribute::SpreadAttribute { argument, .. } => f(argument), - } - } - if let Some(children) = children { - for child in children.iter_mut() { - f(child); - } - } - } - InstructionValue::ObjectExpression { properties, .. } => { - for property in properties.iter_mut() { - match property { - ObjectPropertyOrSpread::Property(prop) => { - if let ObjectPropertyKey::Computed { name } = &mut prop.key { - f(name); - } - f(&mut prop.place); - } - ObjectPropertyOrSpread::Spread(spread) => { - f(&mut spread.place); - } - } - } - } - InstructionValue::ArrayExpression { elements, .. } => { - for elem in elements.iter_mut() { - match elem { - ArrayElement::Place(p) => f(p), - ArrayElement::Spread(s) => f(&mut s.place), - ArrayElement::Hole => {} - } - } - } - InstructionValue::JsxFragment { children, .. } => { - for child in children.iter_mut() { - f(child); - } - } - InstructionValue::FunctionExpression { .. } | InstructionValue::ObjectMethod { .. } => { - // Context places require env access — callers handle separately. - } - InstructionValue::TaggedTemplateExpression { tag, .. } => { - f(tag); - } - InstructionValue::TypeCastExpression { value: val, .. } => { - f(val); - } - InstructionValue::TemplateLiteral { subexprs, .. } => { - for expr in subexprs.iter_mut() { - f(expr); - } - } - InstructionValue::Await { value: val, .. } => { - f(val); - } - InstructionValue::GetIterator { collection, .. } => { - f(collection); - } - InstructionValue::IteratorNext { - iterator, - collection, - .. - } => { - f(iterator); - f(collection); - } - InstructionValue::NextPropertyOf { value: val, .. } => { - f(val); - } - InstructionValue::PostfixUpdate { value: val, .. } - | InstructionValue::PrefixUpdate { value: val, .. } => { - f(val); - } - InstructionValue::StartMemoize { deps, .. } => { - if let Some(deps) = deps { - for dep in deps.iter_mut() { - if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &mut dep.root { - f(value); - } - } - } - } - InstructionValue::FinishMemoize { decl, .. } => { - f(decl); - } - InstructionValue::Debugger { .. } - | InstructionValue::RegExpLiteral { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::LoadGlobal { .. } - | InstructionValue::UnsupportedNode { .. } - | InstructionValue::Primitive { .. } - | InstructionValue::JSXText { .. } => {} - } -} - -/// In-place mutation of call arguments. -pub fn for_each_call_argument_mut(args: &mut [PlaceOrSpread], f: &mut impl FnMut(&mut Place)) { - for arg in args.iter_mut() { - match arg { - PlaceOrSpread::Place(place) => f(place), - PlaceOrSpread::Spread(spread) => f(&mut spread.place), - } - } -} - -/// In-place mutation of an InstructionValue's lvalues (DeclareLocal, StoreLocal, DeclareContext, -/// StoreContext, Destructure, PostfixUpdate, PrefixUpdate). Does NOT include the instruction's -/// top-level lvalue — use `for_each_instruction_lvalue_mut` for that. -pub fn for_each_instruction_value_lvalue_mut( - value: &mut InstructionValue, - f: &mut impl FnMut(&mut Place), -) { - match value { - InstructionValue::DeclareContext { lvalue, .. } - | InstructionValue::StoreContext { lvalue, .. } - | InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::StoreLocal { lvalue, .. } => { - f(&mut lvalue.place); - } - InstructionValue::Destructure { lvalue, .. } => { - for_each_pattern_operand_mut(&mut lvalue.pattern, f); - } - InstructionValue::PostfixUpdate { lvalue, .. } - | InstructionValue::PrefixUpdate { lvalue, .. } => { - f(lvalue); - } - _ => {} - } -} - -/// In-place mutation of the instruction's lvalue and value's lvalues. -/// Matches the same variants as TS `mapInstructionLValues` (skips DeclareContext/StoreContext). -pub fn for_each_instruction_lvalue_mut(instr: &mut Instruction, f: &mut impl FnMut(&mut Place)) { - match &mut instr.value { - InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::StoreLocal { lvalue, .. } => { - f(&mut lvalue.place); - } - InstructionValue::Destructure { lvalue, .. } => { - for_each_pattern_operand_mut(&mut lvalue.pattern, f); - } - InstructionValue::PostfixUpdate { lvalue, .. } - | InstructionValue::PrefixUpdate { lvalue, .. } => { - f(lvalue); - } - _ => {} - } - f(&mut instr.lvalue); -} - -/// In-place mutation of pattern operands. -pub fn for_each_pattern_operand_mut(pattern: &mut Pattern, f: &mut impl FnMut(&mut Place)) { - match pattern { - Pattern::Array(arr) => { - for item in arr.items.iter_mut() { - match item { - ArrayPatternElement::Place(p) => f(p), - ArrayPatternElement::Spread(s) => f(&mut s.place), - ArrayPatternElement::Hole => {} - } - } - } - Pattern::Object(obj) => { - for property in obj.properties.iter_mut() { - match property { - ObjectPropertyOrSpread::Property(prop) => f(&mut prop.place), - ObjectPropertyOrSpread::Spread(spread) => f(&mut spread.place), - } - } - } - } -} - -/// In-place mutation of terminal operand places. -pub fn for_each_terminal_operand_mut(terminal: &mut Terminal, f: &mut impl FnMut(&mut Place)) { - match terminal { - Terminal::If { test, .. } | Terminal::Branch { test, .. } => { - f(test); - } - Terminal::Switch { test, cases, .. } => { - f(test); - for case in cases.iter_mut() { - if let Some(t) = &mut case.test { - f(t); - } - } - } - Terminal::Return { value, .. } | Terminal::Throw { value, .. } => { - f(value); - } - Terminal::Try { - handler_binding, .. - } => { - if let Some(binding) = handler_binding { - f(binding); - } - } - Terminal::MaybeThrow { .. } - | Terminal::Sequence { .. } - | Terminal::Label { .. } - | Terminal::Optional { .. } - | Terminal::Ternary { .. } - | Terminal::Logical { .. } - | Terminal::DoWhile { .. } - | Terminal::While { .. } - | Terminal::For { .. } - | Terminal::ForOf { .. } - | Terminal::ForIn { .. } - | Terminal::Goto { .. } - | Terminal::Unreachable { .. } - | Terminal::Unsupported { .. } - | Terminal::Scope { .. } - | Terminal::PrunedScope { .. } => {} - } -} diff --git a/compiler/crates/react_compiler_inference/Cargo.toml b/compiler/crates/react_compiler_inference/Cargo.toml deleted file mode 100644 index b69182a3ac03..000000000000 --- a/compiler/crates/react_compiler_inference/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "react_compiler_inference" -version = "0.1.0" -edition = "2024" - -[dependencies] -react_compiler_hir = { path = "../react_compiler_hir" } -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" } -react_compiler_lowering = { path = "../react_compiler_lowering" } -react_compiler_optimization = { path = "../react_compiler_optimization" } -react_compiler_ssa = { path = "../react_compiler_ssa" } -react_compiler_utils = { path = "../react_compiler_utils" } -indexmap = "2" diff --git a/compiler/crates/react_compiler_inference/src/align_method_call_scopes.rs b/compiler/crates/react_compiler_inference/src/align_method_call_scopes.rs deleted file mode 100644 index afdad42f8773..000000000000 --- a/compiler/crates/react_compiler_inference/src/align_method_call_scopes.rs +++ /dev/null @@ -1,138 +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. - -//! Ensures that method call instructions have scopes such that either: -//! - Both the MethodCall and its property have the same scope -//! - OR neither has a scope -//! -//! Ported from TypeScript `src/ReactiveScopes/AlignMethodCallScopes.ts`. - -use std::collections::HashMap; - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{EvaluationOrder, HirFunction, IdentifierId, InstructionValue, ScopeId}; -use react_compiler_utils::DisjointSet; - -// ============================================================================= -// Public API -// ============================================================================= - -/// Aligns method call scopes so that either both the MethodCall result and its -/// property operand share the same scope, or neither has a scope. -/// -/// Corresponds to TS `alignMethodCallScopes(fn: HIRFunction): void`. -pub fn align_method_call_scopes(func: &mut HirFunction, env: &mut Environment) { - // Maps an identifier to the scope it should be assigned to (or None to remove scope) - let mut scope_mapping: HashMap<IdentifierId, Option<ScopeId>> = HashMap::new(); - let mut merged_scopes = DisjointSet::<ScopeId>::new(); - - // Phase 1: Walk instructions and collect scope relationships - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::MethodCall { property, .. } => { - let lvalue_scope = env.identifiers[instr.lvalue.identifier.0 as usize].scope; - let property_scope = env.identifiers[property.identifier.0 as usize].scope; - - match (lvalue_scope, property_scope) { - (Some(lvalue_sid), Some(property_sid)) => { - // Both have a scope: merge the scopes - merged_scopes.union(&[lvalue_sid, property_sid]); - } - (Some(lvalue_sid), None) => { - // Call has a scope but not the property: - // record that this property should be in this scope - scope_mapping.insert(property.identifier, Some(lvalue_sid)); - } - (None, Some(_)) => { - // Property has a scope but call doesn't: - // this property does not need a scope - scope_mapping.insert(property.identifier, None); - } - (None, None) => { - // Neither has a scope, nothing to do - } - } - } - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - // Recurse into inner functions - let func_id = lowered_func.func; - let mut inner_func = std::mem::replace( - &mut env.functions[func_id.0 as usize], - react_compiler_ssa::enter_ssa::placeholder_function(), - ); - align_method_call_scopes(&mut inner_func, env); - env.functions[func_id.0 as usize] = inner_func; - } - _ => {} - } - } - } - - // Phase 2: Merge scope ranges for unioned scopes. - // Use a HashMap to accumulate min/max across all scopes mapping to the same root, - // matching TS behavior where root.range is updated in-place during iteration. - let mut range_updates: HashMap<ScopeId, (EvaluationOrder, EvaluationOrder)> = HashMap::new(); - - merged_scopes.for_each(|scope_id, root_id| { - if scope_id == root_id { - return; - } - let scope_range = env.scopes[scope_id.0 as usize].range.clone(); - let root_range = env.scopes[root_id.0 as usize].range.clone(); - - let entry = range_updates - .entry(root_id) - .or_insert_with(|| (root_range.start, root_range.end)); - entry.0 = EvaluationOrder(std::cmp::min(entry.0.0, scope_range.start.0)); - entry.1 = EvaluationOrder(std::cmp::max(entry.1.0, scope_range.end.0)); - }); - - // Save original scope range IDs before updating - let original_range_ids: HashMap<ScopeId, react_compiler_hir::MutableRangeId> = range_updates - .keys() - .map(|&root_id| { - let range_id = env.scopes[root_id.0 as usize].range.id; - (root_id, range_id) - }) - .collect(); - - for (root_id, (new_start, new_end)) in &range_updates { - env.scopes[root_id.0 as usize].range.start = *new_start; - env.scopes[root_id.0 as usize].range.end = *new_end; - } - - // Sync identifier mutable_ranges that shared the old scope range. - // Uses MutableRangeId for exact identity matching instead of value comparison. - for ident in &mut env.identifiers { - if let Some(scope_id) = ident.scope { - if let Some(&orig_range_id) = original_range_ids.get(&scope_id) { - if ident.mutable_range.id == orig_range_id { - let new_range = &env.scopes[scope_id.0 as usize].range; - ident.mutable_range.start = new_range.start; - ident.mutable_range.end = new_range.end; - } - } - } - } - - // Phase 3: Apply scope mappings and merged scope reassignments - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let lvalue_id = func.instructions[instr_id.0 as usize].lvalue.identifier; - - if let Some(mapped_scope) = scope_mapping.get(&lvalue_id) { - env.identifiers[lvalue_id.0 as usize].scope = *mapped_scope; - } else if let Some(current_scope) = env.identifiers[lvalue_id.0 as usize].scope { - // TS: mergedScopes.find() returns null if not in the set - if let Some(merged) = merged_scopes.find_opt(current_scope) { - env.identifiers[lvalue_id.0 as usize].scope = Some(merged); - } - } - } - } -} diff --git a/compiler/crates/react_compiler_inference/src/align_object_method_scopes.rs b/compiler/crates/react_compiler_inference/src/align_object_method_scopes.rs deleted file mode 100644 index 9f32b9d3954b..000000000000 --- a/compiler/crates/react_compiler_inference/src/align_object_method_scopes.rs +++ /dev/null @@ -1,168 +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. - -//! Aligns scopes of object method values to that of their enclosing object expressions. -//! To produce a well-formed JS program in Codegen, object methods and object expressions -//! must be in the same ReactiveBlock as object method definitions must be inlined. -//! -//! Ported from TypeScript `src/ReactiveScopes/AlignObjectMethodScopes.ts`. - -use std::cmp; -use std::collections::{HashMap, HashSet}; - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{ - EvaluationOrder, HirFunction, IdentifierId, InstructionValue, ObjectPropertyOrSpread, ScopeId, -}; -use react_compiler_utils::DisjointSet; - -// ============================================================================= -// findScopesToMerge -// ============================================================================= - -/// Identifies ObjectMethod lvalue identifiers and then finds ObjectExpression -/// instructions whose operands reference those methods. Returns a disjoint set -/// of scopes that must be merged. -fn find_scopes_to_merge(func: &HirFunction, env: &Environment) -> DisjointSet<ScopeId> { - let mut object_method_decls: HashSet<IdentifierId> = HashSet::new(); - let mut merged_scopes = DisjointSet::<ScopeId>::new(); - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::ObjectMethod { .. } => { - object_method_decls.insert(instr.lvalue.identifier); - } - InstructionValue::ObjectExpression { properties, .. } => { - for prop_or_spread in properties { - let operand_place = match prop_or_spread { - ObjectPropertyOrSpread::Property(prop) => &prop.place, - ObjectPropertyOrSpread::Spread(spread) => &spread.place, - }; - if object_method_decls.contains(&operand_place.identifier) { - let operand_scope = - env.identifiers[operand_place.identifier.0 as usize].scope; - let lvalue_scope = - env.identifiers[instr.lvalue.identifier.0 as usize].scope; - - // TS: CompilerError.invariant(operandScope != null && lvalueScope != null, ...) - let operand_sid = operand_scope.expect( - "Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.", - ); - let lvalue_sid = lvalue_scope.expect( - "Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.", - ); - merged_scopes.union(&[operand_sid, lvalue_sid]); - } - } - } - _ => {} - } - } - } - - merged_scopes -} - -// ============================================================================= -// Public API -// ============================================================================= - -/// Aligns object method scopes so that ObjectMethod values and their enclosing -/// ObjectExpression share the same scope. -/// -/// Corresponds to TS `alignObjectMethodScopes(fn: HIRFunction): void`. -pub fn align_object_method_scopes(func: &mut HirFunction, env: &mut Environment) { - // Handle inner functions first (TS recurses before processing the outer function) - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - let func_id = lowered_func.func; - let mut inner_func = std::mem::replace( - &mut env.functions[func_id.0 as usize], - react_compiler_ssa::enter_ssa::placeholder_function(), - ); - align_object_method_scopes(&mut inner_func, env); - env.functions[func_id.0 as usize] = inner_func; - } - _ => {} - } - } - } - - let mut merged_scopes = find_scopes_to_merge(func, env); - - // Step 1: Merge affected scopes to their canonical root. - // Use a HashMap to accumulate min/max across all scopes mapping to the same root, - // matching TS behavior where root.range is updated in-place during iteration. - let mut range_updates: HashMap<ScopeId, (EvaluationOrder, EvaluationOrder)> = HashMap::new(); - - merged_scopes.for_each(|scope_id, root_id| { - if scope_id == root_id { - return; - } - let scope_range = env.scopes[scope_id.0 as usize].range.clone(); - let root_range = env.scopes[root_id.0 as usize].range.clone(); - - let entry = range_updates - .entry(root_id) - .or_insert_with(|| (root_range.start, root_range.end)); - entry.0 = EvaluationOrder(cmp::min(entry.0.0, scope_range.start.0)); - entry.1 = EvaluationOrder(cmp::max(entry.1.0, scope_range.end.0)); - }); - - // Save original scope range IDs before updating - let original_range_ids: HashMap<ScopeId, react_compiler_hir::MutableRangeId> = range_updates - .keys() - .map(|&root_id| { - let range_id = env.scopes[root_id.0 as usize].range.id; - (root_id, range_id) - }) - .collect(); - - for (root_id, (new_start, new_end)) in &range_updates { - env.scopes[root_id.0 as usize].range.start = *new_start; - env.scopes[root_id.0 as usize].range.end = *new_end; - } - - // Sync identifier mutable_ranges that shared the old scope range. - // Uses MutableRangeId for exact identity matching instead of value comparison. - for ident in &mut env.identifiers { - if let Some(scope_id) = ident.scope { - if let Some(&orig_range_id) = original_range_ids.get(&scope_id) { - if ident.mutable_range.id == orig_range_id { - let new_range = &env.scopes[scope_id.0 as usize].range; - ident.mutable_range.start = new_range.start; - ident.mutable_range.end = new_range.end; - } - } - } - } - - // Step 2: Repoint identifiers whose scopes were merged - // Build a map from old scope -> root scope for quick lookup - let mut scope_remap: HashMap<ScopeId, ScopeId> = HashMap::new(); - merged_scopes.for_each(|scope_id, root_id| { - if scope_id != root_id { - scope_remap.insert(scope_id, root_id); - } - }); - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let lvalue_id = func.instructions[instr_id.0 as usize].lvalue.identifier; - - if let Some(current_scope) = env.identifiers[lvalue_id.0 as usize].scope { - if let Some(&root) = scope_remap.get(¤t_scope) { - env.identifiers[lvalue_id.0 as usize].scope = Some(root); - } - } - } - } -} diff --git a/compiler/crates/react_compiler_inference/src/align_reactive_scopes_to_block_scopes_hir.rs b/compiler/crates/react_compiler_inference/src/align_reactive_scopes_to_block_scopes_hir.rs deleted file mode 100644 index 141f92f90348..000000000000 --- a/compiler/crates/react_compiler_inference/src/align_reactive_scopes_to_block_scopes_hir.rs +++ /dev/null @@ -1,334 +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. - -//! Aligns reactive scope boundaries to block scope boundaries in the HIR. -//! -//! Ported from TypeScript `src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts`. -//! -//! This is the 2nd of 4 passes that determine how to break a function into -//! discrete reactive scopes (independently memoizable units of code): -//! 1. InferReactiveScopeVariables (on HIR) determines operands that mutate -//! together and assigns them a unique reactive scope. -//! 2. AlignReactiveScopesToBlockScopes (this pass) aligns reactive scopes -//! to block scopes. -//! 3. MergeOverlappingReactiveScopes ensures scopes do not overlap. -//! 4. BuildReactiveBlocks groups the statements for each scope. -//! -//! 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! - -use std::collections::HashMap; -use std::collections::HashSet; - -use react_compiler_hir::BlockId; -use react_compiler_hir::BlockKind; -use react_compiler_hir::EvaluationOrder; -use react_compiler_hir::HirFunction; -use react_compiler_hir::IdentifierId; -use react_compiler_hir::MutableRange; -use react_compiler_hir::ScopeId; -use react_compiler_hir::Terminal; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors; -use react_compiler_hir::visitors::each_instruction_lvalue_ids; -use react_compiler_hir::visitors::each_instruction_value_operand_ids; -use react_compiler_hir::visitors::each_terminal_operand_ids; - -// ============================================================================= -// ValueBlockNode — stores the valueRange for scope alignment in value blocks -// ============================================================================= - -/// Tracks the value range for a value block. The `children` field from the TS -/// implementation is only used for debug output and is omitted here. -#[derive(Clone)] -struct ValueBlockNode { - value_range: MutableRange, -} - -/// Returns all block IDs referenced by a terminal, including both direct -/// successors and fallthrough. -fn all_terminal_block_ids(terminal: &Terminal) -> Vec<BlockId> { - visitors::each_terminal_all_successors(terminal) -} - -// ============================================================================= -// Helper: get the first EvaluationOrder in a block -// ============================================================================= - -fn block_first_id(func: &HirFunction, block_id: BlockId) -> EvaluationOrder { - let block = func.body.blocks.get(&block_id).unwrap(); - if !block.instructions.is_empty() { - func.instructions[block.instructions[0].0 as usize].id - } else { - block.terminal.evaluation_order() - } -} - -// ============================================================================= -// BlockFallthroughRange -// ============================================================================= - -#[derive(Clone)] -struct BlockFallthroughRange { - fallthrough: BlockId, - range: MutableRange, -} - -// ============================================================================= -// Public API -// ============================================================================= - -/// Aligns reactive scope boundaries to block scope boundaries in the HIR. -/// -/// This pass updates reactive scope boundaries to align to control flow -/// boundaries. For example, if a scope ends partway through an if consequent, -/// the scope is extended to the end of the consequent block. -pub fn align_reactive_scopes_to_block_scopes_hir(func: &mut HirFunction, env: &mut Environment) { - // Save original scope ranges BEFORE this pass modifies them. - // In TS, identifier.mutableRange and scope.range may or may not be the same - // JS object. Only identifiers whose mutableRange IS the scope's range object - // (same reference) automatically see scope range modifications. In Rust, we - // simulate this by recording original ranges and only syncing identifiers - // whose mutableRange matches the original. - let original_scope_ranges: Vec<MutableRange> = - env.scopes.iter().map(|s| s.range.clone()).collect(); - - let mut active_block_fallthrough_ranges: Vec<BlockFallthroughRange> = Vec::new(); - let mut active_scopes: HashSet<ScopeId> = HashSet::new(); - let mut seen: HashSet<ScopeId> = HashSet::new(); - let mut value_block_nodes: HashMap<BlockId, ValueBlockNode> = HashMap::new(); - - let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect(); - - for &block_id in &block_ids { - let starting_id = block_first_id(func, block_id); - - // Retain only active scopes whose range.end > startingId - active_scopes.retain(|&scope_id| env.scopes[scope_id.0 as usize].range.end > starting_id); - - // Check if we've reached a fallthrough block - if let Some(top) = active_block_fallthrough_ranges.last().cloned() { - if top.fallthrough == block_id { - active_block_fallthrough_ranges.pop(); - // All active scopes overlap this block-fallthrough range; - // extend their start to include the range start. - for &scope_id in &active_scopes { - let scope = &mut env.scopes[scope_id.0 as usize]; - scope.range.start = std::cmp::min(scope.range.start, top.range.start); - } - } - } - - let node = value_block_nodes.get(&block_id).cloned(); - - // Visit instruction lvalues and operands - let block = func.body.blocks.get(&block_id).unwrap(); - let instr_ids: Vec<react_compiler_hir::InstructionId> = - block.instructions.iter().copied().collect(); - for &instr_id in &instr_ids { - let instr = &func.instructions[instr_id.0 as usize]; - let eval_order = instr.id; - - let lvalue_ids = each_instruction_lvalue_ids(instr); - for lvalue_id in lvalue_ids { - record_place_id( - eval_order, - lvalue_id, - &node, - env, - &mut active_scopes, - &mut seen, - ); - } - - let operand_ids = each_instruction_value_operand_ids(&instr.value, env); - for operand_id in operand_ids { - record_place_id( - eval_order, - operand_id, - &node, - env, - &mut active_scopes, - &mut seen, - ); - } - } - - // Visit terminal operands - let block = func.body.blocks.get(&block_id).unwrap(); - let terminal_eval_order = block.terminal.evaluation_order(); - let terminal_operand_ids = each_terminal_operand_ids(&block.terminal); - for operand_id in terminal_operand_ids { - record_place_id( - terminal_eval_order, - operand_id, - &node, - env, - &mut active_scopes, - &mut seen, - ); - } - - let block = func.body.blocks.get(&block_id).unwrap(); - let terminal = &block.terminal; - let fallthrough = visitors::terminal_fallthrough(terminal); - let is_branch = matches!(terminal, Terminal::Branch { .. }); - let is_goto = match terminal { - Terminal::Goto { block, .. } => Some(*block), - _ => None, - }; - let is_ternary_logical_optional = matches!( - terminal, - Terminal::Ternary { .. } | Terminal::Logical { .. } | Terminal::Optional { .. } - ); - let all_successors = all_terminal_block_ids(terminal); - - // Handle fallthrough logic - if let Some(ft) = fallthrough { - if !is_branch { - let next_id = block_first_id(func, ft); - - for &scope_id in &active_scopes { - let scope = &mut env.scopes[scope_id.0 as usize]; - if scope.range.end > terminal_eval_order { - scope.range.end = std::cmp::max(scope.range.end, next_id); - } - } - - active_block_fallthrough_ranges.push(BlockFallthroughRange { - fallthrough: ft, - range: env.new_mutable_range(terminal_eval_order, next_id), - }); - - assert!( - !value_block_nodes.contains_key(&ft), - "Expect hir blocks to have unique fallthroughs" - ); - if let Some(n) = &node { - value_block_nodes.insert(ft, n.clone()); - } - } - } else if let Some(goto_block) = is_goto { - // Handle goto to label - let start_pos = active_block_fallthrough_ranges - .iter() - .position(|r| r.fallthrough == goto_block); - let top_idx = if active_block_fallthrough_ranges.is_empty() { - None - } else { - Some(active_block_fallthrough_ranges.len() - 1) - }; - if let Some(pos) = start_pos { - if top_idx != Some(pos) { - let start_range = active_block_fallthrough_ranges[pos].clone(); - let first_id = block_first_id(func, start_range.fallthrough); - - for &scope_id in &active_scopes { - let scope = &mut env.scopes[scope_id.0 as usize]; - if scope.range.end <= terminal_eval_order { - continue; - } - scope.range.start = - std::cmp::min(start_range.range.start, scope.range.start); - scope.range.end = std::cmp::max(first_id, scope.range.end); - } - } - } - } - - // Visit all successors to set up value block nodes - for successor in all_successors { - if value_block_nodes.contains_key(&successor) { - continue; - } - - let successor_block = func.body.blocks.get(&successor).unwrap(); - if successor_block.kind == BlockKind::Block || successor_block.kind == BlockKind::Catch - { - // Block or catch kind: don't create a value block node - } else if node.is_none() || is_ternary_logical_optional { - // Create a new node when transitioning non-value -> value, - // or for ternary/logical/optional terminals. - let value_range = if node.is_none() { - // Transition from block -> value block - let ft = fallthrough.expect("Expected a fallthrough for value block"); - let next_id = block_first_id(func, ft); - env.new_mutable_range(terminal_eval_order, next_id) - } else { - // Value -> value transition (ternary/logical/optional): reuse range - node.as_ref().unwrap().value_range.clone() - }; - - value_block_nodes.insert(successor, ValueBlockNode { value_range }); - } else { - // Value -> value block transition: reuse the node - if let Some(n) = &node { - value_block_nodes.insert(successor, n.clone()); - } - } - } - } - - // Sync identifier mutable_range with their scope's range, but ONLY for - // identifiers whose mutable_range matched their scope's ORIGINAL range - // (before this pass modified it). In TS, identifier.mutableRange and - // scope.range are only the same JS object for identifiers that were the - // "canonical" representative when the scope was created. Other identifiers - // in the same scope have independent mutableRange objects that should NOT - // be updated when the scope's range changes. - for ident in &mut env.identifiers { - if let Some(scope_id) = ident.scope { - let original = &original_scope_ranges[scope_id.0 as usize]; - if ident.mutable_range.same_range(original) { - let scope_range = &env.scopes[scope_id.0 as usize].range; - ident.mutable_range.start = scope_range.start; - ident.mutable_range.end = scope_range.end; - } - } - } -} - -/// Records a place's scope as active and adjusts scope ranges for value blocks. -/// -/// Mirrors TS `recordPlace(id, place, node)`. -fn record_place_id( - id: EvaluationOrder, - identifier_id: IdentifierId, - node: &Option<ValueBlockNode>, - env: &mut Environment, - active_scopes: &mut HashSet<ScopeId>, - seen: &mut HashSet<ScopeId>, -) { - // Get the scope for this identifier, if active at this instruction - let scope_id = match env.identifiers[identifier_id.0 as usize].scope { - Some(scope_id) => { - let scope = &env.scopes[scope_id.0 as usize]; - if id >= scope.range.start && id < scope.range.end { - Some(scope_id) - } else { - None - } - } - None => None, - }; - - if let Some(scope_id) = scope_id { - active_scopes.insert(scope_id); - - if seen.contains(&scope_id) { - return; - } - seen.insert(scope_id); - - if let Some(n) = node { - let scope = &mut env.scopes[scope_id.0 as usize]; - scope.range.start = std::cmp::min(n.value_range.start, scope.range.start); - scope.range.end = std::cmp::max(n.value_range.end, scope.range.end); - } - } -} diff --git a/compiler/crates/react_compiler_inference/src/analyse_functions.rs b/compiler/crates/react_compiler_inference/src/analyse_functions.rs deleted file mode 100644 index 139a8d98cf8e..000000000000 --- a/compiler/crates/react_compiler_inference/src/analyse_functions.rs +++ /dev/null @@ -1,219 +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. - -//! Recursively analyzes nested function expressions and object methods to infer -//! their aliasing effect signatures. -//! -//! Ported from TypeScript `src/Inference/AnalyseFunctions.ts`. -//! -//! Runs inferMutationAliasingEffects, deadCodeElimination, -//! inferMutationAliasingRanges, rewriteInstructionKindsBasedOnReassignment, -//! and inferReactiveScopeVariables on each inner function. - -use indexmap::IndexMap; -use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory}; -use react_compiler_hir::environment::Environment; -use std::collections::HashSet; - -use react_compiler_hir::{ - AliasingEffect, BlockId, Effect, EvaluationOrder, FunctionId, HIR, HirFunction, IdentifierId, - InstructionValue, Place, ReactFunctionType, -}; - -/// Analyse all nested function expressions and object methods in `func`. -/// -/// For each inner function found, runs `lower_with_mutation_aliasing` to infer -/// its aliasing effects, then resets context variable mutable ranges. -/// -/// The optional `debug_logger` callback is invoked after processing each inner -/// function, receiving `(&HirFunction, &Environment)` so the caller can produce -/// debug output. This mirrors the TS `fn.env.logger?.debugLogIRs` call inside -/// `lowerWithMutationAliasing`. -/// -/// Corresponds to TS `analyseFunctions(func: HIRFunction): void`. -pub fn analyse_functions<F>( - func: &mut HirFunction, - env: &mut Environment, - debug_logger: &mut F, -) -> Result<(), CompilerDiagnostic> -where - F: FnMut(&HirFunction, &Environment), -{ - // Collect FunctionIds from FunctionExpression/ObjectMethod instructions. - // We collect first to avoid borrow conflicts with env.functions. - let mut inner_func_ids: Vec<FunctionId> = Vec::new(); - for (_block_id, block) in &func.body.blocks { - for instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - inner_func_ids.push(lowered_func.func); - } - _ => {} - } - } - } - - // Process each inner function - for func_id in inner_func_ids { - // Take the inner function out of the arena to avoid borrow conflicts - let mut inner_func = std::mem::replace( - &mut env.functions[func_id.0 as usize], - placeholder_function(), - ); - - lower_with_mutation_aliasing(&mut inner_func, env, debug_logger)?; - - // If an invariant error was recorded, put the function back and stop processing - if env.has_invariant_errors() { - env.functions[func_id.0 as usize] = inner_func; - return Ok(()); - } - - // Reset mutable range for outer inferMutationAliasingEffects. - // - // NOTE: inferReactiveScopeVariables makes identifiers in the scope - // point to the *same* mutableRange instance (in TS). In Rust, scopes - // are stored in an arena, so we reset both the identifier's range - // and clear its scope. - for operand in &inner_func.context { - let new_range = env.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0)); - let ident = &mut env.identifiers[operand.identifier.0 as usize]; - ident.mutable_range = new_range; - ident.scope = None; - } - - // Put the function back - env.functions[func_id.0 as usize] = inner_func; - } - - Ok(()) -} - -/// Run mutation/aliasing inference on an inner function. -/// -/// Corresponds to TS `lowerWithMutationAliasing(fn: HIRFunction): void`. -fn lower_with_mutation_aliasing<F>( - func: &mut HirFunction, - env: &mut Environment, - debug_logger: &mut F, -) -> Result<(), CompilerDiagnostic> -where - F: FnMut(&HirFunction, &Environment), -{ - // Phase 1: Recursively analyse nested functions first (depth-first) - analyse_functions(func, env, debug_logger)?; - - // inferMutationAliasingEffects on the inner function - crate::infer_mutation_aliasing_effects::infer_mutation_aliasing_effects(func, env, true)?; - - // Check for invariant errors (e.g., uninitialized value kind) - // In TS, these throw from within inferMutationAliasingEffects, aborting - // the rest of the function processing. - if env.has_invariant_errors() { - return Ok(()); - } - - // deadCodeElimination for inner functions - react_compiler_optimization::dead_code_elimination(func, env); - - // inferMutationAliasingRanges — returns the externally-visible function effects - let function_effects = - crate::infer_mutation_aliasing_ranges::infer_mutation_aliasing_ranges(func, env, true)?; - - // rewriteInstructionKindsBasedOnReassignment - if let Err(err) = react_compiler_ssa::rewrite_instruction_kinds_based_on_reassignment(func, env) - { - env.errors.merge(err); - return Ok(()); - } - - // inferReactiveScopeVariables on the inner function - crate::infer_reactive_scope_variables::infer_reactive_scope_variables(func, env)?; - - func.aliasing_effects = Some(function_effects.clone()); - - // Phase 2: Populate the Effect of each context variable to use in inferring - // the outer function. Corresponds to TS Phase 2 in lowerWithMutationAliasing. - let mut captured_or_mutated: HashSet<IdentifierId> = HashSet::new(); - for effect in &function_effects { - match effect { - AliasingEffect::Assign { from, .. } - | AliasingEffect::Alias { from, .. } - | AliasingEffect::Capture { from, .. } - | AliasingEffect::CreateFrom { from, .. } - | AliasingEffect::MaybeAlias { from, .. } => { - captured_or_mutated.insert(from.identifier); - } - AliasingEffect::Mutate { value, .. } - | AliasingEffect::MutateConditionally { value } - | AliasingEffect::MutateTransitive { value } - | AliasingEffect::MutateTransitiveConditionally { value } => { - captured_or_mutated.insert(value.identifier); - } - AliasingEffect::Impure { .. } - | AliasingEffect::Render { .. } - | AliasingEffect::MutateFrozen { .. } - | AliasingEffect::MutateGlobal { .. } - | AliasingEffect::CreateFunction { .. } - | AliasingEffect::Create { .. } - | AliasingEffect::Freeze { .. } - | AliasingEffect::ImmutableCapture { .. } => { - // no-op - } - AliasingEffect::Apply { .. } => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects", - None, - )); - } - } - } - - for operand in &mut func.context { - if captured_or_mutated.contains(&operand.identifier) || operand.effect == Effect::Capture { - operand.effect = Effect::Capture; - } else { - operand.effect = Effect::Read; - } - } - - // Log the inner function's state (mirrors TS: fn.env.logger?.debugLogIRs) - debug_logger(func, env); - - Ok(()) -} - -/// Create a placeholder HirFunction for temporarily swapping an inner function -/// out of `env.functions` via `std::mem::replace`. The placeholder is never -/// read — the real function is swapped back immediately after processing. -fn placeholder_function() -> HirFunction { - HirFunction { - loc: None, - id: None, - name_hint: None, - fn_type: ReactFunctionType::Other, - params: Vec::new(), - return_type_annotation: None, - returns: Place { - identifier: IdentifierId(0), - effect: Effect::Unknown, - reactive: false, - loc: None, - }, - context: Vec::new(), - body: HIR { - entry: BlockId(0), - blocks: IndexMap::new(), - }, - instructions: Vec::new(), - generator: false, - is_async: false, - directives: Vec::new(), - aliasing_effects: None, - } -} diff --git a/compiler/crates/react_compiler_inference/src/build_reactive_scope_terminals_hir.rs b/compiler/crates/react_compiler_inference/src/build_reactive_scope_terminals_hir.rs deleted file mode 100644 index 7c992057b5f7..000000000000 --- a/compiler/crates/react_compiler_inference/src/build_reactive_scope_terminals_hir.rs +++ /dev/null @@ -1,425 +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. - -//! Builds reactive scope terminals in the HIR. -//! -//! Given a function whose reactive scope ranges have been correctly aligned and -//! merged, this pass rewrites blocks to introduce ReactiveScopeTerminals and -//! their fallthrough blocks. -//! -//! Ported from TypeScript `src/HIR/BuildReactiveScopeTerminalsHIR.ts`. - -use std::collections::HashMap; -use std::collections::HashSet; - -use indexmap::IndexMap; -use react_compiler_hir::BasicBlock; -use react_compiler_hir::BlockId; -use react_compiler_hir::EvaluationOrder; -use react_compiler_hir::GotoVariant; -use react_compiler_hir::HirFunction; -use react_compiler_hir::IdentifierId; -use react_compiler_hir::ScopeId; -use react_compiler_hir::Terminal; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors::each_instruction_lvalue_ids; -use react_compiler_hir::visitors::each_instruction_operand_ids; -use react_compiler_hir::visitors::each_terminal_operand_ids; -use react_compiler_lowering::get_reverse_postordered_blocks; -use react_compiler_lowering::mark_instruction_ids; -use react_compiler_lowering::mark_predecessors; - -// ============================================================================= -// getScopes -// ============================================================================= - -/// Collect all unique scopes from places in the function that have non-empty ranges. -/// Corresponds to TS `getScopes(fn)`. -fn get_scopes(func: &HirFunction, env: &Environment) -> Vec<ScopeId> { - let mut scope_ids: HashSet<ScopeId> = HashSet::new(); - - let mut visit_place = |identifier_id: IdentifierId| { - if let Some(scope_id) = env.identifiers[identifier_id.0 as usize].scope { - let range = &env.scopes[scope_id.0 as usize].range; - if range.start != range.end { - scope_ids.insert(scope_id); - } - } - }; - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - // lvalues - for id in each_instruction_lvalue_ids(instr) { - visit_place(id); - } - // operands - for id in each_instruction_operand_ids(instr, env) { - visit_place(id); - } - } - // terminal operands - for id in each_terminal_operand_ids(&block.terminal) { - visit_place(id); - } - } - - scope_ids.into_iter().collect() -} - -// ============================================================================= -// TerminalRewriteInfo -// ============================================================================= - -enum TerminalRewriteInfo { - StartScope { - block_id: BlockId, - fallthrough_id: BlockId, - instr_id: EvaluationOrder, - scope_id: ScopeId, - }, - EndScope { - instr_id: EvaluationOrder, - fallthrough_id: BlockId, - }, -} - -impl TerminalRewriteInfo { - fn instr_id(&self) -> EvaluationOrder { - match self { - TerminalRewriteInfo::StartScope { instr_id, .. } => *instr_id, - TerminalRewriteInfo::EndScope { instr_id, .. } => *instr_id, - } - } -} - -// ============================================================================= -// collectScopeRewrites -// ============================================================================= - -/// Collect all scope rewrites by traversing scopes in pre-order. -fn collect_scope_rewrites(func: &HirFunction, env: &mut Environment) -> Vec<TerminalRewriteInfo> { - let scope_ids = get_scopes(func, env); - - // Sort: ascending by start, descending by end for ties - let mut items: Vec<ScopeId> = scope_ids; - items.sort_by(|a, b| { - let a_range = &env.scopes[a.0 as usize].range; - let b_range = &env.scopes[b.0 as usize].range; - let start_diff = a_range.start.0.cmp(&b_range.start.0); - if start_diff != std::cmp::Ordering::Equal { - return start_diff; - } - b_range.end.0.cmp(&a_range.end.0) - }); - - let mut rewrites: Vec<TerminalRewriteInfo> = Vec::new(); - let mut fallthroughs: HashMap<ScopeId, BlockId> = HashMap::new(); - let mut active_items: Vec<ScopeId> = Vec::new(); - - for i in 0..items.len() { - let curr = items[i]; - let curr_start = env.scopes[curr.0 as usize].range.start; - let curr_end = env.scopes[curr.0 as usize].range.end; - - // Pop active items that are disjoint with current - let mut j = active_items.len(); - while j > 0 { - j -= 1; - let maybe_parent = active_items[j]; - let parent_end = env.scopes[maybe_parent.0 as usize].range.end; - let disjoint = curr_start >= parent_end; - let nested = curr_end <= parent_end; - assert!( - disjoint || nested, - "Invalid nesting in program blocks or scopes" - ); - if disjoint { - // Exit this scope - let fallthrough_id = *fallthroughs - .get(&maybe_parent) - .expect("Expected scope to exist"); - let end_instr_id = env.scopes[maybe_parent.0 as usize].range.end; - rewrites.push(TerminalRewriteInfo::EndScope { - instr_id: end_instr_id, - fallthrough_id, - }); - active_items.truncate(j); - } else { - break; - } - } - - // Enter scope - let block_id = env.next_block_id(); - let fallthrough_id = env.next_block_id(); - let start_instr_id = env.scopes[curr.0 as usize].range.start; - rewrites.push(TerminalRewriteInfo::StartScope { - block_id, - fallthrough_id, - instr_id: start_instr_id, - scope_id: curr, - }); - fallthroughs.insert(curr, fallthrough_id); - active_items.push(curr); - } - - // Exit remaining active items - while let Some(curr) = active_items.pop() { - let fallthrough_id = *fallthroughs.get(&curr).expect("Expected scope to exist"); - let end_instr_id = env.scopes[curr.0 as usize].range.end; - rewrites.push(TerminalRewriteInfo::EndScope { - instr_id: end_instr_id, - fallthrough_id, - }); - } - - rewrites -} - -// ============================================================================= -// handleRewrite -// ============================================================================= - -struct RewriteContext { - next_block_id: BlockId, - next_preds: Vec<BlockId>, - instr_slice_idx: usize, - rewrites: Vec<BasicBlock>, -} - -fn handle_rewrite( - terminal_info: &TerminalRewriteInfo, - idx: usize, - source_block: &BasicBlock, - context: &mut RewriteContext, -) { - let terminal: Terminal = match terminal_info { - TerminalRewriteInfo::StartScope { - block_id, - fallthrough_id, - instr_id, - scope_id, - } => Terminal::Scope { - fallthrough: *fallthrough_id, - block: *block_id, - scope: *scope_id, - id: *instr_id, - loc: None, - }, - TerminalRewriteInfo::EndScope { - instr_id, - fallthrough_id, - } => Terminal::Goto { - variant: GotoVariant::Break, - block: *fallthrough_id, - id: *instr_id, - loc: None, - }, - }; - - let curr_block_id = context.next_block_id; - let mut preds = indexmap::IndexSet::new(); - for &p in &context.next_preds { - preds.insert(p); - } - - context.rewrites.push(BasicBlock { - kind: source_block.kind, - id: curr_block_id, - instructions: source_block.instructions[context.instr_slice_idx..idx].to_vec(), - preds, - // Only the first rewrite should reuse source block phis - phis: if context.rewrites.is_empty() { - source_block.phis.clone() - } else { - Vec::new() - }, - terminal, - }); - - context.next_preds = vec![curr_block_id]; - context.next_block_id = match terminal_info { - TerminalRewriteInfo::StartScope { block_id, .. } => *block_id, - TerminalRewriteInfo::EndScope { fallthrough_id, .. } => *fallthrough_id, - }; - context.instr_slice_idx = idx; -} - -// ============================================================================= -// Public API -// ============================================================================= - -/// Builds reactive scope terminals in the HIR. -/// -/// This pass assumes that all program blocks are properly nested with respect -/// to fallthroughs. Given a function whose reactive scope ranges have been -/// correctly aligned and merged, this pass rewrites blocks to introduce -/// ReactiveScopeTerminals and their fallthrough blocks. -pub fn build_reactive_scope_terminals_hir(func: &mut HirFunction, env: &mut Environment) { - // Step 1: Collect rewrites - let mut queued_rewrites = collect_scope_rewrites(func, env); - - // Step 2: Apply rewrites by splitting blocks - let mut rewritten_final_blocks: HashMap<BlockId, BlockId> = HashMap::new(); - let mut next_blocks: IndexMap<BlockId, BasicBlock> = IndexMap::new(); - - // Reverse so we can pop from the end while traversing in ascending order - queued_rewrites.reverse(); - - for (_block_id, block) in &func.body.blocks { - let preds_vec: Vec<BlockId> = block.preds.iter().copied().collect(); - let mut context = RewriteContext { - next_block_id: block.id, - rewrites: Vec::new(), - next_preds: preds_vec, - instr_slice_idx: 0, - }; - - // Handle queued terminal rewrites at their nearest instruction ID - for i in 0..block.instructions.len() + 1 { - let instr_id = if i < block.instructions.len() { - let instr_idx = block.instructions[i]; - func.instructions[instr_idx.0 as usize].id - } else { - block.terminal.evaluation_order() - }; - - while let Some(rewrite) = queued_rewrites.last() { - if rewrite.instr_id() <= instr_id { - // Need to pop before calling handle_rewrite - let rewrite = queued_rewrites.pop().unwrap(); - handle_rewrite(&rewrite, i, block, &mut context); - } else { - break; - } - } - } - - if !context.rewrites.is_empty() { - let mut final_preds = indexmap::IndexSet::new(); - for &p in &context.next_preds { - final_preds.insert(p); - } - let final_block = BasicBlock { - id: context.next_block_id, - kind: block.kind, - preds: final_preds, - terminal: block.terminal.clone(), - instructions: block.instructions[context.instr_slice_idx..].to_vec(), - phis: Vec::new(), - }; - let final_block_id = final_block.id; - context.rewrites.push(final_block); - for b in context.rewrites { - next_blocks.insert(b.id, b); - } - rewritten_final_blocks.insert(block.id, final_block_id); - } else { - next_blocks.insert(block.id, block.clone()); - } - } - - func.body.blocks = next_blocks; - - // Step 3: Repoint phis when they refer to a rewritten block - for block in func.body.blocks.values_mut() { - for phi in &mut block.phis { - let updates: Vec<(BlockId, BlockId)> = phi - .operands - .keys() - .filter_map(|original_id| { - rewritten_final_blocks - .get(original_id) - .map(|new_id| (*original_id, *new_id)) - }) - .collect(); - for (old_id, new_id) in updates { - if let Some(value) = phi.operands.shift_remove(&old_id) { - phi.operands.insert(new_id, value); - } - } - } - } - - // Step 4: Fixup HIR to restore RPO, correct predecessors, renumber instructions - func.body.blocks = get_reverse_postordered_blocks(&func.body, &func.instructions); - mark_predecessors(&mut func.body); - mark_instruction_ids(&mut func.body, &mut func.instructions); - - // Step 5: Fix scope and identifier ranges to account for renumbered instructions - fix_scope_and_identifier_ranges(func, env); -} - -/// Fix scope ranges after instruction renumbering. -/// Scope ranges should always align to start at the 'scope' terminal -/// and end at the first instruction of the fallthrough block. -/// -/// In TS, `identifier.mutableRange` and `scope.range` are the same object -/// reference (after InferReactiveScopeVariables). When scope.range is updated, -/// all identifiers with that scope automatically see the new range. -/// BUT: after MergeOverlappingReactiveScopesHIR, repointed identifiers have -/// mutableRange pointing to the OLD scope's range, NOT the root scope's range. -/// So only identifiers whose mutableRange matches their scope's pre-renumbering -/// range should be updated. -/// -/// Corresponds to TS `fixScopeAndIdentifierRanges`. -fn fix_scope_and_identifier_ranges(func: &HirFunction, env: &mut Environment) { - // Save original scope ranges before updating them. In TS, - // identifier.mutableRange and scope.range may or may not be the same - // JS object. Only identifiers whose mutableRange shares the same object - // reference as scope.range see the update automatically. We simulate - // this by only syncing identifiers whose mutableRange matches the - // scope's pre-update range. - let original_scope_ranges: Vec<react_compiler_hir::MutableRange> = - env.scopes.iter().map(|s| s.range.clone()).collect(); - - for (_block_id, block) in &func.body.blocks { - match &block.terminal { - Terminal::Scope { - fallthrough, - scope, - id, - .. - } - | Terminal::PrunedScope { - fallthrough, - scope, - id, - .. - } => { - let fallthrough_block = func.body.blocks.get(fallthrough).unwrap(); - let first_id = if !fallthrough_block.instructions.is_empty() { - func.instructions[fallthrough_block.instructions[0].0 as usize].id - } else { - fallthrough_block.terminal.evaluation_order() - }; - env.scopes[scope.0 as usize].range.start = *id; - env.scopes[scope.0 as usize].range.end = first_id; - } - _ => {} - } - } - - // Sync identifier mutable ranges with their scope ranges, but ONLY - // for identifiers whose mutableRange has the same identity as their - // scope's ORIGINAL range (before the updates above). In TS, - // identifier.mutableRange and scope.range are only the same JS object - // for identifiers that were the canonical representative when the scope - // was created. After MergeOverlappingReactiveScopesHIR, repointed - // identifiers have mutableRange pointing to the OLD scope's range, - // not the root scope's range — so they should NOT be synced here. - for ident in &mut env.identifiers { - if let Some(scope_id) = ident.scope { - let original = &original_scope_ranges[scope_id.0 as usize]; - if ident.mutable_range.same_range(original) { - let scope_range = &env.scopes[scope_id.0 as usize].range; - ident.mutable_range.start = scope_range.start; - ident.mutable_range.end = scope_range.end; - } - } - } -} diff --git a/compiler/crates/react_compiler_inference/src/flatten_reactive_loops_hir.rs b/compiler/crates/react_compiler_inference/src/flatten_reactive_loops_hir.rs deleted file mode 100644 index 0fae26693c1f..000000000000 --- a/compiler/crates/react_compiler_inference/src/flatten_reactive_loops_hir.rs +++ /dev/null @@ -1,63 +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. - -//! Prunes any reactive scopes that are within a loop (for, while, etc). We don't yet -//! support memoization within loops because this would require an extra layer of reconciliation -//! (plus a way to identify values across runs, similar to how we use `key` in JSX for lists). -//! Eventually we may integrate more deeply into the runtime so that we can do a single level -//! of reconciliation, but for now we've found it's sufficient to memoize *around* the loop. -//! -//! Analogous to TS `ReactiveScopes/FlattenReactiveLoopsHIR.ts`. - -use react_compiler_hir::{BlockId, HirFunction, Terminal}; - -/// Flattens reactive scopes that are inside loops by converting `Scope` terminals -/// to `PrunedScope` terminals. -pub fn flatten_reactive_loops_hir(func: &mut HirFunction) { - let mut active_loops: Vec<BlockId> = Vec::new(); - - // Collect block ids in iteration order so we can iterate while mutating terminals - let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect(); - - for block_id in block_ids { - // Remove this block from active loops (matching TS retainWhere) - active_loops.retain(|id| *id != block_id); - - let block = &func.body.blocks[&block_id]; - let terminal = &block.terminal; - - match terminal { - Terminal::DoWhile { fallthrough, .. } - | Terminal::For { fallthrough, .. } - | Terminal::ForIn { fallthrough, .. } - | Terminal::ForOf { fallthrough, .. } - | Terminal::While { fallthrough, .. } => { - active_loops.push(*fallthrough); - } - Terminal::Scope { - block, - fallthrough, - scope, - id, - loc, - } => { - if !active_loops.is_empty() { - let new_terminal = Terminal::PrunedScope { - block: *block, - fallthrough: *fallthrough, - scope: *scope, - id: *id, - loc: *loc, - }; - // We need to drop the borrow and reborrow mutably - let block_mut = func.body.blocks.get_mut(&block_id).unwrap(); - block_mut.terminal = new_terminal; - } - } - // All other terminal kinds: no action needed - _ => {} - } - } -} diff --git a/compiler/crates/react_compiler_inference/src/flatten_scopes_with_hooks_or_use_hir.rs b/compiler/crates/react_compiler_inference/src/flatten_scopes_with_hooks_or_use_hir.rs deleted file mode 100644 index 672877c46285..000000000000 --- a/compiler/crates/react_compiler_inference/src/flatten_scopes_with_hooks_or_use_hir.rs +++ /dev/null @@ -1,146 +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. - -//! 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 each, 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. -//! -//! Analogous to TS `ReactiveScopes/FlattenScopesWithHooksOrUseHIR.ts`. - -use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{BlockId, HirFunction, InstructionValue, Terminal, Type}; - -/// Flattens reactive scopes that contain hook calls or `use()` calls. -/// -/// Hooks and `use` must be called unconditionally, so any reactive scope containing -/// such a call must be flattened to avoid making the call conditional. -pub fn flatten_scopes_with_hooks_or_use_hir( - func: &mut HirFunction, - env: &Environment, -) -> Result<(), CompilerDiagnostic> { - let mut active_scopes: Vec<ActiveScope> = Vec::new(); - let mut prune: Vec<BlockId> = Vec::new(); - - // Collect block ids to allow mutation during iteration - let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect(); - - for block_id in &block_ids { - // Remove scopes whose fallthrough matches this block - active_scopes.retain(|scope| scope.fallthrough != *block_id); - - let block = &func.body.blocks[block_id]; - - // Check instructions for hook or use calls - for instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::CallExpression { callee, .. } => { - let callee_ty = - &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize]; - if is_hook_or_use(env, callee_ty)? { - // All active scopes must be pruned - prune.extend(active_scopes.iter().map(|s| s.block)); - active_scopes.clear(); - } - } - InstructionValue::MethodCall { property, .. } => { - let property_ty = &env.types - [env.identifiers[property.identifier.0 as usize].type_.0 as usize]; - if is_hook_or_use(env, property_ty)? { - prune.extend(active_scopes.iter().map(|s| s.block)); - active_scopes.clear(); - } - } - _ => {} - } - } - - // Track scope terminals - if let Terminal::Scope { fallthrough, .. } = &block.terminal { - active_scopes.push(ActiveScope { - block: *block_id, - fallthrough: *fallthrough, - }); - } - } - - // Apply pruning: convert Scope terminals to Label or PrunedScope - for id in prune { - let block = &func.body.blocks[&id]; - let terminal = &block.terminal; - - let (scope_block, fallthrough, eval_id, loc, scope) = match terminal { - Terminal::Scope { - block, - fallthrough, - id, - loc, - scope, - } => (*block, *fallthrough, *id, *loc, *scope), - _ => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!("Expected block bb{} to end in a scope terminal", id.0), - None, - )); - } - }; - - // Check if the scope body is a single-instruction block that goes directly - // to fallthrough — if so, use Label instead of PrunedScope - let body = &func.body.blocks[&scope_block]; - let new_terminal = if body.instructions.len() == 1 - && matches!(&body.terminal, Terminal::Goto { block, .. } if *block == fallthrough) - { - // This was a scope just for a hook call, which doesn't need memoization. - // Flatten it away. We rely on PruneUnusedLabels to do the actual flattening. - Terminal::Label { - block: scope_block, - fallthrough, - id: eval_id, - loc, - } - } else { - Terminal::PrunedScope { - block: scope_block, - fallthrough, - scope, - id: eval_id, - loc, - } - }; - - let block_mut = func.body.blocks.get_mut(&id).unwrap(); - block_mut.terminal = new_terminal; - } - Ok(()) -} - -struct ActiveScope { - block: BlockId, - fallthrough: BlockId, -} - -fn is_hook_or_use(env: &Environment, ty: &Type) -> Result<bool, CompilerDiagnostic> { - Ok(env.get_hook_kind_for_type(ty)?.is_some() || react_compiler_hir::is_use_operator_type(ty)) -} diff --git a/compiler/crates/react_compiler_inference/src/infer_mutation_aliasing_effects.rs b/compiler/crates/react_compiler_inference/src/infer_mutation_aliasing_effects.rs deleted file mode 100644 index cffceb4d97c6..000000000000 --- a/compiler/crates/react_compiler_inference/src/infer_mutation_aliasing_effects.rs +++ /dev/null @@ -1,3664 +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. - -//! Infers the mutation/aliasing effects for instructions and terminals. -//! -//! Ported from TypeScript `src/Inference/InferMutationAliasingEffects.ts`. -//! -//! This pass uses abstract interpretation to compute effects describing -//! creation, aliasing, mutation, freezing, and error conditions for each -//! instruction and terminal in the HIR. - -use std::collections::HashMap; -use std::collections::HashSet; - -use indexmap::IndexSet; -use react_compiler_diagnostics::CompilerDiagnostic; -use react_compiler_diagnostics::CompilerDiagnosticDetail; -use react_compiler_diagnostics::ErrorCategory; -use react_compiler_hir::AliasingEffect; -use react_compiler_hir::AliasingSignature; -use react_compiler_hir::BlockId; -use react_compiler_hir::DeclarationId; -use react_compiler_hir::Effect; -use react_compiler_hir::FunctionId; -use react_compiler_hir::HirFunction; -use react_compiler_hir::IdentifierId; -use react_compiler_hir::InstructionKind; -use react_compiler_hir::InstructionValue; -use react_compiler_hir::MutationReason; -use react_compiler_hir::ParamPattern; -use react_compiler_hir::Place; -use react_compiler_hir::PlaceOrSpread; -use react_compiler_hir::PlaceOrSpreadOrHole; -use react_compiler_hir::ReactFunctionType; -use react_compiler_hir::SourceLocation; -use react_compiler_hir::Type; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::object_shape::BUILT_IN_ARRAY_ID; -use react_compiler_hir::object_shape::BUILT_IN_MAP_ID; -use react_compiler_hir::object_shape::BUILT_IN_SET_ID; -use react_compiler_hir::object_shape::FunctionSignature; -use react_compiler_hir::object_shape::HookKind; -use react_compiler_hir::type_config::ValueKind; -use react_compiler_hir::type_config::ValueReason; -use react_compiler_hir::visitors; - -// ============================================================================= -// Public entry point -// ============================================================================= - -/// Infers mutation/aliasing effects for all instructions and terminals in `func`. -/// -/// Corresponds to TS `inferMutationAliasingEffects(fn, {isFunctionExpression})`. -pub fn infer_mutation_aliasing_effects( - func: &mut HirFunction, - env: &mut Environment, - is_function_expression: bool, -) -> Result<(), CompilerDiagnostic> { - let mut initial_state = InferenceState::empty(env, is_function_expression); - - // Map of blocks to the last (merged) incoming state that was processed - let mut states_by_block: HashMap<BlockId, InferenceState> = HashMap::new(); - - // Initialize context variables - for ctx_place in &func.context { - let value_id = ValueId::new(); - initial_state.initialize( - value_id, - AbstractValue { - kind: ValueKind::Context, - reason: hashset_of(ValueReason::Other), - }, - ); - initial_state.define(ctx_place.identifier, value_id); - } - - let param_kind: AbstractValue = if is_function_expression { - AbstractValue { - kind: ValueKind::Mutable, - reason: hashset_of(ValueReason::Other), - } - } else { - AbstractValue { - kind: ValueKind::Frozen, - reason: hashset_of(ValueReason::ReactiveFunctionArgument), - } - }; - - if func.fn_type == ReactFunctionType::Component { - // Component: at most 2 params (props, ref) - let params_len = func.params.len(); - if params_len > 0 { - infer_param(&func.params[0], &mut initial_state, ¶m_kind); - } - if params_len > 1 { - let ref_place = match &func.params[1] { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - let value_id = ValueId::new(); - initial_state.initialize( - value_id, - AbstractValue { - kind: ValueKind::Mutable, - reason: hashset_of(ValueReason::Other), - }, - ); - initial_state.define(ref_place.identifier, value_id); - } - } else { - for param in &func.params { - infer_param(param, &mut initial_state, ¶m_kind); - } - } - - let mut queued_states: indexmap::IndexMap<BlockId, InferenceState> = indexmap::IndexMap::new(); - - // Queue helper - fn queue( - queued_states: &mut indexmap::IndexMap<BlockId, InferenceState>, - states_by_block: &HashMap<BlockId, InferenceState>, - block_id: BlockId, - state: InferenceState, - ) { - if let Some(queued_state) = queued_states.get(&block_id) { - let merged = queued_state.merge(&state); - let new_state = merged.unwrap_or_else(|| queued_state.clone()); - queued_states.insert(block_id, new_state); - } else { - let prev_state = states_by_block.get(&block_id); - if let Some(prev) = prev_state { - let next_state = prev.merge(&state); - if let Some(next) = next_state { - queued_states.insert(block_id, next); - } - } else { - queued_states.insert(block_id, state); - } - } - } - - queue( - &mut queued_states, - &states_by_block, - func.body.entry, - initial_state, - ); - - let hoisted_context_declarations = find_hoisted_context_declarations(func, env); - let non_mutating_spreads = find_non_mutated_destructure_spreads(func, env); - - let mut context = Context { - interned_effects: HashMap::new(), - instruction_signature_cache: HashMap::new(), - catch_handlers: HashMap::new(), - is_function_expression, - hoisted_context_declarations, - non_mutating_spreads, - effect_value_id_cache: HashMap::new(), - function_values: HashMap::new(), - function_signature_cache: HashMap::new(), - aliasing_config_temp_cache: HashMap::new(), - }; - - let mut iteration_count = 0; - - while !queued_states.is_empty() { - iteration_count += 1; - if iteration_count > 100 { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "[InferMutationAliasingEffects] Potential infinite loop: \ - A value, temporary place, or effect was not cached properly", - None, - )); - } - - // Collect block IDs to process in order - let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect(); - for block_id in block_ids { - let incoming_state = match queued_states.swap_remove(&block_id) { - Some(s) => s, - None => continue, - }; - - states_by_block.insert(block_id, incoming_state.clone()); - let mut state = incoming_state.clone(); - - infer_block(&mut context, &mut state, block_id, func, env)?; - - // Check for uninitialized identifier access (matches TS invariant: - // "Expected value kind to be initialized") - if let Some((uninitialized_id, usage_loc)) = state.uninitialized_access.get() { - let ident_info = env.identifiers.get(uninitialized_id.0 as usize); - let name = ident_info - .and_then(|ident| ident.name.as_ref()) - .map(|n| n.value().to_string()) - .unwrap_or_else(|| "".to_string()); - // Use usage_loc if available, otherwise fall back to identifier's own loc - let error_loc = usage_loc.or_else(|| ident_info.and_then(|i| i.loc)); - // Match TS printPlace format: "<unknown> name$id:type" - let type_str = ident_info - .map(|ident| { - let ty = &env.types[ident.type_.0 as usize]; - format_type_for_print(ty) - }) - .unwrap_or_default(); - let description = format!("<unknown> {}${}{}", name, uninitialized_id.0, type_str); - let diag = CompilerDiagnostic::new( - ErrorCategory::Invariant, - "[InferMutationAliasingEffects] Expected value kind to be initialized", - Some(description), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: error_loc, - message: Some("this is uninitialized".to_string()), - identifier_name: None, - }); - return Err(diag); - } - - // Queue successors - let successors = terminal_successors(&func.body.blocks[&block_id].terminal); - for next_block_id in successors { - queue( - &mut queued_states, - &states_by_block, - next_block_id, - state.clone(), - ); - } - } - } - - Ok(()) -} - -// ============================================================================= -// ValueId: replaces InstructionValue identity as allocation-site key -// ============================================================================= - -/// Unique allocation-site identifier, replacing TS's object-identity on InstructionValue. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -struct ValueId(u32); - -use std::sync::atomic::AtomicU32; -use std::sync::atomic::Ordering; -static NEXT_VALUE_ID: AtomicU32 = AtomicU32::new(1); - -impl ValueId { - fn new() -> Self { - ValueId(NEXT_VALUE_ID.fetch_add(1, Ordering::Relaxed)) - } -} - -// ============================================================================= -// AbstractValue -// ============================================================================= - -#[derive(Debug, Clone)] -struct AbstractValue { - kind: ValueKind, - reason: IndexSet<ValueReason>, -} - -fn hashset_of(r: ValueReason) -> IndexSet<ValueReason> { - let mut s = IndexSet::new(); - s.insert(r); - s -} - -// ============================================================================= -// InferenceState -// ============================================================================= - -/// The abstract state tracked during inference. -/// Uses interior mutability via a struct with direct fields (no Rc needed since -/// we always have exclusive access in the pass). -#[derive(Debug, Clone)] -struct InferenceState { - is_function_expression: bool, - /// The kind of each value, based on its allocation site - values: HashMap<ValueId, AbstractValue>, - /// The set of values pointed to by each identifier - variables: HashMap<IdentifierId, HashSet<ValueId>>, - /// Tracks uninitialized identifier access errors (matches TS invariant). - /// Uses Cell so it can be set from `&self` methods like `kind()`. - /// Stores (IdentifierId, usage_loc) where usage_loc is the source location - /// of the Place that triggered the uninitialized access. - uninitialized_access: std::cell::Cell<Option<(IdentifierId, Option<SourceLocation>)>>, -} - -impl InferenceState { - fn empty(_env: &Environment, is_function_expression: bool) -> Self { - InferenceState { - is_function_expression, - values: HashMap::new(), - variables: HashMap::new(), - uninitialized_access: std::cell::Cell::new(None), - } - } - - /// Check the kind of a place, recording the usage location for error reporting. - fn kind_with_loc( - &self, - place_id: IdentifierId, - usage_loc: Option<SourceLocation>, - ) -> AbstractValue { - let values = match self.variables.get(&place_id) { - Some(v) => v, - None => { - if self.uninitialized_access.get().is_none() { - self.uninitialized_access.set(Some((place_id, usage_loc))); - } - return AbstractValue { - kind: ValueKind::Mutable, - reason: hashset_of(ValueReason::Other), - }; - } - }; - let mut merged_kind: Option<AbstractValue> = None; - for value_id in values { - let kind = match self.values.get(value_id) { - Some(k) => k, - None => continue, - }; - merged_kind = Some(match merged_kind { - Some(prev) => merge_abstract_values(&prev, kind), - None => kind.clone(), - }); - } - merged_kind.unwrap_or_else(|| AbstractValue { - kind: ValueKind::Mutable, - reason: hashset_of(ValueReason::Other), - }) - } - - fn initialize(&mut self, value_id: ValueId, kind: AbstractValue) { - self.values.insert(value_id, kind); - } - - fn define(&mut self, place_id: IdentifierId, value_id: ValueId) { - let mut set = HashSet::new(); - set.insert(value_id); - self.variables.insert(place_id, set); - } - - fn assign(&mut self, into: IdentifierId, from: IdentifierId) { - let values = match self.variables.get(&from) { - Some(v) => v.clone(), - None => { - // Create a stable value for uninitialized identifiers - // Use a deterministic ID based on the from identifier - let vid = ValueId(from.0 | 0x80000000); - let mut set = HashSet::new(); - set.insert(vid); - if !self.values.contains_key(&vid) { - self.values.insert( - vid, - AbstractValue { - kind: ValueKind::Mutable, - reason: hashset_of(ValueReason::Other), - }, - ); - } - set - } - }; - self.variables.insert(into, values); - } - - fn append_alias(&mut self, place: IdentifierId, value: IdentifierId) { - let new_values = match self.variables.get(&value) { - Some(v) => v.clone(), - None => return, - }; - let prev_values = match self.variables.get(&place) { - Some(v) => v.clone(), - None => return, - }; - let merged: HashSet<ValueId> = prev_values.union(&new_values).copied().collect(); - self.variables.insert(place, merged); - } - - fn is_defined(&self, place_id: IdentifierId) -> bool { - self.variables.contains_key(&place_id) - } - - fn values_for(&self, place_id: IdentifierId) -> Vec<ValueId> { - match self.variables.get(&place_id) { - Some(values) => values.iter().copied().collect(), - None => Vec::new(), - } - } - - #[allow(dead_code)] - fn kind_opt(&self, place_id: IdentifierId) -> Option<AbstractValue> { - let values = self.variables.get(&place_id)?; - let mut merged_kind: Option<AbstractValue> = None; - for value_id in values { - let kind = self.values.get(value_id)?; - merged_kind = Some(match merged_kind { - Some(prev) => merge_abstract_values(&prev, kind), - None => kind.clone(), - }); - } - merged_kind - } - - fn kind(&self, place_id: IdentifierId) -> AbstractValue { - self.kind_with_loc(place_id, None) - } - - fn freeze(&mut self, place_id: IdentifierId, reason: ValueReason) -> bool { - // Check if defined first to avoid recording uninitialized access error. - // Freeze on undefined identifiers is a no-op — this matches the TS - // behavior where freeze() is never called on undefined identifiers - // (the invariant in kind() catches this before freeze is reached). - if !self.variables.contains_key(&place_id) { - return false; - } - let value = self.kind(place_id); - match value.kind { - ValueKind::Context | ValueKind::Mutable | ValueKind::MaybeFrozen => { - let value_ids: Vec<ValueId> = self.values_for(place_id); - for vid in value_ids { - self.freeze_value(vid, reason); - } - true - } - ValueKind::Frozen | ValueKind::Global | ValueKind::Primitive => false, - } - } - - fn freeze_value(&mut self, value_id: ValueId, reason: ValueReason) { - self.values.insert( - value_id, - AbstractValue { - kind: ValueKind::Frozen, - reason: hashset_of(reason), - }, - ); - // Note: In TS, this also transitively freezes FunctionExpression captures - // if enableTransitivelyFreezeFunctionExpressions is set. We skip that here - // since we don't have access to the function arena from within state. - } - - #[allow(dead_code)] - fn mutate( - &self, - variant: MutateVariant, - place_id: IdentifierId, - env: &Environment, - ) -> MutationResult { - self.mutate_with_loc(variant, place_id, env, None) - } - - fn mutate_with_loc( - &self, - variant: MutateVariant, - place_id: IdentifierId, - env: &Environment, - usage_loc: Option<SourceLocation>, - ) -> MutationResult { - let ty = &env.types[env.identifiers[place_id.0 as usize].type_.0 as usize]; - if react_compiler_hir::is_ref_or_ref_value(ty) { - return MutationResult::MutateRef; - } - let kind = self.kind_with_loc(place_id, usage_loc).kind; - match variant { - MutateVariant::MutateConditionally | MutateVariant::MutateTransitiveConditionally => { - match kind { - ValueKind::Mutable | ValueKind::Context => MutationResult::Mutate, - _ => MutationResult::None, - } - } - MutateVariant::Mutate | MutateVariant::MutateTransitive => match kind { - ValueKind::Mutable | ValueKind::Context => MutationResult::Mutate, - ValueKind::Primitive => MutationResult::None, - ValueKind::Frozen | ValueKind::MaybeFrozen => MutationResult::MutateFrozen, - ValueKind::Global => MutationResult::MutateGlobal, - }, - } - } - - fn merge(&self, other: &InferenceState) -> Option<InferenceState> { - let mut next_values: Option<HashMap<ValueId, AbstractValue>> = None; - let mut next_variables: Option<HashMap<IdentifierId, HashSet<ValueId>>> = None; - - // Merge values present in both - for (id, this_value) in &self.values { - if let Some(other_value) = other.values.get(id) { - let merged = merge_abstract_values(this_value, other_value); - if merged.kind != this_value.kind - || !is_superset(&this_value.reason, &merged.reason) - { - let nv = next_values.get_or_insert_with(|| self.values.clone()); - nv.insert(*id, merged); - } - } - } - // Add values only in other - for (id, other_value) in &other.values { - if !self.values.contains_key(id) { - let nv = next_values.get_or_insert_with(|| self.values.clone()); - nv.insert(*id, other_value.clone()); - } - } - - // Merge variables present in both - for (id, this_values) in &self.variables { - if let Some(other_values) = other.variables.get(id) { - let mut has_new = false; - for ov in other_values { - if !this_values.contains(ov) { - has_new = true; - break; - } - } - if has_new { - let nvars = next_variables.get_or_insert_with(|| self.variables.clone()); - let merged: HashSet<ValueId> = - this_values.union(other_values).copied().collect(); - nvars.insert(*id, merged); - } - } - } - // Add variables only in other - for (id, other_values) in &other.variables { - if !self.variables.contains_key(id) { - let nvars = next_variables.get_or_insert_with(|| self.variables.clone()); - nvars.insert(*id, other_values.clone()); - } - } - - if next_variables.is_none() && next_values.is_none() { - None - } else { - Some(InferenceState { - is_function_expression: self.is_function_expression, - values: next_values.unwrap_or_else(|| self.values.clone()), - variables: next_variables.unwrap_or_else(|| self.variables.clone()), - uninitialized_access: std::cell::Cell::new(None), - }) - } - } - - fn infer_phi( - &mut self, - phi_place_id: IdentifierId, - phi_operands: &indexmap::IndexMap<BlockId, Place>, - ) { - let mut values: HashSet<ValueId> = HashSet::new(); - for (_, operand) in phi_operands { - if let Some(operand_values) = self.variables.get(&operand.identifier) { - for v in operand_values { - values.insert(*v); - } - } - // If not found, it's a backedge that will be handled later by merge - } - if !values.is_empty() { - self.variables.insert(phi_place_id, values); - } - } -} - -fn is_superset(a: &IndexSet<ValueReason>, b: &IndexSet<ValueReason>) -> bool { - b.iter().all(|x| a.contains(x)) -} - -#[derive(Debug, Clone, Copy)] -enum MutateVariant { - Mutate, - MutateConditionally, - MutateTransitive, - MutateTransitiveConditionally, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum MutationResult { - None, - Mutate, - MutateFrozen, - MutateGlobal, - MutateRef, -} - -// ============================================================================= -// Context -// ============================================================================= - -struct Context { - interned_effects: HashMap<String, AliasingEffect>, - instruction_signature_cache: HashMap<u32, InstructionSignature>, - catch_handlers: HashMap<BlockId, Place>, - is_function_expression: bool, - hoisted_context_declarations: HashMap<DeclarationId, Option<Place>>, - non_mutating_spreads: HashSet<IdentifierId>, - /// Cache of ValueIds keyed by effect hash, ensuring stable allocation-site identity - /// across fixpoint iterations. Mirrors TS `effectInstructionValueCache`. - effect_value_id_cache: HashMap<String, ValueId>, - /// Maps ValueId to FunctionId for function expressions, so we can look up - /// locally-declared functions when processing Apply effects. - function_values: HashMap<ValueId, FunctionId>, - /// Cache of function expression signatures, keyed by FunctionId - function_signature_cache: HashMap<FunctionId, AliasingSignature>, - /// Cache of temporary places created for aliasing signature config temporaries. - /// Keyed by (lvalue_identifier_id, temp_name) to ensure stable allocation - /// across fixpoint iterations. - aliasing_config_temp_cache: HashMap<(IdentifierId, String), Place>, -} - -impl Context { - fn intern_effect(&mut self, effect: AliasingEffect) -> AliasingEffect { - let hash = hash_effect(&effect); - self.interned_effects.entry(hash).or_insert(effect).clone() - } - - /// Get or create a stable ValueId for a given effect, ensuring fixpoint convergence. - fn get_or_create_value_id(&mut self, effect: &AliasingEffect) -> ValueId { - let hash = hash_effect(effect); - *self - .effect_value_id_cache - .entry(hash) - .or_insert_with(ValueId::new) - } -} - -struct InstructionSignature { - effects: Vec<AliasingEffect>, -} - -// ============================================================================= -// Helper: hash_effect -// ============================================================================= - -fn hash_effect(effect: &AliasingEffect) -> String { - match effect { - AliasingEffect::Apply { - receiver, - function, - mutates_function, - args, - into, - .. - } => { - let args_str: Vec<String> = args - .iter() - .map(|a| match a { - PlaceOrSpreadOrHole::Hole => String::new(), - PlaceOrSpreadOrHole::Place(p) => format!("{}", p.identifier.0), - PlaceOrSpreadOrHole::Spread(s) => format!("...{}", s.place.identifier.0), - }) - .collect(); - format!( - "Apply:{}:{}:{}:{}:{}", - receiver.identifier.0, - function.identifier.0, - mutates_function, - args_str.join(","), - into.identifier.0 - ) - } - AliasingEffect::CreateFrom { from, into } => { - format!("CreateFrom:{}:{}", from.identifier.0, into.identifier.0) - } - AliasingEffect::ImmutableCapture { from, into } => format!( - "ImmutableCapture:{}:{}", - from.identifier.0, into.identifier.0 - ), - AliasingEffect::Assign { from, into } => { - format!("Assign:{}:{}", from.identifier.0, into.identifier.0) - } - AliasingEffect::Alias { from, into } => { - format!("Alias:{}:{}", from.identifier.0, into.identifier.0) - } - AliasingEffect::Capture { from, into } => { - format!("Capture:{}:{}", from.identifier.0, into.identifier.0) - } - AliasingEffect::MaybeAlias { from, into } => { - format!("MaybeAlias:{}:{}", from.identifier.0, into.identifier.0) - } - AliasingEffect::Create { - into, - value, - reason, - } => format!("Create:{}:{:?}:{:?}", into.identifier.0, value, reason), - AliasingEffect::Freeze { value, reason } => { - format!("Freeze:{}:{:?}", value.identifier.0, reason) - } - AliasingEffect::Impure { place, .. } => format!("Impure:{}", place.identifier.0), - AliasingEffect::Render { place } => format!("Render:{}", place.identifier.0), - AliasingEffect::MutateFrozen { place, error } => format!( - "MutateFrozen:{}:{}:{:?}", - place.identifier.0, error.reason, error.description - ), - AliasingEffect::MutateGlobal { place, error } => format!( - "MutateGlobal:{}:{}:{:?}", - place.identifier.0, error.reason, error.description - ), - AliasingEffect::Mutate { value, .. } => format!("Mutate:{}", value.identifier.0), - AliasingEffect::MutateConditionally { value } => { - format!("MutateConditionally:{}", value.identifier.0) - } - AliasingEffect::MutateTransitive { value } => { - format!("MutateTransitive:{}", value.identifier.0) - } - AliasingEffect::MutateTransitiveConditionally { value } => { - format!("MutateTransitiveConditionally:{}", value.identifier.0) - } - AliasingEffect::CreateFunction { - into, - function_id, - captures, - } => { - let cap_str: Vec<String> = captures - .iter() - .map(|p| format!("{}", p.identifier.0)) - .collect(); - format!( - "CreateFunction:{}:{}:{}", - into.identifier.0, - function_id.0, - cap_str.join(",") - ) - } - } -} - -// ============================================================================= -// merge helpers -// ============================================================================= - -fn merge_abstract_values(a: &AbstractValue, b: &AbstractValue) -> AbstractValue { - let kind = merge_value_kinds(a.kind, b.kind); - if kind == a.kind && kind == b.kind && is_superset(&a.reason, &b.reason) { - return a.clone(); - } - let mut reason = a.reason.clone(); - for r in &b.reason { - reason.insert(*r); - } - AbstractValue { kind, reason } -} - -fn merge_value_kinds(a: ValueKind, b: ValueKind) -> ValueKind { - if a == b { - return a; - } - if a == ValueKind::MaybeFrozen || b == ValueKind::MaybeFrozen { - return ValueKind::MaybeFrozen; - } - if a == ValueKind::Mutable || b == ValueKind::Mutable { - if a == ValueKind::Frozen || b == ValueKind::Frozen { - return ValueKind::MaybeFrozen; - } else if a == ValueKind::Context || b == ValueKind::Context { - return ValueKind::Context; - } else { - return ValueKind::Mutable; - } - } - if a == ValueKind::Context || b == ValueKind::Context { - if a == ValueKind::Frozen || b == ValueKind::Frozen { - return ValueKind::MaybeFrozen; - } else { - return ValueKind::Context; - } - } - if a == ValueKind::Frozen || b == ValueKind::Frozen { - return ValueKind::Frozen; - } - if a == ValueKind::Global || b == ValueKind::Global { - return ValueKind::Global; - } - ValueKind::Primitive -} - -// ============================================================================= -// Pre-passes -// ============================================================================= - -fn find_hoisted_context_declarations( - func: &HirFunction, - env: &Environment, -) -> HashMap<DeclarationId, Option<Place>> { - let mut hoisted: HashMap<DeclarationId, Option<Place>> = HashMap::new(); - - fn visit( - hoisted: &mut HashMap<DeclarationId, Option<Place>>, - place: &Place, - env: &Environment, - ) { - let decl_id = env.identifiers[place.identifier.0 as usize].declaration_id; - if hoisted.contains_key(&decl_id) && hoisted.get(&decl_id).unwrap().is_none() { - hoisted.insert(decl_id, Some(place.clone())); - } - } - - for (_block_id, block) in &func.body.blocks { - for instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::DeclareContext { lvalue, .. } => { - let kind = lvalue.kind; - if kind == InstructionKind::HoistedConst - || kind == InstructionKind::HoistedFunction - || kind == InstructionKind::HoistedLet - { - let decl_id = - env.identifiers[lvalue.place.identifier.0 as usize].declaration_id; - hoisted.insert(decl_id, None); - } - } - _ => { - for operand in visitors::each_instruction_value_operand(&instr.value, env) { - visit(&mut hoisted, &operand, env); - } - } - } - } - for operand in visitors::each_terminal_operand(&block.terminal) { - visit(&mut hoisted, &operand, env); - } - } - hoisted -} - -fn find_non_mutated_destructure_spreads( - func: &HirFunction, - env: &Environment, -) -> HashSet<IdentifierId> { - let mut known_frozen: HashSet<IdentifierId> = HashSet::new(); - if func.fn_type == ReactFunctionType::Component { - if let Some(param) = func.params.first() { - if let ParamPattern::Place(p) = param { - known_frozen.insert(p.identifier); - } - } - } else { - for param in &func.params { - if let ParamPattern::Place(p) = param { - known_frozen.insert(p.identifier); - } - } - } - - let mut candidate_non_mutating_spreads: HashMap<IdentifierId, IdentifierId> = HashMap::new(); - for (_block_id, block) in &func.body.blocks { - if !candidate_non_mutating_spreads.is_empty() { - for phi in &block.phis { - for (_, operand) in &phi.operands { - if let Some(spread) = candidate_non_mutating_spreads - .get(&operand.identifier) - .copied() - { - candidate_non_mutating_spreads.remove(&spread); - } - } - } - } - for instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - match &instr.value { - InstructionValue::Destructure { lvalue, value, .. } => { - if !known_frozen.contains(&value.identifier) { - continue; - } - if !(lvalue.kind == InstructionKind::Let - || lvalue.kind == InstructionKind::Const) - { - continue; - } - match &lvalue.pattern { - react_compiler_hir::Pattern::Object(obj_pat) => { - for prop in &obj_pat.properties { - if let react_compiler_hir::ObjectPropertyOrSpread::Spread(s) = prop - { - candidate_non_mutating_spreads - .insert(s.place.identifier, s.place.identifier); - } - } - } - _ => continue, - } - } - InstructionValue::LoadLocal { place, .. } => { - if let Some(spread) = candidate_non_mutating_spreads - .get(&place.identifier) - .copied() - { - candidate_non_mutating_spreads.insert(lvalue_id, spread); - } - } - InstructionValue::StoreLocal { - lvalue: sl, - value: sv, - .. - } => { - if let Some(spread) = - candidate_non_mutating_spreads.get(&sv.identifier).copied() - { - candidate_non_mutating_spreads.insert(lvalue_id, spread); - candidate_non_mutating_spreads.insert(sl.place.identifier, spread); - } - } - InstructionValue::JsxFragment { .. } | InstructionValue::JsxExpression { .. } => { - // Passing objects created with spread to jsx can't mutate them - } - InstructionValue::PropertyLoad { .. } => { - // Properties must be frozen since the original value was frozen - } - InstructionValue::CallExpression { callee, .. } - | InstructionValue::MethodCall { - property: callee, .. - } => { - let callee_ty = - &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize]; - if get_hook_kind_for_type(env, callee_ty) - .ok() - .flatten() - .is_some() - { - if !is_ref_or_ref_value_for_id(env, lvalue_id) { - known_frozen.insert(lvalue_id); - } - } else if !candidate_non_mutating_spreads.is_empty() { - for operand in visitors::each_instruction_value_operand(&instr.value, env) { - if let Some(spread) = candidate_non_mutating_spreads - .get(&operand.identifier) - .copied() - { - candidate_non_mutating_spreads.remove(&spread); - } - } - } - } - _ => { - if !candidate_non_mutating_spreads.is_empty() { - for operand in visitors::each_instruction_value_operand(&instr.value, env) { - if let Some(spread) = candidate_non_mutating_spreads - .get(&operand.identifier) - .copied() - { - candidate_non_mutating_spreads.remove(&spread); - } - } - } - } - } - } - } - - let mut non_mutating: HashSet<IdentifierId> = HashSet::new(); - for (key, value) in &candidate_non_mutating_spreads { - if key == value { - non_mutating.insert(*key); - } - } - non_mutating -} - -// ============================================================================= -// inferParam -// ============================================================================= - -fn infer_param(param: &ParamPattern, state: &mut InferenceState, param_kind: &AbstractValue) { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - let value_id = ValueId::new(); - state.initialize(value_id, param_kind.clone()); - state.define(place.identifier, value_id); -} - -// ============================================================================= -// inferBlock -// ============================================================================= - -fn infer_block( - context: &mut Context, - state: &mut InferenceState, - block_id: BlockId, - func: &mut HirFunction, - env: &mut Environment, -) -> Result<(), CompilerDiagnostic> { - let block = &func.body.blocks[&block_id]; - - // Process phis - let phis: Vec<(IdentifierId, indexmap::IndexMap<BlockId, Place>)> = block - .phis - .iter() - .map(|phi| (phi.place.identifier, phi.operands.clone())) - .collect(); - for (place_id, operands) in &phis { - state.infer_phi(*place_id, operands); - } - - // Process instructions - let instr_ids: Vec<u32> = block.instructions.iter().map(|id| id.0).collect(); - for instr_idx in &instr_ids { - let instr_index = *instr_idx as usize; - - // Compute signature if not cached - if !context.instruction_signature_cache.contains_key(instr_idx) { - let sig = compute_signature_for_instruction( - context, - env, - &func.instructions[instr_index], - func, - ); - context.instruction_signature_cache.insert(*instr_idx, sig); - } - - // Apply signature - let effects = apply_signature( - context, - state, - *instr_idx, - &func.instructions[instr_index], - env, - func, - )?; - func.instructions[instr_index].effects = effects; - } - - // Process terminal - // Determine what terminal action to take without holding borrows - enum TerminalAction { - Try { handler: BlockId, binding: Place }, - MaybeThrow { handler_id: BlockId }, - Return, - None, - } - let action = { - let block = &func.body.blocks[&block_id]; - match &block.terminal { - react_compiler_hir::Terminal::Try { - handler, - handler_binding: Some(binding), - .. - } => TerminalAction::Try { - handler: *handler, - binding: binding.clone(), - }, - react_compiler_hir::Terminal::MaybeThrow { - handler: Some(handler_id), - .. - } => TerminalAction::MaybeThrow { - handler_id: *handler_id, - }, - react_compiler_hir::Terminal::Return { .. } => TerminalAction::Return, - _ => TerminalAction::None, - } - }; - - match action { - TerminalAction::Try { handler, binding } => { - context.catch_handlers.insert(handler, binding); - } - TerminalAction::MaybeThrow { handler_id } => { - if let Some(handler_param) = context.catch_handlers.get(&handler_id).cloned() { - if state.is_defined(handler_param.identifier) { - let mut terminal_effects: Vec<AliasingEffect> = Vec::new(); - for instr_idx in &instr_ids { - let instr = &func.instructions[*instr_idx as usize]; - match &instr.value { - InstructionValue::CallExpression { .. } - | InstructionValue::MethodCall { .. } => { - state.append_alias( - handler_param.identifier, - instr.lvalue.identifier, - ); - let kind = state.kind(instr.lvalue.identifier).kind; - if kind == ValueKind::Mutable || kind == ValueKind::Context { - terminal_effects.push(context.intern_effect( - AliasingEffect::Alias { - from: instr.lvalue.clone(), - into: handler_param.clone(), - }, - )); - } - } - _ => {} - } - } - let block_mut = func.body.blocks.get_mut(&block_id).unwrap(); - if let react_compiler_hir::Terminal::MaybeThrow { - effects: ref mut term_effects, - .. - } = block_mut.terminal - { - *term_effects = if terminal_effects.is_empty() { - None - } else { - Some(terminal_effects) - }; - } - } - } - } - TerminalAction::Return => { - if !context.is_function_expression { - let block_mut = func.body.blocks.get_mut(&block_id).unwrap(); - if let react_compiler_hir::Terminal::Return { - ref value, - effects: ref mut term_effects, - .. - } = block_mut.terminal - { - *term_effects = Some(vec![context.intern_effect(AliasingEffect::Freeze { - value: value.clone(), - reason: ValueReason::JsxCaptured, - })]); - } - } - } - TerminalAction::None => {} - } - Ok(()) -} - -// ============================================================================= -// applySignature -// ============================================================================= - -fn apply_signature( - context: &mut Context, - state: &mut InferenceState, - instr_idx: u32, - instr: &react_compiler_hir::Instruction, - env: &mut Environment, - func: &HirFunction, -) -> Result<Option<Vec<AliasingEffect>>, CompilerDiagnostic> { - let mut effects: Vec<AliasingEffect> = Vec::new(); - - // For function instructions, validate frozen mutation - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - let inner_func = &env.functions[lowered_func.func.0 as usize]; - if let Some(ref aliasing_effects) = inner_func.aliasing_effects { - let context_ids: HashSet<IdentifierId> = - inner_func.context.iter().map(|p| p.identifier).collect(); - for effect in aliasing_effects { - let (mutate_value, is_mutate) = match effect { - AliasingEffect::Mutate { value, .. } => (value, true), - AliasingEffect::MutateTransitive { value } => (value, false), - _ => continue, - }; - if !context_ids.contains(&mutate_value.identifier) { - continue; - } - if !state.is_defined(mutate_value.identifier) { - continue; - } - let value_abstract = state.kind(mutate_value.identifier); - if value_abstract.kind == ValueKind::Frozen { - let reason_str = get_write_error_reason(&value_abstract); - let ident = &env.identifiers[mutate_value.identifier.0 as usize]; - let variable = match &ident.name { - Some(react_compiler_hir::IdentifierName::Named(n)) => { - format!("`{}`", n) - } - _ => "value".to_string(), - }; - let mut diagnostic = CompilerDiagnostic::new( - ErrorCategory::Immutability, - "This value cannot be modified", - Some(reason_str), - ); - diagnostic.details.push( - react_compiler_diagnostics::CompilerDiagnosticDetail::Error { - loc: mutate_value.loc, - message: Some(format!("{} cannot be modified", variable)), - identifier_name: None, - }, - ); - if is_mutate { - if let AliasingEffect::Mutate { - reason: Some(MutationReason::AssignCurrentProperty), - .. - } = effect - { - diagnostic.details.push(react_compiler_diagnostics::CompilerDiagnosticDetail::Hint { - message: "Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in \"Ref\".".to_string() - }); - } - } - effects.push(AliasingEffect::MutateFrozen { - place: mutate_value.clone(), - error: diagnostic, - }); - } - } - } - } - _ => {} - } - - // Track which values we've already initialized - let mut initialized: HashSet<IdentifierId> = HashSet::new(); - - // Get the cached signature effects - let sig = context.instruction_signature_cache.get(&instr_idx).unwrap(); - let sig_effects: Vec<AliasingEffect> = sig.effects.clone(); - - for effect in &sig_effects { - apply_effect( - context, - state, - effect.clone(), - &mut initialized, - &mut effects, - env, - func, - )?; - } - - // If lvalue is not yet defined, initialize it with a default value. - // The TS version asserts this as an invariant, but the Rust port may have - // edge cases where effects don't cover the lvalue (e.g. missing signature entries). - if !state.is_defined(instr.lvalue.identifier) { - let vid = ValueId(instr.lvalue.identifier.0 | 0x80000000); - state.initialize( - vid, - AbstractValue { - kind: ValueKind::Mutable, - reason: hashset_of(ValueReason::Other), - }, - ); - state.define(instr.lvalue.identifier, vid); - } - - Ok(if effects.is_empty() { - None - } else { - Some(effects) - }) -} - -// ============================================================================= -// Transitive freeze helper -// ============================================================================= - -/// Recursively freeze through FunctionExpression captures. If `value_id` -/// corresponds to a FunctionExpression, freeze each of its context captures -/// and recurse into any that are themselves FunctionExpressions. This matches -/// the TS `freezeValue` → `freeze` → `freezeValue` recursion chain. -fn freeze_function_captures_transitive( - state: &mut InferenceState, - context: &Context, - env: &Environment, - value_id: ValueId, - reason: ValueReason, -) { - if let Some(&func_id) = context.function_values.get(&value_id) { - let ctx_ids: Vec<IdentifierId> = env.functions[func_id.0 as usize] - .context - .iter() - .map(|p| p.identifier) - .collect(); - for ctx_id in ctx_ids { - // Replicate InferenceState::freeze() logic inline — - // we need to recurse with context/env which freeze() doesn't have. - if !state.variables.contains_key(&ctx_id) { - continue; - } - let kind = state.kind(ctx_id).kind; - match kind { - ValueKind::Context | ValueKind::Mutable | ValueKind::MaybeFrozen => { - let vids: Vec<ValueId> = state.values_for(ctx_id); - for vid in vids { - state.freeze_value(vid, reason); - // Recurse into nested function captures - freeze_function_captures_transitive(state, context, env, vid, reason); - } - } - ValueKind::Frozen | ValueKind::Global | ValueKind::Primitive => { - // Already frozen or immutable — no-op - } - } - } - } -} - -// ============================================================================= -// applyEffect -// ============================================================================= - -fn apply_effect( - context: &mut Context, - state: &mut InferenceState, - effect: AliasingEffect, - initialized: &mut HashSet<IdentifierId>, - effects: &mut Vec<AliasingEffect>, - env: &mut Environment, - func: &HirFunction, -) -> Result<(), CompilerDiagnostic> { - let effect = context.intern_effect(effect); - match effect { - AliasingEffect::Freeze { ref value, reason } => { - let did_freeze = state.freeze(value.identifier, reason); - if did_freeze { - effects.push(effect.clone()); - // Transitively freeze FunctionExpression captures if enabled - // (matches TS freezeValue which recurses into func.context) - let enable_transitive = env.config.enable_preserve_existing_memoization_guarantees - || env.config.enable_transitively_freeze_function_expressions; - if enable_transitive { - // Recursively freeze through function captures. The TS - // freezeValue() calls freeze() on each capture, which - // calls freezeValue() again — creating a transitive - // closure through arbitrarily nested function captures. - let value_ids: Vec<ValueId> = state.values_for(value.identifier); - for vid in &value_ids { - freeze_function_captures_transitive(state, context, env, *vid, reason); - } - } - } - } - AliasingEffect::Create { - ref into, - value: kind, - reason, - } => { - assert!( - !initialized.contains(&into.identifier), - "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction" - ); - initialized.insert(into.identifier); - let value_id = context.get_or_create_value_id(&effect); - state.initialize( - value_id, - AbstractValue { - kind, - reason: hashset_of(reason), - }, - ); - state.define(into.identifier, value_id); - effects.push(effect.clone()); - } - AliasingEffect::ImmutableCapture { ref from, .. } => { - let kind = state.kind(from.identifier).kind; - match kind { - ValueKind::Global | ValueKind::Primitive => { - // no-op: don't track data flow for copy types - } - _ => { - effects.push(effect.clone()); - } - } - } - AliasingEffect::CreateFrom { ref from, ref into } => { - assert!( - !initialized.contains(&into.identifier), - "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction" - ); - initialized.insert(into.identifier); - let from_value = state.kind(from.identifier); - let value_id = context.get_or_create_value_id(&effect); - state.initialize( - value_id, - AbstractValue { - kind: from_value.kind, - reason: from_value.reason.clone(), - }, - ); - state.define(into.identifier, value_id); - match from_value.kind { - ValueKind::Primitive | ValueKind::Global => { - let first_reason = primary_reason(&from_value.reason); - effects.push(AliasingEffect::Create { - value: from_value.kind, - into: into.clone(), - reason: first_reason, - }); - } - ValueKind::Frozen => { - let first_reason = primary_reason(&from_value.reason); - effects.push(AliasingEffect::Create { - value: from_value.kind, - into: into.clone(), - reason: first_reason, - }); - apply_effect( - context, - state, - AliasingEffect::ImmutableCapture { - from: from.clone(), - into: into.clone(), - }, - initialized, - effects, - env, - func, - )?; - } - _ => { - effects.push(effect.clone()); - } - } - } - AliasingEffect::CreateFunction { - ref captures, - function_id, - ref into, - } => { - assert!( - !initialized.contains(&into.identifier), - "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction" - ); - initialized.insert(into.identifier); - effects.push(effect.clone()); - - // Check if function is mutable - let has_captures = captures.iter().any(|capture| { - if !state.is_defined(capture.identifier) { - return false; - } - let k = state.kind(capture.identifier).kind; - k == ValueKind::Context || k == ValueKind::Mutable - }); - - let inner_func = &env.functions[function_id.0 as usize]; - let has_tracked_side_effects = inner_func - .aliasing_effects - .as_ref() - .map(|effs| { - effs.iter().any(|e| { - matches!( - e, - AliasingEffect::MutateFrozen { .. } - | AliasingEffect::MutateGlobal { .. } - | AliasingEffect::Impure { .. } - ) - }) - }) - .unwrap_or(false); - - let captures_ref = inner_func - .context - .iter() - .any(|operand| is_ref_or_ref_value_for_id(env, operand.identifier)); - - let is_mutable = has_captures || has_tracked_side_effects || captures_ref; - - // Update context variable effects - let context_places: Vec<Place> = inner_func.context.clone(); - for operand in &context_places { - if operand.effect != Effect::Capture { - continue; - } - if !state.is_defined(operand.identifier) { - continue; - } - let kind = state.kind(operand.identifier).kind; - if kind == ValueKind::Primitive - || kind == ValueKind::Frozen - || kind == ValueKind::Global - { - // Downgrade to Read - we need to mutate the inner function - let inner_func_mut = &mut env.functions[function_id.0 as usize]; - for ctx in &mut inner_func_mut.context { - if ctx.identifier == operand.identifier && ctx.effect == Effect::Capture { - ctx.effect = Effect::Read; - } - } - } - } - - let value_id = context.get_or_create_value_id(&effect); - // Track this value as a function expression so Apply can look it up - context.function_values.insert(value_id, function_id); - state.initialize( - value_id, - AbstractValue { - kind: if is_mutable { - ValueKind::Mutable - } else { - ValueKind::Frozen - }, - reason: IndexSet::new(), - }, - ); - state.define(into.identifier, value_id); - - for capture in captures { - apply_effect( - context, - state, - AliasingEffect::Capture { - from: capture.clone(), - into: into.clone(), - }, - initialized, - effects, - env, - func, - )?; - } - } - AliasingEffect::MaybeAlias { ref from, ref into } - | AliasingEffect::Alias { ref from, ref into } - | AliasingEffect::Capture { ref from, ref into } => { - let is_capture = matches!(effect, AliasingEffect::Capture { .. }); - let is_maybe_alias = matches!(effect, AliasingEffect::MaybeAlias { .. }); - // For Alias, destination must already be initialized (Capture/MaybeAlias are exempt) - assert!( - is_capture || is_maybe_alias || initialized.contains(&into.identifier), - "[InferMutationAliasingEffects] Expected destination to already be initialized within this instruction" - ); - - // Check destination kind - let into_kind = state.kind_with_loc(into.identifier, into.loc).kind; - let destination_type = match into_kind { - ValueKind::Context => Some("context"), - ValueKind::Mutable | ValueKind::MaybeFrozen => Some("mutable"), - _ => None, - }; - - let from_kind = state.kind_with_loc(from.identifier, from.loc).kind; - let source_type = match from_kind { - ValueKind::Context => Some("context"), - ValueKind::Global | ValueKind::Primitive => None, - ValueKind::MaybeFrozen | ValueKind::Frozen => Some("frozen"), - ValueKind::Mutable => Some("mutable"), - }; - - if source_type == Some("frozen") { - apply_effect( - context, - state, - AliasingEffect::ImmutableCapture { - from: from.clone(), - into: into.clone(), - }, - initialized, - effects, - env, - func, - )?; - } else if (source_type == Some("mutable") && destination_type == Some("mutable")) - || is_maybe_alias - { - effects.push(effect.clone()); - } else if (source_type == Some("context") && destination_type.is_some()) - || (source_type == Some("mutable") && destination_type == Some("context")) - { - apply_effect( - context, - state, - AliasingEffect::MaybeAlias { - from: from.clone(), - into: into.clone(), - }, - initialized, - effects, - env, - func, - )?; - } - } - AliasingEffect::Assign { ref from, ref into } => { - assert!( - !initialized.contains(&into.identifier), - "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction" - ); - initialized.insert(into.identifier); - let from_value = state.kind_with_loc(from.identifier, from.loc); - match from_value.kind { - ValueKind::Frozen => { - apply_effect( - context, - state, - AliasingEffect::ImmutableCapture { - from: from.clone(), - into: into.clone(), - }, - initialized, - effects, - env, - func, - )?; - let cache_key = - format!("Assign_frozen:{}:{}", from.identifier.0, into.identifier.0); - let value_id = *context - .effect_value_id_cache - .entry(cache_key) - .or_insert_with(ValueId::new); - state.initialize( - value_id, - AbstractValue { - kind: from_value.kind, - reason: from_value.reason.clone(), - }, - ); - state.define(into.identifier, value_id); - } - ValueKind::Global | ValueKind::Primitive => { - let cache_key = - format!("Assign_copy:{}:{}", from.identifier.0, into.identifier.0); - let value_id = *context - .effect_value_id_cache - .entry(cache_key) - .or_insert_with(ValueId::new); - state.initialize( - value_id, - AbstractValue { - kind: from_value.kind, - reason: from_value.reason.clone(), - }, - ); - state.define(into.identifier, value_id); - } - _ => { - state.assign(into.identifier, from.identifier); - effects.push(effect.clone()); - } - } - } - AliasingEffect::Apply { - ref receiver, - ref function, - mutates_function, - ref args, - ref into, - ref signature, - ref loc, - } => { - // First, check if the callee is a locally-declared function expression - // whose aliasing effects we already know (TS lines 1016-1068) - if state.is_defined(function.identifier) { - let function_values = state.values_for(function.identifier); - if function_values.len() == 1 { - let value_id = function_values[0]; - if let Some(func_id) = context.function_values.get(&value_id).copied() { - let inner_func = &env.functions[func_id.0 as usize]; - if inner_func.aliasing_effects.is_some() { - // Build or retrieve the signature from the function expression - if !context.function_signature_cache.contains_key(&func_id) { - let sig = build_signature_from_function_expression(env, func_id); - context.function_signature_cache.insert(func_id, sig); - } - let sig = context - .function_signature_cache - .get(&func_id) - .unwrap() - .clone(); - let inner_func = &env.functions[func_id.0 as usize]; - let context_places: Vec<Place> = inner_func.context.clone(); - let sig_effects = compute_effects_for_aliasing_signature( - env, - &sig, - into, - receiver, - args, - &context_places, - loc.as_ref(), - )?; - if let Some(sig_effs) = sig_effects { - // Conditionally mutate the function itself first - apply_effect( - context, - state, - AliasingEffect::MutateTransitiveConditionally { - value: function.clone(), - }, - initialized, - effects, - env, - func, - )?; - for se in sig_effs { - apply_effect( - context, - state, - se, - initialized, - effects, - env, - func, - )?; - } - return Ok(()); - } - } - } - } - } - if let Some(sig) = signature { - // Check known_incompatible (TS line 2351-2370) - if let Some(ref incompatible_msg) = sig.known_incompatible { - if env.enable_validations() { - let mut diagnostic = CompilerDiagnostic::new( - ErrorCategory::IncompatibleLibrary, - "Use of incompatible library", - Some( - "This API returns functions which cannot be memoized without leading to stale UI. \ - To prevent this, by default React Compiler will skip memoizing this component/hook. \ - However, you may see issues if values from this API are passed to other components/hooks that are \ - memoized".to_string(), - ), - ); - diagnostic.details.push(CompilerDiagnosticDetail::Error { - loc: receiver.loc, - message: Some(incompatible_msg.clone()), - identifier_name: None, - }); - // TS throws here, aborting compilation for this function - return Err(diagnostic); - } - } - - if let Some(ref aliasing) = sig.aliasing { - let sig_effects = compute_effects_for_aliasing_signature_config( - env, - aliasing, - into, - receiver, - args, - &[], - loc.as_ref(), - &mut context.aliasing_config_temp_cache, - )?; - if let Some(sig_effs) = sig_effects { - for se in sig_effs { - apply_effect(context, state, se, initialized, effects, env, func)?; - } - return Ok(()); - } - } - - // Legacy signature - let mut todo_errors: Vec<react_compiler_diagnostics::CompilerErrorDetail> = - Vec::new(); - let legacy_effects = compute_effects_for_legacy_signature( - state, - sig, - into, - receiver, - args, - loc.as_ref(), - env, - &context.function_values, - &mut todo_errors, - ); - // Todo errors should short-circuit (TS throws throwTodo) - if let Some(err_detail) = todo_errors.into_iter().next() { - return Err(CompilerDiagnostic::from_detail(err_detail)); - } - for le in legacy_effects { - apply_effect(context, state, le, initialized, effects, env, func)?; - } - } else { - // No signature: default behavior - apply_effect( - context, - state, - AliasingEffect::Create { - into: into.clone(), - value: ValueKind::Mutable, - reason: ValueReason::Other, - }, - initialized, - effects, - env, - func, - )?; - - let all_operands = build_apply_operands(receiver, function, args); - for (operand, _is_function_operand, is_spread) in &all_operands { - // In TS, the check is `operand !== effect.function || effect.mutatesFunction`. - // This compares by reference identity, so for CallExpression/NewExpression - // where receiver === function, BOTH are skipped when !mutatesFunction. - if operand.identifier == function.identifier && !mutates_function { - // Don't mutate callee for non-mutating calls - } else { - apply_effect( - context, - state, - AliasingEffect::MutateTransitiveConditionally { - value: operand.clone(), - }, - initialized, - effects, - env, - func, - )?; - } - - if *is_spread { - let ty = &env.types - [env.identifiers[operand.identifier.0 as usize].type_.0 as usize]; - if let Some(mutate_iter) = conditionally_mutate_iterator(operand, ty) { - apply_effect( - context, - state, - mutate_iter, - initialized, - effects, - env, - func, - )?; - } - } - - apply_effect( - context, - state, - AliasingEffect::MaybeAlias { - from: operand.clone(), - into: into.clone(), - }, - initialized, - effects, - env, - func, - )?; - - // In TS, `other === arg` compares the Place extracted from - // `otherArg` with the original `arg` element. For Identifier - // args, the extracted Place IS the arg, so this is a reference - // identity check. For Spread args, the extracted Place is - // `.place` which is never `===` the Spread wrapper object, - // so NO pairs are skipped when the outer arg is a Spread - // (including self-pairs, producing self-captures). - for (other, _other_is_func, _other_is_spread) in &all_operands { - if !is_spread && other.identifier == operand.identifier { - continue; - } - apply_effect( - context, - state, - AliasingEffect::Capture { - from: operand.clone(), - into: other.clone(), - }, - initialized, - effects, - env, - func, - )?; - } - } - } - } - ref eff @ (AliasingEffect::Mutate { .. } - | AliasingEffect::MutateConditionally { .. } - | AliasingEffect::MutateTransitive { .. } - | AliasingEffect::MutateTransitiveConditionally { .. }) => { - let (mutate_place, variant) = match eff { - AliasingEffect::Mutate { value, .. } => (value, MutateVariant::Mutate), - AliasingEffect::MutateConditionally { value } => { - (value, MutateVariant::MutateConditionally) - } - AliasingEffect::MutateTransitive { value } => { - (value, MutateVariant::MutateTransitive) - } - AliasingEffect::MutateTransitiveConditionally { value } => { - (value, MutateVariant::MutateTransitiveConditionally) - } - _ => unreachable!(), - }; - let value = mutate_place; - let mutation_kind = state.mutate_with_loc(variant, value.identifier, env, value.loc); - if mutation_kind == MutationResult::Mutate { - effects.push(effect.clone()); - } else if mutation_kind == MutationResult::MutateRef { - // no-op - } else if mutation_kind != MutationResult::None - && matches!( - variant, - MutateVariant::Mutate | MutateVariant::MutateTransitive - ) - { - let abstract_value = state.kind(value.identifier); - - let ident = &env.identifiers[value.identifier.0 as usize]; - let decl_id = ident.declaration_id; - - if mutation_kind == MutationResult::MutateFrozen - && context.hoisted_context_declarations.contains_key(&decl_id) - { - let variable = match &ident.name { - Some(react_compiler_hir::IdentifierName::Named(n)) => { - Some(format!("`{}`", n)) - } - _ => None, - }; - let hoisted_access = context - .hoisted_context_declarations - .get(&decl_id) - .cloned() - .flatten(); - let mut diagnostic = CompilerDiagnostic::new( - ErrorCategory::Immutability, - "Cannot access variable before it is declared", - Some(format!( - "{} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time", - variable.as_deref().unwrap_or("This variable") - )), - ); - if let Some(ref access) = hoisted_access { - if access.loc != value.loc { - diagnostic.details.push( - react_compiler_diagnostics::CompilerDiagnosticDetail::Error { - loc: access.loc, - message: Some(format!( - "{} accessed before it is declared", - variable.as_deref().unwrap_or("variable") - )), - identifier_name: None, - }, - ); - } - } - diagnostic.details.push( - react_compiler_diagnostics::CompilerDiagnosticDetail::Error { - loc: value.loc, - message: Some(format!( - "{} is declared here", - variable.as_deref().unwrap_or("variable") - )), - identifier_name: None, - }, - ); - apply_effect( - context, - state, - AliasingEffect::MutateFrozen { - place: value.clone(), - error: diagnostic, - }, - initialized, - effects, - env, - func, - )?; - } else { - let reason_str = get_write_error_reason(&abstract_value); - let variable = match &ident.name { - Some(react_compiler_hir::IdentifierName::Named(n)) => format!("`{}`", n), - _ => "value".to_string(), - }; - let mut diagnostic = CompilerDiagnostic::new( - ErrorCategory::Immutability, - "This value cannot be modified", - Some(reason_str), - ); - diagnostic.details.push( - react_compiler_diagnostics::CompilerDiagnosticDetail::Error { - loc: value.loc, - message: Some(format!("{} cannot be modified", variable)), - identifier_name: None, - }, - ); - - if let AliasingEffect::Mutate { - reason: Some(MutationReason::AssignCurrentProperty), - .. - } = &effect - { - diagnostic.details.push(react_compiler_diagnostics::CompilerDiagnosticDetail::Hint { - message: "Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in \"Ref\".".to_string(), - }); - } - - let error_kind = if abstract_value.kind == ValueKind::Frozen { - AliasingEffect::MutateFrozen { - place: value.clone(), - error: diagnostic, - } - } else { - AliasingEffect::MutateGlobal { - place: value.clone(), - error: diagnostic, - } - }; - apply_effect(context, state, error_kind, initialized, effects, env, func)?; - } - } - } - AliasingEffect::Impure { .. } - | AliasingEffect::Render { .. } - | AliasingEffect::MutateFrozen { .. } - | AliasingEffect::MutateGlobal { .. } => { - effects.push(effect.clone()); - } - } - Ok(()) -} - -// ============================================================================= -// computeSignatureForInstruction -// ============================================================================= - -fn compute_signature_for_instruction( - context: &mut Context, - env: &Environment, - instr: &react_compiler_hir::Instruction, - _func: &HirFunction, -) -> InstructionSignature { - let lvalue = &instr.lvalue; - let value = &instr.value; - let mut effects: Vec<AliasingEffect> = Vec::new(); - - match value { - InstructionValue::ArrayExpression { elements, .. } => { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Mutable, - reason: ValueReason::Other, - }); - for element in elements { - match element { - react_compiler_hir::ArrayElement::Place(p) => { - effects.push(AliasingEffect::Capture { - from: p.clone(), - into: lvalue.clone(), - }); - } - react_compiler_hir::ArrayElement::Spread(s) => { - let ty = &env.types - [env.identifiers[s.place.identifier.0 as usize].type_.0 as usize]; - if let Some(mutate_iter) = conditionally_mutate_iterator(&s.place, ty) { - effects.push(mutate_iter); - } - effects.push(AliasingEffect::Capture { - from: s.place.clone(), - into: lvalue.clone(), - }); - } - react_compiler_hir::ArrayElement::Hole => {} - } - } - } - InstructionValue::ObjectExpression { properties, .. } => { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Mutable, - reason: ValueReason::Other, - }); - for property in properties { - match property { - react_compiler_hir::ObjectPropertyOrSpread::Property(p) => { - effects.push(AliasingEffect::Capture { - from: p.place.clone(), - into: lvalue.clone(), - }); - } - react_compiler_hir::ObjectPropertyOrSpread::Spread(s) => { - effects.push(AliasingEffect::Capture { - from: s.place.clone(), - into: lvalue.clone(), - }); - } - } - } - } - InstructionValue::Await { - value: await_value, .. - } => { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Mutable, - reason: ValueReason::Other, - }); - effects.push(AliasingEffect::MutateTransitiveConditionally { - value: await_value.clone(), - }); - effects.push(AliasingEffect::Capture { - from: await_value.clone(), - into: lvalue.clone(), - }); - } - InstructionValue::NewExpression { callee, args, loc } => { - let sig = get_function_call_signature(env, callee.identifier) - .ok() - .flatten(); - effects.push(AliasingEffect::Apply { - receiver: callee.clone(), - function: callee.clone(), - mutates_function: false, - args: args.iter().map(place_or_spread_to_hole).collect(), - into: lvalue.clone(), - signature: sig, - loc: *loc, - }); - } - InstructionValue::CallExpression { callee, args, loc } => { - let sig = get_function_call_signature(env, callee.identifier) - .ok() - .flatten(); - effects.push(AliasingEffect::Apply { - receiver: callee.clone(), - function: callee.clone(), - mutates_function: true, - args: args.iter().map(place_or_spread_to_hole).collect(), - into: lvalue.clone(), - signature: sig, - loc: *loc, - }); - } - InstructionValue::MethodCall { - receiver, - property, - args, - loc, - } => { - let sig = get_function_call_signature(env, property.identifier) - .ok() - .flatten(); - effects.push(AliasingEffect::Apply { - receiver: receiver.clone(), - function: property.clone(), - mutates_function: false, - args: args.iter().map(place_or_spread_to_hole).collect(), - into: lvalue.clone(), - signature: sig, - loc: *loc, - }); - } - InstructionValue::PropertyDelete { object, .. } - | InstructionValue::ComputedDelete { object, .. } => { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - effects.push(AliasingEffect::Mutate { - value: object.clone(), - reason: None, - }); - } - InstructionValue::PropertyLoad { object, .. } - | InstructionValue::ComputedLoad { object, .. } => { - let ty = &env.types[env.identifiers[lvalue.identifier.0 as usize].type_.0 as usize]; - if react_compiler_hir::is_primitive_type(ty) { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - } else { - effects.push(AliasingEffect::CreateFrom { - from: object.clone(), - into: lvalue.clone(), - }); - } - } - InstructionValue::PropertyStore { - object, - property, - value: store_value, - .. - } => { - let mutation_reason: Option<MutationReason> = { - let obj_ty = - &env.types[env.identifiers[object.identifier.0 as usize].type_.0 as usize]; - if let react_compiler_hir::PropertyLiteral::String(prop_name) = property { - if prop_name == "current" && matches!(obj_ty, Type::TypeVar { .. }) { - Some(MutationReason::AssignCurrentProperty) - } else { - None - } - } else { - None - } - }; - effects.push(AliasingEffect::Mutate { - value: object.clone(), - reason: mutation_reason, - }); - effects.push(AliasingEffect::Capture { - from: store_value.clone(), - into: object.clone(), - }); - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - } - InstructionValue::ComputedStore { - object, - value: store_value, - .. - } => { - effects.push(AliasingEffect::Mutate { - value: object.clone(), - reason: None, - }); - effects.push(AliasingEffect::Capture { - from: store_value.clone(), - into: object.clone(), - }); - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - } - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - let inner_func = &env.functions[lowered_func.func.0 as usize]; - let captures: Vec<Place> = inner_func - .context - .iter() - .filter(|operand| operand.effect == Effect::Capture) - .cloned() - .collect(); - effects.push(AliasingEffect::CreateFunction { - into: lvalue.clone(), - function_id: lowered_func.func, - captures, - }); - } - InstructionValue::GetIterator { collection, .. } => { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Mutable, - reason: ValueReason::Other, - }); - let ty = &env.types[env.identifiers[collection.identifier.0 as usize].type_.0 as usize]; - if is_builtin_collection_type(ty) { - effects.push(AliasingEffect::Capture { - from: collection.clone(), - into: lvalue.clone(), - }); - } else { - effects.push(AliasingEffect::Alias { - from: collection.clone(), - into: lvalue.clone(), - }); - effects.push(AliasingEffect::MutateTransitiveConditionally { - value: collection.clone(), - }); - } - } - InstructionValue::IteratorNext { - iterator, - collection, - .. - } => { - effects.push(AliasingEffect::MutateConditionally { - value: iterator.clone(), - }); - effects.push(AliasingEffect::CreateFrom { - from: collection.clone(), - into: lvalue.clone(), - }); - } - InstructionValue::NextPropertyOf { .. } => { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - } - InstructionValue::JsxExpression { - tag, - props, - children, - .. - } => { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Frozen, - reason: ValueReason::JsxCaptured, - }); - for operand in visitors::each_instruction_value_operand(value, env) { - effects.push(AliasingEffect::Freeze { - value: operand.clone(), - reason: ValueReason::JsxCaptured, - }); - effects.push(AliasingEffect::Capture { - from: operand.clone(), - into: lvalue.clone(), - }); - } - if let JsxTag::Place(tag_place) = tag { - effects.push(AliasingEffect::Render { - place: tag_place.clone(), - }); - } - if let Some(ch) = children { - for child in ch { - effects.push(AliasingEffect::Render { - place: child.clone(), - }); - } - } - for prop in props { - if let react_compiler_hir::JsxAttribute::Attribute { - place: prop_place, .. - } = prop - { - let prop_ty = &env.types - [env.identifiers[prop_place.identifier.0 as usize].type_.0 as usize]; - if let Type::Function { return_type, .. } = prop_ty { - if react_compiler_hir::is_jsx_type(return_type) - || is_phi_with_jsx(return_type) - { - effects.push(AliasingEffect::Render { - place: prop_place.clone(), - }); - } - } - } - } - } - InstructionValue::JsxFragment { children: _, .. } => { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Frozen, - reason: ValueReason::JsxCaptured, - }); - for operand in visitors::each_instruction_value_operand(value, env) { - effects.push(AliasingEffect::Freeze { - value: operand.clone(), - reason: ValueReason::JsxCaptured, - }); - effects.push(AliasingEffect::Capture { - from: operand.clone(), - into: lvalue.clone(), - }); - } - } - InstructionValue::DeclareLocal { lvalue: dl, .. } => { - effects.push(AliasingEffect::Create { - into: dl.place.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - } - InstructionValue::Destructure { - lvalue: dl, - value: dest_value, - .. - } => { - for pat_item in each_pattern_items(&dl.pattern) { - match pat_item { - PatternItem::Place(place) => { - let ty = &env.types - [env.identifiers[place.identifier.0 as usize].type_.0 as usize]; - if react_compiler_hir::is_primitive_type(ty) { - effects.push(AliasingEffect::Create { - into: place.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - } else { - effects.push(AliasingEffect::CreateFrom { - from: dest_value.clone(), - into: place.clone(), - }); - } - } - PatternItem::Spread(place) => { - let value_kind = if context.non_mutating_spreads.contains(&place.identifier) - { - ValueKind::Frozen - } else { - ValueKind::Mutable - }; - effects.push(AliasingEffect::Create { - into: place.clone(), - reason: ValueReason::Other, - value: value_kind, - }); - effects.push(AliasingEffect::Capture { - from: dest_value.clone(), - into: place.clone(), - }); - } - } - } - effects.push(AliasingEffect::Assign { - from: dest_value.clone(), - into: lvalue.clone(), - }); - } - InstructionValue::LoadContext { place, .. } => { - effects.push(AliasingEffect::CreateFrom { - from: place.clone(), - into: lvalue.clone(), - }); - } - InstructionValue::DeclareContext { lvalue: dcl, .. } => { - let decl_id = env.identifiers[dcl.place.identifier.0 as usize].declaration_id; - let kind = dcl.kind; - if !context.hoisted_context_declarations.contains_key(&decl_id) - || kind == InstructionKind::HoistedConst - || kind == InstructionKind::HoistedFunction - || kind == InstructionKind::HoistedLet - { - effects.push(AliasingEffect::Create { - into: dcl.place.clone(), - value: ValueKind::Mutable, - reason: ValueReason::Other, - }); - } else { - effects.push(AliasingEffect::Mutate { - value: dcl.place.clone(), - reason: None, - }); - } - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - } - InstructionValue::StoreContext { - lvalue: scl, - value: sc_value, - .. - } => { - let decl_id = env.identifiers[scl.place.identifier.0 as usize].declaration_id; - if scl.kind == InstructionKind::Reassign - || context.hoisted_context_declarations.contains_key(&decl_id) - { - effects.push(AliasingEffect::Mutate { - value: scl.place.clone(), - reason: None, - }); - } else { - effects.push(AliasingEffect::Create { - into: scl.place.clone(), - value: ValueKind::Mutable, - reason: ValueReason::Other, - }); - } - effects.push(AliasingEffect::Capture { - from: sc_value.clone(), - into: scl.place.clone(), - }); - effects.push(AliasingEffect::Assign { - from: sc_value.clone(), - into: lvalue.clone(), - }); - } - InstructionValue::LoadLocal { place, .. } => { - effects.push(AliasingEffect::Assign { - from: place.clone(), - into: lvalue.clone(), - }); - } - InstructionValue::StoreLocal { - lvalue: sl, - value: sl_value, - .. - } => { - effects.push(AliasingEffect::Assign { - from: sl_value.clone(), - into: sl.place.clone(), - }); - effects.push(AliasingEffect::Assign { - from: sl_value.clone(), - into: lvalue.clone(), - }); - } - InstructionValue::PostfixUpdate { - lvalue: pf_lvalue, .. - } - | InstructionValue::PrefixUpdate { - lvalue: pf_lvalue, .. - } => { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - effects.push(AliasingEffect::Create { - into: pf_lvalue.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - } - InstructionValue::StoreGlobal { - name, - value: sg_value, - loc: _, - .. - } => { - let variable = format!("`{}`", name); - let mut diagnostic = CompilerDiagnostic::new( - ErrorCategory::Globals, - "Cannot reassign variables declared outside of the component/hook", - Some(format!( - "Variable {} is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)", - variable - )), - ); - diagnostic.details.push( - react_compiler_diagnostics::CompilerDiagnosticDetail::Error { - loc: instr.loc, - message: Some(format!("{} cannot be reassigned", variable)), - identifier_name: None, - }, - ); - effects.push(AliasingEffect::MutateGlobal { - place: sg_value.clone(), - error: diagnostic, - }); - effects.push(AliasingEffect::Assign { - from: sg_value.clone(), - into: lvalue.clone(), - }); - } - InstructionValue::TypeCastExpression { - value: tc_value, .. - } => { - effects.push(AliasingEffect::Assign { - from: tc_value.clone(), - into: lvalue.clone(), - }); - } - InstructionValue::LoadGlobal { .. } => { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Global, - reason: ValueReason::Global, - }); - } - InstructionValue::StartMemoize { .. } | InstructionValue::FinishMemoize { .. } => { - if env.config.enable_preserve_existing_memoization_guarantees { - for operand in visitors::each_instruction_value_operand(value, env) { - effects.push(AliasingEffect::Freeze { - value: operand.clone(), - reason: ValueReason::HookCaptured, - }); - } - } - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - } - // All primitive-creating instructions - InstructionValue::TaggedTemplateExpression { .. } - | InstructionValue::BinaryExpression { .. } - | InstructionValue::Debugger { .. } - | InstructionValue::JSXText { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::Primitive { .. } - | InstructionValue::RegExpLiteral { .. } - | InstructionValue::TemplateLiteral { .. } - | InstructionValue::UnaryExpression { .. } - | InstructionValue::UnsupportedNode { .. } => { - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: ValueKind::Primitive, - reason: ValueReason::Other, - }); - } - } - - InstructionSignature { effects } -} - -// ============================================================================= -// Legacy signature support -// ============================================================================= - -fn compute_effects_for_legacy_signature( - state: &InferenceState, - signature: &FunctionSignature, - lvalue: &Place, - receiver: &Place, - args: &[PlaceOrSpreadOrHole], - _loc: Option<&SourceLocation>, - env: &Environment, - function_values: &HashMap<ValueId, FunctionId>, - todo_errors: &mut Vec<react_compiler_diagnostics::CompilerErrorDetail>, -) -> Vec<AliasingEffect> { - let return_value_reason = signature.return_value_reason.unwrap_or(ValueReason::Other); - let mut effects: Vec<AliasingEffect> = Vec::new(); - - effects.push(AliasingEffect::Create { - into: lvalue.clone(), - value: signature.return_value_kind, - reason: return_value_reason, - }); - - if signature.impure && env.config.validate_no_impure_functions_in_render { - let mut diagnostic = CompilerDiagnostic::new( - ErrorCategory::Purity, - "Cannot call impure function during render", - Some(format!( - "{}Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)", - if let Some(ref name) = signature.canonical_name { - format!("`{}` is an impure function. ", name) - } else { - String::new() - } - )), - ); - diagnostic.details.push( - react_compiler_diagnostics::CompilerDiagnosticDetail::Error { - loc: _loc.copied(), - message: Some("Cannot call impure function".to_string()), - identifier_name: None, - }, - ); - effects.push(AliasingEffect::Impure { - place: receiver.clone(), - error: diagnostic, - }); - } - - // TODO: check signature.known_incompatible and throw (TS line 2351-2370) - // This requires threading Result through apply_effect/apply_signature. - - // If the function is mutable only if operands are mutable, and all - // arguments are immutable/non-mutating, short-circuit with simple aliasing. - if signature.mutable_only_if_operands_are_mutable - && are_arguments_immutable_and_non_mutating(state, args, env, function_values) - { - effects.push(AliasingEffect::Alias { - from: receiver.clone(), - into: lvalue.clone(), - }); - for arg in args { - match arg { - PlaceOrSpreadOrHole::Hole => continue, - PlaceOrSpreadOrHole::Place(place) - | PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place }) => { - effects.push(AliasingEffect::ImmutableCapture { - from: place.clone(), - into: lvalue.clone(), - }); - } - } - } - return effects; - } - - let mut stores: Vec<Place> = Vec::new(); - let mut captures: Vec<Place> = Vec::new(); - - let mut visit = |place: &Place, effect: Effect, effects: &mut Vec<AliasingEffect>| match effect - { - Effect::Store => { - effects.push(AliasingEffect::Mutate { - value: place.clone(), - reason: None, - }); - stores.push(place.clone()); - } - Effect::Capture => { - captures.push(place.clone()); - } - Effect::ConditionallyMutate => { - effects.push(AliasingEffect::MutateTransitiveConditionally { - value: place.clone(), - }); - } - Effect::ConditionallyMutateIterator => { - let ty = &env.types[env.identifiers[place.identifier.0 as usize].type_.0 as usize]; - if let Some(mutate_iter) = conditionally_mutate_iterator(place, ty) { - effects.push(mutate_iter); - } - effects.push(AliasingEffect::Capture { - from: place.clone(), - into: lvalue.clone(), - }); - } - Effect::Freeze => { - effects.push(AliasingEffect::Freeze { - value: place.clone(), - reason: return_value_reason, - }); - } - Effect::Mutate => { - effects.push(AliasingEffect::MutateTransitive { - value: place.clone(), - }); - } - Effect::Read => { - effects.push(AliasingEffect::ImmutableCapture { - from: place.clone(), - into: lvalue.clone(), - }); - } - _ => {} - }; - - if signature.callee_effect != Effect::Capture { - effects.push(AliasingEffect::Alias { - from: receiver.clone(), - into: lvalue.clone(), - }); - } - - visit(receiver, signature.callee_effect, &mut effects); - for (i, arg) in args.iter().enumerate() { - match arg { - PlaceOrSpreadOrHole::Hole => continue, - PlaceOrSpreadOrHole::Place(place) - | PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place }) => { - let is_spread = matches!(arg, PlaceOrSpreadOrHole::Spread(_)); - let sig_effect = if !is_spread && i < signature.positional_params.len() { - signature.positional_params[i] - } else { - signature.rest_param.unwrap_or(Effect::ConditionallyMutate) - }; - let (effect, err_detail) = get_argument_effect(sig_effect, is_spread, place.loc); - if let Some(d) = err_detail { - todo_errors.push(d); - } - visit(place, effect, &mut effects); - } - } - } - - if !captures.is_empty() { - if stores.is_empty() { - for capture in &captures { - effects.push(AliasingEffect::Alias { - from: capture.clone(), - into: lvalue.clone(), - }); - } - } else { - for capture in &captures { - for store in &stores { - effects.push(AliasingEffect::Capture { - from: capture.clone(), - into: store.clone(), - }); - } - } - } - } - - effects -} - -fn get_argument_effect( - sig_effect: Effect, - is_spread: bool, - spread_loc: Option<SourceLocation>, -) -> ( - Effect, - Option<react_compiler_diagnostics::CompilerErrorDetail>, -) { - if !is_spread { - (sig_effect, None) - } else if sig_effect == Effect::Mutate || sig_effect == Effect::ConditionallyMutate { - (sig_effect, None) - } else { - // Spread with Freeze effect is unsupported for hook arguments - // (matches TS CompilerError.throwTodo) - let detail = if sig_effect == Effect::Freeze { - Some(react_compiler_diagnostics::CompilerErrorDetail { - reason: "Support spread syntax for hook arguments".to_string(), - description: None, - category: ErrorCategory::Todo, - loc: spread_loc, - suggestions: None, - }) - } else { - None - }; - (Effect::ConditionallyMutateIterator, detail) - } -} - -/// Returns true if all of the arguments are both non-mutable (immutable or frozen) -/// _and_ are not functions which might mutate their arguments. -/// -/// Corresponds to TS `areArgumentsImmutableAndNonMutating`. -fn are_arguments_immutable_and_non_mutating( - state: &InferenceState, - args: &[PlaceOrSpreadOrHole], - env: &Environment, - function_values: &HashMap<ValueId, FunctionId>, -) -> bool { - for arg in args { - match arg { - PlaceOrSpreadOrHole::Hole => continue, - PlaceOrSpreadOrHole::Place(place) - | PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place }) => { - // Check if it's a function type with a known signature - let is_place = matches!(arg, PlaceOrSpreadOrHole::Place(_)); - if is_place { - let ty = - &env.types[env.identifiers[place.identifier.0 as usize].type_.0 as usize]; - if let Type::Function { .. } = ty { - let fn_shape = env.get_function_signature(ty).ok().flatten(); - if let Some(fn_sig) = fn_shape { - let has_mutable_param = fn_sig - .positional_params - .iter() - .any(|e| is_known_mutable_effect(*e)); - let has_mutable_rest = fn_sig - .rest_param - .map_or(false, |e| is_known_mutable_effect(e)); - return !has_mutable_param && !has_mutable_rest; - } - } - } - - let kind = state.kind(place.identifier); - match kind.kind { - ValueKind::Primitive | ValueKind::Frozen => { - // Immutable values are ok, continue checking - } - _ => { - return false; - } - } - - // Check if any value for this place is a function expression - // that mutates its parameters (TS lines 2545-2557) - let value_ids = state.values_for(place.identifier); - for vid in &value_ids { - if let Some(&func_id) = function_values.get(vid) { - let inner_func = &env.functions[func_id.0 as usize]; - let mutates_params = inner_func.params.iter().any(|param| { - let param_id = match param { - ParamPattern::Place(p) => p.identifier, - ParamPattern::Spread(s) => s.place.identifier, - }; - let ident = &env.identifiers[param_id.0 as usize]; - ident.mutable_range.end.0 > ident.mutable_range.start.0 + 1 - }); - if mutates_params { - return false; - } - } - } - } - } - } - true -} - -fn is_known_mutable_effect(effect: Effect) -> bool { - matches!( - effect, - Effect::Store - | Effect::Mutate - | Effect::ConditionallyMutate - | Effect::ConditionallyMutateIterator - ) -} - -// ============================================================================= -// Aliasing signature config support (new-style signatures) -// ============================================================================= - -fn compute_effects_for_aliasing_signature_config( - env: &mut Environment, - config: &react_compiler_hir::type_config::AliasingSignatureConfig, - lvalue: &Place, - receiver: &Place, - args: &[PlaceOrSpreadOrHole], - context: &[Place], - _loc: Option<&SourceLocation>, - temp_cache: &mut HashMap<(IdentifierId, String), Place>, -) -> Result<Option<Vec<AliasingEffect>>, CompilerDiagnostic> { - // Build substitutions from config strings to places - let mut substitutions: HashMap<String, Vec<Place>> = HashMap::new(); - substitutions.insert(config.receiver.clone(), vec![receiver.clone()]); - substitutions.insert(config.returns.clone(), vec![lvalue.clone()]); - - let mut mutable_spreads: HashSet<IdentifierId> = HashSet::new(); - - for (i, arg) in args.iter().enumerate() { - match arg { - PlaceOrSpreadOrHole::Hole => continue, - PlaceOrSpreadOrHole::Place(place) - | PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place }) => { - if i < config.params.len() && !matches!(arg, PlaceOrSpreadOrHole::Spread(_)) { - substitutions.insert(config.params[i].clone(), vec![place.clone()]); - } else if let Some(ref rest) = config.rest { - substitutions - .entry(rest.clone()) - .or_default() - .push(place.clone()); - } else { - return Ok(None); - } - - if matches!(arg, PlaceOrSpreadOrHole::Spread(_)) { - let ty = - &env.types[env.identifiers[place.identifier.0 as usize].type_.0 as usize]; - let mutate_iterator = conditionally_mutate_iterator(place, ty); - if mutate_iterator.is_some() { - mutable_spreads.insert(place.identifier); - } - } - } - } - } - - for operand in context { - let ident = &env.identifiers[operand.identifier.0 as usize]; - if let Some(ref name) = ident.name { - substitutions.insert(format!("@{}", name.value()), vec![operand.clone()]); - } - } - - // Create temporaries (cached by lvalue + temp_name to be stable across fixpoint iterations) - for temp_name in &config.temporaries { - let cache_key = (lvalue.identifier, temp_name.clone()); - let temp_place = temp_cache - .entry(cache_key) - .or_insert_with(|| create_temp_place(env, receiver.loc)) - .clone(); - substitutions.insert(temp_name.clone(), vec![temp_place]); - } - - let mut effects: Vec<AliasingEffect> = Vec::new(); - - for eff_config in &config.effects { - match eff_config { - react_compiler_hir::type_config::AliasingEffectConfig::Freeze { value, reason } => { - let values = substitutions.get(value).cloned().unwrap_or_default(); - for v in values { - if mutable_spreads.contains(&v.identifier) { - return Err(CompilerDiagnostic::todo( - "Support spread syntax for hook arguments", - v.loc, - )); - } - effects.push(AliasingEffect::Freeze { value: v, reason: *reason }); - } - } - react_compiler_hir::type_config::AliasingEffectConfig::Create { into, value, reason } => { - let intos = substitutions.get(into).cloned().unwrap_or_default(); - for v in intos { - effects.push(AliasingEffect::Create { into: v, value: *value, reason: *reason }); - } - } - react_compiler_hir::type_config::AliasingEffectConfig::CreateFrom { from, into } => { - let froms = substitutions.get(from).cloned().unwrap_or_default(); - let intos = substitutions.get(into).cloned().unwrap_or_default(); - for f in &froms { - for t in &intos { - effects.push(AliasingEffect::CreateFrom { from: f.clone(), into: t.clone() }); - } - } - } - react_compiler_hir::type_config::AliasingEffectConfig::Assign { from, into } => { - let froms = substitutions.get(from).cloned().unwrap_or_default(); - let intos = substitutions.get(into).cloned().unwrap_or_default(); - for f in &froms { - for t in &intos { - effects.push(AliasingEffect::Assign { from: f.clone(), into: t.clone() }); - } - } - } - react_compiler_hir::type_config::AliasingEffectConfig::Alias { from, into } => { - let froms = substitutions.get(from).cloned().unwrap_or_default(); - let intos = substitutions.get(into).cloned().unwrap_or_default(); - for f in &froms { - for t in &intos { - effects.push(AliasingEffect::Alias { from: f.clone(), into: t.clone() }); - } - } - } - react_compiler_hir::type_config::AliasingEffectConfig::Capture { from, into } => { - let froms = substitutions.get(from).cloned().unwrap_or_default(); - let intos = substitutions.get(into).cloned().unwrap_or_default(); - for f in &froms { - for t in &intos { - effects.push(AliasingEffect::Capture { from: f.clone(), into: t.clone() }); - } - } - } - react_compiler_hir::type_config::AliasingEffectConfig::ImmutableCapture { from, into } => { - let froms = substitutions.get(from).cloned().unwrap_or_default(); - let intos = substitutions.get(into).cloned().unwrap_or_default(); - for f in &froms { - for t in &intos { - effects.push(AliasingEffect::ImmutableCapture { from: f.clone(), into: t.clone() }); - } - } - } - react_compiler_hir::type_config::AliasingEffectConfig::Impure { place } => { - let values = substitutions.get(place).cloned().unwrap_or_default(); - for v in values { - effects.push(AliasingEffect::Impure { - place: v, - error: CompilerDiagnostic::new(ErrorCategory::Purity, "Impure function call", None), - }); - } - } - react_compiler_hir::type_config::AliasingEffectConfig::Mutate { value } => { - let values = substitutions.get(value).cloned().unwrap_or_default(); - for v in values { - effects.push(AliasingEffect::Mutate { value: v, reason: None }); - } - } - react_compiler_hir::type_config::AliasingEffectConfig::MutateTransitiveConditionally { value } => { - let values = substitutions.get(value).cloned().unwrap_or_default(); - for v in values { - effects.push(AliasingEffect::MutateTransitiveConditionally { value: v }); - } - } - react_compiler_hir::type_config::AliasingEffectConfig::Apply { receiver: r, function: f, mutates_function, args: a, into: i } => { - let recv = substitutions.get(r).and_then(|v| v.first()).cloned(); - let func = substitutions.get(f).and_then(|v| v.first()).cloned(); - let into = substitutions.get(i).and_then(|v| v.first()).cloned(); - if let (Some(recv), Some(func), Some(into)) = (recv, func, into) { - let mut apply_args: Vec<PlaceOrSpreadOrHole> = Vec::new(); - for arg in a { - match arg { - react_compiler_hir::type_config::ApplyArgConfig::Hole { .. } => { - apply_args.push(PlaceOrSpreadOrHole::Hole); - } - react_compiler_hir::type_config::ApplyArgConfig::Place(name) => { - if let Some(places) = substitutions.get(name) { - if let Some(p) = places.first() { - apply_args.push(PlaceOrSpreadOrHole::Place(p.clone())); - } - } - } - react_compiler_hir::type_config::ApplyArgConfig::Spread { place: name, .. } => { - if let Some(places) = substitutions.get(name) { - if let Some(p) = places.first() { - apply_args.push(PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place: p.clone() })); - } - } - } - } - } - effects.push(AliasingEffect::Apply { - receiver: recv, - function: func, - mutates_function: *mutates_function, - args: apply_args, - into, - signature: None, - loc: _loc.copied(), - }); - } else { - return Ok(None); - } - } - } - } - - Ok(Some(effects)) -} - -// ============================================================================= -// Function expression signature building -// ============================================================================= - -/// Build an AliasingSignature from a function expression's params/returns/aliasing effects. -/// Corresponds to TS `buildSignatureFromFunctionExpression`. -fn build_signature_from_function_expression( - env: &mut Environment, - func_id: FunctionId, -) -> AliasingSignature { - let inner_func = &env.functions[func_id.0 as usize]; - let mut params: Vec<IdentifierId> = Vec::new(); - let mut rest: Option<IdentifierId> = None; - for param in &inner_func.params { - match param { - ParamPattern::Place(p) => params.push(p.identifier), - ParamPattern::Spread(s) => rest = Some(s.place.identifier), - } - } - let returns = inner_func.returns.identifier; - let aliasing_effects = inner_func.aliasing_effects.clone().unwrap_or_default(); - let loc = inner_func.loc; - - if rest.is_none() { - let temp = create_temp_place(env, loc); - rest = Some(temp.identifier); - } - - AliasingSignature { - receiver: IdentifierId(0), - params, - rest, - returns, - effects: aliasing_effects, - temporaries: Vec::new(), - } -} - -/// Compute effects by substituting an AliasingSignature (IdentifierId-based) -/// with actual arguments. Corresponds to TS `computeEffectsForSignature`. -fn compute_effects_for_aliasing_signature( - env: &mut Environment, - signature: &AliasingSignature, - lvalue: &Place, - receiver: &Place, - args: &[PlaceOrSpreadOrHole], - context: &[Place], - _loc: Option<&SourceLocation>, -) -> Result<Option<Vec<AliasingEffect>>, CompilerDiagnostic> { - if signature.params.len() > args.len() - || (args.len() > signature.params.len() && signature.rest.is_none()) - { - return Ok(None); - } - - let mut mutable_spreads: HashSet<IdentifierId> = HashSet::new(); - let mut substitutions: HashMap<IdentifierId, Vec<Place>> = HashMap::new(); - substitutions.insert(signature.receiver, vec![receiver.clone()]); - substitutions.insert(signature.returns, vec![lvalue.clone()]); - - for (i, arg) in args.iter().enumerate() { - match arg { - PlaceOrSpreadOrHole::Hole => continue, - PlaceOrSpreadOrHole::Place(place) - | PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place }) => { - let is_spread = matches!(arg, PlaceOrSpreadOrHole::Spread(_)); - if !is_spread && i < signature.params.len() { - substitutions.insert(signature.params[i], vec![place.clone()]); - } else if let Some(rest_id) = signature.rest { - substitutions - .entry(rest_id) - .or_default() - .push(place.clone()); - } else { - return Ok(None); - } - - if is_spread { - let ty = - &env.types[env.identifiers[place.identifier.0 as usize].type_.0 as usize]; - let mutate_iterator = conditionally_mutate_iterator(place, ty); - if mutate_iterator.is_some() { - mutable_spreads.insert(place.identifier); - } - } - } - } - } - - // Add context variable substitutions (identity mapping) - for operand in context { - substitutions.insert(operand.identifier, vec![operand.clone()]); - } - - // Create temporaries - for temp in &signature.temporaries { - let temp_place = create_temp_place(env, receiver.loc); - substitutions.insert(temp.identifier, vec![temp_place]); - } - - let mut effects: Vec<AliasingEffect> = Vec::new(); - - for eff in &signature.effects { - match eff { - AliasingEffect::MaybeAlias { from, into } - | AliasingEffect::Assign { from, into } - | AliasingEffect::ImmutableCapture { from, into } - | AliasingEffect::Alias { from, into } - | AliasingEffect::CreateFrom { from, into } - | AliasingEffect::Capture { from, into } => { - let from_places = substitutions - .get(&from.identifier) - .cloned() - .unwrap_or_default(); - let to_places = substitutions - .get(&into.identifier) - .cloned() - .unwrap_or_default(); - for f in &from_places { - for t in &to_places { - effects.push(match eff { - AliasingEffect::MaybeAlias { .. } => AliasingEffect::MaybeAlias { - from: f.clone(), - into: t.clone(), - }, - AliasingEffect::Assign { .. } => AliasingEffect::Assign { - from: f.clone(), - into: t.clone(), - }, - AliasingEffect::ImmutableCapture { .. } => { - AliasingEffect::ImmutableCapture { - from: f.clone(), - into: t.clone(), - } - } - AliasingEffect::Alias { .. } => AliasingEffect::Alias { - from: f.clone(), - into: t.clone(), - }, - AliasingEffect::CreateFrom { .. } => AliasingEffect::CreateFrom { - from: f.clone(), - into: t.clone(), - }, - AliasingEffect::Capture { .. } => AliasingEffect::Capture { - from: f.clone(), - into: t.clone(), - }, - _ => unreachable!(), - }); - } - } - } - AliasingEffect::Impure { place, error } => { - let values = substitutions - .get(&place.identifier) - .cloned() - .unwrap_or_default(); - for v in values { - effects.push(AliasingEffect::Impure { - place: v, - error: error.clone(), - }); - } - } - AliasingEffect::MutateFrozen { place, error } => { - let values = substitutions - .get(&place.identifier) - .cloned() - .unwrap_or_default(); - for v in values { - effects.push(AliasingEffect::MutateFrozen { - place: v, - error: error.clone(), - }); - } - } - AliasingEffect::MutateGlobal { place, error } => { - let values = substitutions - .get(&place.identifier) - .cloned() - .unwrap_or_default(); - for v in values { - effects.push(AliasingEffect::MutateGlobal { - place: v, - error: error.clone(), - }); - } - } - AliasingEffect::Render { place } => { - let values = substitutions - .get(&place.identifier) - .cloned() - .unwrap_or_default(); - for v in values { - effects.push(AliasingEffect::Render { place: v }); - } - } - AliasingEffect::Mutate { value, reason } => { - let values = substitutions - .get(&value.identifier) - .cloned() - .unwrap_or_default(); - for v in values { - effects.push(AliasingEffect::Mutate { - value: v, - reason: reason.clone(), - }); - } - } - AliasingEffect::MutateConditionally { value } => { - let values = substitutions - .get(&value.identifier) - .cloned() - .unwrap_or_default(); - for v in values { - effects.push(AliasingEffect::MutateConditionally { value: v }); - } - } - AliasingEffect::MutateTransitive { value } => { - let values = substitutions - .get(&value.identifier) - .cloned() - .unwrap_or_default(); - for v in values { - effects.push(AliasingEffect::MutateTransitive { value: v }); - } - } - AliasingEffect::MutateTransitiveConditionally { value } => { - let values = substitutions - .get(&value.identifier) - .cloned() - .unwrap_or_default(); - for v in values { - effects.push(AliasingEffect::MutateTransitiveConditionally { value: v }); - } - } - AliasingEffect::Freeze { value, reason } => { - let values = substitutions - .get(&value.identifier) - .cloned() - .unwrap_or_default(); - for v in values { - if mutable_spreads.contains(&v.identifier) { - return Err(CompilerDiagnostic::todo( - "Support spread syntax for hook arguments", - v.loc, - )); - } - effects.push(AliasingEffect::Freeze { - value: v, - reason: *reason, - }); - } - } - AliasingEffect::Create { - into, - value, - reason, - } => { - let intos = substitutions - .get(&into.identifier) - .cloned() - .unwrap_or_default(); - for v in intos { - effects.push(AliasingEffect::Create { - into: v, - value: *value, - reason: *reason, - }); - } - } - AliasingEffect::Apply { - receiver: r, - function: f, - mutates_function: mf, - args: a, - into: i, - signature: s, - loc: _l, - } => { - let recv = substitutions - .get(&r.identifier) - .and_then(|v| v.first()) - .cloned(); - let func = substitutions - .get(&f.identifier) - .and_then(|v| v.first()) - .cloned(); - let apply_into = substitutions - .get(&i.identifier) - .and_then(|v| v.first()) - .cloned(); - if let (Some(recv), Some(func), Some(apply_into)) = (recv, func, apply_into) { - let mut apply_args: Vec<PlaceOrSpreadOrHole> = Vec::new(); - for arg in a { - match arg { - PlaceOrSpreadOrHole::Hole => apply_args.push(PlaceOrSpreadOrHole::Hole), - PlaceOrSpreadOrHole::Place(p) => { - if let Some(places) = substitutions.get(&p.identifier) { - if let Some(place) = places.first() { - apply_args.push(PlaceOrSpreadOrHole::Place(place.clone())); - } - } - } - PlaceOrSpreadOrHole::Spread(sp) => { - if let Some(places) = substitutions.get(&sp.place.identifier) { - if let Some(place) = places.first() { - apply_args.push(PlaceOrSpreadOrHole::Spread( - react_compiler_hir::SpreadPattern { - place: place.clone(), - }, - )); - } - } - } - } - } - effects.push(AliasingEffect::Apply { - receiver: recv, - function: func, - mutates_function: *mf, - args: apply_args, - into: apply_into, - signature: s.clone(), - loc: _loc.copied(), - }); - } else { - return Ok(None); - } - } - AliasingEffect::CreateFunction { .. } => { - // Not supported in signature substitution - return Ok(None); - } - } - } - - Ok(Some(effects)) -} - -// ============================================================================= -// Helpers -// ============================================================================= - -/// Select the primary (most specific) reason from a set of reasons. -/// TS uses `[...set][0]` which returns the first-inserted element; -/// since the primary reason is always inserted first, this effectively -/// picks the most specific non-Other reason. We replicate this by -/// preferring any non-Other reason over Other. -fn primary_reason(reasons: &IndexSet<ValueReason>) -> ValueReason { - for &r in reasons { - if r != ValueReason::Other { - return r; - } - } - ValueReason::Other -} - -fn get_write_error_reason(abstract_value: &AbstractValue) -> String { - if abstract_value.reason.contains(&ValueReason::Global) { - "Modifying a variable defined outside a component or hook is not allowed. Consider using an effect".to_string() - } else if abstract_value.reason.contains(&ValueReason::JsxCaptured) { - "Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX".to_string() - } else if abstract_value.reason.contains(&ValueReason::Context) { - "Modifying a value returned from 'useContext()' is not allowed.".to_string() - } else if abstract_value - .reason - .contains(&ValueReason::KnownReturnSignature) - { - "Modifying a value returned from a function whose return value should not be mutated" - .to_string() - } else if abstract_value - .reason - .contains(&ValueReason::ReactiveFunctionArgument) - { - "Modifying component props or hook arguments is not allowed. Consider using a local variable instead".to_string() - } else if abstract_value.reason.contains(&ValueReason::State) { - "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead".to_string() - } else if abstract_value.reason.contains(&ValueReason::ReducerState) { - "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead".to_string() - } else if abstract_value.reason.contains(&ValueReason::Effect) { - "Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()".to_string() - } else if abstract_value.reason.contains(&ValueReason::HookCaptured) { - "Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook".to_string() - } else if abstract_value.reason.contains(&ValueReason::HookReturn) { - "Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed".to_string() - } else { - "This modifies a variable that React considers immutable".to_string() - } -} - -fn conditionally_mutate_iterator(place: &Place, ty: &Type) -> Option<AliasingEffect> { - if !is_builtin_collection_type(ty) { - Some(AliasingEffect::MutateTransitiveConditionally { - value: place.clone(), - }) - } else { - None - } -} - -fn is_builtin_collection_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } - if id == BUILT_IN_ARRAY_ID || id == BUILT_IN_SET_ID || id == BUILT_IN_MAP_ID - ) -} - -fn get_function_call_signature( - env: &Environment, - callee_id: IdentifierId, -) -> Result<Option<FunctionSignature>, CompilerDiagnostic> { - let ty = &env.types[env.identifiers[callee_id.0 as usize].type_.0 as usize]; - Ok(env.get_function_signature(ty)?.cloned()) -} - -fn is_ref_or_ref_value_for_id(env: &Environment, id: IdentifierId) -> bool { - let ty = &env.types[env.identifiers[id.0 as usize].type_.0 as usize]; - react_compiler_hir::is_ref_or_ref_value(ty) -} - -fn get_hook_kind_for_type<'a>( - env: &'a Environment, - ty: &Type, -) -> Result<Option<&'a HookKind>, CompilerDiagnostic> { - env.get_hook_kind_for_type(ty) -} - -/// Format a Type for printPlace-style output, matching TS's `printType()`. -fn format_type_for_print(ty: &Type) -> String { - match ty { - Type::Primitive => String::new(), - Type::Function { - shape_id, - return_type, - .. - } => { - if let Some(sid) = shape_id { - let ret = format_type_for_print(return_type); - if ret.is_empty() { - format!(":TFunction<{}>()", sid) - } else { - format!(":TFunction<{}>(): {}", sid, ret) - } - } else { - ":TFunction".to_string() - } - } - Type::Object { shape_id } => { - if let Some(sid) = shape_id { - format!(":TObject<{}>", sid) - } else { - ":TObject".to_string() - } - } - Type::Poly => ":TPoly".to_string(), - Type::Phi { .. } => ":TPhi".to_string(), - Type::Property { .. } => ":TProperty".to_string(), - Type::TypeVar { .. } => String::new(), - Type::ObjectMethod => ":TObjectMethod".to_string(), - } -} - -fn is_phi_with_jsx(ty: &Type) -> bool { - if let Type::Phi { operands } = ty { - operands - .iter() - .any(|op| react_compiler_hir::is_jsx_type(op)) - } else { - false - } -} - -fn place_or_spread_to_hole(pos: &PlaceOrSpread) -> PlaceOrSpreadOrHole { - match pos { - PlaceOrSpread::Place(p) => PlaceOrSpreadOrHole::Place(p.clone()), - PlaceOrSpread::Spread(s) => PlaceOrSpreadOrHole::Spread(s.clone()), - } -} - -use react_compiler_hir::JsxTag; - -fn build_apply_operands( - receiver: &Place, - function: &Place, - args: &[PlaceOrSpreadOrHole], -) -> Vec<(Place, bool, bool)> { - let mut result = vec![ - (receiver.clone(), false, false), - (function.clone(), true, false), - ]; - for arg in args { - match arg { - PlaceOrSpreadOrHole::Hole => continue, - PlaceOrSpreadOrHole::Place(p) => result.push((p.clone(), false, false)), - PlaceOrSpreadOrHole::Spread(s) => result.push((s.place.clone(), false, true)), - } - } - result -} - -fn create_temp_place(env: &mut Environment, loc: Option<SourceLocation>) -> Place { - let id = env.next_identifier_id(); - env.identifiers[id.0 as usize].loc = loc; - Place { - identifier: id, - effect: Effect::Unknown, - reactive: false, - loc, - } -} - -// ============================================================================= -// Terminal successor helper -// ============================================================================= - -/// Returns the successor blocks used for traversal in mutation/aliasing inference. -/// -/// Matches the TS `eachTerminalSuccessor` which yields standard control-flow -/// successors but NOT pseudo-successors (fallthroughs). Fallthroughs for -/// Logical/Ternary/Optional and Try/Scope/PrunedScope are reached naturally -/// via the block iteration order (blocks are stored in topological order). -fn terminal_successors(terminal: &react_compiler_hir::Terminal) -> Vec<BlockId> { - use react_compiler_hir::Terminal; - match terminal { - Terminal::Goto { block, .. } => vec![*block], - Terminal::If { - consequent, - alternate, - .. - } => vec![*consequent, *alternate], - Terminal::Branch { - consequent, - alternate, - .. - } => vec![*consequent, *alternate], - Terminal::Switch { cases, .. } => cases.iter().map(|c| c.block).collect(), - Terminal::For { init, .. } => vec![*init], - Terminal::ForOf { init, .. } | Terminal::ForIn { init, .. } => vec![*init], - Terminal::DoWhile { loop_block, .. } => vec![*loop_block], - Terminal::While { test, .. } => vec![*test], - Terminal::Return { .. } - | Terminal::Throw { .. } - | Terminal::Unreachable { .. } - | Terminal::Unsupported { .. } => vec![], - Terminal::Try { block, .. } => vec![*block], - Terminal::MaybeThrow { - continuation, - handler, - .. - } => { - let mut v = vec![*continuation]; - if let Some(h) = handler { - v.push(*h); - } - v - } - Terminal::Label { block, .. } | Terminal::Sequence { block, .. } => vec![*block], - Terminal::Logical { test, .. } | Terminal::Ternary { test, .. } => vec![*test], - Terminal::Optional { test, .. } => vec![*test], - Terminal::Scope { block, .. } | Terminal::PrunedScope { block, .. } => vec![*block], - } -} - -/// Pattern item helper for Destructure. -/// -/// NOTE: This cannot use `visitors::each_pattern_operand` because callers need -/// to distinguish Place from Spread elements — Spread elements get different -/// aliasing effects (Create + Capture) vs Place elements (Create or CreateFrom). -enum PatternItem<'a> { - Place(&'a Place), - Spread(&'a Place), -} - -fn each_pattern_items(pattern: &react_compiler_hir::Pattern) -> Vec<PatternItem<'_>> { - let mut items = Vec::new(); - match pattern { - react_compiler_hir::Pattern::Array(arr) => { - for el in &arr.items { - match el { - react_compiler_hir::ArrayPatternElement::Place(p) => { - items.push(PatternItem::Place(p)) - } - react_compiler_hir::ArrayPatternElement::Spread(s) => { - items.push(PatternItem::Spread(&s.place)) - } - react_compiler_hir::ArrayPatternElement::Hole => {} - } - } - } - react_compiler_hir::Pattern::Object(obj) => { - for prop in &obj.properties { - match prop { - react_compiler_hir::ObjectPropertyOrSpread::Property(p) => { - items.push(PatternItem::Place(&p.place)) - } - react_compiler_hir::ObjectPropertyOrSpread::Spread(s) => { - items.push(PatternItem::Spread(&s.place)) - } - } - } - } - } - items -} diff --git a/compiler/crates/react_compiler_inference/src/infer_mutation_aliasing_ranges.rs b/compiler/crates/react_compiler_inference/src/infer_mutation_aliasing_ranges.rs deleted file mode 100644 index 9a24249d1c72..000000000000 --- a/compiler/crates/react_compiler_inference/src/infer_mutation_aliasing_ranges.rs +++ /dev/null @@ -1,1183 +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. - -//! Infers mutable ranges for identifiers and populates Place effects. -//! -//! Ported from TypeScript `src/Inference/InferMutationAliasingRanges.ts`. -//! -//! This pass builds an abstract model of the heap and interprets the effects of -//! the given function in order to determine: -//! - The mutable ranges of all identifiers in the function -//! - The externally-visible effects of the function (mutations of params/context -//! vars, aliasing between params/context-vars/return-value) -//! - The legacy `Effect` to store on each Place - -use std::collections::{HashMap, HashSet}; - -use indexmap::IndexMap; - -use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::type_config::{ValueKind, ValueReason}; -use react_compiler_hir::visitors::{ - each_instruction_value_lvalue, for_each_instruction_value_lvalue_mut, - for_each_instruction_value_operand_mut, for_each_terminal_operand_mut, -}; -use react_compiler_hir::{ - AliasingEffect, BlockId, Effect, EvaluationOrder, FunctionId, HirFunction, IdentifierId, - InstructionValue, MutationReason, Place, SourceLocation, is_jsx_type, is_primitive_type, -}; - -// ============================================================================= -// MutationKind -// ============================================================================= - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[allow(dead_code)] -enum MutationKind { - None = 0, - Conditional = 1, - Definite = 2, -} - -// ============================================================================= -// Node and AliasingState -// ============================================================================= - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum EdgeKind { - Capture, - Alias, - MaybeAlias, -} - -#[derive(Debug, Clone)] -struct Edge { - index: usize, - node: IdentifierId, - kind: EdgeKind, -} - -#[derive(Debug, Clone)] -struct MutationInfo { - kind: MutationKind, - loc: Option<SourceLocation>, -} - -#[derive(Debug, Clone)] -enum NodeValue { - Object, - Phi, - Function { function_id: FunctionId }, -} - -#[derive(Debug, Clone)] -struct Node { - id: IdentifierId, - created_from: IndexMap<IdentifierId, usize>, - captures: IndexMap<IdentifierId, usize>, - aliases: IndexMap<IdentifierId, usize>, - maybe_aliases: IndexMap<IdentifierId, usize>, - edges: Vec<Edge>, - transitive: Option<MutationInfo>, - local: Option<MutationInfo>, - last_mutated: usize, - mutation_reason: Option<MutationReason>, - value: NodeValue, -} - -impl Node { - fn new(id: IdentifierId, value: NodeValue) -> Self { - Node { - id, - created_from: IndexMap::new(), - captures: IndexMap::new(), - aliases: IndexMap::new(), - maybe_aliases: IndexMap::new(), - edges: Vec::new(), - transitive: None, - local: None, - last_mutated: 0, - mutation_reason: None, - value, - } - } -} - -struct AliasingState { - nodes: IndexMap<IdentifierId, Node>, -} - -impl AliasingState { - fn new() -> Self { - AliasingState { - nodes: IndexMap::new(), - } - } - - fn create(&mut self, place: &Place, value: NodeValue) { - self.nodes - .insert(place.identifier, Node::new(place.identifier, value)); - } - - fn create_from(&mut self, index: usize, from: &Place, into: &Place) { - self.create(into, NodeValue::Object); - let from_id = from.identifier; - let into_id = into.identifier; - // Add forward edge from -> into on the from node - if let Some(from_node) = self.nodes.get_mut(&from_id) { - from_node.edges.push(Edge { - index, - node: into_id, - kind: EdgeKind::Alias, - }); - } - // Add created_from on the into node - if let Some(to_node) = self.nodes.get_mut(&into_id) { - to_node.created_from.entry(from_id).or_insert(index); - } - } - - fn capture(&mut self, index: usize, from: &Place, into: &Place) { - let from_id = from.identifier; - let into_id = into.identifier; - if !self.nodes.contains_key(&from_id) || !self.nodes.contains_key(&into_id) { - return; - } - self.nodes.get_mut(&from_id).unwrap().edges.push(Edge { - index, - node: into_id, - kind: EdgeKind::Capture, - }); - self.nodes - .get_mut(&into_id) - .unwrap() - .captures - .entry(from_id) - .or_insert(index); - } - - fn assign(&mut self, index: usize, from: &Place, into: &Place) { - let from_id = from.identifier; - let into_id = into.identifier; - if !self.nodes.contains_key(&from_id) || !self.nodes.contains_key(&into_id) { - return; - } - self.nodes.get_mut(&from_id).unwrap().edges.push(Edge { - index, - node: into_id, - kind: EdgeKind::Alias, - }); - self.nodes - .get_mut(&into_id) - .unwrap() - .aliases - .entry(from_id) - .or_insert(index); - } - - fn maybe_alias(&mut self, index: usize, from: &Place, into: &Place) { - let from_id = from.identifier; - let into_id = into.identifier; - if !self.nodes.contains_key(&from_id) || !self.nodes.contains_key(&into_id) { - return; - } - self.nodes.get_mut(&from_id).unwrap().edges.push(Edge { - index, - node: into_id, - kind: EdgeKind::MaybeAlias, - }); - self.nodes - .get_mut(&into_id) - .unwrap() - .maybe_aliases - .entry(from_id) - .or_insert(index); - } - - fn render(&self, index: usize, start: IdentifierId, env: &mut Environment) { - let mut seen = HashSet::new(); - let mut queue: Vec<IdentifierId> = vec![start]; - while let Some(current) = queue.pop() { - if !seen.insert(current) { - continue; - } - let node = match self.nodes.get(¤t) { - Some(n) => n, - None => continue, - }; - if node.transitive.is_some() || node.local.is_some() { - continue; - } - if let NodeValue::Function { function_id } = &node.value { - append_function_errors(env, *function_id); - } - for (&alias, &when) in &node.created_from { - if when >= index { - continue; - } - queue.push(alias); - } - for (&alias, &when) in &node.aliases { - if when >= index { - continue; - } - queue.push(alias); - } - for (&capture, &when) in &node.captures { - if when >= index { - continue; - } - queue.push(capture); - } - } - } - - fn mutate( - &mut self, - index: usize, - start: IdentifierId, - end: Option<EvaluationOrder>, // None for simulated mutations - transitive: bool, - start_kind: MutationKind, - loc: Option<SourceLocation>, - reason: Option<MutationReason>, - env: &mut Environment, - should_record_errors: bool, - ) { - #[derive(Clone)] - struct QueueEntry { - place: IdentifierId, - transitive: bool, - direction: Direction, - kind: MutationKind, - } - #[derive(Clone, Copy, PartialEq)] - enum Direction { - Backwards, - Forwards, - } - - let mut seen: HashMap<IdentifierId, MutationKind> = HashMap::new(); - let mut queue: Vec<QueueEntry> = vec![QueueEntry { - place: start, - transitive, - direction: Direction::Backwards, - kind: start_kind, - }]; - - while let Some(entry) = queue.pop() { - let current = entry.place; - let previous_kind = seen.get(¤t).copied(); - if let Some(prev) = previous_kind { - if prev >= entry.kind { - continue; - } - } - seen.insert(current, entry.kind); - - let node = match self.nodes.get_mut(¤t) { - Some(n) => n, - None => continue, - }; - - if node.mutation_reason.is_none() { - node.mutation_reason = reason.clone(); - } - node.last_mutated = node.last_mutated.max(index); - - if let Some(end_val) = end { - let ident = &mut env.identifiers[node.id.0 as usize]; - ident.mutable_range.end = EvaluationOrder(ident.mutable_range.end.0.max(end_val.0)); - } - - if let NodeValue::Function { function_id } = &node.value { - if node.transitive.is_none() && node.local.is_none() { - if should_record_errors { - append_function_errors(env, *function_id); - } - } - } - - if entry.transitive { - match &node.transitive { - None => { - node.transitive = Some(MutationInfo { - kind: entry.kind, - loc, - }); - } - Some(existing) if existing.kind < entry.kind => { - node.transitive = Some(MutationInfo { - kind: entry.kind, - loc, - }); - } - _ => {} - } - } else { - match &node.local { - None => { - node.local = Some(MutationInfo { - kind: entry.kind, - loc, - }); - } - Some(existing) if existing.kind < entry.kind => { - node.local = Some(MutationInfo { - kind: entry.kind, - loc, - }); - } - _ => {} - } - } - - // Forward edges: Capture a -> b, Alias a -> b: mutate(a) => mutate(b) - // Collect edges to avoid borrow conflict - let edges: Vec<Edge> = node.edges.clone(); - let node_value_kind = match &node.value { - NodeValue::Phi => "Phi", - _ => "Other", - }; - let node_aliases: Vec<(IdentifierId, usize)> = - node.aliases.iter().map(|(&k, &v)| (k, v)).collect(); - let node_maybe_aliases: Vec<(IdentifierId, usize)> = - node.maybe_aliases.iter().map(|(&k, &v)| (k, v)).collect(); - let node_captures: Vec<(IdentifierId, usize)> = - node.captures.iter().map(|(&k, &v)| (k, v)).collect(); - let node_created_from: Vec<(IdentifierId, usize)> = - node.created_from.iter().map(|(&k, &v)| (k, v)).collect(); - - for edge in &edges { - if edge.index >= index { - break; - } - queue.push(QueueEntry { - place: edge.node, - transitive: entry.transitive, - direction: Direction::Forwards, - // MaybeAlias edges downgrade to conditional mutation - kind: if edge.kind == EdgeKind::MaybeAlias { - MutationKind::Conditional - } else { - entry.kind - }, - }); - } - - for (alias, when) in &node_created_from { - if *when >= index { - continue; - } - queue.push(QueueEntry { - place: *alias, - transitive: true, - direction: Direction::Backwards, - kind: entry.kind, - }); - } - - if entry.direction == Direction::Backwards || node_value_kind != "Phi" { - // Backward alias edges - for (alias, when) in &node_aliases { - if *when >= index { - continue; - } - queue.push(QueueEntry { - place: *alias, - transitive: entry.transitive, - direction: Direction::Backwards, - kind: entry.kind, - }); - } - // MaybeAlias backward edges (downgrade to conditional) - for (alias, when) in &node_maybe_aliases { - if *when >= index { - continue; - } - queue.push(QueueEntry { - place: *alias, - transitive: entry.transitive, - direction: Direction::Backwards, - kind: MutationKind::Conditional, - }); - } - } - - // Only transitive mutations affect captures backward - if entry.transitive { - for (capture, when) in &node_captures { - if *when >= index { - continue; - } - queue.push(QueueEntry { - place: *capture, - transitive: entry.transitive, - direction: Direction::Backwards, - kind: entry.kind, - }); - } - } - } - } -} - -// ============================================================================= -// Helper: append function errors -// ============================================================================= - -fn append_function_errors(env: &mut Environment, function_id: FunctionId) { - let func = &env.functions[function_id.0 as usize]; - if let Some(ref effects) = func.aliasing_effects { - // Collect errors first to avoid borrow conflict - let errors: Vec<_> = effects - .iter() - .filter_map(|effect| match effect { - AliasingEffect::Impure { error, .. } - | AliasingEffect::MutateFrozen { error, .. } - | AliasingEffect::MutateGlobal { error, .. } => Some(error.clone()), - _ => None, - }) - .collect(); - for error in errors { - env.record_diagnostic(error); - } - } -} - -// ============================================================================= -// Public entry point -// ============================================================================= - -/// Infers mutable ranges for identifiers and populates Place effects. -/// -/// Returns the externally-visible effects of the function (mutations of -/// params/context-vars, aliasing between params/context-vars/return). -/// -/// Corresponds to TS `inferMutationAliasingRanges(fn, {isFunctionExpression})`. -pub fn infer_mutation_aliasing_ranges( - func: &mut HirFunction, - env: &mut Environment, - is_function_expression: bool, -) -> Result<Vec<AliasingEffect>, CompilerDiagnostic> { - let mut function_effects: Vec<AliasingEffect> = Vec::new(); - - // ========================================================================= - // Part 1: Build data flow graph and infer mutable ranges - // ========================================================================= - let mut state = AliasingState::new(); - - struct PendingPhiOperand { - from: Place, - into: Place, - index: usize, - } - let mut pending_phis: HashMap<BlockId, Vec<PendingPhiOperand>> = HashMap::new(); - - struct PendingMutation { - index: usize, - id: EvaluationOrder, - transitive: bool, - kind: MutationKind, - place: Place, - reason: Option<MutationReason>, - } - let mut mutations: Vec<PendingMutation> = Vec::new(); - - struct PendingRender { - index: usize, - place: Place, - } - let mut renders: Vec<PendingRender> = Vec::new(); - - let mut index: usize = 0; - - let should_record_errors = !is_function_expression && env.enable_validations(); - - // Create nodes for params, context vars, and return - for param in &func.params { - let place = match param { - react_compiler_hir::ParamPattern::Place(p) => p, - react_compiler_hir::ParamPattern::Spread(s) => &s.place, - }; - state.create(place, NodeValue::Object); - } - for ctx in &func.context { - state.create(ctx, NodeValue::Object); - } - state.create(&func.returns, NodeValue::Object); - - let mut seen_blocks: HashSet<BlockId> = HashSet::new(); - - // Collect block iteration data to avoid borrow conflicts - let block_order: Vec<BlockId> = func.body.blocks.keys().cloned().collect(); - - for &block_id in &block_order { - let block = &func.body.blocks[&block_id]; - - // Process phis - for phi in &block.phis { - state.create(&phi.place, NodeValue::Phi); - for (&pred, operand) in &phi.operands { - if !seen_blocks.contains(&pred) { - pending_phis - .entry(pred) - .or_insert_with(Vec::new) - .push(PendingPhiOperand { - from: operand.clone(), - into: phi.place.clone(), - index: index, - }); - index += 1; - } else { - state.assign(index, operand, &phi.place); - index += 1; - } - } - } - seen_blocks.insert(block_id); - - // Process instruction effects - let instr_ids: Vec<_> = block.instructions.clone(); - for instr_id in &instr_ids { - let instr = &func.instructions[instr_id.0 as usize]; - let instr_eval_order = instr.id; - let effects = match &instr.effects { - Some(e) => e.clone(), - None => continue, - }; - for effect in &effects { - match effect { - AliasingEffect::Create { into, .. } => { - state.create(into, NodeValue::Object); - } - AliasingEffect::CreateFunction { - into, function_id, .. - } => { - state.create( - into, - NodeValue::Function { - function_id: *function_id, - }, - ); - } - AliasingEffect::CreateFrom { from, into } => { - state.create_from(index, from, into); - index += 1; - } - AliasingEffect::Assign { from, into } => { - if !state.nodes.contains_key(&into.identifier) { - state.create(into, NodeValue::Object); - } - state.assign(index, from, into); - index += 1; - } - AliasingEffect::Alias { from, into } => { - state.assign(index, from, into); - index += 1; - } - AliasingEffect::MaybeAlias { from, into } => { - state.maybe_alias(index, from, into); - index += 1; - } - AliasingEffect::Capture { from, into } => { - state.capture(index, from, into); - index += 1; - } - AliasingEffect::MutateTransitive { value } - | AliasingEffect::MutateTransitiveConditionally { value } => { - let is_transitive_conditional = - matches!(effect, AliasingEffect::MutateTransitiveConditionally { .. }); - mutations.push(PendingMutation { - index: index, - id: instr_eval_order, - transitive: true, - kind: if is_transitive_conditional { - MutationKind::Conditional - } else { - MutationKind::Definite - }, - reason: None, - place: value.clone(), - }); - index += 1; - } - AliasingEffect::Mutate { value, reason } => { - mutations.push(PendingMutation { - index: index, - id: instr_eval_order, - transitive: false, - kind: MutationKind::Definite, - reason: reason.clone(), - place: value.clone(), - }); - index += 1; - } - AliasingEffect::MutateConditionally { value } => { - mutations.push(PendingMutation { - index: index, - id: instr_eval_order, - transitive: false, - kind: MutationKind::Conditional, - reason: None, - place: value.clone(), - }); - index += 1; - } - AliasingEffect::MutateFrozen { .. } - | AliasingEffect::MutateGlobal { .. } - | AliasingEffect::Impure { .. } => { - if should_record_errors { - match effect { - AliasingEffect::MutateFrozen { error, .. } - | AliasingEffect::MutateGlobal { error, .. } - | AliasingEffect::Impure { error, .. } => { - env.record_diagnostic(error.clone()); - } - _ => unreachable!(), - } - } - function_effects.push(effect.clone()); - } - AliasingEffect::Render { place } => { - renders.push(PendingRender { - index: index, - place: place.clone(), - }); - index += 1; - function_effects.push(effect.clone()); - } - // Other effects (Freeze, ImmutableCapture, Apply) are no-ops here - _ => {} - } - } - } - - // Process pending phis for this block - let block = &func.body.blocks[&block_id]; - if let Some(block_phis) = pending_phis.remove(&block_id) { - for pending in block_phis { - state.assign(pending.index, &pending.from, &pending.into); - } - } - - // Handle return terminal - let terminal = &block.terminal; - if let react_compiler_hir::Terminal::Return { value, .. } = terminal { - state.assign(index, value, &func.returns); - index += 1; - } - - // Handle terminal effects (MaybeThrow and Return) - let terminal_effects = match terminal { - react_compiler_hir::Terminal::MaybeThrow { effects, .. } - | react_compiler_hir::Terminal::Return { effects, .. } => effects.clone(), - _ => None, - }; - if let Some(effects) = terminal_effects { - for effect in &effects { - match effect { - AliasingEffect::Alias { from, into } => { - state.assign(index, from, into); - index += 1; - } - AliasingEffect::Freeze { .. } => { - // Expected for MaybeThrow terminals, skip - } - _ => { - // TS: CompilerError.invariant(effect.kind === 'Freeze', ...) - // We skip non-Alias, non-Freeze effects - } - } - } - } - } - - // Process mutations - for mutation in &mutations { - state.mutate( - mutation.index, - mutation.place.identifier, - Some(EvaluationOrder(mutation.id.0 + 1)), - mutation.transitive, - mutation.kind, - mutation.place.loc, - mutation.reason.clone(), - env, - should_record_errors, - ); - } - - // Process renders - for render in &renders { - if should_record_errors { - state.render(render.index, render.place.identifier, env); - } - } - - // Collect function effects for context vars and params - // NOTE: TS iterates [...fn.context, ...fn.params] — context first, then params - for ctx in &func.context { - collect_param_effects(&state, ctx, &mut function_effects); - } - for param in &func.params { - let place = match param { - react_compiler_hir::ParamPattern::Place(p) => p, - react_compiler_hir::ParamPattern::Spread(s) => &s.place, - }; - collect_param_effects(&state, place, &mut function_effects); - } - - // Set effect on mutated params/context vars - // We need to do this in a separate pass because we need to know which params - // were mutated before setting effects - let mut captured_params: HashSet<IdentifierId> = HashSet::new(); - for param in &func.params { - let place = match param { - react_compiler_hir::ParamPattern::Place(p) => p, - react_compiler_hir::ParamPattern::Spread(s) => &s.place, - }; - if let Some(node) = state.nodes.get(&place.identifier) { - if node.local.is_some() || node.transitive.is_some() { - captured_params.insert(place.identifier); - } - } - } - for ctx in &func.context { - if let Some(node) = state.nodes.get(&ctx.identifier) { - if node.local.is_some() || node.transitive.is_some() { - captured_params.insert(ctx.identifier); - } - } - } - - // Now mutate the effects on params/context in place - for param in &mut func.params { - let place = match param { - react_compiler_hir::ParamPattern::Place(p) => p, - react_compiler_hir::ParamPattern::Spread(s) => &mut s.place, - }; - if captured_params.contains(&place.identifier) { - place.effect = Effect::Capture; - } - } - for ctx in &mut func.context { - if captured_params.contains(&ctx.identifier) { - ctx.effect = Effect::Capture; - } - } - - // ========================================================================= - // Part 2: Add legacy operand-specific effects based on instruction effects - // and mutable ranges. Also fix up mutable range start values. - // ========================================================================= - // Part 2 loop - for &block_id in &block_order { - let block = &func.body.blocks[&block_id]; - - // Process phis - let phi_data: Vec<_> = block - .phis - .iter() - .map(|phi| { - let first_instr_id = block - .instructions - .first() - .map(|id| func.instructions[id.0 as usize].id) - .unwrap_or_else(|| block.terminal.evaluation_order()); - - let is_mutated_after_creation = env.identifiers[phi.place.identifier.0 as usize] - .mutable_range - .end - > first_instr_id; - - ( - phi.place.identifier, - phi.operands - .values() - .map(|o| o.identifier) - .collect::<Vec<_>>(), - is_mutated_after_creation, - first_instr_id, - ) - }) - .collect(); - - for (phi_id, _operand_ids, is_mutated_after_creation, first_instr_id) in &phi_data { - // Set phi place effect to Store - // We need to find this phi in the block and set it - let block = func.body.blocks.get_mut(&block_id).unwrap(); - for phi in &mut block.phis { - if phi.place.identifier == *phi_id { - phi.place.effect = Effect::Store; - for operand in phi.operands.values_mut() { - operand.effect = if *is_mutated_after_creation { - Effect::Capture - } else { - Effect::Read - }; - } - break; - } - } - - if *is_mutated_after_creation { - let ident = &mut env.identifiers[phi_id.0 as usize]; - if ident.mutable_range.start == EvaluationOrder(0) { - ident.mutable_range.start = EvaluationOrder(first_instr_id.0.saturating_sub(1)); - } - } - } - - let block = &func.body.blocks[&block_id]; - let instr_ids: Vec<_> = block.instructions.clone(); - - for instr_id in &instr_ids { - let instr = &func.instructions[instr_id.0 as usize]; - let eval_order = instr.id; - - // Set lvalue effect to ConditionallyMutate and fix up mutable range - // This covers the top-level lvalue - let lvalue_id = instr.lvalue.identifier; - { - let ident = &mut env.identifiers[lvalue_id.0 as usize]; - if ident.mutable_range.start == EvaluationOrder(0) { - ident.mutable_range.start = eval_order; - } - if ident.mutable_range.end == EvaluationOrder(0) { - ident.mutable_range.end = - EvaluationOrder((eval_order.0 + 1).max(ident.mutable_range.end.0)); - } - } - func.instructions[instr_id.0 as usize].lvalue.effect = Effect::ConditionallyMutate; - - // Also handle value-level lvalues (DeclareLocal, StoreLocal, etc.) - let value_lvalue_ids: Vec<IdentifierId> = - each_instruction_value_lvalue(&func.instructions[instr_id.0 as usize].value) - .into_iter() - .map(|p| p.identifier) - .collect(); - for vlid in &value_lvalue_ids { - let ident = &mut env.identifiers[vlid.0 as usize]; - if ident.mutable_range.start == EvaluationOrder(0) { - ident.mutable_range.start = eval_order; - } - if ident.mutable_range.end == EvaluationOrder(0) { - ident.mutable_range.end = - EvaluationOrder((eval_order.0 + 1).max(ident.mutable_range.end.0)); - } - } - for_each_instruction_value_lvalue_mut( - &mut func.instructions[instr_id.0 as usize].value, - &mut |place| { - place.effect = Effect::ConditionallyMutate; - }, - ); - - // Set operand effects to Read - for_each_instruction_value_operand_mut( - &mut func.instructions[instr_id.0 as usize].value, - &mut |place| { - place.effect = Effect::Read; - }, - ); - - let instr = &func.instructions[instr_id.0 as usize]; - if instr.effects.is_none() { - continue; - } - - // Compute operand effects from instruction effects - let effects = instr.effects.as_ref().unwrap().clone(); - let mut operand_effects: HashMap<IdentifierId, Effect> = HashMap::new(); - - for effect in &effects { - match effect { - AliasingEffect::Assign { from, into, .. } - | AliasingEffect::Alias { from, into } - | AliasingEffect::Capture { from, into } - | AliasingEffect::CreateFrom { from, into } - | AliasingEffect::MaybeAlias { from, into } => { - let is_mutated_or_reassigned = env.identifiers[into.identifier.0 as usize] - .mutable_range - .end - > eval_order; - if is_mutated_or_reassigned { - operand_effects.insert(from.identifier, Effect::Capture); - operand_effects.insert(into.identifier, Effect::Store); - } else { - operand_effects.insert(from.identifier, Effect::Read); - operand_effects.insert(into.identifier, Effect::Store); - } - } - AliasingEffect::CreateFunction { .. } | AliasingEffect::Create { .. } => { - // no-op - } - AliasingEffect::Mutate { value, .. } => { - operand_effects.insert(value.identifier, Effect::Store); - } - AliasingEffect::Apply { .. } => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects", - None, - )); - } - AliasingEffect::MutateTransitive { value, .. } - | AliasingEffect::MutateConditionally { value } - | AliasingEffect::MutateTransitiveConditionally { value } => { - operand_effects.insert(value.identifier, Effect::ConditionallyMutate); - } - AliasingEffect::Freeze { value, .. } => { - operand_effects.insert(value.identifier, Effect::Freeze); - } - AliasingEffect::ImmutableCapture { .. } => { - // no-op, Read is the default - } - AliasingEffect::Impure { .. } - | AliasingEffect::Render { .. } - | AliasingEffect::MutateFrozen { .. } - | AliasingEffect::MutateGlobal { .. } => { - // no-op - } - } - } - - // Apply operand effects to top-level lvalue - let instr = &mut func.instructions[instr_id.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - if let Some(&effect) = operand_effects.get(&lvalue_id) { - instr.lvalue.effect = effect; - } - // Apply operand effects to value-level lvalues - for_each_instruction_value_lvalue_mut(&mut instr.value, &mut |place| { - if let Some(&effect) = operand_effects.get(&place.identifier) { - place.effect = effect; - } - }); - - // Apply operand effects to value operands and fix up mutable ranges - { - let mut apply = |place: &mut Place| { - // Fix up mutable range start - let ident = &env.identifiers[place.identifier.0 as usize]; - if ident.mutable_range.end > eval_order - && ident.mutable_range.start == EvaluationOrder(0) - { - env.identifiers[place.identifier.0 as usize] - .mutable_range - .start = eval_order; - } - // Apply effect - if let Some(&effect) = operand_effects.get(&place.identifier) { - place.effect = effect; - } - }; - for_each_instruction_value_operand_mut(&mut instr.value, &mut apply); - - // FunctionExpression/ObjectMethod context variables are operands that - // require env access (they live in env.functions[func_id].context). - if let InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } = &instr.value - { - let func_id = lowered_func.func; - let ctx_ids: Vec<IdentifierId> = env.functions[func_id.0 as usize] - .context - .iter() - .map(|c| c.identifier) - .collect(); - for ctx_id in &ctx_ids { - let ident = &env.identifiers[ctx_id.0 as usize]; - if ident.mutable_range.end > eval_order - && ident.mutable_range.start == EvaluationOrder(0) - { - env.identifiers[ctx_id.0 as usize].mutable_range.start = eval_order; - } - let effect = operand_effects.get(ctx_id).copied().unwrap_or(Effect::Read); - let inner_func = &mut env.functions[func_id.0 as usize]; - for ctx_place in &mut inner_func.context { - if ctx_place.identifier == *ctx_id { - ctx_place.effect = effect; - } - } - } - } - } - - // Handle StoreContext case: extend rvalue range if needed - let instr = &func.instructions[instr_id.0 as usize]; - if let InstructionValue::StoreContext { value, .. } = &instr.value { - let val_id = value.identifier; - let val_range_end = env.identifiers[val_id.0 as usize].mutable_range.end; - if val_range_end <= eval_order { - env.identifiers[val_id.0 as usize].mutable_range.end = - EvaluationOrder(eval_order.0 + 1); - } - } - } - - // Set terminal operand effects - let block = func.body.blocks.get_mut(&block_id).unwrap(); - match &mut block.terminal { - react_compiler_hir::Terminal::Return { value, .. } => { - value.effect = if is_function_expression { - Effect::Read - } else { - Effect::Freeze - }; - } - terminal => { - for_each_terminal_operand_mut(terminal, &mut |place| { - place.effect = Effect::Read; - }); - } - } - } - - // ========================================================================= - // Part 3: Finish populating the externally visible effects - // ========================================================================= - let returns_id = func.returns.identifier; - let returns_type_id = env.identifiers[returns_id.0 as usize].type_; - let returns_type = &env.types[returns_type_id.0 as usize]; - let return_value_kind = if is_primitive_type(returns_type) { - ValueKind::Primitive - } else if is_jsx_type(returns_type) { - ValueKind::Frozen - } else { - ValueKind::Mutable - }; - - function_effects.push(AliasingEffect::Create { - into: func.returns.clone(), - value: return_value_kind, - reason: ValueReason::KnownReturnSignature, - }); - - // Determine precise data-flow effects by simulating transitive mutations - let mut tracked: Vec<Place> = Vec::new(); - for param in &func.params { - let place = match param { - react_compiler_hir::ParamPattern::Place(p) => p.clone(), - react_compiler_hir::ParamPattern::Spread(s) => s.place.clone(), - }; - tracked.push(place); - } - for ctx in &func.context { - tracked.push(ctx.clone()); - } - tracked.push(func.returns.clone()); - - let returns_identifier_id = func.returns.identifier; - - for i in 0..tracked.len() { - let into = tracked[i].clone(); - let mutation_index = index; - index += 1; - - state.mutate( - mutation_index, - into.identifier, - None, // simulated mutation - true, - MutationKind::Conditional, - into.loc, - None, - env, - false, // never record errors for simulated mutations - ); - - for j in 0..tracked.len() { - let from = &tracked[j]; - if from.identifier == into.identifier || from.identifier == returns_identifier_id { - continue; - } - - let from_node = state.nodes.get(&from.identifier); - assert!( - from_node.is_some(), - "Expected a node to exist for all parameters and context variables" - ); - let from_node = from_node.unwrap(); - - if from_node.last_mutated == mutation_index { - if into.identifier == returns_identifier_id { - function_effects.push(AliasingEffect::Alias { - from: from.clone(), - into: into.clone(), - }); - } else { - function_effects.push(AliasingEffect::Capture { - from: from.clone(), - into: into.clone(), - }); - } - } - } - } - - Ok(function_effects) -} - -// ============================================================================= -// Helper: collect param/context mutation effects -// ============================================================================= - -fn collect_param_effects( - state: &AliasingState, - place: &Place, - function_effects: &mut Vec<AliasingEffect>, -) { - let node = match state.nodes.get(&place.identifier) { - Some(n) => n, - None => return, - }; - - if let Some(ref local) = node.local { - match local.kind { - MutationKind::Conditional => { - function_effects.push(AliasingEffect::MutateConditionally { - value: Place { - loc: local.loc, - ..place.clone() - }, - }); - } - MutationKind::Definite => { - function_effects.push(AliasingEffect::Mutate { - value: Place { - loc: local.loc, - ..place.clone() - }, - reason: node.mutation_reason.clone(), - }); - } - MutationKind::None => {} - } - } - - if let Some(ref transitive) = node.transitive { - match transitive.kind { - MutationKind::Conditional => { - function_effects.push(AliasingEffect::MutateTransitiveConditionally { - value: Place { - loc: transitive.loc, - ..place.clone() - }, - }); - } - MutationKind::Definite => { - function_effects.push(AliasingEffect::MutateTransitive { - value: Place { - loc: transitive.loc, - ..place.clone() - }, - }); - } - MutationKind::None => {} - } - } -} diff --git a/compiler/crates/react_compiler_inference/src/infer_reactive_places.rs b/compiler/crates/react_compiler_inference/src/infer_reactive_places.rs deleted file mode 100644 index e3c8d00aee05..000000000000 --- a/compiler/crates/react_compiler_inference/src/infer_reactive_places.rs +++ /dev/null @@ -1,785 +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. - -//! Infers which `Place`s are reactive. -//! -//! Ported from TypeScript `src/Inference/InferReactivePlaces.ts`. -//! -//! A place is reactive if it derives from any source of reactivity: -//! 1. Props (component parameters may change between renders) -//! 2. Hooks (can access state or context) -//! 3. `use` operator (can access context) -//! 4. Mutation with reactive operands -//! 5. Conditional assignment based on reactive control flow - -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory}; -use react_compiler_hir::dominator::post_dominator_frontier; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::object_shape::HookKind; -use react_compiler_hir::visitors; -use react_compiler_hir::{ - BlockId, Effect, FunctionId, HirFunction, IdentifierId, InstructionValue, ParamPattern, - Terminal, Type, -}; - -use react_compiler_utils::DisjointSet; - -use crate::infer_reactive_scope_variables::find_disjoint_mutable_values; - -// ============================================================================= -// Public API -// ============================================================================= - -/// Infer which places in a function are reactive. -/// -/// Corresponds to TS `inferReactivePlaces(fn: HIRFunction): void`. -pub fn infer_reactive_places( - func: &mut HirFunction, - env: &mut Environment, -) -> Result<(), CompilerDiagnostic> { - let mut aliased_identifiers = find_disjoint_mutable_values(func, env); - let mut reactive_map = ReactivityMap::new(&mut aliased_identifiers); - let mut stable_sidemap = StableSidemap::new(); - - // Mark all function parameters as reactive - for param in &func.params { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - reactive_map.mark_reactive(place.identifier); - } - - // Compute control dominators - let post_dominators = react_compiler_hir::dominator::compute_post_dominator_tree( - func, - env.next_block_id().0, - false, - )?; - - // Collect block IDs for iteration - let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect(); - - // Track phi operand reactive flags during fixpoint. - // In TS, isReactive() sets place.reactive as a side effect. But when a phi - // is already reactive, the TS `continue`s and skips operand processing. - // We track which phi operand Places should be marked reactive. - // Key: (block_id, phi_idx, operand_idx), Value: should be reactive - let mut phi_operand_reactive: HashMap<(BlockId, usize, usize), bool> = HashMap::new(); - - // Fixpoint iteration — compute reactive set - loop { - for block_id in &block_ids { - let block = func.body.blocks.get(block_id).unwrap(); - let has_reactive_control = - is_reactive_controlled_block(block.id, func, &post_dominators, &mut reactive_map); - - // Process phi nodes - let block = func.body.blocks.get(block_id).unwrap(); - for (phi_idx, phi) in block.phis.iter().enumerate() { - if reactive_map.is_reactive(phi.place.identifier) { - // TS does `continue` here — skips operand isReactive calls. - // phi operand reactive flags stay as they were from last visit. - continue; - } - let mut is_phi_reactive = false; - for (op_idx, (_pred, operand)) in phi.operands.iter().enumerate() { - let op_reactive = reactive_map.is_reactive(operand.identifier); - // Record the reactive state for this operand at this point - phi_operand_reactive.insert((*block_id, phi_idx, op_idx), op_reactive); - if op_reactive { - is_phi_reactive = true; - break; // TS breaks here — remaining operands NOT visited - } - } - if is_phi_reactive { - reactive_map.mark_reactive(phi.place.identifier); - } else { - for (pred, _operand) in &phi.operands { - if is_reactive_controlled_block( - *pred, - func, - &post_dominators, - &mut reactive_map, - ) { - reactive_map.mark_reactive(phi.place.identifier); - break; - } - } - } - } - - // Process instructions - let block = func.body.blocks.get(block_id).unwrap(); - for instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - - // Handle stable identifier sources - stable_sidemap.handle_instruction(instr, env); - - let value = &instr.value; - - // Check if any operand is reactive - let mut has_reactive_input = false; - let operands: Vec<IdentifierId> = - visitors::each_instruction_value_operand(value, env) - .into_iter() - .map(|p| p.identifier) - .collect(); - for &op_id in &operands { - let reactive = reactive_map.is_reactive(op_id); - has_reactive_input = has_reactive_input || reactive; - } - - // Hooks and `use` operator are sources of reactivity - match value { - InstructionValue::CallExpression { callee, .. } => { - let callee_ty = &env.types - [env.identifiers[callee.identifier.0 as usize].type_.0 as usize]; - if get_hook_kind_for_type(env, callee_ty)?.is_some() - || is_use_operator_type(callee_ty) - { - has_reactive_input = true; - } - } - InstructionValue::MethodCall { property, .. } => { - let property_ty = &env.types - [env.identifiers[property.identifier.0 as usize].type_.0 as usize]; - if get_hook_kind_for_type(env, property_ty)?.is_some() - || is_use_operator_type(property_ty) - { - has_reactive_input = true; - } - } - _ => {} - } - - if has_reactive_input { - // Mark lvalues reactive (unless stable) - let lvalue_ids: Vec<IdentifierId> = visitors::each_instruction_lvalue(instr) - .into_iter() - .map(|p| p.identifier) - .collect(); - for lvalue_id in lvalue_ids { - if stable_sidemap.is_stable(lvalue_id) { - continue; - } - reactive_map.mark_reactive(lvalue_id); - } - } - - if has_reactive_input || has_reactive_control { - // Mark mutable operands reactive - let operand_places = visitors::each_instruction_value_operand(value, env); - for op_place in &operand_places { - match op_place.effect { - Effect::Capture - | Effect::Store - | Effect::ConditionallyMutate - | Effect::ConditionallyMutateIterator - | Effect::Mutate => { - let op_range = - &env.identifiers[op_place.identifier.0 as usize].mutable_range; - if op_range.contains(instr.id) { - reactive_map.mark_reactive(op_place.identifier); - } - } - Effect::Freeze | Effect::Read => { - // no-op - } - Effect::Unknown => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - &format!("Unexpected unknown effect at {:?}", op_place.loc), - None, - )); - } - } - } - } - } - - // Process terminal operands (just to mark them reactive for output) - for op in visitors::each_terminal_operand(&block.terminal) { - reactive_map.is_reactive(op.identifier); - } - } - - if !reactive_map.snapshot() { - break; - } - } - - // Propagate reactivity to inner functions (read-only phase, just queries reactive_map) - propagate_reactivity_to_inner_functions_outer(func, env, &mut reactive_map); - - // Now apply reactive flags by replaying the traversal pattern. - apply_reactive_flags_replay( - func, - env, - &mut reactive_map, - &mut stable_sidemap, - &phi_operand_reactive, - ); - - Ok(()) -} - -// ============================================================================= -// ReactivityMap -// ============================================================================= - -struct ReactivityMap<'a> { - has_changes: bool, - reactive: HashSet<IdentifierId>, - aliased_identifiers: &'a mut DisjointSet<IdentifierId>, -} - -impl<'a> ReactivityMap<'a> { - fn new(aliased_identifiers: &'a mut DisjointSet<IdentifierId>) -> Self { - ReactivityMap { - has_changes: false, - reactive: HashSet::new(), - aliased_identifiers, - } - } - - fn is_reactive(&mut self, id: IdentifierId) -> bool { - let canonical = self.aliased_identifiers.find_opt(id).unwrap_or(id); - self.reactive.contains(&canonical) - } - - fn mark_reactive(&mut self, id: IdentifierId) { - let canonical = self.aliased_identifiers.find_opt(id).unwrap_or(id); - if self.reactive.insert(canonical) { - self.has_changes = true; - } - } - - /// Reset change tracking, returns true if there were changes. - fn snapshot(&mut self) -> bool { - let had_changes = self.has_changes; - self.has_changes = false; - had_changes - } -} - -// ============================================================================= -// StableSidemap -// ============================================================================= - -struct StableSidemap { - map: HashMap<IdentifierId, bool>, -} - -impl StableSidemap { - fn new() -> Self { - StableSidemap { - map: HashMap::new(), - } - } - - fn handle_instruction(&mut self, instr: &react_compiler_hir::Instruction, env: &Environment) { - let lvalue_id = instr.lvalue.identifier; - let value = &instr.value; - - match value { - InstructionValue::CallExpression { callee, .. } => { - let callee_ty = - &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize]; - if evaluates_to_stable_type_or_container(env, callee_ty) { - let lvalue_ty = - &env.types[env.identifiers[lvalue_id.0 as usize].type_.0 as usize]; - if is_stable_type(lvalue_ty) { - self.map.insert(lvalue_id, true); - } else { - self.map.insert(lvalue_id, false); - } - } - } - InstructionValue::MethodCall { property, .. } => { - let property_ty = - &env.types[env.identifiers[property.identifier.0 as usize].type_.0 as usize]; - if evaluates_to_stable_type_or_container(env, property_ty) { - let lvalue_ty = - &env.types[env.identifiers[lvalue_id.0 as usize].type_.0 as usize]; - if is_stable_type(lvalue_ty) { - self.map.insert(lvalue_id, true); - } else { - self.map.insert(lvalue_id, false); - } - } - } - InstructionValue::PropertyLoad { object, .. } => { - let source_id = object.identifier; - if self.map.contains_key(&source_id) { - let lvalue_ty = - &env.types[env.identifiers[lvalue_id.0 as usize].type_.0 as usize]; - if is_stable_type_container(lvalue_ty) { - self.map.insert(lvalue_id, false); - } else if is_stable_type(lvalue_ty) { - self.map.insert(lvalue_id, true); - } - } - } - InstructionValue::Destructure { value: val, .. } => { - let source_id = val.identifier; - if self.map.contains_key(&source_id) { - let lvalue_ids: Vec<IdentifierId> = visitors::each_instruction_lvalue(instr) - .into_iter() - .map(|p| p.identifier) - .collect(); - for lid in lvalue_ids { - let lid_ty = &env.types[env.identifiers[lid.0 as usize].type_.0 as usize]; - if is_stable_type_container(lid_ty) { - self.map.insert(lid, false); - } else if is_stable_type(lid_ty) { - self.map.insert(lid, true); - } - } - } - } - InstructionValue::StoreLocal { - lvalue, value: val, .. - } => { - if let Some(&entry) = self.map.get(&val.identifier) { - self.map.insert(lvalue_id, entry); - self.map.insert(lvalue.place.identifier, entry); - } - } - InstructionValue::LoadLocal { place, .. } => { - if let Some(&entry) = self.map.get(&place.identifier) { - self.map.insert(lvalue_id, entry); - } - } - _ => {} - } - } - - fn is_stable(&self, id: IdentifierId) -> bool { - self.map.get(&id).copied().unwrap_or(false) - } -} - -// ============================================================================= -// Control dominators (ported from ControlDominators.ts) -// ============================================================================= - -fn is_reactive_controlled_block( - block_id: BlockId, - func: &HirFunction, - post_dominators: &react_compiler_hir::dominator::PostDominator, - reactive_map: &mut ReactivityMap, -) -> bool { - let frontier = post_dominator_frontier(func, post_dominators, block_id); - for frontier_block_id in &frontier { - let control_block = func.body.blocks.get(frontier_block_id).unwrap(); - match &control_block.terminal { - Terminal::If { test, .. } | Terminal::Branch { test, .. } => { - if reactive_map.is_reactive(test.identifier) { - return true; - } - } - Terminal::Switch { test, cases, .. } => { - if reactive_map.is_reactive(test.identifier) { - return true; - } - for case in cases { - if let Some(ref case_test) = case.test { - if reactive_map.is_reactive(case_test.identifier) { - return true; - } - } - } - } - _ => {} - } - } - false -} - -// ============================================================================= -// Type helpers (ported from HIR.ts) -// ============================================================================= - -use react_compiler_hir::is_use_operator_type; - -fn get_hook_kind_for_type<'a>( - env: &'a Environment, - ty: &Type, -) -> Result<Option<&'a HookKind>, CompilerDiagnostic> { - env.get_hook_kind_for_type(ty) -} - -fn is_stable_type(ty: &Type) -> bool { - match ty { - Type::Function { - shape_id: Some(id), .. - } => { - matches!( - id.as_str(), - "BuiltInSetState" - | "BuiltInSetActionState" - | "BuiltInDispatch" - | "BuiltInStartTransition" - | "BuiltInSetOptimistic" - ) - } - Type::Object { shape_id: Some(id) } => { - matches!(id.as_str(), "BuiltInUseRefId") - } - _ => false, - } -} - -fn is_stable_type_container(ty: &Type) -> bool { - match ty { - Type::Object { shape_id: Some(id) } => { - matches!( - id.as_str(), - "BuiltInUseState" - | "BuiltInUseActionState" - | "BuiltInUseReducer" - | "BuiltInUseOptimistic" - | "BuiltInUseTransition" - ) - } - _ => false, - } -} - -fn evaluates_to_stable_type_or_container(env: &Environment, callee_ty: &Type) -> bool { - if let Some(hook_kind) = get_hook_kind_for_type(env, callee_ty).ok().flatten() { - matches!( - hook_kind, - HookKind::UseState - | HookKind::UseReducer - | HookKind::UseActionState - | HookKind::UseRef - | HookKind::UseTransition - | HookKind::UseOptimistic - ) - } else { - false - } -} - -// ============================================================================= -// Propagate reactivity to inner functions -// ============================================================================= - -fn propagate_reactivity_to_inner_functions_outer( - func: &HirFunction, - env: &Environment, - reactive_map: &mut ReactivityMap, -) { - for (_block_id, block) in &func.body.blocks { - for instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - propagate_reactivity_to_inner_functions_inner( - lowered_func.func, - env, - reactive_map, - ); - } - _ => {} - } - } - } -} - -fn propagate_reactivity_to_inner_functions_inner( - func_id: FunctionId, - env: &Environment, - reactive_map: &mut ReactivityMap, -) { - let inner_func = &env.functions[func_id.0 as usize]; - - for (_block_id, block) in &inner_func.body.blocks { - for instr_id in &block.instructions { - let instr = &inner_func.instructions[instr_id.0 as usize]; - - for op in visitors::each_instruction_value_operand(&instr.value, env) { - reactive_map.is_reactive(op.identifier); - } - - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - propagate_reactivity_to_inner_functions_inner( - lowered_func.func, - env, - reactive_map, - ); - } - _ => {} - } - } - - for op in visitors::each_terminal_operand(&block.terminal) { - reactive_map.is_reactive(op.identifier); - } - } -} - -// ============================================================================= -// Apply reactive flags to the HIR (replay pass) -// ============================================================================= - -fn apply_reactive_flags_replay( - func: &mut HirFunction, - env: &mut Environment, - reactive_map: &mut ReactivityMap, - stable_sidemap: &mut StableSidemap, - phi_operand_reactive: &HashMap<(BlockId, usize, usize), bool>, -) { - let reactive_ids = build_reactive_id_set(reactive_map); - - // 1. Mark params - for param in &mut func.params { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &mut s.place, - }; - place.reactive = true; - } - - // 2. Walk blocks - let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect(); - - for block_id in &block_ids { - let block = func.body.blocks.get(block_id).unwrap(); - - // 2a. Phi nodes - let phi_count = block.phis.len(); - for phi_idx in 0..phi_count { - let block = func.body.blocks.get_mut(block_id).unwrap(); - let phi = &mut block.phis[phi_idx]; - - if reactive_ids.contains(&phi.place.identifier) { - phi.place.reactive = true; - } - - for (op_idx, (_pred, operand)) in phi.operands.iter_mut().enumerate() { - if let Some(&is_reactive) = phi_operand_reactive.get(&(*block_id, phi_idx, op_idx)) - { - if is_reactive { - operand.reactive = true; - } - } - } - } - - // 2b. Instructions - let block = func.body.blocks.get(block_id).unwrap(); - let instr_ids: Vec<react_compiler_hir::InstructionId> = block.instructions.clone(); - - for instr_id in &instr_ids { - let instr = &func.instructions[instr_id.0 as usize]; - - // Compute hasReactiveInput by checking value operands - let value_operand_ids: Vec<IdentifierId> = - visitors::each_instruction_value_operand(&instr.value, env) - .into_iter() - .map(|p| p.identifier) - .collect(); - let mut has_reactive_input = false; - for &op_id in &value_operand_ids { - if reactive_ids.contains(&op_id) { - has_reactive_input = true; - } - } - - // Check hooks/use - match &instr.value { - InstructionValue::CallExpression { callee, .. } => { - let callee_ty = - &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize]; - if get_hook_kind_for_type(env, callee_ty) - .ok() - .flatten() - .is_some() - || is_use_operator_type(callee_ty) - { - has_reactive_input = true; - } - } - InstructionValue::MethodCall { property, .. } => { - let property_ty = &env.types - [env.identifiers[property.identifier.0 as usize].type_.0 as usize]; - if get_hook_kind_for_type(env, property_ty) - .ok() - .flatten() - .is_some() - || is_use_operator_type(property_ty) - { - has_reactive_input = true; - } - } - _ => {} - } - - // Value operands: set reactive flag using canonical visitor - let instr = &mut func.instructions[instr_id.0 as usize]; - visitors::for_each_instruction_value_operand_mut(&mut instr.value, &mut |place| { - if reactive_ids.contains(&place.identifier) { - place.reactive = true; - } - }); - // FunctionExpression/ObjectMethod context variables require env access - if let InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } = &mut instr.value - { - let inner_func = &mut env.functions[lowered_func.func.0 as usize]; - for ctx in &mut inner_func.context { - if reactive_ids.contains(&ctx.identifier) { - ctx.reactive = true; - } - } - } - - // Lvalues: markReactive is called only when hasReactiveInput - if has_reactive_input { - let lvalue_id = instr.lvalue.identifier; - if !stable_sidemap.is_stable(lvalue_id) && reactive_ids.contains(&lvalue_id) { - instr.lvalue.reactive = true; - } - // Handle value lvalues — includes DeclareContext/StoreContext which - // for_each_instruction_lvalue_mut skips, so we use a direct match. - match &mut instr.value { - InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::DeclareContext { lvalue, .. } - | InstructionValue::StoreLocal { lvalue, .. } - | InstructionValue::StoreContext { lvalue, .. } => { - let id = lvalue.place.identifier; - if !stable_sidemap.is_stable(id) && reactive_ids.contains(&id) { - lvalue.place.reactive = true; - } - } - InstructionValue::Destructure { lvalue, .. } => { - visitors::for_each_pattern_operand_mut(&mut lvalue.pattern, &mut |place| { - if !stable_sidemap.is_stable(place.identifier) - && reactive_ids.contains(&place.identifier) - { - place.reactive = true; - } - }); - } - InstructionValue::PrefixUpdate { lvalue, .. } - | InstructionValue::PostfixUpdate { lvalue, .. } => { - let id = lvalue.identifier; - if !stable_sidemap.is_stable(id) && reactive_ids.contains(&id) { - lvalue.reactive = true; - } - } - _ => {} - } - } - } - - // 2c. Terminal operands - let block = func.body.blocks.get_mut(block_id).unwrap(); - visitors::for_each_terminal_operand_mut(&mut block.terminal, &mut |place| { - if reactive_ids.contains(&place.identifier) { - place.reactive = true; - } - }); - } - - // 3. Apply to inner functions - apply_reactive_flags_to_inner_functions(func, env, &reactive_ids); -} - -fn build_reactive_id_set(reactive_map: &mut ReactivityMap) -> HashSet<IdentifierId> { - let mut result = HashSet::new(); - for &id in &reactive_map.reactive { - result.insert(id); - } - let reactive = &reactive_map.reactive; - reactive_map.aliased_identifiers.for_each(|id, canonical| { - if reactive.contains(&canonical) { - result.insert(id); - } - }); - result -} - -fn apply_reactive_flags_to_inner_functions( - func: &HirFunction, - env: &mut Environment, - reactive_ids: &HashSet<IdentifierId>, -) { - for (_block_id, block) in &func.body.blocks { - for instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - apply_reactive_flags_to_inner_func(lowered_func.func, env, reactive_ids); - } - _ => {} - } - } - } -} - -fn apply_reactive_flags_to_inner_func( - func_id: FunctionId, - env: &mut Environment, - reactive_ids: &HashSet<IdentifierId>, -) { - // Collect nested function IDs first to avoid borrow issues - let nested_func_ids: Vec<FunctionId> = { - let func = &env.functions[func_id.0 as usize]; - let mut ids = Vec::new(); - for (_block_id, block) in &func.body.blocks { - for instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - ids.push(lowered_func.func); - } - _ => {} - } - } - } - ids - }; - - // Apply reactive flags using canonical visitors - let inner_func = &mut env.functions[func_id.0 as usize]; - for (_block_id, block) in &mut inner_func.body.blocks { - for instr_id in &block.instructions { - let instr = &mut inner_func.instructions[instr_id.0 as usize]; - visitors::for_each_instruction_value_operand_mut(&mut instr.value, &mut |place| { - if reactive_ids.contains(&place.identifier) { - place.reactive = true; - } - }); - } - visitors::for_each_terminal_operand_mut(&mut block.terminal, &mut |place| { - if reactive_ids.contains(&place.identifier) { - place.reactive = true; - } - }); - } - - // Recurse into nested functions, and set reactive on their context variables - for nested_id in nested_func_ids { - let nested_func = &mut env.functions[nested_id.0 as usize]; - for ctx in &mut nested_func.context { - if reactive_ids.contains(&ctx.identifier) { - ctx.reactive = true; - } - } - apply_reactive_flags_to_inner_func(nested_id, env, reactive_ids); - } -} diff --git a/compiler/crates/react_compiler_inference/src/infer_reactive_scope_variables.rs b/compiler/crates/react_compiler_inference/src/infer_reactive_scope_variables.rs deleted file mode 100644 index a64c73e91411..000000000000 --- a/compiler/crates/react_compiler_inference/src/infer_reactive_scope_variables.rs +++ /dev/null @@ -1,389 +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. - -//! Infers which variables belong to reactive scopes. -//! -//! Ported from TypeScript `src/ReactiveScopes/InferReactiveScopeVariables.ts`. -//! -//! This is the 1st of 4 passes that determine how to break a function into -//! discrete reactive scopes (independently memoizable units of code): -//! 1. InferReactiveScopeVariables (this pass, on HIR) determines operands that -//! mutate together and assigns them a unique reactive scope. -//! 2. AlignReactiveScopesToBlockScopes aligns reactive scopes to block scopes. -//! 3. MergeOverlappingReactiveScopes ensures scopes do not overlap. -//! 4. BuildReactiveBlocks groups the statements for each scope. - -use std::collections::HashMap; - -use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors; -use react_compiler_hir::{ - DeclarationId, EvaluationOrder, HirFunction, IdentifierId, InstructionValue, Pattern, Position, - SourceLocation, -}; -use react_compiler_utils::DisjointSet; - -// ============================================================================= -// Public API -// ============================================================================= - -/// Infer reactive scope variables for a function. -/// -/// For each mutable variable, infers a reactive scope which will construct that -/// variable. Variables that co-mutate are assigned to the same reactive scope. -/// -/// Corresponds to TS `inferReactiveScopeVariables(fn: HIRFunction): void`. -pub fn infer_reactive_scope_variables( - func: &mut HirFunction, - env: &mut Environment, -) -> Result<(), CompilerDiagnostic> { - // Phase 1: find disjoint sets of co-mutating identifiers - let mut scope_identifiers = find_disjoint_mutable_values(func, env); - - // Phase 2: assign scopes - // Maps each group root identifier to the ScopeId assigned to that group. - let mut scopes: HashMap<IdentifierId, ScopeState> = HashMap::new(); - - scope_identifiers.for_each(|identifier_id, group_id| { - let ident_range = env.identifiers[identifier_id.0 as usize] - .mutable_range - .clone(); - let ident_loc = env.identifiers[identifier_id.0 as usize].loc; - - let state = scopes.entry(group_id).or_insert_with(|| { - let scope_id = env.next_scope_id(); - // Initialize scope range from the first member - let scope = &mut env.scopes[scope_id.0 as usize]; - scope.range = ident_range.clone(); - ScopeState { - scope_id, - loc: ident_loc, - } - }); - - // Update scope range - let scope = &mut env.scopes[state.scope_id.0 as usize]; - - // If this is not the first identifier (scope was already created), merge ranges - if scope.range.start != ident_range.start || scope.range.end != ident_range.end { - if scope.range.start == EvaluationOrder(0) { - scope.range.start = ident_range.start; - } else if ident_range.start != EvaluationOrder(0) { - scope.range.start = EvaluationOrder(scope.range.start.0.min(ident_range.start.0)); - } - scope.range.end = EvaluationOrder(scope.range.end.0.max(ident_range.end.0)); - } - - // Merge location - state.loc = merge_location(state.loc, ident_loc); - - // Assign the scope to this identifier - let scope_id = state.scope_id; - env.identifiers[identifier_id.0 as usize].scope = Some(scope_id); - }); - - // Set loc on each scope - for (_group_id, state) in &scopes { - env.scopes[state.scope_id.0 as usize].loc = state.loc; - } - - // Update each identifier's mutable_range to match its scope's range - for (&_identifier_id, state) in &scopes { - let scope_range = env.scopes[state.scope_id.0 as usize].range.clone(); - // Find all identifiers with this scope and update their mutable_range - // We iterate through all identifiers and check their scope - for ident in &mut env.identifiers { - if ident.scope == Some(state.scope_id) { - ident.mutable_range = scope_range.clone(); - } - } - } - - // Validate scope ranges - let mut max_instruction = EvaluationOrder(0); - for (_block_id, block) in &func.body.blocks { - for instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - max_instruction = EvaluationOrder(max_instruction.0.max(instr.id.0)); - } - max_instruction = - EvaluationOrder(max_instruction.0.max(block.terminal.evaluation_order().0)); - } - - for (_group_id, state) in &scopes { - let scope = &env.scopes[state.scope_id.0 as usize]; - if scope.range.start == EvaluationOrder(0) - || scope.range.end == EvaluationOrder(0) - || max_instruction == EvaluationOrder(0) - || scope.range.end.0 > max_instruction.0 + 1 - { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - &format!( - "Invalid mutable range for scope: Scope @{} has range [{}:{}] but the valid range is [1:{}]", - scope.id.0, - scope.range.start.0, - scope.range.end.0, - max_instruction.0 + 1, - ), - None, - )); - } - } - - Ok(()) -} - -struct ScopeState { - scope_id: react_compiler_hir::ScopeId, - loc: Option<SourceLocation>, -} - -/// Merge two source locations, preferring non-None values. -/// Corresponds to TS `mergeLocation`. -fn merge_location(l: Option<SourceLocation>, r: Option<SourceLocation>) -> Option<SourceLocation> { - match (l, r) { - (None, r) => r, - (l, None) => l, - (Some(l), Some(r)) => Some(SourceLocation { - start: Position { - line: l.start.line.min(r.start.line), - column: l.start.column.min(r.start.column), - index: match (l.start.index, r.start.index) { - (Some(a), Some(b)) => Some(a.min(b)), - (a, b) => a.or(b), - }, - }, - end: Position { - line: l.end.line.max(r.end.line), - column: l.end.column.max(r.end.column), - index: match (l.end.index, r.end.index) { - (Some(a), Some(b)) => Some(a.max(b)), - (a, b) => a.or(b), - }, - }, - }), - } -} - -// ============================================================================= -// is_mutable / in_range helpers -// ============================================================================= - -// ============================================================================= -// may_allocate -// ============================================================================= - -/// Check if an instruction may allocate. Corresponds to TS `mayAllocate`. -fn may_allocate(value: &InstructionValue, lvalue_type_is_primitive: bool) -> bool { - match value { - InstructionValue::Destructure { lvalue, .. } => { - visitors::does_pattern_contain_spread_element(&lvalue.pattern) - } - InstructionValue::PostfixUpdate { .. } - | InstructionValue::PrefixUpdate { .. } - | InstructionValue::Await { .. } - | InstructionValue::DeclareLocal { .. } - | InstructionValue::DeclareContext { .. } - | InstructionValue::StoreLocal { .. } - | InstructionValue::LoadGlobal { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::TypeCastExpression { .. } - | InstructionValue::LoadLocal { .. } - | InstructionValue::LoadContext { .. } - | InstructionValue::StoreContext { .. } - | InstructionValue::PropertyDelete { .. } - | InstructionValue::ComputedLoad { .. } - | InstructionValue::ComputedDelete { .. } - | InstructionValue::JSXText { .. } - | InstructionValue::TemplateLiteral { .. } - | InstructionValue::Primitive { .. } - | InstructionValue::GetIterator { .. } - | InstructionValue::IteratorNext { .. } - | InstructionValue::NextPropertyOf { .. } - | InstructionValue::Debugger { .. } - | InstructionValue::StartMemoize { .. } - | InstructionValue::FinishMemoize { .. } - | InstructionValue::UnaryExpression { .. } - | InstructionValue::BinaryExpression { .. } - | InstructionValue::PropertyLoad { .. } - | InstructionValue::StoreGlobal { .. } => false, - - InstructionValue::TaggedTemplateExpression { .. } - | InstructionValue::CallExpression { .. } - | InstructionValue::MethodCall { .. } => !lvalue_type_is_primitive, - - InstructionValue::RegExpLiteral { .. } - | InstructionValue::PropertyStore { .. } - | InstructionValue::ComputedStore { .. } - | InstructionValue::ArrayExpression { .. } - | InstructionValue::JsxExpression { .. } - | InstructionValue::JsxFragment { .. } - | InstructionValue::NewExpression { .. } - | InstructionValue::ObjectExpression { .. } - | InstructionValue::UnsupportedNode { .. } - | InstructionValue::ObjectMethod { .. } - | InstructionValue::FunctionExpression { .. } => true, - } -} - -// ============================================================================= -// Pattern helpers -// ============================================================================= - -/// Collect all Place identifiers from a destructure pattern. -/// Corresponds to TS `eachPatternOperand`. -fn each_pattern_operand(pattern: &Pattern) -> Vec<IdentifierId> { - visitors::each_pattern_operand(pattern) - .into_iter() - .map(|p| p.identifier) - .collect() -} - -/// Collect all operand identifiers from an instruction value. -/// Corresponds to TS `eachInstructionValueOperand`. -fn each_instruction_value_operand( - value: &InstructionValue, - env: &Environment, -) -> Vec<IdentifierId> { - visitors::each_instruction_value_operand(value, env) - .into_iter() - .map(|p| p.identifier) - .collect() -} - -// ============================================================================= -// findDisjointMutableValues -// ============================================================================= - -/// Find disjoint sets of co-mutating identifier IDs. -/// -/// Corresponds to TS `findDisjointMutableValues(fn: HIRFunction): DisjointSet<Identifier>`. -pub(crate) fn find_disjoint_mutable_values( - func: &HirFunction, - env: &Environment, -) -> DisjointSet<IdentifierId> { - let mut scope_identifiers = DisjointSet::<IdentifierId>::new(); - let mut declarations: HashMap<DeclarationId, IdentifierId> = HashMap::new(); - - let enable_forest = env.config.enable_forest; - - for (_block_id, block) in &func.body.blocks { - // Handle phi nodes - for phi in &block.phis { - let phi_id = phi.place.identifier; - let phi_range = &env.identifiers[phi_id.0 as usize].mutable_range; - let phi_decl_id = env.identifiers[phi_id.0 as usize].declaration_id; - - let first_instr_id = block - .instructions - .first() - .map(|iid| func.instructions[iid.0 as usize].id) - .unwrap_or(block.terminal.evaluation_order()); - - if phi_range.start.0 + 1 != phi_range.end.0 && phi_range.end > first_instr_id { - let mut operands = vec![phi_id]; - if let Some(&decl_id) = declarations.get(&phi_decl_id) { - operands.push(decl_id); - } - for (_pred_id, phi_operand) in &phi.operands { - operands.push(phi_operand.identifier); - } - scope_identifiers.union(&operands); - } else if enable_forest { - for (_pred_id, phi_operand) in &phi.operands { - scope_identifiers.union(&[phi_id, phi_operand.identifier]); - } - } - } - - // Handle instructions - for instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let mut operands: Vec<IdentifierId> = Vec::new(); - - let lvalue_id = instr.lvalue.identifier; - let lvalue_range = &env.identifiers[lvalue_id.0 as usize].mutable_range; - let lvalue_type = &env.types[env.identifiers[lvalue_id.0 as usize].type_.0 as usize]; - let lvalue_type_is_primitive = react_compiler_hir::is_primitive_type(lvalue_type); - - if lvalue_range.end.0 > lvalue_range.start.0 + 1 - || may_allocate(&instr.value, lvalue_type_is_primitive) - { - operands.push(lvalue_id); - } - - match &instr.value { - InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::DeclareContext { lvalue, .. } => { - let place_id = lvalue.place.identifier; - let decl_id = env.identifiers[place_id.0 as usize].declaration_id; - declarations.entry(decl_id).or_insert(place_id); - } - InstructionValue::StoreLocal { lvalue, value, .. } - | InstructionValue::StoreContext { lvalue, value, .. } => { - let place_id = lvalue.place.identifier; - let decl_id = env.identifiers[place_id.0 as usize].declaration_id; - declarations.entry(decl_id).or_insert(place_id); - - let place_range = &env.identifiers[place_id.0 as usize].mutable_range; - if place_range.end.0 > place_range.start.0 + 1 { - operands.push(place_id); - } - - let value_range = &env.identifiers[value.identifier.0 as usize].mutable_range; - if value_range.contains(instr.id) && value_range.start.0 > 0 { - operands.push(value.identifier); - } - } - InstructionValue::Destructure { lvalue, value, .. } => { - let pattern_places = each_pattern_operand(&lvalue.pattern); - for place_id in &pattern_places { - let decl_id = env.identifiers[place_id.0 as usize].declaration_id; - declarations.entry(decl_id).or_insert(*place_id); - - let place_range = &env.identifiers[place_id.0 as usize].mutable_range; - if place_range.end.0 > place_range.start.0 + 1 { - operands.push(*place_id); - } - } - - let value_range = &env.identifiers[value.identifier.0 as usize].mutable_range; - if value_range.contains(instr.id) && value_range.start.0 > 0 { - operands.push(value.identifier); - } - } - InstructionValue::MethodCall { property, .. } => { - // For MethodCall: include all mutable operands plus the computed property - let all_operands = each_instruction_value_operand(&instr.value, env); - for op_id in &all_operands { - let op_range = &env.identifiers[op_id.0 as usize].mutable_range; - if op_range.contains(instr.id) && op_range.start.0 > 0 { - operands.push(*op_id); - } - } - // Ensure method property is in the same scope as the call - operands.push(property.identifier); - } - _ => { - // For all other instructions: include mutable operands - let all_operands = each_instruction_value_operand(&instr.value, env); - for op_id in &all_operands { - let op_range = &env.identifiers[op_id.0 as usize].mutable_range; - if op_range.contains(instr.id) && op_range.start.0 > 0 { - operands.push(*op_id); - } - } - } - } - - if !operands.is_empty() { - scope_identifiers.union(&operands); - } - } - } - scope_identifiers -} diff --git a/compiler/crates/react_compiler_inference/src/lib.rs b/compiler/crates/react_compiler_inference/src/lib.rs deleted file mode 100644 index 45021a25e1a6..000000000000 --- a/compiler/crates/react_compiler_inference/src/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -pub mod align_method_call_scopes; -pub mod align_object_method_scopes; -pub mod align_reactive_scopes_to_block_scopes_hir; -pub mod analyse_functions; -pub mod build_reactive_scope_terminals_hir; -pub mod flatten_reactive_loops_hir; -pub mod flatten_scopes_with_hooks_or_use_hir; -pub mod infer_mutation_aliasing_effects; -pub mod infer_mutation_aliasing_ranges; -pub mod infer_reactive_places; -pub mod infer_reactive_scope_variables; -pub mod memoize_fbt_and_macro_operands_in_same_scope; -pub mod merge_overlapping_reactive_scopes_hir; -pub mod propagate_scope_dependencies_hir; - -pub use align_method_call_scopes::align_method_call_scopes; -pub use align_object_method_scopes::align_object_method_scopes; -pub use align_reactive_scopes_to_block_scopes_hir::align_reactive_scopes_to_block_scopes_hir; -pub use analyse_functions::analyse_functions; -pub use build_reactive_scope_terminals_hir::build_reactive_scope_terminals_hir; -pub use flatten_reactive_loops_hir::flatten_reactive_loops_hir; -pub use flatten_scopes_with_hooks_or_use_hir::flatten_scopes_with_hooks_or_use_hir; -pub use infer_mutation_aliasing_effects::infer_mutation_aliasing_effects; -pub use infer_mutation_aliasing_ranges::infer_mutation_aliasing_ranges; -pub use infer_reactive_places::infer_reactive_places; -pub use infer_reactive_scope_variables::infer_reactive_scope_variables; -pub use memoize_fbt_and_macro_operands_in_same_scope::memoize_fbt_and_macro_operands_in_same_scope; -pub use merge_overlapping_reactive_scopes_hir::merge_overlapping_reactive_scopes_hir; -pub use propagate_scope_dependencies_hir::propagate_scope_dependencies_hir; diff --git a/compiler/crates/react_compiler_inference/src/memoize_fbt_and_macro_operands_in_same_scope.rs b/compiler/crates/react_compiler_inference/src/memoize_fbt_and_macro_operands_in_same_scope.rs deleted file mode 100644 index f63ca1c418ef..000000000000 --- a/compiler/crates/react_compiler_inference/src/memoize_fbt_and_macro_operands_in_same_scope.rs +++ /dev/null @@ -1,376 +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. - -//! Port of MemoizeFbtAndMacroOperandsInSameScope from TypeScript. -//! -//! Ensures that FBT (Facebook Translation) expressions and their operands -//! are memoized within the same reactive scope. Also supports user-configured -//! custom macro-like APIs via `customMacros` configuration. -//! -//! The pass has two phases: -//! 1. Forward data-flow: identify all macro tags (including property loads like `fbt.param`) -//! 2. Reverse data-flow: merge arguments of macro invocations into the same scope - -use std::collections::{HashMap, HashSet}; - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors; -use react_compiler_hir::{ - HirFunction, IdentifierId, InstructionValue, JsxTag, PrimitiveValue, PropertyLiteral, ScopeId, -}; - -/// Whether a macro requires its arguments to be transitively inlined (e.g., fbt) -/// or just avoids having the top-level values be converted to variables (e.g., fbt.param). -#[derive(Debug, Clone)] -enum InlineLevel { - Transitive, - Shallow, -} - -/// Defines how a macro and its properties should be handled. -#[derive(Debug, Clone)] -struct MacroDefinition { - level: InlineLevel, - /// Maps property names to their own MacroDefinition. `"*"` is a wildcard. - properties: Option<HashMap<String, MacroDefinition>>, -} - -fn shallow_macro() -> MacroDefinition { - MacroDefinition { - level: InlineLevel::Shallow, - properties: None, - } -} - -fn transitive_macro() -> MacroDefinition { - MacroDefinition { - level: InlineLevel::Transitive, - properties: None, - } -} - -fn fbt_macro() -> MacroDefinition { - let mut props = HashMap::new(); - props.insert("*".to_string(), shallow_macro()); - // fbt.enum gets FBT_MACRO (recursive/transitive) - // We'll fill this in after construction since it's self-referential. - // Instead, we use a special marker and handle it in property lookup. - let mut fbt = MacroDefinition { - level: InlineLevel::Transitive, - properties: Some(props), - }; - // Add "enum" as a recursive reference (same as FBT_MACRO) - // Since we can't do self-referential structs, we clone the structure. - let enum_macro = MacroDefinition { - level: InlineLevel::Transitive, - properties: Some({ - let mut p = HashMap::new(); - p.insert("*".to_string(), shallow_macro()); - // enum's enum is also recursive, but in practice the depth is bounded - p.insert("enum".to_string(), transitive_macro()); - p - }), - }; - fbt.properties - .as_mut() - .unwrap() - .insert("enum".to_string(), enum_macro); - fbt -} - -/// Built-in FBT tags and their macro definitions. -fn fbt_tags() -> HashMap<String, MacroDefinition> { - let mut tags = HashMap::new(); - tags.insert("fbt".to_string(), fbt_macro()); - tags.insert("fbt:param".to_string(), shallow_macro()); - tags.insert("fbt:enum".to_string(), fbt_macro()); - tags.insert("fbt:plural".to_string(), shallow_macro()); - tags.insert("fbs".to_string(), fbt_macro()); - tags.insert("fbs:param".to_string(), shallow_macro()); - tags.insert("fbs:enum".to_string(), fbt_macro()); - tags.insert("fbs:plural".to_string(), shallow_macro()); - tags -} - -/// Main entry point. Returns the set of identifier IDs that are fbt/macro operands. -pub fn memoize_fbt_and_macro_operands_in_same_scope( - func: &HirFunction, - env: &mut Environment, -) -> HashSet<IdentifierId> { - // Phase 1: Build macro kinds map from built-in FBT tags + custom macros - let mut macro_kinds: HashMap<String, MacroDefinition> = fbt_tags(); - if let Some(ref custom_macros) = env.config.custom_macros { - for name in custom_macros { - macro_kinds.insert(name.clone(), transitive_macro()); - } - } - - // Phase 2: Forward data-flow to identify all macro tags - let mut macro_tags = populate_macro_tags(func, ¯o_kinds); - - // Phase 3: Reverse data-flow to merge arguments of macro invocations - let macro_values = merge_macro_arguments(func, env, &mut macro_tags, ¯o_kinds); - - macro_values -} - -/// Forward data-flow analysis to identify all macro tags, including -/// things like `fbt.foo.bar(...)`. -fn populate_macro_tags( - func: &HirFunction, - macro_kinds: &HashMap<String, MacroDefinition>, -) -> HashMap<IdentifierId, MacroDefinition> { - let mut macro_tags: HashMap<IdentifierId, MacroDefinition> = HashMap::new(); - - for block in func.body.blocks.values() { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - - match &instr.value { - InstructionValue::Primitive { - value: PrimitiveValue::String(s), - .. - } => { - if let Some(macro_def) = macro_kinds.get(s.as_str()) { - // We don't distinguish between tag names and strings, so record - // all `fbt` string literals in case they are used as a jsx tag. - macro_tags.insert(lvalue_id, macro_def.clone()); - } - } - InstructionValue::LoadGlobal { binding, .. } => { - let name = binding.name(); - if let Some(macro_def) = macro_kinds.get(name) { - macro_tags.insert(lvalue_id, macro_def.clone()); - } - } - InstructionValue::PropertyLoad { - object, property, .. - } => { - if let PropertyLiteral::String(prop_name) = property { - if let Some(macro_def) = macro_tags.get(&object.identifier).cloned() { - let property_macro = if let Some(ref props) = macro_def.properties { - let prop_def = - props.get(prop_name.as_str()).or_else(|| props.get("*")); - match prop_def { - Some(def) => def.clone(), - None => macro_def.clone(), - } - } else { - macro_def.clone() - }; - macro_tags.insert(lvalue_id, property_macro); - } - } - } - _ => {} - } - } - } - - macro_tags -} - -/// Reverse data-flow analysis to merge arguments to macro *invocations* -/// based on the kind of the macro. -fn merge_macro_arguments( - func: &HirFunction, - env: &mut Environment, - macro_tags: &mut HashMap<IdentifierId, MacroDefinition>, - macro_kinds: &HashMap<String, MacroDefinition>, -) -> HashSet<IdentifierId> { - let mut macro_values: HashSet<IdentifierId> = macro_tags.keys().copied().collect(); - - // Iterate blocks in reverse order - let block_ids: Vec<_> = func.body.blocks.keys().copied().collect(); - for &block_id in block_ids.iter().rev() { - let block = &func.body.blocks[&block_id]; - - // Iterate instructions in reverse order - for &instr_id in block.instructions.iter().rev() { - let instr = &func.instructions[instr_id.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - - match &instr.value { - // Instructions that never need to be merged - InstructionValue::DeclareContext { .. } - | InstructionValue::DeclareLocal { .. } - | InstructionValue::Destructure { .. } - | InstructionValue::LoadContext { .. } - | InstructionValue::LoadLocal { .. } - | InstructionValue::PostfixUpdate { .. } - | InstructionValue::PrefixUpdate { .. } - | InstructionValue::StoreContext { .. } - | InstructionValue::StoreLocal { .. } => { - // Skip these - } - - InstructionValue::CallExpression { callee, .. } - | InstructionValue::MethodCall { - property: callee, .. - } => { - let scope_id = match env.identifiers[lvalue_id.0 as usize].scope { - Some(s) => s, - None => continue, - }; - - let macro_def = macro_tags - .get(&callee.identifier) - .or_else(|| macro_tags.get(&lvalue_id)) - .cloned(); - - if let Some(macro_def) = macro_def { - visit_operands( - ¯o_def, - scope_id, - lvalue_id, - &instr.value, - env, - &mut macro_values, - macro_tags, - ); - } - } - - InstructionValue::JsxExpression { tag, .. } => { - let scope_id = match env.identifiers[lvalue_id.0 as usize].scope { - Some(s) => s, - None => continue, - }; - - let macro_def = match tag { - JsxTag::Place(place) => macro_tags.get(&place.identifier).cloned(), - JsxTag::Builtin(builtin) => macro_kinds.get(builtin.name.as_str()).cloned(), - }; - - let macro_def = macro_def.or_else(|| macro_tags.get(&lvalue_id).cloned()); - - if let Some(macro_def) = macro_def { - visit_operands( - ¯o_def, - scope_id, - lvalue_id, - &instr.value, - env, - &mut macro_values, - macro_tags, - ); - } - } - - // Default case: check if lvalue is a macro tag - _ => { - let scope_id = match env.identifiers[lvalue_id.0 as usize].scope { - Some(s) => s, - None => continue, - }; - - let macro_def = macro_tags.get(&lvalue_id).cloned(); - if let Some(macro_def) = macro_def { - visit_operands( - ¯o_def, - scope_id, - lvalue_id, - &instr.value, - env, - &mut macro_values, - macro_tags, - ); - } - } - } - } - - // Handle phis - let block = &func.body.blocks[&block_id]; - for phi in &block.phis { - let scope_id = match env.identifiers[phi.place.identifier.0 as usize].scope { - Some(s) => s, - None => continue, - }; - - let macro_def = match macro_tags.get(&phi.place.identifier).cloned() { - Some(def) => def, - None => continue, - }; - - if matches!(macro_def.level, InlineLevel::Shallow) { - continue; - } - - macro_values.insert(phi.place.identifier); - - // Collect operand updates to avoid borrow issues - let operand_updates: Vec<(IdentifierId, MacroDefinition)> = phi - .operands - .values() - .map(|operand| (operand.identifier, macro_def.clone())) - .collect(); - - for (operand_id, def) in operand_updates { - env.identifiers[operand_id.0 as usize].scope = Some(scope_id); - expand_fbt_scope_range(env, scope_id, operand_id); - macro_tags.insert(operand_id, def); - macro_values.insert(operand_id); - } - } - } - - macro_values -} - -/// Expand the scope range on the environment, reading from identifier's mutable_range. -/// Also syncs mutable_range for all identifiers in this scope whose range matched -/// the old scope range. In TS, identifier.mutableRange shares the same object as -/// scope.range, so mutations are automatically visible; in Rust we must propagate. -/// Equivalent to TS `expandFbtScopeRange`. -fn expand_fbt_scope_range(env: &mut Environment, scope_id: ScopeId, operand_id: IdentifierId) { - let extend_start = env.identifiers[operand_id.0 as usize].mutable_range.start; - if extend_start.0 == 0 { - return; - } - let old_range_id = env.scopes[scope_id.0 as usize].range.id; - let old_start = env.scopes[scope_id.0 as usize].range.start; - let new_start = old_start.0.min(extend_start.0); - if new_start == old_start.0 { - return; - } - env.scopes[scope_id.0 as usize].range.start.0 = new_start; - let new_range = env.scopes[scope_id.0 as usize].range.clone(); - for ident in &mut env.identifiers { - if ident.scope == Some(scope_id) && ident.mutable_range.id == old_range_id { - ident.mutable_range = new_range.clone(); - } - } -} - -/// Visit operands for an instruction value, merging them into the same scope -/// if the macro definition requires transitive inlining. -fn visit_operands( - macro_def: &MacroDefinition, - scope_id: ScopeId, - lvalue_id: IdentifierId, - value: &InstructionValue, - env: &mut Environment, - macro_values: &mut HashSet<IdentifierId>, - macro_tags: &mut HashMap<IdentifierId, MacroDefinition>, -) { - macro_values.insert(lvalue_id); - - let operand_ids: Vec<IdentifierId> = - visitors::each_instruction_value_operand_with_functions(value, &env.functions) - .into_iter() - .map(|p| p.identifier) - .collect(); - - for operand_id in operand_ids { - if matches!(macro_def.level, InlineLevel::Transitive) { - env.identifiers[operand_id.0 as usize].scope = Some(scope_id); - expand_fbt_scope_range(env, scope_id, operand_id); - macro_tags.insert(operand_id, macro_def.clone()); - } - macro_values.insert(operand_id); - } -} diff --git a/compiler/crates/react_compiler_inference/src/merge_overlapping_reactive_scopes_hir.rs b/compiler/crates/react_compiler_inference/src/merge_overlapping_reactive_scopes_hir.rs deleted file mode 100644 index 3b8f574bee06..000000000000 --- a/compiler/crates/react_compiler_inference/src/merge_overlapping_reactive_scopes_hir.rs +++ /dev/null @@ -1,414 +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. - -//! Merges reactive scopes that have overlapping ranges. -//! -//! While previous passes ensure that reactive scopes span valid sets of program -//! blocks, pairs of reactive scopes may still be inconsistent with respect to -//! each other. Two scopes must either be entirely disjoint or one must be nested -//! within the other. This pass detects overlapping scopes and merges them. -//! -//! Additionally, if an instruction mutates an outer scope while a different -//! scope is active, those scopes are merged. -//! -//! Ported from TypeScript `src/HIR/MergeOverlappingReactiveScopesHIR.ts`. - -use std::cmp; -use std::collections::HashMap; - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors; -use react_compiler_hir::visitors::{each_instruction_lvalue_ids, each_terminal_operand_ids}; -use react_compiler_hir::{ - EvaluationOrder, HirFunction, IdentifierId, InstructionValue, ScopeId, Type, -}; -use react_compiler_utils::DisjointSet; - -// ============================================================================= -// ScopeInfo -// ============================================================================= - -struct ScopeStartEntry { - id: EvaluationOrder, - scopes: Vec<ScopeId>, -} - -struct ScopeEndEntry { - id: EvaluationOrder, - scopes: Vec<ScopeId>, -} - -struct ScopeInfo { - /// Sorted descending by id (so we can pop from the end for smallest) - scope_starts: Vec<ScopeStartEntry>, - /// Sorted descending by id (so we can pop from the end for smallest) - scope_ends: Vec<ScopeEndEntry>, - /// Maps IdentifierId -> ScopeId for all places that have a scope - place_scopes: HashMap<IdentifierId, ScopeId>, -} - -// ============================================================================= -// TraversalState -// ============================================================================= - -struct TraversalState { - joined: DisjointSet<ScopeId>, - active_scopes: Vec<ScopeId>, -} - -// ============================================================================= -// Helper functions -// ============================================================================= - -/// Check if a scope is active at the given instruction id. -/// Corresponds to TS `isScopeActive(scope, id)`. -fn is_scope_active(env: &Environment, scope_id: ScopeId, id: EvaluationOrder) -> bool { - env.scopes[scope_id.0 as usize].range.contains(id) -} - -/// Get the scope for a place if it's active at the given instruction. -/// Corresponds to TS `getPlaceScope(id, place)`. -fn get_place_scope( - env: &Environment, - id: EvaluationOrder, - identifier_id: IdentifierId, -) -> Option<ScopeId> { - let scope_id = env.identifiers[identifier_id.0 as usize].scope?; - if is_scope_active(env, scope_id, id) { - Some(scope_id) - } else { - None - } -} - -/// Check if a place is mutable at the given instruction. -/// Corresponds to TS `isMutable({id}, place)`. -fn is_mutable(env: &Environment, id: EvaluationOrder, identifier_id: IdentifierId) -> bool { - let range = &env.identifiers[identifier_id.0 as usize].mutable_range; - range.contains(id) -} - -// ============================================================================= -// collectScopeInfo -// ============================================================================= - -fn collect_scope_info(func: &HirFunction, env: &Environment) -> ScopeInfo { - let mut scope_starts_map: HashMap<EvaluationOrder, Vec<ScopeId>> = HashMap::new(); - let mut scope_ends_map: HashMap<EvaluationOrder, Vec<ScopeId>> = HashMap::new(); - let mut place_scopes: HashMap<IdentifierId, ScopeId> = HashMap::new(); - - let mut collect_place_scope = |identifier_id: IdentifierId, env: &Environment| { - let scope_id = match env.identifiers[identifier_id.0 as usize].scope { - Some(s) => s, - None => return, - }; - place_scopes.insert(identifier_id, scope_id); - let range = &env.scopes[scope_id.0 as usize].range; - if range.start != range.end { - scope_starts_map - .entry(range.start) - .or_default() - .push(scope_id); - scope_ends_map.entry(range.end).or_default().push(scope_id); - } - }; - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - // lvalues - let lvalue_ids = each_instruction_lvalue_ids(instr); - for id in lvalue_ids { - collect_place_scope(id, env); - } - // operands - let operand_ids: Vec<IdentifierId> = visitors::each_instruction_operand(instr, env) - .into_iter() - .map(|p| p.identifier) - .collect(); - for id in operand_ids { - collect_place_scope(id, env); - } - } - // terminal operands - let terminal_op_ids = each_terminal_operand_ids(&block.terminal); - for id in terminal_op_ids { - collect_place_scope(id, env); - } - } - - // Deduplicate scope IDs in each entry, preserving insertion order. - // The TS uses Set<ReactiveScope> which preserves insertion order and deduplicates. - // We must NOT sort by ScopeId here — the insertion order determines which scope - // becomes the root in the disjoint set union. - fn dedup_preserve_order(scopes: &mut Vec<ScopeId>) { - let mut seen = std::collections::HashSet::new(); - scopes.retain(|s| seen.insert(*s)); - } - for scopes in scope_starts_map.values_mut() { - dedup_preserve_order(scopes); - } - for scopes in scope_ends_map.values_mut() { - dedup_preserve_order(scopes); - } - - // Convert to sorted vecs (descending by id for pop-from-end) - let mut scope_starts: Vec<ScopeStartEntry> = scope_starts_map - .into_iter() - .map(|(id, scopes)| ScopeStartEntry { id, scopes }) - .collect(); - scope_starts.sort_by(|a, b| b.id.cmp(&a.id)); - - let mut scope_ends: Vec<ScopeEndEntry> = scope_ends_map - .into_iter() - .map(|(id, scopes)| ScopeEndEntry { id, scopes }) - .collect(); - scope_ends.sort_by(|a, b| b.id.cmp(&a.id)); - - ScopeInfo { - scope_starts, - scope_ends, - place_scopes, - } -} - -// ============================================================================= -// visitInstructionId -// ============================================================================= - -fn visit_instruction_id( - id: EvaluationOrder, - scope_info: &mut ScopeInfo, - state: &mut TraversalState, - env: &Environment, -) { - // Handle all scopes that end at this instruction - if let Some(top) = scope_info.scope_ends.last() { - if top.id <= id { - let scope_end_entry = scope_info.scope_ends.pop().unwrap(); - - // Sort scopes by start descending (matching active_scopes order) - let mut scopes_sorted = scope_end_entry.scopes; - scopes_sorted.sort_by(|a, b| { - let a_start = env.scopes[a.0 as usize].range.start; - let b_start = env.scopes[b.0 as usize].range.start; - b_start.cmp(&a_start) - }); - - for scope in &scopes_sorted { - let idx = state.active_scopes.iter().position(|s| s == scope); - if let Some(idx) = idx { - // Detect and merge all overlapping scopes - if idx != state.active_scopes.len() - 1 { - let mut to_union: Vec<ScopeId> = vec![*scope]; - to_union.extend_from_slice(&state.active_scopes[idx + 1..]); - state.joined.union(&to_union); - } - state.active_scopes.remove(idx); - } - } - } - } - - // Handle all scopes that begin at this instruction - if let Some(top) = scope_info.scope_starts.last() { - if top.id <= id { - let scope_start_entry = scope_info.scope_starts.pop().unwrap(); - - // Sort by end descending - let mut scopes_sorted = scope_start_entry.scopes; - scopes_sorted.sort_by(|a, b| { - let a_end = env.scopes[a.0 as usize].range.end; - let b_end = env.scopes[b.0 as usize].range.end; - b_end.cmp(&a_end) - }); - - state.active_scopes.extend_from_slice(&scopes_sorted); - - // Merge all identical scopes (same start and end) - for i in 1..scopes_sorted.len() { - let prev = scopes_sorted[i - 1]; - let curr = scopes_sorted[i]; - if env.scopes[prev.0 as usize].range.end == env.scopes[curr.0 as usize].range.end { - state.joined.union(&[prev, curr]); - } - } - } - } -} - -// ============================================================================= -// visitPlace -// ============================================================================= - -fn visit_place( - id: EvaluationOrder, - identifier_id: IdentifierId, - state: &mut TraversalState, - env: &Environment, -) { - // If an instruction mutates an outer scope, flatten all scopes from top - // of the stack to the mutated outer scope - let place_scope = get_place_scope(env, id, identifier_id); - if let Some(scope_id) = place_scope { - if is_mutable(env, id, identifier_id) { - let place_scope_idx = state.active_scopes.iter().position(|s| *s == scope_id); - if let Some(idx) = place_scope_idx { - if idx != state.active_scopes.len() - 1 { - let mut to_union: Vec<ScopeId> = vec![scope_id]; - to_union.extend_from_slice(&state.active_scopes[idx + 1..]); - state.joined.union(&to_union); - } - } - } - } -} - -// ============================================================================= -// getOverlappingReactiveScopes -// ============================================================================= - -fn get_overlapping_reactive_scopes( - func: &HirFunction, - env: &Environment, - mut scope_info: ScopeInfo, -) -> DisjointSet<ScopeId> { - let mut state = TraversalState { - joined: DisjointSet::<ScopeId>::new(), - active_scopes: Vec::new(), - }; - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - visit_instruction_id(instr.id, &mut scope_info, &mut state, env); - - // Visit operands - let is_func_or_method = matches!( - &instr.value, - InstructionValue::FunctionExpression { .. } | InstructionValue::ObjectMethod { .. } - ); - let operand_ids = each_instruction_operand_ids_with_types(instr, env); - for (op_id, type_) in &operand_ids { - if is_func_or_method && matches!(type_, Type::Primitive) { - continue; - } - visit_place(instr.id, *op_id, &mut state, env); - } - - // Visit lvalues - let lvalue_ids = each_instruction_lvalue_ids(instr); - for lvalue_id in lvalue_ids { - visit_place(instr.id, lvalue_id, &mut state, env); - } - } - - let terminal_id = block.terminal.evaluation_order(); - visit_instruction_id(terminal_id, &mut scope_info, &mut state, env); - - let terminal_op_ids = each_terminal_operand_ids(&block.terminal); - for op_id in terminal_op_ids { - visit_place(terminal_id, op_id, &mut state, env); - } - } - - state.joined -} - -// ============================================================================= -// Public API -// ============================================================================= - -/// Merges reactive scopes that have overlapping ranges. -/// -/// Corresponds to TS `mergeOverlappingReactiveScopesHIR(fn: HIRFunction): void`. -pub fn merge_overlapping_reactive_scopes_hir(func: &mut HirFunction, env: &mut Environment) { - // Collect scope info - let scope_info = collect_scope_info(func, env); - - // Save place_scopes before moving scope_info - let place_scopes = scope_info.place_scopes.clone(); - - // Find overlapping scopes - let mut joined_scopes = get_overlapping_reactive_scopes(func, env, scope_info); - - // Merge scope ranges: collect all (scope, root) pairs, then update root ranges - // by accumulating min start / max end from all members of each group. - // This matches TS behavior where groupScope.range is updated in-place during iteration. - let mut scope_groups: Vec<(ScopeId, ScopeId)> = Vec::new(); - joined_scopes.for_each(|scope_id, root_id| { - if scope_id != root_id { - scope_groups.push((scope_id, root_id)); - } - }); - // Collect root scopes' ORIGINAL range IDs BEFORE updating them. - // In TS, identifier.mutableRange shares the same object reference as scope.range. - // When scope.range is updated, ALL identifiers referencing that range object - // automatically see the new values. We use MutableRangeId to identify which - // identifiers share the same logical range as a root scope. - let mut original_root_range_ids: HashMap<ScopeId, react_compiler_hir::MutableRangeId> = - HashMap::new(); - for (_, root_id) in &scope_groups { - if !original_root_range_ids.contains_key(root_id) { - let range_id = env.scopes[root_id.0 as usize].range.id; - original_root_range_ids.insert(*root_id, range_id); - } - } - - // Update root scope ranges - for (scope_id, root_id) in &scope_groups { - let scope_start = env.scopes[scope_id.0 as usize].range.start; - let scope_end = env.scopes[scope_id.0 as usize].range.end; - let root_range = &mut env.scopes[root_id.0 as usize].range; - root_range.start = EvaluationOrder(cmp::min(root_range.start.0, scope_start.0)); - root_range.end = EvaluationOrder(cmp::max(root_range.end.0, scope_end.0)); - } - // Sync mutable_range for ALL identifiers whose mutable_range has the same - // identity as a root scope's original range. In TS, identifier.mutableRange - // shares the same object reference as scope.range, so when scope.range is - // updated, all identifiers referencing that range object automatically see - // the new values. We use MutableRangeId for exact identity matching. - for ident in &mut env.identifiers { - for (root_id, orig_range_id) in &original_root_range_ids { - if ident.mutable_range.id == *orig_range_id { - let new_range = &env.scopes[root_id.0 as usize].range; - ident.mutable_range.start = new_range.start; - ident.mutable_range.end = new_range.end; - break; - } - } - } - - // Rewrite all references: for each place that had a scope, point to the merged root. - // Note: we intentionally do NOT update mutable_range for repointed identifiers, - // matching TS behavior where identifier.mutableRange still references the old scope's - // range object after scope repointing. - for (identifier_id, original_scope) in &place_scopes { - let next_scope = joined_scopes.find(*original_scope); - if next_scope != *original_scope { - env.identifiers[identifier_id.0 as usize].scope = Some(next_scope); - } - } -} - -// ============================================================================= -// Instruction visitor helpers (delegating to canonical visitors) -// ============================================================================= - -/// Collect operand IdentifierIds with their types from an instruction value. -/// Used to check for Primitive type on FunctionExpression/ObjectMethod operands. -fn each_instruction_operand_ids_with_types( - instr: &react_compiler_hir::Instruction, - env: &Environment, -) -> Vec<(IdentifierId, Type)> { - visitors::each_instruction_operand(instr, env) - .into_iter() - .map(|p| { - let type_ = - env.types[env.identifiers[p.identifier.0 as usize].type_.0 as usize].clone(); - (p.identifier, type_) - }) - .collect() -} diff --git a/compiler/crates/react_compiler_inference/src/propagate_scope_dependencies_hir.rs b/compiler/crates/react_compiler_inference/src/propagate_scope_dependencies_hir.rs deleted file mode 100644 index 0628a59144a0..000000000000 --- a/compiler/crates/react_compiler_inference/src/propagate_scope_dependencies_hir.rs +++ /dev/null @@ -1,2338 +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. - -//! Propagates scope dependencies through the HIR, computing which values each -//! reactive scope depends on. -//! -//! Ported from TypeScript: -//! - `src/HIR/PropagateScopeDependenciesHIR.ts` -//! - `src/HIR/CollectOptionalChainDependencies.ts` -//! - `src/HIR/CollectHoistablePropertyLoads.ts` -//! - `src/HIR/DeriveMinimalDependenciesHIR.ts` - -use indexmap::IndexMap; -use std::collections::{BTreeSet, HashMap, HashSet}; - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors::{ScopeBlockInfo, ScopeBlockTraversal}; -use react_compiler_hir::{ - BasicBlock, BlockId, DeclarationId, DependencyPathEntry, EvaluationOrder, FunctionId, - GotoVariant, HirFunction, IdentifierId, Instruction, InstructionId, InstructionKind, - InstructionValue, MutableRange, ParamPattern, Place, PlaceOrSpread, PropertyLiteral, - ReactFunctionType, ReactiveScopeDependency, ScopeId, Terminal, Type, visitors, -}; - -// ============================================================================= -// Public entry point -// ============================================================================= - -/// Main entry point: propagate scope dependencies through the HIR. -/// Corresponds to TS `propagateScopeDependenciesHIR(fn)`. -pub fn propagate_scope_dependencies_hir(func: &mut HirFunction, env: &mut Environment) { - let used_outside_declaring_scope = find_temporaries_used_outside_declaring_scope(func, env); - let temporaries = collect_temporaries_sidemap(func, env, &used_outside_declaring_scope); - - let OptionalChainSidemap { - temporaries_read_in_optional, - processed_instrs_in_optional, - hoistable_objects, - } = collect_optional_chain_sidemap(func, env); - - let hoistable_property_loads = { - let (working, registry) = - collect_hoistable_and_propagate(func, env, &temporaries, &hoistable_objects); - // Convert to scope-keyed map with full dependency paths - let mut keyed: HashMap<ScopeId, Vec<ReactiveScopeDependency>> = HashMap::new(); - for (_block_id, block) in &func.body.blocks { - if let Terminal::Scope { - scope, - block: inner_block, - .. - } = &block.terminal - { - if let Some(node_indices) = working.get(inner_block) { - let deps: Vec<ReactiveScopeDependency> = node_indices - .iter() - .map(|&idx| registry.nodes[idx].full_path.clone()) - .collect(); - keyed.insert(*scope, deps); - } - } - } - keyed - }; - - // Merge temporaries + temporariesReadInOptional - let mut merged_temporaries = temporaries; - for (k, v) in temporaries_read_in_optional { - merged_temporaries.insert(k, v); - } - - let scope_deps = collect_dependencies( - func, - env, - &used_outside_declaring_scope, - &merged_temporaries, - &processed_instrs_in_optional, - ); - - // Derive the minimal set of hoistable dependencies for each scope. - for (scope_id, deps) in &scope_deps { - if deps.is_empty() { - continue; - } - - let hoistables = hoistable_property_loads.get(scope_id); - let hoistables = - hoistables.expect("[PropagateScopeDependencies] Scope not found in tracked blocks"); - - // Step 2: Calculate hoistable dependencies using the tree. - let mut tree = ReactiveScopeDependencyTreeHIR::new(hoistables.iter(), env); - for dep in deps { - tree.add_dependency(dep.clone(), env); - } - - // Step 3: Reduce dependencies to a minimal set. - let candidates = tree.derive_minimal_dependencies(env); - let scope = &mut env.scopes[scope_id.0 as usize]; - for candidate_dep in candidates { - let already_exists = scope.dependencies.iter().any(|existing_dep| { - let existing_decl_id = - env.identifiers[existing_dep.identifier.0 as usize].declaration_id; - let candidate_decl_id = - env.identifiers[candidate_dep.identifier.0 as usize].declaration_id; - existing_decl_id == candidate_decl_id - && are_equal_paths(&existing_dep.path, &candidate_dep.path) - }); - if !already_exists { - scope.dependencies.push(candidate_dep); - } - } - } -} - -fn are_equal_paths(a: &[DependencyPathEntry], b: &[DependencyPathEntry]) -> bool { - a.len() == b.len() - && a.iter() - .zip(b.iter()) - .all(|(ai, bi)| ai.property == bi.property && ai.optional == bi.optional) -} - -// ============================================================================= -// findTemporariesUsedOutsideDeclaringScope -// ============================================================================= - -/// Corresponds to TS `findTemporariesUsedOutsideDeclaringScope`. -fn find_temporaries_used_outside_declaring_scope( - func: &HirFunction, - env: &Environment, -) -> HashSet<DeclarationId> { - let mut declarations: HashMap<DeclarationId, ScopeId> = HashMap::new(); - let mut pruned_scopes: HashSet<ScopeId> = HashSet::new(); - let mut traversal = ScopeBlockTraversal::new(); - let mut used_outside_declaring_scope: HashSet<DeclarationId> = HashSet::new(); - - let handle_place = |place_id: IdentifierId, - declarations: &HashMap<DeclarationId, ScopeId>, - traversal: &ScopeBlockTraversal, - pruned_scopes: &HashSet<ScopeId>, - used_outside: &mut HashSet<DeclarationId>, - env: &Environment| { - let decl_id = env.identifiers[place_id.0 as usize].declaration_id; - if let Some(&declaring_scope) = declarations.get(&decl_id) { - if !traversal.is_scope_active(declaring_scope) - && !pruned_scopes.contains(&declaring_scope) - { - used_outside.insert(decl_id); - } - } - }; - - for (block_id, block) in &func.body.blocks { - // recordScopes - traversal.record_scopes(block); - - let scope_start_info = traversal.block_infos.get(block_id); - if let Some(ScopeBlockInfo::Begin { - scope, - pruned: true, - .. - }) = scope_start_info - { - pruned_scopes.insert(*scope); - } - - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - // Handle operands - for op_id in visitors::each_instruction_operand(instr, env) - .into_iter() - .map(|p| p.identifier) - .collect::<Vec<_>>() - { - handle_place( - op_id, - &declarations, - &traversal, - &pruned_scopes, - &mut used_outside_declaring_scope, - env, - ); - } - // Handle instruction (track declarations) - let current_scope = traversal.current_scope(); - if let Some(scope) = current_scope { - if !pruned_scopes.contains(&scope) { - match &instr.value { - InstructionValue::LoadLocal { .. } - | InstructionValue::LoadContext { .. } - | InstructionValue::PropertyLoad { .. } => { - let decl_id = - env.identifiers[instr.lvalue.identifier.0 as usize].declaration_id; - declarations.insert(decl_id, scope); - } - _ => {} - } - } - } - } - - // Terminal operands - for op_id in visitors::each_terminal_operand(&block.terminal) - .into_iter() - .map(|p| p.identifier) - .collect::<Vec<_>>() - { - handle_place( - op_id, - &declarations, - &traversal, - &pruned_scopes, - &mut used_outside_declaring_scope, - env, - ); - } - } - - used_outside_declaring_scope -} - -// ============================================================================= -// collectTemporariesSidemap -// ============================================================================= - -/// Corresponds to TS `collectTemporariesSidemap`. -fn collect_temporaries_sidemap( - func: &HirFunction, - env: &Environment, - used_outside_declaring_scope: &HashSet<DeclarationId>, -) -> HashMap<IdentifierId, ReactiveScopeDependency> { - let mut temporaries = HashMap::new(); - collect_temporaries_sidemap_impl( - func, - env, - used_outside_declaring_scope, - &mut temporaries, - None, - ); - temporaries -} - -/// Corresponds to TS `isLoadContextMutable`. -fn is_load_context_mutable( - value: &InstructionValue, - id: EvaluationOrder, - env: &Environment, -) -> bool { - if let InstructionValue::LoadContext { place, .. } = value { - if let Some(scope_id) = env.identifiers[place.identifier.0 as usize].scope { - let scope_range = &env.scopes[scope_id.0 as usize].range; - return id >= scope_range.end; - } - } - false -} - -/// Corresponds to TS `convertHoistedLValueKind` — returns None for non-hoisted kinds. -fn convert_hoisted_lvalue_kind(kind: InstructionKind) -> Option<InstructionKind> { - match kind { - InstructionKind::HoistedLet => Some(InstructionKind::Let), - InstructionKind::HoistedConst => Some(InstructionKind::Const), - InstructionKind::HoistedFunction => Some(InstructionKind::Function), - _ => None, - } -} - -/// Recursive implementation. Corresponds to TS `collectTemporariesSidemapImpl`. -fn collect_temporaries_sidemap_impl( - func: &HirFunction, - env: &Environment, - used_outside_declaring_scope: &HashSet<DeclarationId>, - temporaries: &mut HashMap<IdentifierId, ReactiveScopeDependency>, - inner_fn_context: Option<EvaluationOrder>, -) { - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let instr_eval_order = if let Some(outer_id) = inner_fn_context { - outer_id - } else { - instr.id - }; - let lvalue_decl_id = env.identifiers[instr.lvalue.identifier.0 as usize].declaration_id; - let used_outside = used_outside_declaring_scope.contains(&lvalue_decl_id); - - match &instr.value { - InstructionValue::PropertyLoad { - object, - property, - loc, - .. - } if !used_outside => { - if inner_fn_context.is_none() || temporaries.contains_key(&object.identifier) { - let prop = get_property(object, property, false, *loc, temporaries, env); - temporaries.insert(instr.lvalue.identifier, prop); - } - } - InstructionValue::LoadLocal { place, loc, .. } - if env.identifiers[instr.lvalue.identifier.0 as usize] - .name - .is_none() - && env.identifiers[place.identifier.0 as usize].name.is_some() - && !used_outside => - { - if inner_fn_context.is_none() - || func - .context - .iter() - .any(|ctx| ctx.identifier == place.identifier) - { - temporaries.insert( - instr.lvalue.identifier, - ReactiveScopeDependency { - identifier: place.identifier, - reactive: place.reactive, - path: vec![], - loc: *loc, - }, - ); - } - } - value @ InstructionValue::LoadContext { place, loc, .. } - if is_load_context_mutable(value, instr_eval_order, env) - && env.identifiers[instr.lvalue.identifier.0 as usize] - .name - .is_none() - && env.identifiers[place.identifier.0 as usize].name.is_some() - && !used_outside => - { - if inner_fn_context.is_none() - || func - .context - .iter() - .any(|ctx| ctx.identifier == place.identifier) - { - temporaries.insert( - instr.lvalue.identifier, - ReactiveScopeDependency { - identifier: place.identifier, - reactive: place.reactive, - path: vec![], - loc: *loc, - }, - ); - } - } - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - let inner_func = &env.functions[lowered_func.func.0 as usize]; - let ctx = inner_fn_context.unwrap_or(instr.id); - collect_temporaries_sidemap_impl( - inner_func, - env, - used_outside_declaring_scope, - temporaries, - Some(ctx), - ); - } - _ => {} - } - } - } -} - -/// Corresponds to TS `getProperty`. -fn get_property( - object: &Place, - property_name: &PropertyLiteral, - optional: bool, - loc: Option<react_compiler_hir::SourceLocation>, - temporaries: &HashMap<IdentifierId, ReactiveScopeDependency>, - _env: &Environment, -) -> ReactiveScopeDependency { - let resolved = temporaries.get(&object.identifier); - if let Some(resolved) = resolved { - let mut path = resolved.path.clone(); - path.push(DependencyPathEntry { - property: property_name.clone(), - optional, - loc, - }); - ReactiveScopeDependency { - identifier: resolved.identifier, - reactive: resolved.reactive, - path, - loc, - } - } else { - ReactiveScopeDependency { - identifier: object.identifier, - reactive: object.reactive, - path: vec![DependencyPathEntry { - property: property_name.clone(), - optional, - loc, - }], - loc, - } - } -} - -// ============================================================================= -// CollectOptionalChainDependencies -// ============================================================================= - -struct OptionalChainSidemap { - temporaries_read_in_optional: HashMap<IdentifierId, ReactiveScopeDependency>, - processed_instrs_in_optional: HashSet<ProcessedInstr>, - hoistable_objects: HashMap<BlockId, ReactiveScopeDependency>, -} - -/// We track processed instructions/terminals by their lvalue IdentifierId + block id. -/// In TS this uses reference identity (Set<Instruction | Terminal>). -/// We use IdentifierId for instructions (globally unique across functions) and -/// BlockId for terminals. Note: EvaluationOrder (instruction id) is NOT unique -/// across functions, so we cannot use it here. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum ProcessedInstr { - Instruction(IdentifierId), - Terminal(BlockId), -} - -fn collect_optional_chain_sidemap(func: &HirFunction, env: &Environment) -> OptionalChainSidemap { - let mut ctx = OptionalTraversalContext { - seen_optionals: HashSet::new(), - processed_instrs_in_optional: HashSet::new(), - temporaries_read_in_optional: HashMap::new(), - hoistable_objects: HashMap::new(), - }; - - traverse_function_optional(func, env, &mut ctx); - - OptionalChainSidemap { - temporaries_read_in_optional: ctx.temporaries_read_in_optional, - processed_instrs_in_optional: ctx.processed_instrs_in_optional, - hoistable_objects: ctx.hoistable_objects, - } -} - -struct OptionalTraversalContext { - seen_optionals: HashSet<BlockId>, - processed_instrs_in_optional: HashSet<ProcessedInstr>, - temporaries_read_in_optional: HashMap<IdentifierId, ReactiveScopeDependency>, - hoistable_objects: HashMap<BlockId, ReactiveScopeDependency>, -} - -fn traverse_function_optional( - func: &HirFunction, - env: &Environment, - ctx: &mut OptionalTraversalContext, -) { - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - let inner_func = &env.functions[lowered_func.func.0 as usize]; - traverse_function_optional(inner_func, env, ctx); - } - _ => {} - } - } - if let Terminal::Optional { .. } = &block.terminal { - if !ctx.seen_optionals.contains(&block.id) { - traverse_optional_block(block, func, env, ctx, None); - } - } - } -} - -struct MatchConsequentResult { - consequent_id: IdentifierId, - property: PropertyLiteral, - property_id: IdentifierId, - store_local_lvalue_id: IdentifierId, - consequent_goto: BlockId, - property_load_loc: Option<react_compiler_hir::SourceLocation>, -} - -fn match_optional_test_block( - test: &Terminal, - func: &HirFunction, - _env: &Environment, -) -> Option<MatchConsequentResult> { - let (test_place, consequent_block_id, alternate_block_id) = match test { - Terminal::Branch { - test, - consequent, - alternate, - .. - } => (test, *consequent, *alternate), - _ => return None, - }; - - let consequent_block = func.body.blocks.get(&consequent_block_id)?; - if consequent_block.instructions.len() != 2 { - return None; - } - - let instr0 = &func.instructions[consequent_block.instructions[0].0 as usize]; - let instr1 = &func.instructions[consequent_block.instructions[1].0 as usize]; - - let (property_load_object, property, property_load_loc) = match &instr0.value { - InstructionValue::PropertyLoad { - object, - property, - loc, - } => (object, property, loc), - _ => return None, - }; - - let store_local_value = match &instr1.value { - InstructionValue::StoreLocal { value, lvalue, .. } => { - // Verify the store local's value matches the property load's lvalue - if value.identifier != instr0.lvalue.identifier { - return None; - } - &lvalue.place - } - _ => return None, - }; - - // Verify property load's object matches the test - if property_load_object.identifier != test_place.identifier { - return None; - } - - // Check consequent block terminal is goto break - match &consequent_block.terminal { - Terminal::Goto { - variant: GotoVariant::Break, - block: goto_block, - .. - } => { - // Verify alternate block structure - let alternate_block = func.body.blocks.get(&alternate_block_id)?; - if alternate_block.instructions.len() != 2 { - return None; - } - let alt_instr0 = &func.instructions[alternate_block.instructions[0].0 as usize]; - let alt_instr1 = &func.instructions[alternate_block.instructions[1].0 as usize]; - match (&alt_instr0.value, &alt_instr1.value) { - (InstructionValue::Primitive { .. }, InstructionValue::StoreLocal { .. }) => {} - _ => return None, - } - - Some(MatchConsequentResult { - consequent_id: store_local_value.identifier, - property: property.clone(), - property_id: instr0.lvalue.identifier, - store_local_lvalue_id: instr1.lvalue.identifier, - consequent_goto: *goto_block, - property_load_loc: *property_load_loc, - }) - } - _ => None, - } -} - -fn traverse_optional_block( - optional_block: &BasicBlock, - func: &HirFunction, - env: &Environment, - ctx: &mut OptionalTraversalContext, - outer_alternate: Option<BlockId>, -) -> Option<IdentifierId> { - ctx.seen_optionals.insert(optional_block.id); - - let (test_block_id, is_optional, fallthrough_block_id) = match &optional_block.terminal { - Terminal::Optional { - test, - optional, - fallthrough, - .. - } => (*test, *optional, *fallthrough), - _ => return None, - }; - - let maybe_test_block = func.body.blocks.get(&test_block_id)?; - - let (test_terminal, base_object) = match &maybe_test_block.terminal { - Terminal::Branch { .. } => { - // Base case: optional must be true - if !is_optional { - return None; - } - // Match base expression that is straightforward PropertyLoad chain - if maybe_test_block.instructions.is_empty() { - return None; - } - let first_instr = &func.instructions[maybe_test_block.instructions[0].0 as usize]; - if !matches!(&first_instr.value, InstructionValue::LoadLocal { .. }) { - return None; - } - - let mut path: Vec<DependencyPathEntry> = Vec::new(); - for i in 1..maybe_test_block.instructions.len() { - let curr_instr = &func.instructions[maybe_test_block.instructions[i].0 as usize]; - let prev_instr = - &func.instructions[maybe_test_block.instructions[i - 1].0 as usize]; - match &curr_instr.value { - InstructionValue::PropertyLoad { - object, - property, - loc, - .. - } if object.identifier == prev_instr.lvalue.identifier => { - path.push(DependencyPathEntry { - property: property.clone(), - optional: false, - loc: *loc, - }); - } - _ => return None, - } - } - - // Verify test expression matches last instruction's lvalue - let last_instr_id = *maybe_test_block.instructions.last().unwrap(); - let last_instr = &func.instructions[last_instr_id.0 as usize]; - let test_ident = match &maybe_test_block.terminal { - Terminal::Branch { test, .. } => test.identifier, - _ => return None, - }; - if test_ident != last_instr.lvalue.identifier { - return None; - } - - let first_place = match &first_instr.value { - InstructionValue::LoadLocal { place, .. } => place, - _ => return None, - }; - - let base = ReactiveScopeDependency { - identifier: first_place.identifier, - reactive: first_place.reactive, - path, - loc: first_place.loc, - }; - (&maybe_test_block.terminal, base) - } - Terminal::Optional { - fallthrough: inner_fallthrough, - optional: _inner_optional, - .. - } => { - let test_block = func.body.blocks.get(inner_fallthrough)?; - if !matches!(&test_block.terminal, Terminal::Branch { .. }) { - return None; - } - - // Recurse into inner optional - let inner_alternate = match &test_block.terminal { - Terminal::Branch { alternate, .. } => Some(*alternate), - _ => None, - }; - let inner_optional_result = - traverse_optional_block(maybe_test_block, func, env, ctx, inner_alternate); - let inner_optional_id = inner_optional_result?; - - // Check that inner optional is part of the same chain - let test_ident = match &test_block.terminal { - Terminal::Branch { test, .. } => test.identifier, - _ => return None, - }; - if test_ident != inner_optional_id { - return None; - } - - if !is_optional { - // Non-optional load: record that PropertyLoads from inner optional are hoistable - if let Some(inner_dep) = ctx.temporaries_read_in_optional.get(&inner_optional_id) { - ctx.hoistable_objects - .insert(optional_block.id, inner_dep.clone()); - } - } - - let base = ctx - .temporaries_read_in_optional - .get(&inner_optional_id)? - .clone(); - (&test_block.terminal, base) - } - _ => return None, - }; - - // Verify alternate matches outer_alternate if present - if let Some(outer_alt) = outer_alternate { - let test_alternate = match test_terminal { - Terminal::Branch { alternate, .. } => *alternate, - _ => return None, - }; - if test_alternate == outer_alt { - // Verify optional block has no instructions - if !optional_block.instructions.is_empty() { - return None; - } - } - } - - let match_result = match_optional_test_block(test_terminal, func, env)?; - - // Verify consequent goto matches optional fallthrough - if match_result.consequent_goto != fallthrough_block_id { - return None; - } - - let load = ReactiveScopeDependency { - identifier: base_object.identifier, - reactive: base_object.reactive, - path: { - let mut p = base_object.path.clone(); - p.push(DependencyPathEntry { - property: match_result.property.clone(), - optional: is_optional, - loc: match_result.property_load_loc, - }); - p - }, - loc: match_result.property_load_loc, - }; - - ctx.processed_instrs_in_optional - .insert(ProcessedInstr::Instruction( - match_result.store_local_lvalue_id, - )); - ctx.processed_instrs_in_optional - .insert(ProcessedInstr::Terminal(match &test_terminal { - Terminal::Branch { .. } => { - // Find the block ID for this terminal - // The terminal belongs to either maybe_test_block or the fallthrough block of inner optional - // We need to identify which block this terminal belongs to. - // For the base case, it's test_block_id. - // For nested optional, it's the fallthrough block. - // We'll use the block_id approach based on what we know. - // Actually, we tracked the terminal by its block, so we need to find which block - // contains this terminal. Let's use a pragmatic approach: - // The test terminal we matched was from maybe_test_block or from the inner fallthrough block. - // We'll search for it. - - // For the base case (Branch terminal at maybe_test_block), block_id = test_block_id - // For the nested case, the test terminal is at the fallthrough block of inner optional - // In either case, we stored the terminal as test_terminal which comes from a known block. - // We need to find the block that owns this terminal. - - // Let's take a simpler approach: find the block whose terminal matches - // This is the block we got test_terminal from. - // In the first branch of the match, test_terminal = &maybe_test_block.terminal - // and maybe_test_block.id = test_block_id - // In the second branch, test_terminal = &test_block.terminal - // and test_block = func.body.blocks.get(inner_fallthrough) - // We can't easily tell which case we're in here since we're past the match. - - // Actually, since test_terminal is a reference to a terminal in a block, - // we can just look up which block it belongs to by finding blocks whose terminal - // pointer matches. But that's expensive. Instead, let's use the block approach - // and find the block from the terminal's properties. - - // For simplicity, use a sentinel approach: just check all blocks. - // This is O(n) but only happens for optional chains. - let mut found_block = BlockId(0); - for (bid, blk) in &func.body.blocks { - if std::ptr::eq(&blk.terminal, test_terminal) { - found_block = *bid; - break; - } - } - found_block - } - _ => BlockId(0), - })); - ctx.temporaries_read_in_optional - .insert(match_result.consequent_id, load.clone()); - ctx.temporaries_read_in_optional - .insert(match_result.property_id, load); - - Some(match_result.consequent_id) -} - -// ============================================================================= -// CollectHoistablePropertyLoads -// ============================================================================= - -#[derive(Debug, Clone)] -struct PropertyPathNode { - properties: HashMap<PropertyLiteral, usize>, // index into registry - optional_properties: HashMap<PropertyLiteral, usize>, // index into registry - #[allow(dead_code)] - parent: Option<usize>, - full_path: ReactiveScopeDependency, - has_optional: bool, - #[allow(dead_code)] - root: Option<IdentifierId>, -} - -struct PropertyPathRegistry { - nodes: Vec<PropertyPathNode>, - roots: HashMap<IdentifierId, usize>, -} - -impl PropertyPathRegistry { - fn new() -> Self { - Self { - nodes: Vec::new(), - roots: HashMap::new(), - } - } - - fn get_or_create_identifier( - &mut self, - identifier_id: IdentifierId, - reactive: bool, - loc: Option<react_compiler_hir::SourceLocation>, - ) -> usize { - if let Some(&idx) = self.roots.get(&identifier_id) { - return idx; - } - let idx = self.nodes.len(); - self.nodes.push(PropertyPathNode { - properties: HashMap::new(), - optional_properties: HashMap::new(), - parent: None, - full_path: ReactiveScopeDependency { - identifier: identifier_id, - reactive, - path: vec![], - loc, - }, - has_optional: false, - root: Some(identifier_id), - }); - self.roots.insert(identifier_id, idx); - idx - } - - fn get_or_create_property_entry( - &mut self, - parent_idx: usize, - entry: &DependencyPathEntry, - ) -> usize { - let map_key = entry.property.clone(); - let existing = if entry.optional { - self.nodes[parent_idx] - .optional_properties - .get(&map_key) - .copied() - } else { - self.nodes[parent_idx].properties.get(&map_key).copied() - }; - if let Some(idx) = existing { - return idx; - } - let parent_full_path = self.nodes[parent_idx].full_path.clone(); - let parent_has_optional = self.nodes[parent_idx].has_optional; - let idx = self.nodes.len(); - let mut new_path = parent_full_path.path.clone(); - new_path.push(entry.clone()); - self.nodes.push(PropertyPathNode { - properties: HashMap::new(), - optional_properties: HashMap::new(), - parent: Some(parent_idx), - full_path: ReactiveScopeDependency { - identifier: parent_full_path.identifier, - reactive: parent_full_path.reactive, - path: new_path, - loc: entry.loc, - }, - has_optional: parent_has_optional || entry.optional, - root: None, - }); - if entry.optional { - self.nodes[parent_idx] - .optional_properties - .insert(map_key, idx); - } else { - self.nodes[parent_idx].properties.insert(map_key, idx); - } - idx - } - - fn get_or_create_property(&mut self, dep: &ReactiveScopeDependency) -> usize { - let mut curr = self.get_or_create_identifier(dep.identifier, dep.reactive, dep.loc); - for entry in &dep.path { - curr = self.get_or_create_property_entry(curr, entry); - } - curr - } -} - -/// Reduces optional chains in a set of property path nodes. -/// -/// Any two optional chains with different operations (`.` vs `?.`) but the same set -/// of property string paths de-duplicates. If unconditional reads from `<base>` are -/// hoistable (i.e., `<base>` is in the set), we replace `<base>?.PROPERTY` with -/// `<base>.PROPERTY`. -/// -/// Port of `reduceMaybeOptionalChains` from CollectHoistablePropertyLoads.ts. -fn reduce_maybe_optional_chains(nodes: &mut BTreeSet<usize>, registry: &mut PropertyPathRegistry) { - // Collect indices of nodes that have optional in their path - let mut optional_chain_nodes: BTreeSet<usize> = nodes - .iter() - .copied() - .filter(|&idx| registry.nodes[idx].has_optional) - .collect(); - - if optional_chain_nodes.is_empty() { - return; - } - - loop { - let mut changed = false; - - // Collect the indices to process (snapshot to avoid borrow issues) - let to_process: Vec<usize> = optional_chain_nodes.iter().copied().collect(); - - for original_idx in to_process { - let full_path = registry.nodes[original_idx].full_path.clone(); - - let mut curr_node = registry.get_or_create_identifier( - full_path.identifier, - full_path.reactive, - full_path.loc, - ); - - for entry in &full_path.path { - // If the base is known to be non-null (in the set), replace optional with non-optional - let next_entry = if entry.optional && nodes.contains(&curr_node) { - DependencyPathEntry { - property: entry.property.clone(), - optional: false, - loc: entry.loc, - } - } else { - entry.clone() - }; - curr_node = registry.get_or_create_property_entry(curr_node, &next_entry); - } - - if curr_node != original_idx { - changed = true; - optional_chain_nodes.remove(&original_idx); - optional_chain_nodes.insert(curr_node); - nodes.remove(&original_idx); - nodes.insert(curr_node); - } - } - - if !changed { - break; - } - } -} - -#[derive(Debug, Clone)] -struct BlockInfo { - assumed_non_null_objects: BTreeSet<usize>, // indices into PropertyPathRegistry -} - -#[allow(dead_code)] -fn collect_hoistable_property_loads( - func: &HirFunction, - env: &Environment, - temporaries: &HashMap<IdentifierId, ReactiveScopeDependency>, - hoistable_from_optionals: &HashMap<BlockId, ReactiveScopeDependency>, -) -> HashMap<BlockId, BlockInfo> { - let mut registry = PropertyPathRegistry::new(); - let known_immutable_identifiers: HashSet<IdentifierId> = if func.fn_type - == ReactFunctionType::Component - || func.fn_type == ReactFunctionType::Hook - { - func.params - .iter() - .filter_map(|p| match p { - ParamPattern::Place(place) => Some(place.identifier), - _ => None, - }) - .collect() - } else { - HashSet::new() - }; - - let assumed_invoked_fns = get_assumed_invoked_functions(func, env); - let ctx = CollectHoistableContext { - temporaries, - known_immutable_identifiers: &known_immutable_identifiers, - hoistable_from_optionals, - nested_fn_immutable_context: None, - assumed_invoked_fns: &assumed_invoked_fns, - }; - - collect_hoistable_property_loads_impl(func, env, &ctx, &mut registry) -} - -struct CollectHoistableContext<'a> { - temporaries: &'a HashMap<IdentifierId, ReactiveScopeDependency>, - known_immutable_identifiers: &'a HashSet<IdentifierId>, - hoistable_from_optionals: &'a HashMap<BlockId, ReactiveScopeDependency>, - nested_fn_immutable_context: Option<&'a HashSet<IdentifierId>>, - assumed_invoked_fns: &'a HashSet<FunctionId>, -} - -fn is_immutable_at_instr( - identifier_id: IdentifierId, - instr_id: EvaluationOrder, - env: &Environment, - ctx: &CollectHoistableContext, -) -> bool { - if let Some(nested_ctx) = ctx.nested_fn_immutable_context { - return nested_ctx.contains(&identifier_id); - } - let ident = &env.identifiers[identifier_id.0 as usize]; - let mutable_at_instr = ident.mutable_range.end - > EvaluationOrder(ident.mutable_range.start.0 + 1) - && ident.scope.is_some() - && { - let scope = &env.scopes[ident.scope.unwrap().0 as usize]; - in_range(instr_id, &scope.range) - }; - !mutable_at_instr || ctx.known_immutable_identifiers.contains(&identifier_id) -} - -fn in_range(id: EvaluationOrder, range: &MutableRange) -> bool { - id >= range.start && id < range.end -} - -fn get_maybe_non_null_in_instruction( - value: &InstructionValue, - temporaries: &HashMap<IdentifierId, ReactiveScopeDependency>, -) -> Option<ReactiveScopeDependency> { - match value { - InstructionValue::PropertyLoad { object, .. } => Some( - temporaries - .get(&object.identifier) - .cloned() - .unwrap_or_else(|| ReactiveScopeDependency { - identifier: object.identifier, - reactive: object.reactive, - path: vec![], - loc: object.loc, - }), - ), - InstructionValue::Destructure { value: val, .. } => { - temporaries.get(&val.identifier).cloned() - } - InstructionValue::ComputedLoad { object, .. } => { - temporaries.get(&object.identifier).cloned() - } - _ => None, - } -} - -#[allow(dead_code)] -fn collect_hoistable_property_loads_impl( - func: &HirFunction, - env: &Environment, - ctx: &CollectHoistableContext, - registry: &mut PropertyPathRegistry, -) -> HashMap<BlockId, BlockInfo> { - let nodes = collect_non_nulls_in_blocks(func, env, ctx, registry); - let working = propagate_non_null(func, &nodes, registry); - // Return the propagated results, converting HashSet<usize> back to BlockInfo - working - .into_iter() - .map(|(k, v)| { - ( - k, - BlockInfo { - assumed_non_null_objects: v, - }, - ) - }) - .collect() -} - -/// Corresponds to TS `getAssumedInvokedFunctions`. -/// Returns the set of LoweredFunction FunctionIds that are assumed to be invoked. -/// The `temporaries` map is shared across recursive calls (matching TS behavior where -/// the same Map is passed to recursive invocations for inner functions). -fn get_assumed_invoked_functions(func: &HirFunction, env: &Environment) -> HashSet<FunctionId> { - let mut temporaries: HashMap<IdentifierId, (FunctionId, HashSet<FunctionId>)> = HashMap::new(); - get_assumed_invoked_functions_impl(func, env, &mut temporaries) -} - -fn get_assumed_invoked_functions_impl( - func: &HirFunction, - env: &Environment, - temporaries: &mut HashMap<IdentifierId, (FunctionId, HashSet<FunctionId>)>, -) -> HashSet<FunctionId> { - let mut hoistable: HashSet<FunctionId> = HashSet::new(); - - // Step 1: Collect identifier to function expression mappings - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } => { - temporaries - .insert(instr.lvalue.identifier, (lowered_func.func, HashSet::new())); - } - InstructionValue::StoreLocal { - value: val, lvalue, .. - } => { - if let Some(entry) = temporaries.get(&val.identifier).cloned() { - temporaries.insert(lvalue.place.identifier, entry); - } - } - InstructionValue::LoadLocal { place, .. } => { - if let Some(entry) = temporaries.get(&place.identifier).cloned() { - temporaries.insert(instr.lvalue.identifier, entry); - } - } - _ => {} - } - } - } - - // Step 2: Forward pass to analyze assumed function calls - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::CallExpression { callee, args, .. } => { - let callee_ty = - &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize]; - let maybe_hook = env.get_hook_kind_for_type(callee_ty).ok().flatten(); - if let Some(entry) = temporaries.get(&callee.identifier) { - // Direct calls - hoistable.insert(entry.0); - } else if maybe_hook.is_some() { - // Assume arguments to all hooks are safe to invoke - for arg in args { - if let PlaceOrSpread::Place(p) = arg { - if let Some(entry) = temporaries.get(&p.identifier) { - hoistable.insert(entry.0); - } - } - } - } - } - InstructionValue::JsxExpression { - props, children, .. - } => { - // Assume JSX attributes and children are safe to invoke - for prop in props { - if let react_compiler_hir::JsxAttribute::Attribute { place, .. } = prop { - if let Some(entry) = temporaries.get(&place.identifier) { - hoistable.insert(entry.0); - } - } - } - if let Some(children) = children { - for child in children { - if let Some(entry) = temporaries.get(&child.identifier) { - hoistable.insert(entry.0); - } - } - } - } - InstructionValue::JsxFragment { children, .. } => { - for child in children { - if let Some(entry) = temporaries.get(&child.identifier) { - hoistable.insert(entry.0); - } - } - } - InstructionValue::FunctionExpression { lowered_func, .. } => { - // Recursively traverse into other function expressions - // TS passes the shared temporaries map to the recursive call - let inner_func = &env.functions[lowered_func.func.0 as usize]; - let lambdas_called = - get_assumed_invoked_functions_impl(inner_func, env, temporaries); - if let Some(entry) = temporaries.get_mut(&instr.lvalue.identifier) { - for called in lambdas_called { - entry.1.insert(called); - } - } - } - _ => {} - } - } - - // Assume directly returned functions are safe to call - if let Terminal::Return { value, .. } = &block.terminal { - if let Some(entry) = temporaries.get(&value.identifier) { - hoistable.insert(entry.0); - } - } - } - - // Step 3: Propagate assumed-invoked status through mayInvoke chains - let mut changed = true; - while changed { - changed = false; - // Two-phase: collect then insert - let mut to_add = Vec::new(); - for (_, (func_id, may_invoke)) in temporaries.iter() { - if hoistable.contains(func_id) { - for &called in may_invoke { - if !hoistable.contains(&called) { - to_add.push(called); - } - } - } - } - for id in to_add { - changed = true; - hoistable.insert(id); - } - if !changed { - break; - } - } - - hoistable -} - -fn collect_non_nulls_in_blocks( - func: &HirFunction, - env: &Environment, - ctx: &CollectHoistableContext, - registry: &mut PropertyPathRegistry, -) -> HashMap<BlockId, BlockInfo> { - // Known non-null identifiers (e.g. component props) - let mut known_non_null: BTreeSet<usize> = BTreeSet::new(); - if func.fn_type == ReactFunctionType::Component && !func.params.is_empty() { - if let ParamPattern::Place(place) = &func.params[0] { - let node_idx = registry.get_or_create_identifier(place.identifier, true, place.loc); - known_non_null.insert(node_idx); - } - } - - let mut nodes: HashMap<BlockId, BlockInfo> = HashMap::new(); - - for (block_id, block) in &func.body.blocks { - let mut assumed = known_non_null.clone(); - - // Check hoistable from optionals - if let Some(optional_chain) = ctx.hoistable_from_optionals.get(block_id) { - let node_idx = registry.get_or_create_property(optional_chain); - assumed.insert(node_idx); - } - - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - if let Some(path) = get_maybe_non_null_in_instruction(&instr.value, ctx.temporaries) { - let path_ident = path.identifier; - if is_immutable_at_instr(path_ident, instr.id, env, ctx) { - let node_idx = registry.get_or_create_property(&path); - assumed.insert(node_idx); - } - } - - // Handle StartMemoize deps for enablePreserveExistingMemoizationGuarantees - if env.enable_preserve_existing_memoization_guarantees { - if let InstructionValue::StartMemoize { - deps: Some(deps), .. - } = &instr.value - { - for dep in deps { - if let react_compiler_hir::ManualMemoDependencyRoot::NamedLocal { - value: val, - .. - } = &dep.root - { - if !is_immutable_at_instr(val.identifier, instr.id, env, ctx) { - continue; - } - for i in 0..dep.path.len() { - if dep.path[i].optional { - break; - } - let sub_dep = ReactiveScopeDependency { - identifier: val.identifier, - reactive: val.reactive, - path: dep.path[..i].to_vec(), - loc: dep.loc, - }; - let node_idx = registry.get_or_create_property(&sub_dep); - assumed.insert(node_idx); - } - } - } - } - } - - // Handle assumed-invoked inner functions - if let InstructionValue::FunctionExpression { lowered_func, .. } = &instr.value { - if ctx.assumed_invoked_fns.contains(&lowered_func.func) { - let inner_func = &env.functions[lowered_func.func.0 as usize]; - // Build nested fn immutable context - let nested_fn_immutable_context: HashSet<IdentifierId> = - if ctx.nested_fn_immutable_context.is_some() { - // Already in a nested fn context, use existing - ctx.nested_fn_immutable_context.unwrap().clone() - } else { - inner_func - .context - .iter() - .filter(|place| { - is_immutable_at_instr(place.identifier, instr.id, env, ctx) - }) - .map(|place| place.identifier) - .collect() - }; - let inner_assumed = get_assumed_invoked_functions(inner_func, env); - let inner_ctx = CollectHoistableContext { - temporaries: ctx.temporaries, - known_immutable_identifiers: &HashSet::new(), - hoistable_from_optionals: ctx.hoistable_from_optionals, - nested_fn_immutable_context: Some(&nested_fn_immutable_context), - assumed_invoked_fns: &inner_assumed, - }; - let inner_nodes = - collect_non_nulls_in_blocks(inner_func, env, &inner_ctx, registry); - // Propagate non-null from inner function - let inner_working = propagate_non_null(inner_func, &inner_nodes, registry); - // Get hoistables from inner function's entry block (after propagation) - let inner_entry = inner_func.body.entry; - if let Some(inner_set) = inner_working.get(&inner_entry) { - for &node_idx in inner_set { - assumed.insert(node_idx); - } - } - } - } - } - - nodes.insert( - *block_id, - BlockInfo { - assumed_non_null_objects: assumed, - }, - ); - } - - nodes -} - -/// Recursive DFS propagation of non-null information through the CFG. -/// Uses 'active'/'done' state tracking to correctly handle cycles (backedges in loops). -/// -/// Port of TS `propagateNonNull` which uses `recursivelyPropagateNonNull`. -/// Key insight: when computing the intersection of neighbor sets, only include -/// neighbors that are 'done' (not 'active'). Active neighbors are part of a cycle -/// and should be filtered out, allowing non-null info to propagate through non-cyclic paths. -fn propagate_non_null( - func: &HirFunction, - nodes: &HashMap<BlockId, BlockInfo>, - registry: &mut PropertyPathRegistry, -) -> HashMap<BlockId, BTreeSet<usize>> { - // Build successor map. Use BTreeSet to iterate successors in sorted BlockId - // order, matching the TS Set<BlockId> insertion order (blocks are created in - // ascending BlockId order). - let mut block_successors: HashMap<BlockId, BTreeSet<BlockId>> = HashMap::new(); - for (block_id, block) in &func.body.blocks { - for pred in &block.preds { - block_successors.entry(*pred).or_default().insert(*block_id); - } - } - - // Clone nodes into mutable working set - let mut working: HashMap<BlockId, BTreeSet<usize>> = nodes - .iter() - .map(|(k, v)| (*k, v.assumed_non_null_objects.clone())) - .collect(); - - let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect(); - let mut reversed_block_ids = block_ids.clone(); - reversed_block_ids.reverse(); - - for _ in 0..100 { - let mut changed = false; - - // Forward pass (using predecessors) - let mut traversal_state: HashMap<BlockId, TraversalState> = HashMap::new(); - for &block_id in &block_ids { - let block_changed = recursively_propagate_non_null( - block_id, - PropagationDirection::Forward, - &mut traversal_state, - &mut working, - func, - &block_successors, - registry, - ); - changed |= block_changed; - } - - // Backward pass (using successors) - traversal_state.clear(); - for &block_id in &reversed_block_ids { - let block_changed = recursively_propagate_non_null( - block_id, - PropagationDirection::Backward, - &mut traversal_state, - &mut working, - func, - &block_successors, - registry, - ); - changed |= block_changed; - } - - if !changed { - break; - } - } - - working -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum TraversalState { - Active, - Done, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum PropagationDirection { - Forward, - Backward, -} - -fn recursively_propagate_non_null( - node_id: BlockId, - direction: PropagationDirection, - traversal_state: &mut HashMap<BlockId, TraversalState>, - working: &mut HashMap<BlockId, BTreeSet<usize>>, - func: &HirFunction, - block_successors: &HashMap<BlockId, BTreeSet<BlockId>>, - registry: &mut PropertyPathRegistry, -) -> bool { - // Avoid re-visiting computed or currently active nodes - if traversal_state.contains_key(&node_id) { - return false; - } - traversal_state.insert(node_id, TraversalState::Active); - - let neighbors: Vec<BlockId> = match direction { - PropagationDirection::Backward => block_successors - .get(&node_id) - .map(|s| s.iter().copied().collect()) - .unwrap_or_default(), - PropagationDirection::Forward => func - .body - .blocks - .get(&node_id) - .map(|b| b.preds.iter().copied().collect()) - .unwrap_or_default(), - }; - - let mut changed = false; - for &neighbor in &neighbors { - if !traversal_state.contains_key(&neighbor) { - let neighbor_changed = recursively_propagate_non_null( - neighbor, - direction, - traversal_state, - working, - func, - block_successors, - registry, - ); - changed |= neighbor_changed; - } - } - - // Compute intersection of 'done' neighbors only (filter out 'active' = cycle nodes) - let done_neighbor_sets: Vec<BTreeSet<usize>> = neighbors - .iter() - .filter(|n| traversal_state.get(n) == Some(&TraversalState::Done)) - .filter_map(|n| working.get(n).cloned()) - .collect(); - - let neighbor_intersection = if done_neighbor_sets.is_empty() { - BTreeSet::new() - } else { - let mut iter = done_neighbor_sets.into_iter(); - let first = iter.next().unwrap(); - iter.fold(first, |acc, s| acc.intersection(&s).copied().collect()) - }; - - let prev_objects = working.get(&node_id).cloned().unwrap_or_default(); - let mut merged: BTreeSet<usize> = prev_objects - .union(&neighbor_intersection) - .copied() - .collect(); - reduce_maybe_optional_chains(&mut merged, registry); - - working.insert(node_id, merged.clone()); - traversal_state.insert(node_id, TraversalState::Done); - - // Compare with previous value — can't just check size due to reduce_maybe_optional_chains - changed |= prev_objects != merged; - changed -} - -fn collect_hoistable_and_propagate( - func: &HirFunction, - env: &Environment, - temporaries: &HashMap<IdentifierId, ReactiveScopeDependency>, - hoistable_from_optionals: &HashMap<BlockId, ReactiveScopeDependency>, -) -> (HashMap<BlockId, BTreeSet<usize>>, PropertyPathRegistry) { - let mut registry = PropertyPathRegistry::new(); - let assumed_invoked_fns = get_assumed_invoked_functions(func, env); - let known_immutable_identifiers: HashSet<IdentifierId> = if func.fn_type - == ReactFunctionType::Component - || func.fn_type == ReactFunctionType::Hook - { - func.params - .iter() - .filter_map(|p| match p { - ParamPattern::Place(place) => Some(place.identifier), - _ => None, - }) - .collect() - } else { - HashSet::new() - }; - - let ctx = CollectHoistableContext { - temporaries, - known_immutable_identifiers: &known_immutable_identifiers, - hoistable_from_optionals, - nested_fn_immutable_context: None, - assumed_invoked_fns: &assumed_invoked_fns, - }; - - let nodes = collect_non_nulls_in_blocks(func, env, &ctx, &mut registry); - let working = propagate_non_null(func, &nodes, &mut registry); - - (working, registry) -} - -// Restructured version used by the main entry point -#[allow(dead_code)] -fn key_by_scope_id( - func: &HirFunction, - block_keyed: &HashMap<BlockId, BlockInfo>, -) -> HashMap<ScopeId, BlockInfo> { - let mut keyed: HashMap<ScopeId, BlockInfo> = HashMap::new(); - for (_block_id, block) in &func.body.blocks { - if let Terminal::Scope { - scope, - block: inner_block, - .. - } = &block.terminal - { - if let Some(info) = block_keyed.get(inner_block) { - keyed.insert(*scope, info.clone()); - } - } - } - keyed -} - -// ============================================================================= -// DeriveMinimalDependenciesHIR -// ============================================================================= - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum PropertyAccessType { - OptionalAccess, - UnconditionalAccess, - OptionalDependency, - UnconditionalDependency, -} - -fn is_optional_access(access: PropertyAccessType) -> bool { - matches!( - access, - PropertyAccessType::OptionalAccess | PropertyAccessType::OptionalDependency - ) -} - -fn is_dependency_access(access: PropertyAccessType) -> bool { - matches!( - access, - PropertyAccessType::OptionalDependency | PropertyAccessType::UnconditionalDependency - ) -} - -fn merge_access(a: PropertyAccessType, b: PropertyAccessType) -> PropertyAccessType { - let is_unconditional = !(is_optional_access(a) && is_optional_access(b)); - let is_dep = is_dependency_access(a) || is_dependency_access(b); - match (is_unconditional, is_dep) { - (true, true) => PropertyAccessType::UnconditionalDependency, - (true, false) => PropertyAccessType::UnconditionalAccess, - (false, true) => PropertyAccessType::OptionalDependency, - (false, false) => PropertyAccessType::OptionalAccess, - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum HoistableAccessType { - Optional, - NonNull, -} - -struct HoistableNode { - properties: HashMap<PropertyLiteral, Box<HoistableNodeEntry>>, - access_type: HoistableAccessType, -} - -struct HoistableNodeEntry { - node: HoistableNode, -} - -struct DependencyNode { - properties: IndexMap<PropertyLiteral, Box<DependencyNodeEntry>>, - access_type: PropertyAccessType, - loc: Option<react_compiler_hir::SourceLocation>, -} - -struct DependencyNodeEntry { - node: DependencyNode, -} - -struct ReactiveScopeDependencyTreeHIR { - hoistable_roots: HashMap<IdentifierId, (HoistableNode, bool)>, // node + reactive - dep_roots: IndexMap<IdentifierId, (DependencyNode, bool)>, // node + reactive (preserves insertion order like JS Map) -} - -impl ReactiveScopeDependencyTreeHIR { - fn new<'a>( - hoistable_objects: impl Iterator<Item = &'a ReactiveScopeDependency>, - _env: &Environment, - ) -> Self { - let mut hoistable_roots: HashMap<IdentifierId, (HoistableNode, bool)> = HashMap::new(); - - // Sort hoistable objects so that entries with optional first path come - // before non-optional ones. This matches the TS behavior where - // hoistableFromOptionals entries are inserted into the JS Set before - // instruction-based entries, and the first insertion determines the - // root access type. - let mut sorted_deps: Vec<&ReactiveScopeDependency> = hoistable_objects.collect(); - sorted_deps.sort_by(|a, b| { - let a_optional = !a.path.is_empty() && a.path[0].optional; - let b_optional = !b.path.is_empty() && b.path[0].optional; - b_optional.cmp(&a_optional) - }); - - for dep in sorted_deps { - let root = hoistable_roots.entry(dep.identifier).or_insert_with(|| { - let access_type = if !dep.path.is_empty() && dep.path[0].optional { - HoistableAccessType::Optional - } else { - HoistableAccessType::NonNull - }; - ( - HoistableNode { - properties: HashMap::new(), - access_type, - }, - dep.reactive, - ) - }); - - let mut curr = &mut root.0; - for i in 0..dep.path.len() { - let access_type = if i + 1 < dep.path.len() && dep.path[i + 1].optional { - HoistableAccessType::Optional - } else { - HoistableAccessType::NonNull - }; - let entry = curr - .properties - .entry(dep.path[i].property.clone()) - .or_insert_with(|| { - Box::new(HoistableNodeEntry { - node: HoistableNode { - properties: HashMap::new(), - access_type, - }, - }) - }); - curr = &mut entry.node; - } - } - - Self { - hoistable_roots, - dep_roots: IndexMap::new(), - } - } - - fn add_dependency(&mut self, dep: ReactiveScopeDependency, _env: &Environment) { - let root = self.dep_roots.entry(dep.identifier).or_insert_with(|| { - ( - DependencyNode { - properties: IndexMap::new(), - access_type: PropertyAccessType::UnconditionalAccess, - loc: dep.loc, - }, - dep.reactive, - ) - }); - - let mut dep_cursor = &mut root.0; - let hoistable_cursor_root = self.hoistable_roots.get(&dep.identifier); - let mut hoistable_ptr: Option<&HoistableNode> = hoistable_cursor_root.map(|(n, _)| n); - - for entry in &dep.path { - let next_hoistable: Option<&HoistableNode>; - let access_type: PropertyAccessType; - - if entry.optional { - next_hoistable = - hoistable_ptr.and_then(|h| h.properties.get(&entry.property).map(|e| &e.node)); - - if hoistable_ptr.is_some() - && hoistable_ptr.unwrap().access_type == HoistableAccessType::NonNull - { - access_type = PropertyAccessType::UnconditionalAccess; - } else { - access_type = PropertyAccessType::OptionalAccess; - } - } else if hoistable_ptr.is_some() - && hoistable_ptr.unwrap().access_type == HoistableAccessType::NonNull - { - next_hoistable = - hoistable_ptr.and_then(|h| h.properties.get(&entry.property).map(|e| &e.node)); - access_type = PropertyAccessType::UnconditionalAccess; - } else { - // Break: truncate dependency - break; - } - - // make_or_merge_property - let child = dep_cursor - .properties - .entry(entry.property.clone()) - .or_insert_with(|| { - Box::new(DependencyNodeEntry { - node: DependencyNode { - properties: IndexMap::new(), - access_type, - loc: entry.loc, - }, - }) - }); - child.node.access_type = merge_access(child.node.access_type, access_type); - - dep_cursor = &mut child.node; - hoistable_ptr = next_hoistable; - } - - // Mark final node as dependency - dep_cursor.access_type = merge_access( - dep_cursor.access_type, - PropertyAccessType::OptionalDependency, - ); - } - - fn derive_minimal_dependencies(&self, _env: &Environment) -> Vec<ReactiveScopeDependency> { - let mut results = Vec::new(); - for (&root_id, (root_node, reactive)) in &self.dep_roots { - collect_minimal_deps_in_subtree(root_node, *reactive, root_id, &[], &mut results); - } - results - } -} - -fn collect_minimal_deps_in_subtree( - node: &DependencyNode, - reactive: bool, - root_id: IdentifierId, - path: &[DependencyPathEntry], - results: &mut Vec<ReactiveScopeDependency>, -) { - if is_dependency_access(node.access_type) { - results.push(ReactiveScopeDependency { - identifier: root_id, - reactive, - path: path.to_vec(), - loc: node.loc, - }); - } else { - for (child_name, child_entry) in &node.properties { - let mut new_path = path.to_vec(); - new_path.push(DependencyPathEntry { - property: child_name.clone(), - optional: is_optional_access(child_entry.node.access_type), - loc: child_entry.node.loc, - }); - collect_minimal_deps_in_subtree( - &child_entry.node, - reactive, - root_id, - &new_path, - results, - ); - } - } -} - -// ============================================================================= -// collectDependencies -// ============================================================================= - -/// A declaration record: instruction id + scope stack at declaration time. -#[derive(Clone)] -struct Decl { - id: EvaluationOrder, - scope_stack: Vec<ScopeId>, // copy of the scope stack at time of declaration -} - -/// Context for dependency collection. -struct DependencyCollectionContext<'a> { - declarations: HashMap<DeclarationId, Decl>, - reassignments: HashMap<IdentifierId, Decl>, - scope_stack: Vec<ScopeId>, - dep_stack: Vec<Vec<ReactiveScopeDependency>>, - deps: IndexMap<ScopeId, Vec<ReactiveScopeDependency>>, - temporaries: &'a HashMap<IdentifierId, ReactiveScopeDependency>, - #[allow(dead_code)] - temporaries_used_outside_scope: &'a HashSet<DeclarationId>, - processed_instrs_in_optional: &'a HashSet<ProcessedInstr>, - inner_fn_context: Option<EvaluationOrder>, -} - -impl<'a> DependencyCollectionContext<'a> { - fn new( - temporaries_used_outside_scope: &'a HashSet<DeclarationId>, - temporaries: &'a HashMap<IdentifierId, ReactiveScopeDependency>, - processed_instrs_in_optional: &'a HashSet<ProcessedInstr>, - ) -> Self { - Self { - declarations: HashMap::new(), - reassignments: HashMap::new(), - scope_stack: Vec::new(), - dep_stack: Vec::new(), - deps: IndexMap::new(), - temporaries, - temporaries_used_outside_scope, - processed_instrs_in_optional, - inner_fn_context: None, - } - } - - fn enter_scope(&mut self, scope_id: ScopeId) { - self.dep_stack.push(Vec::new()); - self.scope_stack.push(scope_id); - } - - fn exit_scope(&mut self, scope_id: ScopeId, pruned: bool, env: &mut Environment) { - let scoped_deps = self - .dep_stack - .pop() - .expect("[PropagateScopeDeps]: Unexpected scope mismatch"); - self.scope_stack.pop(); - - // Propagate dependencies upward - for dep in &scoped_deps { - if self.check_valid_dependency(dep, env) { - if let Some(top) = self.dep_stack.last_mut() { - top.push(dep.clone()); - } - } - } - - if !pruned { - self.deps.insert(scope_id, scoped_deps); - } - } - - fn current_scope(&self) -> Option<ScopeId> { - self.scope_stack.last().copied() - } - - fn declare(&mut self, identifier_id: IdentifierId, decl: Decl, env: &Environment) { - if self.inner_fn_context.is_some() { - return; - } - let decl_id = env.identifiers[identifier_id.0 as usize].declaration_id; - if !self.declarations.contains_key(&decl_id) { - self.declarations.insert(decl_id, decl.clone()); - } - self.reassignments.insert(identifier_id, decl); - } - - fn has_declared(&self, identifier_id: IdentifierId, env: &Environment) -> bool { - let decl_id = env.identifiers[identifier_id.0 as usize].declaration_id; - self.declarations.contains_key(&decl_id) - } - - fn check_valid_dependency(&self, dep: &ReactiveScopeDependency, env: &Environment) -> bool { - // Ref value is not a valid dep - let ty = &env.types[env.identifiers[dep.identifier.0 as usize].type_.0 as usize]; - if react_compiler_hir::is_ref_value_type(ty) { - return false; - } - // Object methods are not deps - if matches!(ty, Type::ObjectMethod) { - return false; - } - - let ident = &env.identifiers[dep.identifier.0 as usize]; - let current_declaration = self - .reassignments - .get(&dep.identifier) - .or_else(|| self.declarations.get(&ident.declaration_id)); - - if let Some(current_scope) = self.current_scope() { - if let Some(decl) = current_declaration { - let scope_range_start = env.scopes[current_scope.0 as usize].range.start; - return decl.id < scope_range_start; - } - } - false - } - - fn visit_operand(&mut self, place: &Place, env: &mut Environment) { - let dep = self - .temporaries - .get(&place.identifier) - .cloned() - .unwrap_or_else(|| ReactiveScopeDependency { - identifier: place.identifier, - reactive: place.reactive, - path: vec![], - loc: place.loc, - }); - self.visit_dependency(dep, env); - } - - fn visit_property( - &mut self, - object: &Place, - property: &PropertyLiteral, - optional: bool, - loc: Option<react_compiler_hir::SourceLocation>, - env: &mut Environment, - ) { - let dep = get_property(object, property, optional, loc, self.temporaries, env); - self.visit_dependency(dep, env); - } - - fn visit_dependency(&mut self, dep: ReactiveScopeDependency, env: &mut Environment) { - let ident = &env.identifiers[dep.identifier.0 as usize]; - let decl_id = ident.declaration_id; - - // Record scope declarations for values used outside their declaring scope - if let Some(original_decl) = self.declarations.get(&decl_id) { - if !original_decl.scope_stack.is_empty() { - let orig_scope_stack = original_decl.scope_stack.clone(); - for &scope_id in &orig_scope_stack { - if !self.scope_stack.contains(&scope_id) { - // Check if already declared in this scope - let scope = &env.scopes[scope_id.0 as usize]; - let already_declared = scope.declarations.iter().any(|(_, d)| { - env.identifiers[d.identifier.0 as usize].declaration_id == decl_id - }); - if !already_declared { - let orig_scope_id = *orig_scope_stack.last().unwrap(); - let new_decl = react_compiler_hir::ReactiveScopeDeclaration { - identifier: dep.identifier, - scope: orig_scope_id, - }; - env.scopes[scope_id.0 as usize] - .declarations - .push((dep.identifier, new_decl)); - } - } - } - } - } - - // Handle ref.current access - let dep = if react_compiler_hir::is_use_ref_type( - &env.types[env.identifiers[dep.identifier.0 as usize].type_.0 as usize], - ) && dep - .path - .first() - .map(|p| p.property == PropertyLiteral::String("current".to_string())) - .unwrap_or(false) - { - ReactiveScopeDependency { - identifier: dep.identifier, - reactive: dep.reactive, - path: vec![], - loc: dep.loc, - } - } else { - dep - }; - - if self.check_valid_dependency(&dep, env) { - if let Some(top) = self.dep_stack.last_mut() { - top.push(dep); - } - } - } - - fn visit_reassignment(&mut self, place: &Place, env: &mut Environment) { - if let Some(current_scope) = self.current_scope() { - let scope = &env.scopes[current_scope.0 as usize]; - let already = scope.reassignments.iter().any(|id| { - env.identifiers[id.0 as usize].declaration_id - == env.identifiers[place.identifier.0 as usize].declaration_id - }); - if !already - && self.check_valid_dependency( - &ReactiveScopeDependency { - identifier: place.identifier, - reactive: place.reactive, - path: vec![], - loc: place.loc, - }, - env, - ) - { - env.scopes[current_scope.0 as usize] - .reassignments - .push(place.identifier); - } - } - } - - fn is_deferred_dependency_instr(&self, instr: &Instruction) -> bool { - self.processed_instrs_in_optional - .contains(&ProcessedInstr::Instruction(instr.lvalue.identifier)) - || self.temporaries.contains_key(&instr.lvalue.identifier) - } - - fn is_deferred_dependency_terminal(&self, block_id: BlockId) -> bool { - self.processed_instrs_in_optional - .contains(&ProcessedInstr::Terminal(block_id)) - } -} - -/// Recursively visit an inner function's blocks, processing all instructions -/// including nested FunctionExpressions. This mirrors the TS pattern of -/// `context.enterInnerFn(instr, () => handleFunction(innerFn))`. -fn visit_inner_function_blocks( - func_id: FunctionId, - ctx: &mut DependencyCollectionContext, - env: &mut Environment, -) { - // Clone inner function's instructions and block structure to avoid - // borrow conflicts when mutating env through handle_instruction. - let inner_instrs: Vec<Instruction> = env.functions[func_id.0 as usize].instructions.clone(); - let inner_blocks: Vec<( - BlockId, - Vec<InstructionId>, - Vec<(BlockId, IdentifierId)>, - Terminal, - )> = env.functions[func_id.0 as usize] - .body - .blocks - .iter() - .map(|(bid, blk)| { - let phi_ops: Vec<(BlockId, IdentifierId)> = blk - .phis - .iter() - .flat_map(|phi| { - phi.operands - .iter() - .map(|(pred, place)| (*pred, place.identifier)) - }) - .collect(); - ( - *bid, - blk.instructions.clone(), - phi_ops, - blk.terminal.clone(), - ) - }) - .collect(); - - for (inner_bid, inner_instr_ids, inner_phis, inner_terminal) in &inner_blocks { - for &(_pred_id, op_id) in inner_phis { - if let Some(maybe_optional) = ctx.temporaries.get(&op_id) { - ctx.visit_dependency(maybe_optional.clone(), env); - } - } - - for &iid in inner_instr_ids { - let inner_instr = &inner_instrs[iid.0 as usize]; - match &inner_instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - // Recursively visit nested function expressions - let scope_stack_copy = ctx.scope_stack.clone(); - ctx.declare( - inner_instr.lvalue.identifier, - Decl { - id: inner_instr.id, - scope_stack: scope_stack_copy, - }, - env, - ); - visit_inner_function_blocks(lowered_func.func, ctx, env); - } - _ => { - handle_instruction(inner_instr, ctx, env); - } - } - } - - if !ctx.is_deferred_dependency_terminal(*inner_bid) { - let terminal_ops = visitors::each_terminal_operand(inner_terminal); - for op in &terminal_ops { - ctx.visit_operand(op, env); - } - } - } -} - -fn handle_instruction( - instr: &Instruction, - ctx: &mut DependencyCollectionContext, - env: &mut Environment, -) { - let id = instr.id; - let scope_stack_copy = ctx.scope_stack.clone(); - ctx.declare( - instr.lvalue.identifier, - Decl { - id, - scope_stack: scope_stack_copy, - }, - env, - ); - - if ctx.is_deferred_dependency_instr(instr) { - return; - } - - match &instr.value { - InstructionValue::PropertyLoad { - object, - property, - loc, - .. - } => { - ctx.visit_property(object, property, false, *loc, env); - } - InstructionValue::StoreLocal { - value: val, lvalue, .. - } => { - ctx.visit_operand(val, env); - if lvalue.kind == InstructionKind::Reassign { - ctx.visit_reassignment(&lvalue.place, env); - } - let scope_stack_copy = ctx.scope_stack.clone(); - ctx.declare( - lvalue.place.identifier, - Decl { - id, - scope_stack: scope_stack_copy, - }, - env, - ); - } - InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::DeclareContext { lvalue, .. } => { - if convert_hoisted_lvalue_kind(lvalue.kind).is_none() { - let scope_stack_copy = ctx.scope_stack.clone(); - ctx.declare( - lvalue.place.identifier, - Decl { - id, - scope_stack: scope_stack_copy, - }, - env, - ); - } - } - InstructionValue::Destructure { - value: val, lvalue, .. - } => { - ctx.visit_operand(val, env); - let pattern_places = visitors::each_pattern_operand(&lvalue.pattern); - for place in &pattern_places { - if lvalue.kind == InstructionKind::Reassign { - ctx.visit_reassignment(place, env); - } - let scope_stack_copy = ctx.scope_stack.clone(); - ctx.declare( - place.identifier, - Decl { - id, - scope_stack: scope_stack_copy, - }, - env, - ); - } - } - InstructionValue::StoreContext { - lvalue, value: val, .. - } => { - if !ctx.has_declared(lvalue.place.identifier, env) - || lvalue.kind != InstructionKind::Reassign - { - let scope_stack_copy = ctx.scope_stack.clone(); - ctx.declare( - lvalue.place.identifier, - Decl { - id, - scope_stack: scope_stack_copy, - }, - env, - ); - } - // Visit all operands (lvalue.place AND value) - ctx.visit_operand(&lvalue.place, env); - ctx.visit_operand(val, env); - } - _ => { - // Visit all value operands - let operands = visitors::each_instruction_value_operand(&instr.value, env); - for operand in &operands { - ctx.visit_operand(operand, env); - } - } - } -} - -fn collect_dependencies( - func: &HirFunction, - env: &mut Environment, - used_outside_declaring_scope: &HashSet<DeclarationId>, - temporaries: &HashMap<IdentifierId, ReactiveScopeDependency>, - processed_instrs_in_optional: &HashSet<ProcessedInstr>, -) -> IndexMap<ScopeId, Vec<ReactiveScopeDependency>> { - let mut ctx = DependencyCollectionContext::new( - used_outside_declaring_scope, - temporaries, - processed_instrs_in_optional, - ); - - // Declare params - for param in &func.params { - match param { - ParamPattern::Place(place) => { - ctx.declare( - place.identifier, - Decl { - id: EvaluationOrder(0), - scope_stack: vec![], - }, - env, - ); - } - ParamPattern::Spread(spread) => { - ctx.declare( - spread.place.identifier, - Decl { - id: EvaluationOrder(0), - scope_stack: vec![], - }, - env, - ); - } - } - } - - let mut traversal = ScopeBlockTraversal::new(); - - handle_function_deps(func, env, &mut ctx, &mut traversal); - - ctx.deps -} - -fn handle_function_deps( - func: &HirFunction, - env: &mut Environment, - ctx: &mut DependencyCollectionContext, - traversal: &mut ScopeBlockTraversal, -) { - for (block_id, block) in &func.body.blocks { - // Record scopes - traversal.record_scopes(block); - - let scope_block_info = traversal.block_infos.get(block_id).cloned(); - match &scope_block_info { - Some(ScopeBlockInfo::Begin { scope, .. }) => { - ctx.enter_scope(*scope); - } - Some(ScopeBlockInfo::End { scope, pruned, .. }) => { - ctx.exit_scope(*scope, *pruned, env); - } - None => {} - } - - // Record phi operands - for phi in &block.phis { - for (_pred_id, operand) in &phi.operands { - if let Some(maybe_optional_chain) = ctx.temporaries.get(&operand.identifier) { - ctx.visit_dependency(maybe_optional_chain.clone(), env); - } - } - } - - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - let scope_stack_copy = ctx.scope_stack.clone(); - ctx.declare( - instr.lvalue.identifier, - Decl { - id: instr.id, - scope_stack: scope_stack_copy, - }, - env, - ); - - // Recursively visit inner function - let inner_func_id = lowered_func.func; - let prev_inner = ctx.inner_fn_context; - if ctx.inner_fn_context.is_none() { - ctx.inner_fn_context = Some(instr.id); - } - - visit_inner_function_blocks(inner_func_id, ctx, env); - - ctx.inner_fn_context = prev_inner; - } - _ => { - handle_instruction(instr, ctx, env); - } - } - } - - // Terminal operands - if !ctx.is_deferred_dependency_terminal(*block_id) { - let terminal_ops = visitors::each_terminal_operand(&block.terminal); - for op in &terminal_ops { - ctx.visit_operand(op, env); - } - } - } -} diff --git a/compiler/crates/react_compiler_lowering/Cargo.toml b/compiler/crates/react_compiler_lowering/Cargo.toml deleted file mode 100644 index 0b586cfb2f1a..000000000000 --- a/compiler/crates/react_compiler_lowering/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "react_compiler_lowering" -version = "0.1.0" -edition = "2024" - -[dependencies] -react_compiler_ast = { path = "../react_compiler_ast" } -react_compiler_hir = { path = "../react_compiler_hir" } -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" } -indexmap = "2" -serde_json = "1" diff --git a/compiler/crates/react_compiler_lowering/src/build_hir.rs b/compiler/crates/react_compiler_lowering/src/build_hir.rs deleted file mode 100644 index ec605a0061ef..000000000000 --- a/compiler/crates/react_compiler_lowering/src/build_hir.rs +++ /dev/null @@ -1,7358 +0,0 @@ -use std::collections::HashSet; - -use indexmap::IndexMap; -use indexmap::IndexSet; -use react_compiler_ast::scope::BindingId; -use react_compiler_ast::scope::BindingKind as AstBindingKind; -use react_compiler_ast::scope::ScopeId; -use react_compiler_ast::scope::ScopeInfo; -use react_compiler_ast::scope::ScopeKind; -use react_compiler_diagnostics::CompilerDiagnostic; -use react_compiler_diagnostics::CompilerDiagnosticDetail; -use react_compiler_diagnostics::CompilerError; -use react_compiler_diagnostics::CompilerErrorDetail; -use react_compiler_diagnostics::ErrorCategory; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::*; - -use crate::FunctionNode; -use crate::find_context_identifiers::find_context_identifiers; -use crate::hir_builder::HirBuilder; -use crate::hir_builder::is_always_reserved_word; -use crate::hir_builder::reserved_identifier_diagnostic; -use crate::identifier_loc_index::IdentifierLocIndex; -use crate::identifier_loc_index::build_identifier_loc_index; - -// ============================================================================= -// Source location conversion -// ============================================================================= - -/// Convert an AST SourceLocation to an HIR SourceLocation. -fn convert_loc(loc: &react_compiler_ast::common::SourceLocation) -> SourceLocation { - SourceLocation { - start: Position { - line: loc.start.line, - column: loc.start.column, - index: loc.start.index, - }, - end: Position { - line: loc.end.line, - column: loc.end.column, - index: loc.end.index, - }, - } -} - -/// Convert an optional AST SourceLocation to an optional HIR SourceLocation. -fn convert_opt_loc( - loc: &Option<react_compiler_ast::common::SourceLocation>, -) -> Option<SourceLocation> { - loc.as_ref().map(convert_loc) -} - -/// Serialize an expression to a serde_json::Value for UnsupportedNode's original_node. -/// Returns None if serialization fails (should not happen for valid AST nodes). -/// This should ONLY be called on error/bail paths — never eagerly before deciding -/// to create an UnsupportedNode. -fn serialize_expression( - expr: &react_compiler_ast::expressions::Expression, -) -> Option<serde_json::Value> { - serde_json::to_value(expr).ok() -} - -/// Serialize a statement to a serde_json::Value for UnsupportedNode's original_node. -fn serialize_statement( - stmt: &react_compiler_ast::statements::Statement, -) -> Option<serde_json::Value> { - serde_json::to_value(stmt).ok() -} - -/// Serialize a pattern to a serde_json::Value for UnsupportedNode's original_node. -fn serialize_pattern(pat: &react_compiler_ast::patterns::PatternLike) -> Option<serde_json::Value> { - serde_json::to_value(pat).ok() -} - -fn pattern_like_loc( - pattern: &react_compiler_ast::patterns::PatternLike, -) -> Option<react_compiler_ast::common::SourceLocation> { - use react_compiler_ast::patterns::PatternLike; - match pattern { - PatternLike::Identifier(id) => id.base.loc.clone(), - PatternLike::ObjectPattern(p) => p.base.loc.clone(), - PatternLike::ArrayPattern(p) => p.base.loc.clone(), - PatternLike::AssignmentPattern(p) => p.base.loc.clone(), - PatternLike::RestElement(p) => p.base.loc.clone(), - PatternLike::MemberExpression(p) => p.base.loc.clone(), - PatternLike::TSAsExpression(p) => p.base.loc.clone(), - PatternLike::TSSatisfiesExpression(p) => p.base.loc.clone(), - PatternLike::TSNonNullExpression(p) => p.base.loc.clone(), - PatternLike::TSTypeAssertion(p) => p.base.loc.clone(), - PatternLike::TypeCastExpression(p) => p.base.loc.clone(), - } -} - -/// Extract the HIR SourceLocation from an Expression AST node. -fn expression_loc(expr: &react_compiler_ast::expressions::Expression) -> Option<SourceLocation> { - use react_compiler_ast::expressions::Expression; - let loc = match expr { - Expression::Identifier(e) => e.base.loc.clone(), - Expression::StringLiteral(e) => e.base.loc.clone(), - Expression::NumericLiteral(e) => e.base.loc.clone(), - Expression::BooleanLiteral(e) => e.base.loc.clone(), - Expression::NullLiteral(e) => e.base.loc.clone(), - Expression::BigIntLiteral(e) => e.base.loc.clone(), - Expression::RegExpLiteral(e) => e.base.loc.clone(), - Expression::CallExpression(e) => e.base.loc.clone(), - Expression::MemberExpression(e) => e.base.loc.clone(), - Expression::OptionalCallExpression(e) => e.base.loc.clone(), - Expression::OptionalMemberExpression(e) => e.base.loc.clone(), - Expression::BinaryExpression(e) => e.base.loc.clone(), - Expression::LogicalExpression(e) => e.base.loc.clone(), - Expression::UnaryExpression(e) => e.base.loc.clone(), - Expression::UpdateExpression(e) => e.base.loc.clone(), - Expression::ConditionalExpression(e) => e.base.loc.clone(), - Expression::AssignmentExpression(e) => e.base.loc.clone(), - Expression::SequenceExpression(e) => e.base.loc.clone(), - Expression::ArrowFunctionExpression(e) => e.base.loc.clone(), - Expression::FunctionExpression(e) => e.base.loc.clone(), - Expression::ObjectExpression(e) => e.base.loc.clone(), - Expression::ArrayExpression(e) => e.base.loc.clone(), - Expression::NewExpression(e) => e.base.loc.clone(), - Expression::TemplateLiteral(e) => e.base.loc.clone(), - Expression::TaggedTemplateExpression(e) => e.base.loc.clone(), - Expression::AwaitExpression(e) => e.base.loc.clone(), - Expression::YieldExpression(e) => e.base.loc.clone(), - Expression::SpreadElement(e) => e.base.loc.clone(), - Expression::MetaProperty(e) => e.base.loc.clone(), - Expression::ClassExpression(e) => e.base.loc.clone(), - Expression::PrivateName(e) => e.base.loc.clone(), - Expression::Super(e) => e.base.loc.clone(), - Expression::Import(e) => e.base.loc.clone(), - Expression::ThisExpression(e) => e.base.loc.clone(), - Expression::ParenthesizedExpression(e) => e.base.loc.clone(), - Expression::JSXElement(e) => e.base.loc.clone(), - Expression::JSXFragment(e) => e.base.loc.clone(), - Expression::AssignmentPattern(e) => e.base.loc.clone(), - Expression::TSAsExpression(e) => e.base.loc.clone(), - Expression::TSSatisfiesExpression(e) => e.base.loc.clone(), - Expression::TSNonNullExpression(e) => e.base.loc.clone(), - Expression::TSTypeAssertion(e) => e.base.loc.clone(), - Expression::TSInstantiationExpression(e) => e.base.loc.clone(), - Expression::TypeCastExpression(e) => e.base.loc.clone(), - }; - convert_opt_loc(&loc) -} - -fn validate_ts_this_parameter( - scope_info: &ScopeInfo, - function_scope: ScopeId, -) -> Result<(), CompilerError> { - let Some(scope) = scope_info.scopes.get(function_scope.0 as usize) else { - return Ok(()); - }; - let Some(binding_id) = scope.bindings.get("this") else { - return Ok(()); - }; - let Some(binding) = scope_info.bindings.get(binding_id.0 as usize) else { - return Ok(()); - }; - if matches!(binding.kind, AstBindingKind::Param) { - return Err(CompilerError::from(reserved_identifier_diagnostic("this"))); - } - Ok(()) -} - -fn is_class_scope_descendant(scope_info: &ScopeInfo, mut scope_id: ScopeId) -> bool { - while let Some(scope) = scope_info.scopes.get(scope_id.0 as usize) { - let Some(parent) = scope.parent else { - return false; - }; - let Some(parent_scope) = scope_info.scopes.get(parent.0 as usize) else { - return false; - }; - if matches!(parent_scope.kind, ScopeKind::Class) { - return true; - } - scope_id = parent; - } - false -} - -fn validate_ts_this_parameters_in_function_range( - scope_info: &ScopeInfo, - start: u32, - end: u32, -) -> Result<(), CompilerError> { - if start >= end { - return Ok(()); - } - for (node_start, scope_id) in &scope_info.node_to_scope { - if *node_start < start || *node_start >= end { - continue; - } - let Some(scope) = scope_info.scopes.get(scope_id.0 as usize) else { - continue; - }; - if !matches!(scope.kind, ScopeKind::Function) - || is_class_scope_descendant(scope_info, *scope_id) - { - continue; - } - validate_ts_this_parameter(scope_info, *scope_id)?; - } - Ok(()) -} - -/// Get the Babel-style type name of an Expression node (e.g. "Identifier", "NumericLiteral"). -fn expression_type_name(expr: &react_compiler_ast::expressions::Expression) -> &'static str { - use react_compiler_ast::expressions::Expression; - match expr { - Expression::Identifier(_) => "Identifier", - Expression::StringLiteral(_) => "StringLiteral", - Expression::NumericLiteral(_) => "NumericLiteral", - Expression::BooleanLiteral(_) => "BooleanLiteral", - Expression::NullLiteral(_) => "NullLiteral", - Expression::BigIntLiteral(_) => "BigIntLiteral", - Expression::RegExpLiteral(_) => "RegExpLiteral", - Expression::CallExpression(_) => "CallExpression", - Expression::MemberExpression(_) => "MemberExpression", - Expression::OptionalCallExpression(_) => "OptionalCallExpression", - Expression::OptionalMemberExpression(_) => "OptionalMemberExpression", - Expression::BinaryExpression(_) => "BinaryExpression", - Expression::LogicalExpression(_) => "LogicalExpression", - Expression::UnaryExpression(_) => "UnaryExpression", - Expression::UpdateExpression(_) => "UpdateExpression", - Expression::ConditionalExpression(_) => "ConditionalExpression", - Expression::AssignmentExpression(_) => "AssignmentExpression", - Expression::SequenceExpression(_) => "SequenceExpression", - Expression::ArrowFunctionExpression(_) => "ArrowFunctionExpression", - Expression::FunctionExpression(_) => "FunctionExpression", - Expression::ObjectExpression(_) => "ObjectExpression", - Expression::ArrayExpression(_) => "ArrayExpression", - Expression::NewExpression(_) => "NewExpression", - Expression::TemplateLiteral(_) => "TemplateLiteral", - Expression::TaggedTemplateExpression(_) => "TaggedTemplateExpression", - Expression::AwaitExpression(_) => "AwaitExpression", - Expression::YieldExpression(_) => "YieldExpression", - Expression::SpreadElement(_) => "SpreadElement", - Expression::MetaProperty(_) => "MetaProperty", - Expression::ClassExpression(_) => "ClassExpression", - Expression::PrivateName(_) => "PrivateName", - Expression::Super(_) => "Super", - Expression::Import(_) => "Import", - Expression::ThisExpression(_) => "ThisExpression", - Expression::ParenthesizedExpression(_) => "ParenthesizedExpression", - Expression::JSXElement(_) => "JSXElement", - Expression::JSXFragment(_) => "JSXFragment", - Expression::AssignmentPattern(_) => "AssignmentPattern", - Expression::TSAsExpression(_) => "TSAsExpression", - Expression::TSSatisfiesExpression(_) => "TSSatisfiesExpression", - Expression::TSNonNullExpression(_) => "TSNonNullExpression", - Expression::TSTypeAssertion(_) => "TSTypeAssertion", - Expression::TSInstantiationExpression(_) => "TSInstantiationExpression", - Expression::TypeCastExpression(_) => "TypeCastExpression", - } -} - -/// Extract the type annotation name from an identifier's typeAnnotation field. -/// The Babel AST stores type annotations as: -/// { "type": "TSTypeAnnotation", "typeAnnotation": { "type": "TSTypeReference", ... } } -/// or { "type": "TypeAnnotation", "typeAnnotation": { "type": "GenericTypeAnnotation", ... } } -/// We extract the inner typeAnnotation's `type` field name. -fn extract_type_annotation_name( - type_annotation: &Option<react_compiler_ast::common::RawNode>, -) -> Option<String> { - let val = type_annotation.as_ref()?.parse_value(); - // Navigate: typeAnnotation.typeAnnotation.type - let inner = val.get("typeAnnotation")?; - let type_name = inner.get("type")?.as_str()?; - Some(type_name.to_string()) -} - -// ============================================================================= -// Helper functions -// ============================================================================= - -fn build_temporary_place(builder: &mut HirBuilder, loc: Option<SourceLocation>) -> Place { - let id = builder.make_temporary(loc.clone()); - Place { - identifier: id, - reactive: false, - effect: Effect::Unknown, - loc, - } -} - -/// Promote a temporary identifier to a named identifier (for destructuring). -/// Corresponds to TS `promoteTemporary(identifier)`. -fn promote_temporary(builder: &mut HirBuilder, identifier_id: IdentifierId) { - let env = builder.environment_mut(); - let decl_id = env.identifiers[identifier_id.0 as usize].declaration_id; - env.identifiers[identifier_id.0 as usize].name = - Some(IdentifierName::Promoted(format!("#t{}", decl_id.0))); -} - -fn lower_value_to_temporary( - builder: &mut HirBuilder, - value: InstructionValue, -) -> Result<Place, CompilerError> { - // Optimization: if loading an unnamed temporary, skip creating a new instruction - if let InstructionValue::LoadLocal { ref place, .. } = value { - let ident = &builder.environment().identifiers[place.identifier.0 as usize]; - if ident.name.is_none() { - return Ok(place.clone()); - } - } - let loc = value.loc().cloned(); - let place = build_temporary_place(builder, loc.clone()); - builder.push(Instruction { - id: EvaluationOrder(0), - lvalue: place.clone(), - value, - loc, - effects: None, - }); - Ok(place) -} - -fn lower_expression_to_temporary( - builder: &mut HirBuilder, - expr: &react_compiler_ast::expressions::Expression, -) -> Result<Place, CompilerError> { - let value = lower_expression(builder, expr)?; - Ok(lower_value_to_temporary(builder, value)?) -} - -// ============================================================================= -// Operator conversion -// ============================================================================= - -fn convert_binary_operator(op: &react_compiler_ast::operators::BinaryOperator) -> BinaryOperator { - use react_compiler_ast::operators::BinaryOperator as AstOp; - match op { - AstOp::Add => BinaryOperator::Add, - AstOp::Sub => BinaryOperator::Subtract, - AstOp::Mul => BinaryOperator::Multiply, - AstOp::Div => BinaryOperator::Divide, - AstOp::Rem => BinaryOperator::Modulo, - AstOp::Exp => BinaryOperator::Exponent, - AstOp::Eq => BinaryOperator::Equal, - AstOp::StrictEq => BinaryOperator::StrictEqual, - AstOp::Neq => BinaryOperator::NotEqual, - AstOp::StrictNeq => BinaryOperator::StrictNotEqual, - AstOp::Lt => BinaryOperator::LessThan, - AstOp::Lte => BinaryOperator::LessEqual, - AstOp::Gt => BinaryOperator::GreaterThan, - AstOp::Gte => BinaryOperator::GreaterEqual, - AstOp::Shl => BinaryOperator::ShiftLeft, - AstOp::Shr => BinaryOperator::ShiftRight, - AstOp::UShr => BinaryOperator::UnsignedShiftRight, - AstOp::BitOr => BinaryOperator::BitwiseOr, - AstOp::BitXor => BinaryOperator::BitwiseXor, - AstOp::BitAnd => BinaryOperator::BitwiseAnd, - AstOp::In => BinaryOperator::In, - AstOp::Instanceof => BinaryOperator::InstanceOf, - AstOp::Pipeline => { - unreachable!("Pipeline operator is checked before calling convert_binary_operator") - } - } -} - -fn convert_unary_operator(op: &react_compiler_ast::operators::UnaryOperator) -> UnaryOperator { - use react_compiler_ast::operators::UnaryOperator as AstOp; - match op { - AstOp::Neg => UnaryOperator::Minus, - AstOp::Plus => UnaryOperator::Plus, - AstOp::Not => UnaryOperator::Not, - AstOp::BitNot => UnaryOperator::BitwiseNot, - AstOp::TypeOf => UnaryOperator::TypeOf, - AstOp::Void => UnaryOperator::Void, - AstOp::Delete | AstOp::Throw => unreachable!("delete/throw handled separately"), - } -} - -// ============================================================================= -// lower_identifier -// ============================================================================= - -/// Resolve an identifier to a Place. -/// -/// For local/context identifiers, returns a Place referencing the binding's identifier. -/// For globals/imports, emits a LoadGlobal instruction and returns the temporary Place. -fn lower_identifier( - builder: &mut HirBuilder, - name: &str, - start: u32, - loc: Option<SourceLocation>, - node_id: Option<u32>, -) -> Result<Place, CompilerError> { - let binding = builder.resolve_identifier(name, start, loc.clone(), node_id)?; - match binding { - VariableBinding::Identifier { identifier, .. } => Ok(Place { - identifier, - effect: Effect::Unknown, - reactive: false, - loc, - }), - _ => { - if let VariableBinding::Global { ref name } = binding { - if name == "eval" { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::UnsupportedSyntax, - reason: "The 'eval' function is not supported".to_string(), - description: Some( - "Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler".to_string(), - ), - loc: loc.clone(), - suggestions: None, - })?; - } - } - let non_local_binding = match binding { - VariableBinding::Global { name } => NonLocalBinding::Global { name }, - VariableBinding::ImportDefault { name, module } => { - NonLocalBinding::ImportDefault { name, module } - } - VariableBinding::ImportSpecifier { - name, - module, - imported, - } => NonLocalBinding::ImportSpecifier { - name, - module, - imported, - }, - VariableBinding::ImportNamespace { name, module } => { - NonLocalBinding::ImportNamespace { name, module } - } - VariableBinding::ModuleLocal { name } => NonLocalBinding::ModuleLocal { name }, - VariableBinding::Identifier { .. } => unreachable!(), - }; - let instr_value = InstructionValue::LoadGlobal { - binding: non_local_binding, - loc: loc.clone(), - }; - Ok(lower_value_to_temporary(builder, instr_value)?) - } - } -} - -// ============================================================================= -// lower_arguments -// ============================================================================= - -fn lower_arguments( - builder: &mut HirBuilder, - args: &[react_compiler_ast::expressions::Expression], -) -> Result<Vec<PlaceOrSpread>, CompilerError> { - use react_compiler_ast::expressions::Expression; - let mut result = Vec::new(); - for arg in args { - match arg { - Expression::SpreadElement(spread) => { - let place = lower_expression_to_temporary(builder, &spread.argument)?; - result.push(PlaceOrSpread::Spread(SpreadPattern { place })); - } - _ => { - let place = lower_expression_to_temporary(builder, arg)?; - result.push(PlaceOrSpread::Place(place)); - } - } - } - Ok(result) -} - -fn convert_update_operator(op: &react_compiler_ast::operators::UpdateOperator) -> UpdateOperator { - match op { - react_compiler_ast::operators::UpdateOperator::Increment => UpdateOperator::Increment, - react_compiler_ast::operators::UpdateOperator::Decrement => UpdateOperator::Decrement, - } -} - -// ============================================================================= -// lower_member_expression -// ============================================================================= - -enum MemberProperty { - Literal(PropertyLiteral), - Computed(Place), -} - -struct LoweredMemberExpression { - object: Place, - property: MemberProperty, - value: InstructionValue, -} - -fn lower_member_expression( - builder: &mut HirBuilder, - member: &react_compiler_ast::expressions::MemberExpression, -) -> Result<LoweredMemberExpression, CompilerError> { - Ok(lower_member_expression_impl(builder, member, None)?) -} - -fn lower_member_expression_with_object( - builder: &mut HirBuilder, - member: &react_compiler_ast::expressions::OptionalMemberExpression, - lowered_object: Place, -) -> Result<LoweredMemberExpression, CompilerError> { - // OptionalMemberExpression has the same shape as MemberExpression for property access - use react_compiler_ast::expressions::Expression; - let loc = convert_opt_loc(&member.base.loc); - let object = lowered_object; - - if !member.computed { - let prop_literal = match member.property.as_ref() { - Expression::Identifier(id) => PropertyLiteral::String(id.name.clone()), - Expression::NumericLiteral(lit) => { - PropertyLiteral::Number(FloatValue::new(lit.precise_value())) - } - _ => { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: format!( - "(BuildHIR::lowerMemberExpression) Handle {:?} property", - member.property - ), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(LoweredMemberExpression { - object, - property: MemberProperty::Literal(PropertyLiteral::String("".to_string())), - value: InstructionValue::UnsupportedNode { - node_type: Some("OptionalMemberExpression".to_string()), - original_node: serialize_expression( - &react_compiler_ast::expressions::Expression::OptionalMemberExpression( - member.clone(), - ), - ), - loc, - }, - }); - } - }; - let value = InstructionValue::PropertyLoad { - object: object.clone(), - property: prop_literal.clone(), - loc, - }; - Ok(LoweredMemberExpression { - object, - property: MemberProperty::Literal(prop_literal), - value, - }) - } else { - if let Expression::NumericLiteral(lit) = member.property.as_ref() { - let prop_literal = PropertyLiteral::Number(FloatValue::new(lit.precise_value())); - let value = InstructionValue::PropertyLoad { - object: object.clone(), - property: prop_literal.clone(), - loc, - }; - return Ok(LoweredMemberExpression { - object, - property: MemberProperty::Literal(prop_literal), - value, - }); - } - let property = lower_expression_to_temporary(builder, &member.property)?; - let value = InstructionValue::ComputedLoad { - object: object.clone(), - property: property.clone(), - loc, - }; - Ok(LoweredMemberExpression { - object, - property: MemberProperty::Computed(property), - value, - }) - } -} - -fn lower_member_expression_impl( - builder: &mut HirBuilder, - member: &react_compiler_ast::expressions::MemberExpression, - lowered_object: Option<Place>, -) -> Result<LoweredMemberExpression, CompilerError> { - use react_compiler_ast::expressions::Expression; - let loc = convert_opt_loc(&member.base.loc); - let object = match lowered_object { - Some(obj) => obj, - None => lower_expression_to_temporary(builder, &member.object)?, - }; - - if !member.computed { - // Non-computed: property must be an identifier or numeric literal - let prop_literal = match member.property.as_ref() { - Expression::Identifier(id) => PropertyLiteral::String(id.name.clone()), - Expression::NumericLiteral(lit) => { - PropertyLiteral::Number(FloatValue::new(lit.precise_value())) - } - _ => { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: format!( - "(BuildHIR::lowerMemberExpression) Handle {:?} property", - member.property - ), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(LoweredMemberExpression { - object, - property: MemberProperty::Literal(PropertyLiteral::String("".to_string())), - value: InstructionValue::UnsupportedNode { - node_type: Some("MemberExpression".to_string()), - original_node: serialize_expression( - &react_compiler_ast::expressions::Expression::MemberExpression( - member.clone(), - ), - ), - loc, - }, - }); - } - }; - let value = InstructionValue::PropertyLoad { - object: object.clone(), - property: prop_literal.clone(), - loc, - }; - Ok(LoweredMemberExpression { - object, - property: MemberProperty::Literal(prop_literal), - value, - }) - } else { - // Computed: check for numeric literal first (treated as PropertyLoad in TS) - if let Expression::NumericLiteral(lit) = member.property.as_ref() { - let prop_literal = PropertyLiteral::Number(FloatValue::new(lit.precise_value())); - let value = InstructionValue::PropertyLoad { - object: object.clone(), - property: prop_literal.clone(), - loc, - }; - return Ok(LoweredMemberExpression { - object, - property: MemberProperty::Literal(prop_literal), - value, - }); - } - // Otherwise lower property to temporary for ComputedLoad - let property = lower_expression_to_temporary(builder, &member.property)?; - let value = InstructionValue::ComputedLoad { - object: object.clone(), - property: property.clone(), - loc, - }; - Ok(LoweredMemberExpression { - object, - property: MemberProperty::Computed(property), - value, - }) - } -} - -// ============================================================================= -// lower_expression -// ============================================================================= - -fn lower_expression( - builder: &mut HirBuilder, - expr: &react_compiler_ast::expressions::Expression, -) -> Result<InstructionValue, CompilerError> { - use react_compiler_ast::expressions::Expression; - - match expr { - Expression::Identifier(ident) => { - let loc = convert_opt_loc(&ident.base.loc); - let start = ident.base.start.unwrap_or(0); - let place = - lower_identifier(builder, &ident.name, start, loc.clone(), ident.base.node_id)?; - // Determine LoadLocal vs LoadContext based on context identifier check - if builder.is_context_identifier(&ident.name, start, ident.base.node_id) { - Ok(InstructionValue::LoadContext { place, loc }) - } else { - Ok(InstructionValue::LoadLocal { place, loc }) - } - } - Expression::NullLiteral(lit) => { - let loc = convert_opt_loc(&lit.base.loc); - Ok(InstructionValue::Primitive { - value: PrimitiveValue::Null, - loc, - }) - } - Expression::BooleanLiteral(lit) => { - let loc = convert_opt_loc(&lit.base.loc); - Ok(InstructionValue::Primitive { - value: PrimitiveValue::Boolean(lit.value), - loc, - }) - } - Expression::NumericLiteral(lit) => { - let loc = convert_opt_loc(&lit.base.loc); - Ok(InstructionValue::Primitive { - value: PrimitiveValue::Number(FloatValue::new(lit.precise_value())), - loc, - }) - } - Expression::StringLiteral(lit) => { - let loc = convert_opt_loc(&lit.base.loc); - Ok(InstructionValue::Primitive { - value: PrimitiveValue::String(lit.value.clone()), - loc, - }) - } - Expression::BinaryExpression(bin) => { - let loc = convert_opt_loc(&bin.base.loc); - // Check for pipeline operator before lowering operands - if matches!( - bin.operator, - react_compiler_ast::operators::BinaryOperator::Pipeline - ) { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Pipe operator not supported".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(InstructionValue::UnsupportedNode { - node_type: Some("BinaryExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }); - } - let left = lower_expression_to_temporary(builder, &bin.left)?; - let right = lower_expression_to_temporary(builder, &bin.right)?; - let operator = convert_binary_operator(&bin.operator); - Ok(InstructionValue::BinaryExpression { - operator, - left, - right, - loc, - }) - } - Expression::UnaryExpression(unary) => { - let loc = convert_opt_loc(&unary.base.loc); - match &unary.operator { - react_compiler_ast::operators::UnaryOperator::Delete => { - // Delete can be on member expressions or identifiers - let loc = convert_opt_loc(&unary.base.loc); - match &*unary.argument { - Expression::MemberExpression(member) => { - let object = lower_expression_to_temporary(builder, &member.object)?; - if !member.computed { - match &*member.property { - Expression::Identifier(prop_id) => { - Ok(InstructionValue::PropertyDelete { - object, - property: PropertyLiteral::String(prop_id.name.clone()), - loc, - }) - } - _ => { - builder.record_error(CompilerErrorDetail { - reason: "Unsupported delete target".to_string(), - category: ErrorCategory::Todo, - loc: loc.clone(), - description: None, - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("UnaryExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - } - } else { - let property = - lower_expression_to_temporary(builder, &member.property)?; - Ok(InstructionValue::ComputedDelete { - object, - property, - loc, - }) - } - } - _ => { - // delete on non-member expression (e.g., optional chain, identifier) - builder.record_error(CompilerErrorDetail { - reason: "Only object properties can be deleted".to_string(), - category: ErrorCategory::Syntax, - loc: loc.clone(), - description: None, - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("UnaryExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - } - } - react_compiler_ast::operators::UnaryOperator::Throw => { - // throw as unary operator (Babel-specific) - let loc = convert_opt_loc(&unary.base.loc); - builder.record_error(CompilerErrorDetail { - reason: "throw expressions are not supported".to_string(), - category: ErrorCategory::Todo, - loc: loc.clone(), - description: None, - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("UnaryExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - op => { - let value = lower_expression_to_temporary(builder, &unary.argument)?; - let operator = convert_unary_operator(op); - Ok(InstructionValue::UnaryExpression { - operator, - value, - loc, - }) - } - } - } - Expression::CallExpression(call) => { - let loc = convert_opt_loc(&call.base.loc); - // Check if callee is a MemberExpression => MethodCall - if let Expression::MemberExpression(member) = call.callee.as_ref() { - let lowered = lower_member_expression(builder, member)?; - let property = lower_value_to_temporary(builder, lowered.value)?; - let args = lower_arguments(builder, &call.arguments)?; - Ok(InstructionValue::MethodCall { - receiver: lowered.object, - property, - args, - loc, - }) - } else { - let callee = lower_expression_to_temporary(builder, &call.callee)?; - let args = lower_arguments(builder, &call.arguments)?; - Ok(InstructionValue::CallExpression { callee, args, loc }) - } - } - Expression::MemberExpression(member) => { - let lowered = lower_member_expression(builder, member)?; - Ok(lowered.value) - } - Expression::OptionalCallExpression(opt_call) => { - Ok(lower_optional_call_expression(builder, opt_call)?) - } - Expression::OptionalMemberExpression(opt_member) => { - Ok(lower_optional_member_expression(builder, opt_member)?) - } - Expression::LogicalExpression(expr) => { - let loc = convert_opt_loc(&expr.base.loc); - let continuation_block = builder.reserve(builder.current_block_kind()); - let continuation_id = continuation_block.id; - let test_block = builder.reserve(BlockKind::Value); - let test_block_id = test_block.id; - let place = build_temporary_place(builder, loc.clone()); - let left_loc = expression_loc(&expr.left); - let left_place = build_temporary_place(builder, left_loc); - - // Block for short-circuit case: store left value as result, goto continuation - let consequent_block = builder.try_enter(BlockKind::Value, |builder, _block_id| { - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Const, - place: place.clone(), - }, - value: left_place.clone(), - type_annotation: None, - loc: left_place.loc.clone(), - }, - )?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: left_place.loc.clone(), - }) - }); - - // Block for evaluating right side - let alternate_block = builder.try_enter(BlockKind::Value, |builder, _block_id| { - let right = lower_expression_to_temporary(builder, &expr.right)?; - let right_loc = right.loc.clone(); - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Const, - place: place.clone(), - }, - value: right, - type_annotation: None, - loc: right_loc.clone(), - }, - )?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: right_loc, - }) - }); - - let hir_op = match expr.operator { - react_compiler_ast::operators::LogicalOperator::And => LogicalOperator::And, - react_compiler_ast::operators::LogicalOperator::Or => LogicalOperator::Or, - react_compiler_ast::operators::LogicalOperator::NullishCoalescing => { - LogicalOperator::NullishCoalescing - } - }; - - builder.terminate_with_continuation( - Terminal::Logical { - operator: hir_op, - test: test_block_id, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - test_block, - ); - - // Now in test block: lower left expression, copy to left_place - let left_value = lower_expression_to_temporary(builder, &expr.left)?; - builder.push(Instruction { - id: EvaluationOrder(0), - lvalue: left_place.clone(), - value: InstructionValue::LoadLocal { - place: left_value, - loc: loc.clone(), - }, - effects: None, - loc: loc.clone(), - }); - - builder.terminate_with_continuation( - Terminal::Branch { - test: left_place, - consequent: consequent_block?, - alternate: alternate_block?, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - continuation_block, - ); - - Ok(InstructionValue::LoadLocal { - place: place.clone(), - loc: place.loc.clone(), - }) - } - Expression::UpdateExpression(update) => { - let loc = convert_opt_loc(&update.base.loc); - match update.argument.as_ref() { - Expression::MemberExpression(member) => { - let binary_op = match &update.operator { - react_compiler_ast::operators::UpdateOperator::Increment => { - BinaryOperator::Add - } - react_compiler_ast::operators::UpdateOperator::Decrement => { - BinaryOperator::Subtract - } - }; - // Use the member expression's loc (not the update expression's) - // to match TS behavior where the inner operations use leftExpr.node.loc - let member_loc = convert_opt_loc(&member.base.loc); - let lowered = lower_member_expression(builder, member)?; - let object = lowered.object; - let lowered_property = lowered.property; - let prev_value = lower_value_to_temporary(builder, lowered.value)?; - - let one = lower_value_to_temporary( - builder, - InstructionValue::Primitive { - value: PrimitiveValue::Number(FloatValue::new(1.0)), - loc: None, - }, - )?; - let updated = lower_value_to_temporary( - builder, - InstructionValue::BinaryExpression { - operator: binary_op, - left: prev_value.clone(), - right: one, - loc: member_loc.clone(), - }, - )?; - - // Store back using the property from the lowered member expression. - // For prefix, the result is the PropertyStore/ComputedStore lvalue - // (matching TS which uses newValuePlace). For postfix, it's prev_value. - let new_value_place = match lowered_property { - MemberProperty::Literal(prop_literal) => lower_value_to_temporary( - builder, - InstructionValue::PropertyStore { - object, - property: prop_literal, - value: updated.clone(), - loc: member_loc, - }, - )?, - MemberProperty::Computed(prop_place) => lower_value_to_temporary( - builder, - InstructionValue::ComputedStore { - object, - property: prop_place, - value: updated.clone(), - loc: member_loc, - }, - )?, - }; - - // Return previous for postfix, newValuePlace for prefix - let result_place = if update.prefix { - new_value_place - } else { - prev_value - }; - Ok(InstructionValue::LoadLocal { - place: result_place.clone(), - loc: result_place.loc.clone(), - }) - } - Expression::Identifier(ident) => { - let start = ident.base.start.unwrap_or(0); - if builder.is_context_identifier(&ident.name, start, ident.base.node_id) { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Handle UpdateExpression to variables captured within lambdas.".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(InstructionValue::UnsupportedNode { - node_type: Some("UpdateExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }); - } - - let ident_loc = convert_opt_loc(&ident.base.loc); - let binding = builder.resolve_identifier( - &ident.name, - start, - ident_loc.clone(), - ident.base.node_id, - )?; - match &binding { - VariableBinding::Global { .. } => { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "UpdateExpression where argument is a global is not yet supported".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(InstructionValue::UnsupportedNode { - node_type: Some("UpdateExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }); - } - _ => {} - } - let identifier = match binding { - VariableBinding::Identifier { identifier, .. } => identifier, - _ => { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Support UpdateExpression where argument is a global".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(InstructionValue::UnsupportedNode { - node_type: Some("UpdateExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }); - } - }; - let lvalue_place = Place { - identifier, - effect: Effect::Unknown, - reactive: false, - loc: ident_loc.clone(), - }; - - // Load the current value - let value = lower_identifier( - builder, - &ident.name, - start, - ident_loc, - ident.base.node_id, - )?; - - let operation = convert_update_operator(&update.operator); - - if update.prefix { - Ok(InstructionValue::PrefixUpdate { - lvalue: lvalue_place, - operation, - value, - loc, - }) - } else { - Ok(InstructionValue::PostfixUpdate { - lvalue: lvalue_place, - operation, - value, - loc, - }) - } - } - _ => { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: format!("UpdateExpression with unsupported argument type"), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("UpdateExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - } - } - Expression::ConditionalExpression(expr) => { - let loc = convert_opt_loc(&expr.base.loc); - let continuation_block = builder.reserve(builder.current_block_kind()); - let continuation_id = continuation_block.id; - let test_block = builder.reserve(BlockKind::Value); - let test_block_id = test_block.id; - let place = build_temporary_place(builder, loc.clone()); - - // Block for the consequent (test is truthy) - let consequent_ast_loc = expression_loc(&expr.consequent); - let consequent_block = builder.try_enter(BlockKind::Value, |builder, _block_id| { - let consequent = lower_expression_to_temporary(builder, &expr.consequent)?; - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Const, - place: place.clone(), - }, - value: consequent, - type_annotation: None, - loc: loc.clone(), - }, - )?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: consequent_ast_loc, - }) - }); - - // Block for the alternate (test is falsy) - let alternate_ast_loc = expression_loc(&expr.alternate); - let alternate_block = builder.try_enter(BlockKind::Value, |builder, _block_id| { - let alternate = lower_expression_to_temporary(builder, &expr.alternate)?; - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Const, - place: place.clone(), - }, - value: alternate, - type_annotation: None, - loc: loc.clone(), - }, - )?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: alternate_ast_loc, - }) - }); - - builder.terminate_with_continuation( - Terminal::Ternary { - test: test_block_id, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - test_block, - ); - - // Now in test block: lower test expression - let test_place = lower_expression_to_temporary(builder, &expr.test)?; - builder.terminate_with_continuation( - Terminal::Branch { - test: test_place, - consequent: consequent_block?, - alternate: alternate_block?, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - continuation_block, - ); - - Ok(InstructionValue::LoadLocal { - place: place.clone(), - loc: place.loc.clone(), - }) - } - Expression::AssignmentExpression(expr) => { - use react_compiler_ast::operators::AssignmentOperator; - let loc = convert_opt_loc(&expr.base.loc); - - if matches!(expr.operator, AssignmentOperator::Assign) { - // Simple `=` assignment - match &*expr.left { - react_compiler_ast::patterns::PatternLike::Identifier(ident) => { - // Handle simple identifier assignment directly - let start = ident.base.start.unwrap_or(0); - let right = lower_expression_to_temporary(builder, &expr.right)?; - let ident_loc = convert_opt_loc(&ident.base.loc); - let binding = builder.resolve_identifier( - &ident.name, - start, - ident_loc.clone(), - ident.base.node_id, - )?; - match binding { - VariableBinding::Identifier { - identifier, - binding_kind, - } => { - // Check for const reassignment - if binding_kind == BindingKind::Const { - builder.record_error(CompilerErrorDetail { - reason: "Cannot reassign a `const` variable".to_string(), - category: ErrorCategory::Syntax, - loc: ident_loc.clone(), - description: Some(format!( - "`{}` is declared as const", - &ident.name - )), - suggestions: None, - })?; - return Ok(InstructionValue::UnsupportedNode { - node_type: Some("Identifier".to_string()), - original_node: serialize_expression( - &Expression::AssignmentExpression(expr.clone()), - ), - loc: ident_loc, - }); - } - let place = Place { - identifier, - reactive: false, - effect: Effect::Unknown, - loc: ident_loc, - }; - if builder.is_context_identifier( - &ident.name, - start, - ident.base.node_id, - ) { - let temp = lower_value_to_temporary( - builder, - InstructionValue::StoreContext { - lvalue: LValue { - kind: InstructionKind::Reassign, - place: place.clone(), - }, - value: right, - loc: place.loc.clone(), - }, - )?; - Ok(InstructionValue::LoadLocal { - place: temp.clone(), - loc: temp.loc.clone(), - }) - } else { - let temp = lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Reassign, - place: place.clone(), - }, - value: right, - type_annotation: None, - loc: place.loc.clone(), - }, - )?; - Ok(InstructionValue::LoadLocal { - place: temp.clone(), - loc: temp.loc.clone(), - }) - } - } - _ => { - // Global or import assignment - let name = ident.name.clone(); - let temp = lower_value_to_temporary( - builder, - InstructionValue::StoreGlobal { - name, - value: right, - loc: ident_loc, - }, - )?; - Ok(InstructionValue::LoadLocal { - place: temp.clone(), - loc: temp.loc.clone(), - }) - } - } - } - react_compiler_ast::patterns::PatternLike::MemberExpression(member) => { - // Member expression assignment: a.b = value or a[b] = value - let right = lower_expression_to_temporary(builder, &expr.right)?; - let left_loc = convert_opt_loc(&member.base.loc); - let object = lower_expression_to_temporary(builder, &member.object)?; - let temp = if !member.computed - || matches!( - &*member.property, - react_compiler_ast::expressions::Expression::NumericLiteral(_) - ) { - match &*member.property { - react_compiler_ast::expressions::Expression::Identifier( - prop_id, - ) => lower_value_to_temporary( - builder, - InstructionValue::PropertyStore { - object, - property: PropertyLiteral::String(prop_id.name.clone()), - value: right, - loc: left_loc, - }, - )?, - react_compiler_ast::expressions::Expression::NumericLiteral( - num, - ) => lower_value_to_temporary( - builder, - InstructionValue::PropertyStore { - object, - property: PropertyLiteral::Number(FloatValue::new( - num.precise_value(), - )), - value: right, - loc: left_loc, - }, - )?, - _ => { - let prop = - lower_expression_to_temporary(builder, &member.property)?; - lower_value_to_temporary( - builder, - InstructionValue::ComputedStore { - object, - property: prop, - value: right, - loc: left_loc, - }, - )? - } - } - } else { - let prop = lower_expression_to_temporary(builder, &member.property)?; - lower_value_to_temporary( - builder, - InstructionValue::ComputedStore { - object, - property: prop, - value: right, - loc: left_loc, - }, - )? - }; - Ok(InstructionValue::LoadLocal { - place: temp.clone(), - loc: temp.loc.clone(), - }) - } - _ => { - // Destructuring assignment - let right = lower_expression_to_temporary(builder, &expr.right)?; - let left_loc = pattern_like_hir_loc(&expr.left); - let result = lower_assignment( - builder, - left_loc, - InstructionKind::Reassign, - &expr.left, - right.clone(), - AssignmentStyle::Destructure, - )?; - match result { - Some(place) => Ok(InstructionValue::LoadLocal { - place: place.clone(), - loc: place.loc.clone(), - }), - None => Ok(InstructionValue::LoadLocal { place: right, loc }), - } - } - } - } else { - // Compound assignment operators - let binary_op = match expr.operator { - AssignmentOperator::AddAssign => Some(BinaryOperator::Add), - AssignmentOperator::SubAssign => Some(BinaryOperator::Subtract), - AssignmentOperator::MulAssign => Some(BinaryOperator::Multiply), - AssignmentOperator::DivAssign => Some(BinaryOperator::Divide), - AssignmentOperator::RemAssign => Some(BinaryOperator::Modulo), - AssignmentOperator::ExpAssign => Some(BinaryOperator::Exponent), - AssignmentOperator::ShlAssign => Some(BinaryOperator::ShiftLeft), - AssignmentOperator::ShrAssign => Some(BinaryOperator::ShiftRight), - AssignmentOperator::UShrAssign => Some(BinaryOperator::UnsignedShiftRight), - AssignmentOperator::BitOrAssign => Some(BinaryOperator::BitwiseOr), - AssignmentOperator::BitXorAssign => Some(BinaryOperator::BitwiseXor), - AssignmentOperator::BitAndAssign => Some(BinaryOperator::BitwiseAnd), - AssignmentOperator::OrAssign - | AssignmentOperator::AndAssign - | AssignmentOperator::NullishAssign => { - // Logical assignment operators (||=, &&=, ??=) - not yet supported - builder.record_error(CompilerErrorDetail { - reason: - "Logical assignment operators (||=, &&=, ??=) are not yet supported" - .to_string(), - category: ErrorCategory::Todo, - loc: loc.clone(), - description: None, - suggestions: None, - })?; - return Ok(InstructionValue::UnsupportedNode { - node_type: Some("AssignmentExpression".to_string()), - original_node: serialize_expression(&Expression::AssignmentExpression( - expr.clone(), - )), - loc, - }); - } - AssignmentOperator::Assign => unreachable!(), - }; - let binary_op = match binary_op { - Some(op) => op, - None => { - return Ok(InstructionValue::UnsupportedNode { - node_type: Some("AssignmentExpression".to_string()), - original_node: serialize_expression(&Expression::AssignmentExpression( - expr.clone(), - )), - loc, - }); - } - }; - - match &*expr.left { - react_compiler_ast::patterns::PatternLike::Identifier(ident) => { - let start = ident.base.start.unwrap_or(0); - let left_place = lower_expression_to_temporary( - builder, - &react_compiler_ast::expressions::Expression::Identifier(ident.clone()), - )?; - let right = lower_expression_to_temporary(builder, &expr.right)?; - let binary_place = lower_value_to_temporary( - builder, - InstructionValue::BinaryExpression { - operator: binary_op, - left: left_place, - right, - loc: loc.clone(), - }, - )?; - let ident_loc = convert_opt_loc(&ident.base.loc); - let binding = builder.resolve_identifier( - &ident.name, - start, - ident_loc.clone(), - ident.base.node_id, - )?; - match binding { - VariableBinding::Identifier { identifier, .. } => { - let place = Place { - identifier, - reactive: false, - effect: Effect::Unknown, - loc: ident_loc, - }; - if builder.is_context_identifier( - &ident.name, - start, - ident.base.node_id, - ) { - lower_value_to_temporary( - builder, - InstructionValue::StoreContext { - lvalue: LValue { - kind: InstructionKind::Reassign, - place: place.clone(), - }, - value: binary_place, - loc: loc.clone(), - }, - )?; - Ok(InstructionValue::LoadContext { place, loc }) - } else { - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Reassign, - place: place.clone(), - }, - value: binary_place, - type_annotation: None, - loc: loc.clone(), - }, - )?; - Ok(InstructionValue::LoadLocal { place, loc }) - } - } - _ => { - // Global assignment - let name = ident.name.clone(); - let temp = lower_value_to_temporary( - builder, - InstructionValue::StoreGlobal { - name, - value: binary_place, - loc: loc.clone(), - }, - )?; - Ok(InstructionValue::LoadLocal { - place: temp.clone(), - loc: temp.loc.clone(), - }) - } - } - } - react_compiler_ast::patterns::PatternLike::MemberExpression(member) => { - // a.b += right: read, compute, store - // Match TS behavior: return the PropertyStore/ComputedStore value - // directly (let the caller lower it to a temporary) - let member_loc = convert_opt_loc(&member.base.loc); - let lowered = lower_member_expression(builder, member)?; - let object = lowered.object; - let lowered_property = lowered.property; - let current_value = lower_value_to_temporary(builder, lowered.value)?; - let right = lower_expression_to_temporary(builder, &expr.right)?; - let result = lower_value_to_temporary( - builder, - InstructionValue::BinaryExpression { - operator: binary_op, - left: current_value, - right, - loc: member_loc.clone(), - }, - )?; - // Return the store instruction value directly (matching TS behavior) - match lowered_property { - MemberProperty::Literal(prop_literal) => { - Ok(InstructionValue::PropertyStore { - object, - property: prop_literal, - value: result, - loc: member_loc, - }) - } - MemberProperty::Computed(prop_place) => { - Ok(InstructionValue::ComputedStore { - object, - property: prop_place, - value: result, - loc: member_loc, - }) - } - } - } - _ => { - builder.record_error(CompilerErrorDetail { - reason: "Compound assignment to complex pattern is not yet supported" - .to_string(), - category: ErrorCategory::Todo, - loc: loc.clone(), - description: None, - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("AssignmentExpression".to_string()), - original_node: serialize_expression(&Expression::AssignmentExpression( - expr.clone(), - )), - loc, - }) - } - } - } - } - Expression::SequenceExpression(seq) => { - let loc = convert_opt_loc(&seq.base.loc); - - if seq.expressions.is_empty() { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Syntax, - reason: "Expected sequence expression to have at least one expression" - .to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(InstructionValue::UnsupportedNode { - node_type: Some("SequenceExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }); - } - - let continuation_block = builder.reserve(builder.current_block_kind()); - let continuation_id = continuation_block.id; - let place = build_temporary_place(builder, loc.clone()); - - let sequence_block = builder.try_enter(BlockKind::Sequence, |builder, _block_id| { - let mut last: Option<Place> = None; - for item in &seq.expressions { - last = Some(lower_expression_to_temporary(builder, item)?); - } - if let Some(last) = last { - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Const, - place: place.clone(), - }, - value: last, - type_annotation: None, - loc: loc.clone(), - }, - )?; - } - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: loc.clone(), - }) - }); - - builder.terminate_with_continuation( - Terminal::Sequence { - block: sequence_block?, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - continuation_block, - ); - Ok(InstructionValue::LoadLocal { place, loc }) - } - Expression::ArrowFunctionExpression(_) => Ok(lower_function_to_value( - builder, - expr, - FunctionExpressionType::ArrowFunctionExpression, - )?), - Expression::FunctionExpression(_) => Ok(lower_function_to_value( - builder, - expr, - FunctionExpressionType::FunctionExpression, - )?), - Expression::ObjectExpression(obj) => { - let loc = convert_opt_loc(&obj.base.loc); - let mut properties: Vec<ObjectPropertyOrSpread> = Vec::new(); - for prop in &obj.properties { - match prop { - react_compiler_ast::expressions::ObjectExpressionProperty::ObjectProperty( - p, - ) => { - let key = lower_object_property_key(builder, &p.key, p.computed)?; - let key = match key { - Some(k) => k, - None => continue, - }; - let value = lower_expression_to_temporary(builder, &p.value)?; - properties.push(ObjectPropertyOrSpread::Property(ObjectProperty { - key, - property_type: ObjectPropertyType::Property, - place: value, - })); - } - react_compiler_ast::expressions::ObjectExpressionProperty::SpreadElement( - spread, - ) => { - let place = lower_expression_to_temporary(builder, &spread.argument)?; - properties.push(ObjectPropertyOrSpread::Spread(SpreadPattern { place })); - } - react_compiler_ast::expressions::ObjectExpressionProperty::ObjectMethod( - method, - ) => { - if let Some(prop) = lower_object_method(builder, method)? { - properties.push(ObjectPropertyOrSpread::Property(prop)); - } - } - } - } - Ok(InstructionValue::ObjectExpression { properties, loc }) - } - Expression::ArrayExpression(arr) => { - let loc = convert_opt_loc(&arr.base.loc); - let mut elements: Vec<ArrayElement> = Vec::new(); - for element in &arr.elements { - match element { - None => { - elements.push(ArrayElement::Hole); - } - Some(Expression::SpreadElement(spread)) => { - let place = lower_expression_to_temporary(builder, &spread.argument)?; - elements.push(ArrayElement::Spread(SpreadPattern { place })); - } - Some(expr) => { - let place = lower_expression_to_temporary(builder, expr)?; - elements.push(ArrayElement::Place(place)); - } - } - } - Ok(InstructionValue::ArrayExpression { elements, loc }) - } - Expression::NewExpression(new_expr) => { - let loc = convert_opt_loc(&new_expr.base.loc); - let callee = lower_expression_to_temporary(builder, &new_expr.callee)?; - let args = lower_arguments(builder, &new_expr.arguments)?; - Ok(InstructionValue::NewExpression { callee, args, loc }) - } - Expression::TemplateLiteral(tmpl) => { - let loc = convert_opt_loc(&tmpl.base.loc); - let subexprs: Vec<Place> = tmpl - .expressions - .iter() - .map(|e| lower_expression_to_temporary(builder, e)) - .collect::<Result<Vec<_>, _>>()?; - let quasis: Vec<TemplateQuasi> = tmpl - .quasis - .iter() - .map(|q| TemplateQuasi { - raw: q.value.raw.clone(), - cooked: q.value.cooked.clone(), - }) - .collect(); - Ok(InstructionValue::TemplateLiteral { - subexprs, - quasis, - loc, - }) - } - Expression::TaggedTemplateExpression(tagged) => { - let loc = convert_opt_loc(&tagged.base.loc); - if !tagged.quasi.expressions.is_empty() { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: - "(BuildHIR::lowerExpression) Handle tagged template with interpolations" - .to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(InstructionValue::UnsupportedNode { - node_type: Some("TaggedTemplateExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }); - } - assert!( - tagged.quasi.quasis.len() == 1, - "there should be only one quasi as we don't support interpolations yet" - ); - let quasi = &tagged.quasi.quasis[0]; - // Check if raw and cooked values differ (e.g., graphql tagged templates) - if quasi.value.raw != quasi.value.cooked.clone().unwrap_or_default() { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(InstructionValue::UnsupportedNode { - node_type: Some("TaggedTemplateExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }); - } - let value = TemplateQuasi { - raw: quasi.value.raw.clone(), - cooked: quasi.value.cooked.clone(), - }; - let tag = lower_expression_to_temporary(builder, &tagged.tag)?; - Ok(InstructionValue::TaggedTemplateExpression { tag, value, loc }) - } - Expression::AwaitExpression(await_expr) => { - let loc = convert_opt_loc(&await_expr.base.loc); - let value = lower_expression_to_temporary(builder, &await_expr.argument)?; - Ok(InstructionValue::Await { value, loc }) - } - Expression::YieldExpression(yld) => { - let loc = convert_opt_loc(&yld.base.loc); - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Handle YieldExpression expressions" - .to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("YieldExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - Expression::SpreadElement(spread) => { - // SpreadElement should be handled by the parent context (array/object/call) - // If we reach here, just lower the argument expression - Ok(lower_expression(builder, &spread.argument)?) - } - Expression::MetaProperty(meta) => { - let loc = convert_opt_loc(&meta.base.loc); - if meta.meta.name == "import" && meta.property.name == "meta" { - Ok(InstructionValue::MetaProperty { - meta: meta.meta.name.clone(), - property: meta.property.name.clone(), - loc, - }) - } else { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Handle MetaProperty expressions other than import.meta".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("MetaProperty".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - } - Expression::ClassExpression(cls) => { - let loc = convert_opt_loc(&cls.base.loc); - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Handle ClassExpression expressions" - .to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("ClassExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - Expression::PrivateName(pn) => { - let loc = convert_opt_loc(&pn.base.loc); - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Handle PrivateName expressions".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("PrivateName".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - Expression::Super(sup) => { - let loc = convert_opt_loc(&sup.base.loc); - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Handle Super expressions".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("Super".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - Expression::Import(imp) => { - let loc = convert_opt_loc(&imp.base.loc); - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Handle Import expressions".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("Import".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - Expression::ThisExpression(this) => { - let loc = convert_opt_loc(&this.base.loc); - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Handle ThisExpression expressions".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("ThisExpression".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - Expression::ParenthesizedExpression(paren) => { - Ok(lower_expression(builder, &paren.expression)?) - } - Expression::JSXElement(jsx_element) => { - let loc = convert_opt_loc(&jsx_element.base.loc); - let opening_loc = convert_opt_loc(&jsx_element.opening_element.base.loc); - let closing_loc = jsx_element - .closing_element - .as_ref() - .and_then(|c| convert_opt_loc(&c.base.loc)); - - // Lower the tag name - let tag = lower_jsx_element_name(builder, &jsx_element.opening_element.name)?; - - // Lower attributes (props) - let mut props: Vec<JsxAttribute> = Vec::new(); - for attr_item in &jsx_element.opening_element.attributes { - use react_compiler_ast::jsx::JSXAttributeItem; - use react_compiler_ast::jsx::JSXAttributeName; - use react_compiler_ast::jsx::JSXAttributeValue; - match attr_item { - JSXAttributeItem::JSXSpreadAttribute(spread) => { - let argument = lower_expression_to_temporary(builder, &spread.argument)?; - props.push(JsxAttribute::SpreadAttribute { argument }); - } - JSXAttributeItem::JSXAttribute(attr) => { - // Get the attribute name - let prop_name = match &attr.name { - JSXAttributeName::JSXIdentifier(id) => { - let name = &id.name; - if name.contains(':') { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: format!( - "(BuildHIR::lowerExpression) Unexpected colon in attribute name `{}`", - name - ), - description: None, - loc: convert_opt_loc(&id.base.loc), - suggestions: None, - })?; - } - name.clone() - } - JSXAttributeName::JSXNamespacedName(ns) => { - format!("{}:{}", ns.namespace.name, ns.name.name) - } - }; - - // Get the attribute value - let value = match &attr.value { - Some(JSXAttributeValue::StringLiteral(s)) => { - let str_loc = convert_opt_loc(&s.base.loc); - lower_value_to_temporary( - builder, - InstructionValue::Primitive { - value: PrimitiveValue::String(s.value.clone()), - loc: str_loc, - }, - )? - } - Some(JSXAttributeValue::JSXExpressionContainer(container)) => { - use react_compiler_ast::jsx::JSXExpressionContainerExpr; - match &container.expression { - JSXExpressionContainerExpr::JSXEmptyExpression(_) => { - // Empty expression container - skip this attribute - continue; - } - JSXExpressionContainerExpr::Expression(expr) => { - lower_expression_to_temporary(builder, expr)? - } - } - } - Some(JSXAttributeValue::JSXElement(el)) => { - let val = lower_expression( - builder, - &react_compiler_ast::expressions::Expression::JSXElement( - el.clone(), - ), - )?; - lower_value_to_temporary(builder, val)? - } - Some(JSXAttributeValue::JSXFragment(frag)) => { - let val = lower_expression( - builder, - &react_compiler_ast::expressions::Expression::JSXFragment( - frag.clone(), - ), - )?; - lower_value_to_temporary(builder, val)? - } - None => { - // No value means boolean true (e.g., <div disabled />) - let attr_loc = convert_opt_loc(&attr.base.loc); - lower_value_to_temporary( - builder, - InstructionValue::Primitive { - value: PrimitiveValue::Boolean(true), - loc: attr_loc, - }, - )? - } - }; - - props.push(JsxAttribute::Attribute { - name: prop_name, - place: value, - }); - } - } - } - - // Check if this is an fbt/fbs tag, which requires special whitespace handling - let is_fbt = matches!(&tag, JsxTag::Builtin(b) if b.name == "fbt" || b.name == "fbs"); - - // Check that fbt/fbs tags are module-level imports, not local bindings. - // Matches TS: CompilerError.invariant(tagIdentifier.kind !== 'Identifier', ...) - if is_fbt { - let tag_name = match &tag { - JsxTag::Builtin(b) => b.name.clone(), - _ => "fbt".to_string(), - }; - // Get the opening element's name identifier and check if it's a local binding - if let react_compiler_ast::jsx::JSXElementName::JSXIdentifier(jsx_id) = - &jsx_element.opening_element.name - { - let id_loc = convert_opt_loc(&jsx_id.base.loc); - // Check if fbt/fbs tag name resolves to a local binding. - // JSX identifiers may not be in our position-based reference map, - // so check if ANY binding with this name exists in the function scope. - let is_local_binding = builder.has_local_binding(&jsx_id.name); - if is_local_binding { - // Record as a Diagnostic (not ErrorDetail) to match TS behavior - // where CompilerError.invariant creates a CompilerDiagnostic. - // TS invariant() throws immediately, so only the first fbt error - // is reported. We return Err to match this behavior. - let reason = format!("<{}> tags should be module-level imports", tag_name); - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - &reason, - None, - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: id_loc.clone(), - message: Some(reason.clone()), - identifier_name: None, - }) - .into()); - } - } - } - - // Check for duplicate fbt:enum, fbt:plural, fbt:pronoun tags - if is_fbt { - let tag_name = match &tag { - JsxTag::Builtin(b) => b.name.as_str(), - _ => "fbt", - }; - let mut enum_locs: Vec<Option<SourceLocation>> = Vec::new(); - let mut plural_locs: Vec<Option<SourceLocation>> = Vec::new(); - let mut pronoun_locs: Vec<Option<SourceLocation>> = Vec::new(); - collect_fbt_sub_tags( - &jsx_element.children, - tag_name, - &mut enum_locs, - &mut plural_locs, - &mut pronoun_locs, - ); - - for (name, locations) in [ - ("enum", &enum_locs), - ("plural", &plural_locs), - ("pronoun", &pronoun_locs), - ] { - if locations.len() > 1 { - use react_compiler_diagnostics::CompilerDiagnosticDetail; - let details: Vec<CompilerDiagnosticDetail> = locations - .iter() - .map(|loc| CompilerDiagnosticDetail::Error { - message: Some(format!( - "Multiple `<{}:{}>` tags found", - tag_name, name - )), - loc: loc.clone(), - identifier_name: None, - }) - .collect(); - let mut diag = react_compiler_diagnostics::CompilerDiagnostic::new( - ErrorCategory::Todo, - "Support duplicate fbt tags", - Some(format!( - "Support `<{}>` tags with multiple `<{}:{}>` values", - tag_name, tag_name, name - )), - ); - diag.details = details; - builder.environment_mut().record_diagnostic(diag); - } - } - } - - // Increment fbt counter before traversing into children, as whitespace - // in jsx text is handled differently for fbt subtrees. - if is_fbt { - builder.fbt_depth += 1; - } - - // Lower children - let children: Vec<Place> = jsx_element - .children - .iter() - .map(|child| lower_jsx_element(builder, child)) - .collect::<Result<Vec<_>, _>>()? - .into_iter() - .flatten() - .collect(); - - if is_fbt { - builder.fbt_depth -= 1; - } - - Ok(InstructionValue::JsxExpression { - tag, - props, - children: if children.is_empty() { - None - } else { - Some(children) - }, - loc, - opening_loc, - closing_loc, - }) - } - Expression::JSXFragment(jsx_fragment) => { - let loc = convert_opt_loc(&jsx_fragment.base.loc); - - // Lower children - let children: Vec<Place> = jsx_fragment - .children - .iter() - .map(|child| lower_jsx_element(builder, child)) - .collect::<Result<Vec<_>, _>>()? - .into_iter() - .flatten() - .collect(); - - Ok(InstructionValue::JsxFragment { children, loc }) - } - Expression::AssignmentPattern(_) => { - let loc = convert_opt_loc(&match expr { - Expression::AssignmentPattern(p) => p.base.loc.clone(), - _ => unreachable!(), - }); - builder.record_error(CompilerErrorDetail { - reason: "(BuildHIR::lowerExpression) Handle AssignmentPattern expressions" - .to_string(), - category: ErrorCategory::Todo, - loc: loc.clone(), - description: None, - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("AssignmentPattern".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - Expression::TSAsExpression(ts) => { - let loc = convert_opt_loc(&ts.base.loc); - let value = lower_expression_to_temporary(builder, &ts.expression)?; - let type_annotation = ts.type_annotation.parse_value(); - let type_ = lower_type_annotation(&type_annotation, builder); - let type_annotation_name = get_type_annotation_name(&type_annotation); - Ok(InstructionValue::TypeCastExpression { - value, - type_, - type_annotation_name, - type_annotation_kind: Some("as".to_string()), - type_annotation: Some(Box::new(type_annotation)), - loc, - }) - } - Expression::TSSatisfiesExpression(ts) => { - let loc = convert_opt_loc(&ts.base.loc); - let value = lower_expression_to_temporary(builder, &ts.expression)?; - let type_annotation = ts.type_annotation.parse_value(); - let type_ = lower_type_annotation(&type_annotation, builder); - let type_annotation_name = get_type_annotation_name(&type_annotation); - Ok(InstructionValue::TypeCastExpression { - value, - type_, - type_annotation_name, - type_annotation_kind: Some("satisfies".to_string()), - type_annotation: Some(Box::new(type_annotation)), - loc, - }) - } - Expression::TSNonNullExpression(ts) => Ok(lower_expression(builder, &ts.expression)?), - Expression::TSTypeAssertion(ts) => { - let loc = convert_opt_loc(&ts.base.loc); - let value = lower_expression_to_temporary(builder, &ts.expression)?; - let type_annotation = ts.type_annotation.parse_value(); - let type_ = lower_type_annotation(&type_annotation, builder); - let type_annotation_name = get_type_annotation_name(&type_annotation); - Ok(InstructionValue::TypeCastExpression { - value, - type_, - type_annotation_name, - type_annotation_kind: Some("as".to_string()), - type_annotation: Some(Box::new(type_annotation)), - loc, - }) - } - Expression::TSInstantiationExpression(ts) => Ok(lower_expression(builder, &ts.expression)?), - Expression::TypeCastExpression(tc) => { - let loc = convert_opt_loc(&tc.base.loc); - let value = lower_expression_to_temporary(builder, &tc.expression)?; - let annotation_value = tc.type_annotation.parse_value(); - // Flow TypeCastExpression: typeAnnotation is a TypeAnnotation node wrapping the actual type - let inner_type = annotation_value - .get("typeAnnotation") - .unwrap_or(&annotation_value); - let type_ = lower_type_annotation(inner_type, builder); - let type_annotation_name = get_type_annotation_name(inner_type); - Ok(InstructionValue::TypeCastExpression { - value, - type_, - type_annotation_name, - type_annotation_kind: Some("cast".to_string()), - type_annotation: Some(Box::new(annotation_value)), - loc, - }) - } - Expression::BigIntLiteral(big) => { - let loc = convert_opt_loc(&big.base.loc); - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerExpression) Handle BigIntLiteral expressions".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - Ok(InstructionValue::UnsupportedNode { - node_type: Some("BigIntLiteral".to_string()), - original_node: serialize_expression(expr), - loc, - }) - } - Expression::RegExpLiteral(re) => { - let loc = convert_opt_loc(&re.base.loc); - Ok(InstructionValue::RegExpLiteral { - pattern: re.pattern.clone(), - flags: re.flags.clone(), - loc, - }) - } - } -} - -/// Check if a binding's declaration is a direct statement of the block -/// (not inside a nested control flow block like if/for/while). -/// Uses the binding's declaration_start position to check if it falls within -/// one of the block's direct VariableDeclaration, FunctionDeclaration, or -/// ClassDeclaration statements. This avoids false positives when two bindings -/// share the same name but are declared in different scopes (e.g., `const x` -/// inside an if-branch and `const x` after it). -fn is_binding_in_block_direct_statements( - binding: &react_compiler_ast::scope::BindingData, - stmts: &[react_compiler_ast::statements::Statement], -) -> bool { - use react_compiler_ast::statements::Statement; - let decl_start = match binding.declaration_start { - Some(pos) => pos, - None => return false, - }; - for stmt in stmts { - match stmt { - Statement::VariableDeclaration(vd) => { - let start = vd.base.start.unwrap_or(0); - let end = vd.base.end.unwrap_or(u32::MAX); - if decl_start >= start && decl_start < end { - return true; - } - } - Statement::FunctionDeclaration(fd) => { - let start = fd.base.start.unwrap_or(0); - let end = fd.base.end.unwrap_or(u32::MAX); - if decl_start >= start && decl_start < end { - return true; - } - } - Statement::ClassDeclaration(cd) => { - let start = cd.base.start.unwrap_or(0); - let end = cd.base.end.unwrap_or(u32::MAX); - if decl_start >= start && decl_start < end { - return true; - } - } - _ => {} - } - } - false -} - -#[allow(dead_code)] -fn pattern_declares_name(pattern: &react_compiler_ast::patterns::PatternLike, name: &str) -> bool { - use react_compiler_ast::patterns::PatternLike; - match pattern { - PatternLike::Identifier(id) => id.name == name, - PatternLike::ObjectPattern(op) => op.properties.iter().any(|prop| match prop { - react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => { - pattern_declares_name(&p.value, name) - } - react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => { - pattern_declares_name(&r.argument, name) - } - }), - PatternLike::ArrayPattern(ap) => ap.elements.iter().any(|el| { - el.as_ref() - .map_or(false, |e| pattern_declares_name(e, name)) - }), - PatternLike::AssignmentPattern(ap) => pattern_declares_name(&ap.left, name), - PatternLike::RestElement(r) => pattern_declares_name(&r.argument, name), - PatternLike::MemberExpression(_) => false, - PatternLike::TSAsExpression(_) - | PatternLike::TSSatisfiesExpression(_) - | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) - | PatternLike::TypeCastExpression(_) => false, - } -} - -// ============================================================================= -// Statement position helpers -// ============================================================================= - -fn statement_start(stmt: &react_compiler_ast::statements::Statement) -> Option<u32> { - use react_compiler_ast::statements::Statement; - match stmt { - Statement::BlockStatement(s) => s.base.start, - Statement::ReturnStatement(s) => s.base.start, - Statement::IfStatement(s) => s.base.start, - Statement::ForStatement(s) => s.base.start, - Statement::WhileStatement(s) => s.base.start, - Statement::DoWhileStatement(s) => s.base.start, - Statement::ForInStatement(s) => s.base.start, - Statement::ForOfStatement(s) => s.base.start, - Statement::SwitchStatement(s) => s.base.start, - Statement::ThrowStatement(s) => s.base.start, - Statement::TryStatement(s) => s.base.start, - Statement::BreakStatement(s) => s.base.start, - Statement::ContinueStatement(s) => s.base.start, - Statement::LabeledStatement(s) => s.base.start, - Statement::ExpressionStatement(s) => s.base.start, - Statement::EmptyStatement(s) => s.base.start, - Statement::DebuggerStatement(s) => s.base.start, - Statement::WithStatement(s) => s.base.start, - Statement::VariableDeclaration(s) => s.base.start, - Statement::FunctionDeclaration(s) => s.base.start, - Statement::ClassDeclaration(s) => s.base.start, - Statement::ImportDeclaration(s) => s.base.start, - Statement::ExportNamedDeclaration(s) => s.base.start, - Statement::ExportDefaultDeclaration(s) => s.base.start, - Statement::ExportAllDeclaration(s) => s.base.start, - Statement::TSTypeAliasDeclaration(s) => s.base.start, - Statement::TSInterfaceDeclaration(s) => s.base.start, - Statement::TSEnumDeclaration(s) => s.base.start, - Statement::TSModuleDeclaration(s) => s.base.start, - Statement::TSDeclareFunction(s) => s.base.start, - Statement::TypeAlias(s) => s.base.start, - Statement::OpaqueType(s) => s.base.start, - Statement::InterfaceDeclaration(s) => s.base.start, - Statement::DeclareVariable(s) => s.base.start, - Statement::DeclareFunction(s) => s.base.start, - Statement::DeclareClass(s) => s.base.start, - Statement::DeclareModule(s) => s.base.start, - Statement::DeclareModuleExports(s) => s.base.start, - Statement::DeclareExportDeclaration(s) => s.base.start, - Statement::DeclareExportAllDeclaration(s) => s.base.start, - Statement::DeclareInterface(s) => s.base.start, - Statement::DeclareTypeAlias(s) => s.base.start, - Statement::DeclareOpaqueType(s) => s.base.start, - Statement::EnumDeclaration(s) => s.base.start, - Statement::Unknown(s) => s.base().start, - } -} - -fn statement_end(stmt: &react_compiler_ast::statements::Statement) -> Option<u32> { - use react_compiler_ast::statements::Statement; - match stmt { - Statement::BlockStatement(s) => s.base.end, - Statement::ReturnStatement(s) => s.base.end, - Statement::IfStatement(s) => s.base.end, - Statement::ForStatement(s) => s.base.end, - Statement::WhileStatement(s) => s.base.end, - Statement::DoWhileStatement(s) => s.base.end, - Statement::ForInStatement(s) => s.base.end, - Statement::ForOfStatement(s) => s.base.end, - Statement::SwitchStatement(s) => s.base.end, - Statement::ThrowStatement(s) => s.base.end, - Statement::TryStatement(s) => s.base.end, - Statement::BreakStatement(s) => s.base.end, - Statement::ContinueStatement(s) => s.base.end, - Statement::LabeledStatement(s) => s.base.end, - Statement::ExpressionStatement(s) => s.base.end, - Statement::EmptyStatement(s) => s.base.end, - Statement::DebuggerStatement(s) => s.base.end, - Statement::WithStatement(s) => s.base.end, - Statement::VariableDeclaration(s) => s.base.end, - Statement::FunctionDeclaration(s) => s.base.end, - Statement::ClassDeclaration(s) => s.base.end, - Statement::ImportDeclaration(s) => s.base.end, - Statement::ExportNamedDeclaration(s) => s.base.end, - Statement::ExportDefaultDeclaration(s) => s.base.end, - Statement::ExportAllDeclaration(s) => s.base.end, - Statement::TSTypeAliasDeclaration(s) => s.base.end, - Statement::TSInterfaceDeclaration(s) => s.base.end, - Statement::TSEnumDeclaration(s) => s.base.end, - Statement::TSModuleDeclaration(s) => s.base.end, - Statement::TSDeclareFunction(s) => s.base.end, - Statement::TypeAlias(s) => s.base.end, - Statement::OpaqueType(s) => s.base.end, - Statement::InterfaceDeclaration(s) => s.base.end, - Statement::DeclareVariable(s) => s.base.end, - Statement::DeclareFunction(s) => s.base.end, - Statement::DeclareClass(s) => s.base.end, - Statement::DeclareModule(s) => s.base.end, - Statement::DeclareModuleExports(s) => s.base.end, - Statement::DeclareExportDeclaration(s) => s.base.end, - Statement::DeclareExportAllDeclaration(s) => s.base.end, - Statement::DeclareInterface(s) => s.base.end, - Statement::DeclareTypeAlias(s) => s.base.end, - Statement::DeclareOpaqueType(s) => s.base.end, - Statement::EnumDeclaration(s) => s.base.end, - Statement::Unknown(s) => s.base().end, - } -} - -/// Extract the HIR SourceLocation from a Statement AST node. -fn statement_loc(stmt: &react_compiler_ast::statements::Statement) -> Option<SourceLocation> { - use react_compiler_ast::statements::Statement; - let loc = match stmt { - Statement::BlockStatement(s) => s.base.loc.clone(), - Statement::ReturnStatement(s) => s.base.loc.clone(), - Statement::IfStatement(s) => s.base.loc.clone(), - Statement::ForStatement(s) => s.base.loc.clone(), - Statement::WhileStatement(s) => s.base.loc.clone(), - Statement::DoWhileStatement(s) => s.base.loc.clone(), - Statement::ForInStatement(s) => s.base.loc.clone(), - Statement::ForOfStatement(s) => s.base.loc.clone(), - Statement::SwitchStatement(s) => s.base.loc.clone(), - Statement::ThrowStatement(s) => s.base.loc.clone(), - Statement::TryStatement(s) => s.base.loc.clone(), - Statement::BreakStatement(s) => s.base.loc.clone(), - Statement::ContinueStatement(s) => s.base.loc.clone(), - Statement::LabeledStatement(s) => s.base.loc.clone(), - Statement::ExpressionStatement(s) => s.base.loc.clone(), - Statement::EmptyStatement(s) => s.base.loc.clone(), - Statement::DebuggerStatement(s) => s.base.loc.clone(), - Statement::WithStatement(s) => s.base.loc.clone(), - Statement::VariableDeclaration(s) => s.base.loc.clone(), - Statement::FunctionDeclaration(s) => s.base.loc.clone(), - Statement::ClassDeclaration(s) => s.base.loc.clone(), - Statement::ImportDeclaration(s) => s.base.loc.clone(), - Statement::ExportNamedDeclaration(s) => s.base.loc.clone(), - Statement::ExportDefaultDeclaration(s) => s.base.loc.clone(), - Statement::ExportAllDeclaration(s) => s.base.loc.clone(), - Statement::TSTypeAliasDeclaration(s) => s.base.loc.clone(), - Statement::TSInterfaceDeclaration(s) => s.base.loc.clone(), - Statement::TSEnumDeclaration(s) => s.base.loc.clone(), - Statement::TSModuleDeclaration(s) => s.base.loc.clone(), - Statement::TSDeclareFunction(s) => s.base.loc.clone(), - Statement::TypeAlias(s) => s.base.loc.clone(), - Statement::OpaqueType(s) => s.base.loc.clone(), - Statement::InterfaceDeclaration(s) => s.base.loc.clone(), - Statement::DeclareVariable(s) => s.base.loc.clone(), - Statement::DeclareFunction(s) => s.base.loc.clone(), - Statement::DeclareClass(s) => s.base.loc.clone(), - Statement::DeclareModule(s) => s.base.loc.clone(), - Statement::DeclareModuleExports(s) => s.base.loc.clone(), - Statement::DeclareExportDeclaration(s) => s.base.loc.clone(), - Statement::DeclareExportAllDeclaration(s) => s.base.loc.clone(), - Statement::DeclareInterface(s) => s.base.loc.clone(), - Statement::DeclareTypeAlias(s) => s.base.loc.clone(), - Statement::DeclareOpaqueType(s) => s.base.loc.clone(), - Statement::EnumDeclaration(s) => s.base.loc.clone(), - Statement::Unknown(s) => s.base().loc.clone(), - }; - convert_opt_loc(&loc) -} - -/// Collect binding names from a pattern that are declared in the given scope. -fn collect_binding_names_from_pattern( - pattern: &react_compiler_ast::patterns::PatternLike, - scope_id: react_compiler_ast::scope::ScopeId, - scope_info: &ScopeInfo, - out: &mut HashSet<BindingId>, -) { - use react_compiler_ast::patterns::PatternLike; - match pattern { - PatternLike::Identifier(id) => { - if let Some(&binding_id) = scope_info.scopes[scope_id.0 as usize] - .bindings - .get(&id.name) - { - out.insert(binding_id); - } - } - PatternLike::ObjectPattern(obj) => { - for prop in &obj.properties { - match prop { - react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => { - collect_binding_names_from_pattern(&p.value, scope_id, scope_info, out); - } - react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => { - collect_binding_names_from_pattern(&r.argument, scope_id, scope_info, out); - } - } - } - } - PatternLike::ArrayPattern(arr) => { - for elem in &arr.elements { - if let Some(e) = elem { - collect_binding_names_from_pattern(e, scope_id, scope_info, out); - } - } - } - PatternLike::AssignmentPattern(assign) => { - collect_binding_names_from_pattern(&assign.left, scope_id, scope_info, out); - } - PatternLike::RestElement(rest) => { - collect_binding_names_from_pattern(&rest.argument, scope_id, scope_info, out); - } - PatternLike::MemberExpression(_) => {} - PatternLike::TSAsExpression(_) - | PatternLike::TSSatisfiesExpression(_) - | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) - | PatternLike::TypeCastExpression(_) => {} - } -} - -// ============================================================================= -// lower_block_statement (with hoisting) -// ============================================================================= - -/// Lower a BlockStatement with hoisting support. -/// -/// Implements the TS BlockStatement hoisting pass: identifies forward references to -/// block-scoped bindings and emits DeclareContext instructions to hoist them. -fn lower_block_statement( - builder: &mut HirBuilder, - block: &react_compiler_ast::statements::BlockStatement, - parent_scope: Option<react_compiler_ast::scope::ScopeId>, -) -> Result<(), CompilerError> { - let _ = lower_block_statement_inner(builder, block, None, parent_scope); - Ok(()) -} - -fn lower_block_statement_with_scope( - builder: &mut HirBuilder, - block: &react_compiler_ast::statements::BlockStatement, - scope_override: react_compiler_ast::scope::ScopeId, -) -> Result<(), CompilerError> { - let _ = lower_block_statement_inner(builder, block, Some(scope_override), None); - Ok(()) -} - -fn lower_block_statement_inner( - builder: &mut HirBuilder, - block: &react_compiler_ast::statements::BlockStatement, - scope_override: Option<react_compiler_ast::scope::ScopeId>, - parent_scope: Option<react_compiler_ast::scope::ScopeId>, -) -> Result<(), CompilerDiagnostic> { - use react_compiler_ast::scope::BindingKind as AstBindingKind; - use react_compiler_ast::statements::Statement; - - // Look up the block's scope to identify hoistable bindings. - // Use the scope override if provided (for function body blocks that share the function's scope). - let block_scope_id = scope_override.or_else(|| { - let found = builder - .scope_info() - .resolve_scope_for_node(block.base.node_id); - if found.is_some() { - return found; - } - // Fallback for synthetic blocks (start=0 from Hermes match desugar): - // find a descendant scope of the parent that contains the block's declarations. - let mut decl_names = Vec::new(); - for stmt in &block.body { - if let Statement::VariableDeclaration(vd) = stmt { - for d in &vd.declarations { - if let react_compiler_ast::patterns::PatternLike::Identifier(id) = &d.id { - decl_names.push(id.name.as_str()); - } - } - } - } - if decl_names.is_empty() { - return None; - } - let search_parent = parent_scope.unwrap_or_else(|| builder.function_scope()); - let found = - builder - .scope_info() - .find_block_scope_by_bindings(&decl_names, search_parent, |sid| { - builder.is_synthetic_scope_claimed(sid) - }); - if let Some(sid) = found { - builder.claim_synthetic_scope(sid); - } - found - }); - - let scope_id = match block_scope_id { - Some(id) => id, - None => { - for body_stmt in &block.body { - lower_statement(builder, body_stmt, None, parent_scope)?; - } - return Ok(()); - } - }; - - // Collect hoistable bindings from this scope AND direct child block scopes. - // In Babel, a function body BlockStatement shares the function's scope, so - // all bindings (var, const, let) are in one scope. But our scope extraction - // may split them: function scope has params/var, child block scope has const/let. - // Including child block scope bindings matches TS behavior where - // stmt.scope.bindings includes all bindings accessible in the block. - // - // IMPORTANT: Only include bindings whose declaration falls within THIS block's - // statement range. Bindings declared in nested blocks (e.g., inside an `if` - // branch) should NOT be hoisted at the parent level — they'll be handled when - // that nested block is recursively lowered. This prevents DeclareContext from - // being emitted before an `if` terminal for variables declared within the branch. - let hoistable: Vec<( - BindingId, - String, - AstBindingKind, - String, - Option<u32>, - Option<u32>, - )> = builder - .scope_info() - .scope_bindings_with_children(scope_id) - .filter(|b| { - !matches!(b.kind, AstBindingKind::Param | AstBindingKind::Module) - && b.declaration_type != "FunctionExpression" - && b.declaration_type != "TypeAlias" - && b.declaration_type != "OpaqueType" - && b.declaration_type != "InterfaceDeclaration" - && b.declaration_type != "TSTypeAliasDeclaration" - && b.declaration_type != "TSInterfaceDeclaration" - && b.declaration_type != "TSEnumDeclaration" - }) - .map(|b| { - ( - b.id, - b.name.clone(), - b.kind.clone(), - b.declaration_type.clone(), - b.declaration_start, - b.declaration_node_id, - ) - }) - .collect(); - - if hoistable.is_empty() { - // No hoistable bindings, just lower statements normally - for body_stmt in &block.body { - lower_statement(builder, body_stmt, None, Some(scope_id))?; - } - return Ok(()); - } - - // Track which bindings have been "declared" (their declaration statement has been seen) - let mut declared: HashSet<BindingId> = HashSet::new(); - - for body_stmt in &block.body { - let stmt_start = statement_start(body_stmt).unwrap_or(0); - let stmt_end = statement_end(body_stmt).unwrap_or(u32::MAX); - let is_function_decl = matches!(body_stmt, Statement::FunctionDeclaration(_)); - - // Collect ranges of nested function scopes within this statement. - // Used to check per-reference whether a reference is inside a nested function, - // rather than checking once per-statement. - let nested_function_ranges: Vec<(u32, u32)> = if is_function_decl { - // For function declarations, fnDepth starts at 1 (all refs are inside) - vec![(stmt_start, stmt_end)] - } else { - let scope_info = builder.scope_info(); - scope_info - .node_to_scope - .iter() - .filter(|&(&pos, &sid)| { - pos > stmt_start - && pos < stmt_end - && matches!(scope_info.scopes[sid.0 as usize].kind, ScopeKind::Function) - }) - .filter_map(|(&pos, _)| { - scope_info - .node_to_scope_end - .get(&pos) - .map(|&end| (pos, end)) - }) - .collect() - }; - - // Find references to not-yet-declared hoistable bindings within this statement - struct HoistInfo { - binding_id: BindingId, - name: String, - kind: AstBindingKind, - declaration_type: String, - first_ref_pos: u32, - first_ref_nid: u32, - } - let mut will_hoist: Vec<HoistInfo> = Vec::new(); - - for (binding_id, name, kind, decl_type, _decl_start, decl_node_id) in &hoistable { - if declared.contains(binding_id) { - continue; - } - - // Find the first reference (not declaration) to this binding in the statement's range. - // Exclude JSX identifier references: while Babel's scope system links JSX - // tag names to local bindings (and the context capture pass includes them), - // the TS hoisting analysis does NOT traverse JSX elements. This mismatch - // is intentional — it matches the TS behavior where <colgroup> adds - // "colgroup" to the context but does NOT trigger hoisting, causing - // EnterSSA to error with "Expected identifier to be defined before use". - // - // The decl_start filter excludes the binding's own declaration position from - // counting as a reference. For hoisted bindings (function declarations), this - // filter is only applied when the current statement IS a FunctionDeclaration, - // since that's the only statement type where decl_start is a declaration, not - // a reference. - let apply_decl_filter = !matches!(kind, AstBindingKind::Hoisted) || is_function_decl; - let refs_in_stmt: Vec<(u32, u32)> = builder - .scope_info() - .ref_node_id_to_binding - .iter() - .filter_map(|(&ref_nid, &ref_bid)| { - if ref_bid != *binding_id { - return None; - } - let entry = builder.identifier_locs().get(&ref_nid)?; - let ref_start = entry.start; - if ref_start < stmt_start || ref_start >= stmt_end { - return None; - } - if apply_decl_filter && *decl_node_id == Some(ref_nid) { - return None; - } - if entry.is_jsx { - return None; - } - Some((ref_start, ref_nid)) - }) - .collect(); - - if refs_in_stmt.is_empty() { - continue; - } - - let (first_ref_pos, first_ref_nid) = - *refs_in_stmt.iter().min_by_key(|(pos, _)| *pos).unwrap(); - - // Hoist if: (1) binding is "hoisted" kind (function declaration), or - // (2) any reference to this binding is inside a nested function scope. - // Check per-reference rather than per-statement to correctly handle - // statements that contain both nested functions and top-level code. - let is_hoisted_kind = matches!(kind, AstBindingKind::Hoisted); - let refs_in_nested_fn: Vec<(u32, u32)> = refs_in_stmt - .iter() - .copied() - .filter(|&(ref_pos, _)| { - nested_function_ranges - .iter() - .any(|&(fn_start, fn_end)| ref_pos >= fn_start && ref_pos < fn_end) - }) - .collect(); - let should_hoist = is_hoisted_kind || !refs_in_nested_fn.is_empty(); - if should_hoist { - // Bindings pulled in from CHILD block scopes (the - // scope_bindings_with_children descent compensates for scope - // splitting) only hoist when declared as a direct statement of - // THIS block; ones declared inside nested control-flow blocks - // are handled when those blocks are recursively lowered. TS - // never sees child-block bindings here (Babel's - // stmt.scope.bindings holds only the block's own scope), so the - // guard must NOT apply to own-scope bindings: catch params and - // for-in/for-of head vars belong to the block's scope without - // being declared by any direct statement, and TS hoists them. - let binding_data = &builder.scope_info().bindings[binding_id.0 as usize]; - if binding_data.scope != scope_id - && !is_binding_in_block_direct_statements(binding_data, &block.body) - { - continue; - } - // For hoisted bindings (function declarations), use the first reference - // overall. For non-hoisted bindings, use the first reference inside a - // nested function. - let (hoist_ref_pos, hoist_ref_nid) = if is_hoisted_kind { - (first_ref_pos, first_ref_nid) - } else { - *refs_in_nested_fn - .iter() - .min_by_key(|(pos, _)| *pos) - .unwrap() - }; - will_hoist.push(HoistInfo { - binding_id: *binding_id, - name: name.clone(), - kind: kind.clone(), - declaration_type: decl_type.clone(), - first_ref_pos: hoist_ref_pos, - first_ref_nid: hoist_ref_nid, - }); - } - } - - // Sort by first reference position to match TS traversal order - will_hoist.sort_by_key(|h| h.first_ref_pos); - - // Emit DeclareContext for hoisted bindings - for info in &will_hoist { - if builder - .environment() - .is_hoisted_identifier(info.binding_id.0) - { - continue; - } - - let hoist_kind = match info.kind { - AstBindingKind::Const | AstBindingKind::Var => InstructionKind::HoistedConst, - AstBindingKind::Let => InstructionKind::HoistedLet, - AstBindingKind::Hoisted => InstructionKind::HoistedFunction, - _ => { - if info.declaration_type == "FunctionDeclaration" { - InstructionKind::HoistedFunction - } else if info.declaration_type == "VariableDeclarator" { - // Unsupported hoisting for this declaration kind - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "Handle non-const declarations for hoisting".to_string(), - description: Some(format!( - "variable \"{}\" declared with {:?}", - info.name, info.kind - )), - loc: None, - suggestions: None, - })?; - continue; - } else { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "Unsupported declaration type for hoisting".to_string(), - description: Some(format!( - "variable \"{}\" declared with {}", - info.name, info.declaration_type - )), - loc: None, - suggestions: None, - })?; - continue; - } - } - }; - - // Look up the reference location for the DeclareContext instruction. - let ref_loc = builder - .identifier_locs() - .get(&info.first_ref_nid) - .map(|e| e.loc.clone()); - let identifier = builder.resolve_binding(&info.name, info.binding_id)?; - let place = Place { - effect: Effect::Unknown, - identifier, - reactive: false, - loc: ref_loc.clone(), - }; - lower_value_to_temporary( - builder, - InstructionValue::DeclareContext { - lvalue: LValue { - kind: hoist_kind, - place, - }, - loc: ref_loc, - }, - )?; - builder - .environment_mut() - .add_hoisted_identifier(info.binding_id.0); - // Hoisted identifiers also become context identifiers (matching TS addHoistedIdentifier) - builder.add_context_identifier(info.binding_id); - } - - // After processing the statement, mark any bindings it declares as "seen". - // This must cover all statement types that can introduce bindings. - match body_stmt { - Statement::FunctionDeclaration(func) => { - if let Some(id) = &func.id { - if let Some(&binding_id) = builder.scope_info().scopes[scope_id.0 as usize] - .bindings - .get(&id.name) - { - declared.insert(binding_id); - } - } - } - Statement::VariableDeclaration(var_decl) => { - for decl in &var_decl.declarations { - collect_binding_names_from_pattern( - &decl.id, - scope_id, - builder.scope_info(), - &mut declared, - ); - } - } - Statement::ClassDeclaration(cls) => { - if let Some(id) = &cls.id { - if let Some(&binding_id) = builder.scope_info().scopes[scope_id.0 as usize] - .bindings - .get(&id.name) - { - declared.insert(binding_id); - } - } - } - _ => { - // For other statement types (e.g. ForStatement with VariableDeclaration in init), - // we rely on the reference_to_binding check for forward references. - // Any bindings declared by child scopes won't be in this block's scope anyway. - } - } - - lower_statement(builder, body_stmt, None, Some(scope_id))?; - } - Ok(()) -} - -// ============================================================================= -// lower_statement -// ============================================================================= - -fn lower_statement( - builder: &mut HirBuilder, - stmt: &react_compiler_ast::statements::Statement, - label: Option<&str>, - parent_scope: Option<react_compiler_ast::scope::ScopeId>, -) -> Result<(), CompilerDiagnostic> { - use react_compiler_ast::statements::Statement; - - match stmt { - Statement::EmptyStatement(_) => { - // no-op - } - Statement::DebuggerStatement(dbg) => { - let loc = convert_opt_loc(&dbg.base.loc); - let value = InstructionValue::Debugger { loc }; - lower_value_to_temporary(builder, value)?; - } - Statement::ExpressionStatement(expr_stmt) => { - lower_expression_to_temporary(builder, &expr_stmt.expression)?; - } - Statement::ReturnStatement(ret) => { - let loc = convert_opt_loc(&ret.base.loc); - let value = if let Some(arg) = &ret.argument { - lower_expression_to_temporary(builder, arg)? - } else { - let undefined_value = InstructionValue::Primitive { - value: PrimitiveValue::Undefined, - loc: None, - }; - lower_value_to_temporary(builder, undefined_value)? - }; - let fallthrough = builder.reserve(BlockKind::Block); - builder.terminate_with_continuation( - Terminal::Return { - value, - return_variant: ReturnVariant::Explicit, - id: EvaluationOrder(0), - loc, - effects: None, - }, - fallthrough, - ); - } - Statement::ThrowStatement(throw) => { - let loc = convert_opt_loc(&throw.base.loc); - let value = lower_expression_to_temporary(builder, &throw.argument)?; - - // Check for throw handler (try/catch) - if let Some(_handler) = builder.resolve_throw_handler() { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch" - .to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - } - - let fallthrough = builder.reserve(BlockKind::Block); - builder.terminate_with_continuation( - Terminal::Throw { - value, - id: EvaluationOrder(0), - loc, - }, - fallthrough, - ); - } - Statement::BlockStatement(block) => { - lower_block_statement(builder, block, parent_scope)?; - } - Statement::VariableDeclaration(var_decl) => { - use react_compiler_ast::patterns::PatternLike; - use react_compiler_ast::statements::VariableDeclarationKind; - if matches!(var_decl.kind, VariableDeclarationKind::Var) { - builder.record_error(CompilerErrorDetail { - reason: "(BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration" - .to_string(), - category: ErrorCategory::Todo, - loc: convert_opt_loc(&var_decl.base.loc), - description: None, - suggestions: None, - })?; - // Treat `var` as `let` so references to the variable don't break - } - let kind = match var_decl.kind { - VariableDeclarationKind::Let | VariableDeclarationKind::Var => InstructionKind::Let, - VariableDeclarationKind::Const | VariableDeclarationKind::Using => { - InstructionKind::Const - } - }; - for declarator in &var_decl.declarations { - let stmt_loc = convert_opt_loc(&var_decl.base.loc); - if let Some(init) = &declarator.init { - let value = lower_expression_to_temporary(builder, init)?; - let assign_style = match &declarator.id { - PatternLike::ObjectPattern(_) | PatternLike::ArrayPattern(_) => { - AssignmentStyle::Destructure - } - _ => AssignmentStyle::Assignment, - }; - lower_assignment(builder, stmt_loc, kind, &declarator.id, value, assign_style)?; - } else if let PatternLike::Identifier(id) = &declarator.id { - // No init: emit DeclareLocal or DeclareContext - let id_loc = convert_opt_loc(&id.base.loc); - let mut binding = builder.resolve_identifier( - &id.name, - id.base.start.unwrap_or(0), - id_loc.clone(), - id.base.node_id, - )?; - if !matches!(binding, VariableBinding::Identifier { .. }) { - // Position-based resolution failed (synthetic $$gen vars - // at position 0). Try scope lookup including descendants. - if let Some((binding_id, binding_data)) = builder - .scope_info() - .find_binding_id_in_descendants(&id.name, builder.function_scope()) - { - let binding_kind = crate::convert_binding_kind(&binding_data.kind); - let identifier = builder.resolve_binding_with_loc( - &id.name, - binding_id, - id_loc.clone(), - )?; - binding = VariableBinding::Identifier { - identifier, - binding_kind, - }; - } - } - match binding { - VariableBinding::Identifier { identifier, .. } => { - // Update the identifier's loc to the declaration site - // (it may have been first created at a reference site during hoisting) - builder.set_identifier_declaration_loc(identifier, &id_loc); - let place = Place { - identifier, - effect: Effect::Unknown, - reactive: false, - loc: id_loc.clone(), - }; - if builder.is_context_identifier( - &id.name, - id.base.start.unwrap_or(0), - id.base.node_id, - ) { - if kind == InstructionKind::Const { - builder.record_error(CompilerErrorDetail { - reason: "Expect `const` declaration not to be reassigned" - .to_string(), - category: ErrorCategory::Syntax, - loc: id_loc.clone(), - description: None, - suggestions: None, - })?; - } - lower_value_to_temporary( - builder, - InstructionValue::DeclareContext { - lvalue: LValue { - kind: InstructionKind::Let, - place, - }, - loc: id_loc, - }, - )?; - } else { - let type_annotation = - extract_type_annotation_name(&id.type_annotation); - lower_value_to_temporary( - builder, - InstructionValue::DeclareLocal { - lvalue: LValue { kind, place }, - type_annotation, - loc: id_loc, - }, - )?; - } - } - _ => { - builder.record_error(CompilerErrorDetail { - reason: "Could not find binding for declaration".to_string(), - category: ErrorCategory::Invariant, - loc: id_loc, - description: None, - suggestions: None, - })?; - } - } - } else { - builder.record_error(CompilerErrorDetail { - reason: "Expected variable declaration to be an identifier if no initializer was provided".to_string(), - category: ErrorCategory::Syntax, - loc: convert_opt_loc(&declarator.base.loc), - description: None, - suggestions: None, - })?; - } - } - } - Statement::BreakStatement(brk) => { - let loc = convert_opt_loc(&brk.base.loc); - let label_name = brk.label.as_ref().map(|l| l.name.as_str()); - let target = builder.lookup_break(label_name)?; - let fallthrough = builder.reserve(BlockKind::Block); - builder.terminate_with_continuation( - Terminal::Goto { - block: target, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc, - }, - fallthrough, - ); - } - Statement::ContinueStatement(cont) => { - let loc = convert_opt_loc(&cont.base.loc); - let label_name = cont.label.as_ref().map(|l| l.name.as_str()); - let target = builder.lookup_continue(label_name)?; - let fallthrough = builder.reserve(BlockKind::Block); - builder.terminate_with_continuation( - Terminal::Goto { - block: target, - variant: GotoVariant::Continue, - id: EvaluationOrder(0), - loc, - }, - fallthrough, - ); - } - Statement::IfStatement(if_stmt) => { - let loc = convert_opt_loc(&if_stmt.base.loc); - // Block for code following the if - let continuation_block = builder.reserve(BlockKind::Block); - let continuation_id = continuation_block.id; - - // Block for the consequent (if the test is truthy) - let consequent_loc = statement_loc(&if_stmt.consequent); - let consequent_block = builder.try_enter(BlockKind::Block, |builder, _block_id| { - lower_statement(builder, &if_stmt.consequent, None, parent_scope)?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: consequent_loc, - }) - })?; - - // Block for the alternate (if the test is not truthy) - let alternate_block = if let Some(alternate) = &if_stmt.alternate { - let alternate_loc = statement_loc(alternate); - builder.try_enter(BlockKind::Block, |builder, _block_id| { - lower_statement(builder, alternate, None, parent_scope)?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: alternate_loc, - }) - })? - } else { - // If there is no else clause, use the continuation directly - continuation_id - }; - - let test = lower_expression_to_temporary(builder, &if_stmt.test)?; - builder.terminate_with_continuation( - Terminal::If { - test, - consequent: consequent_block, - alternate: alternate_block, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc, - }, - continuation_block, - ); - } - Statement::ForStatement(for_stmt) => { - let loc = convert_opt_loc(&for_stmt.base.loc); - - let test_block = builder.reserve(BlockKind::Loop); - let test_block_id = test_block.id; - // Block for code following the loop - let continuation_block = builder.reserve(BlockKind::Block); - let continuation_id = continuation_block.id; - - // Init block: lower init expression/declaration, then goto test - let init_block = builder.try_enter(BlockKind::Loop, |builder, _block_id| { - let init_loc = match &for_stmt.init { - None => { - // No init expression (e.g., `for (; ...)`), add a placeholder - let placeholder = InstructionValue::Primitive { - value: PrimitiveValue::Undefined, - loc: loc.clone(), - }; - lower_value_to_temporary(builder, placeholder)?; - loc.clone() - } - Some(init) => { - match init.as_ref() { - react_compiler_ast::statements::ForInit::VariableDeclaration(var_decl) => { - let init_loc = convert_opt_loc(&var_decl.base.loc); - lower_statement(builder, &Statement::VariableDeclaration(var_decl.clone()), None, parent_scope)?; - init_loc - } - react_compiler_ast::statements::ForInit::Expression(expr) => { - let init_loc = expression_loc(expr); - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - lower_expression_to_temporary(builder, expr)?; - init_loc - } - } - } - }; - Ok(Terminal::Goto { - block: test_block_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: init_loc, - }) - })?; - - // Update block (optional) - let update_block_id = if let Some(update) = &for_stmt.update { - let update_loc = expression_loc(update); - Some(builder.try_enter(BlockKind::Loop, |builder, _block_id| { - lower_expression_to_temporary(builder, update)?; - Ok(Terminal::Goto { - block: test_block_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: update_loc, - }) - })?) - } else { - None - }; - - // Loop body block - let continue_target = update_block_id.unwrap_or(test_block_id); - let body_loc = statement_loc(&for_stmt.body); - let body_block = builder.try_enter(BlockKind::Block, |builder, _block_id| { - builder.loop_scope( - label.map(|s| s.to_string()), - continue_target, - continuation_id, - |builder| { - lower_statement(builder, &for_stmt.body, None, parent_scope)?; - Ok(Terminal::Goto { - block: continue_target, - variant: GotoVariant::Continue, - id: EvaluationOrder(0), - loc: body_loc, - }) - }, - ) - })?; - - // Emit For terminal, then fill in the test block - builder.terminate_with_continuation( - Terminal::For { - init: init_block, - test: test_block_id, - update: update_block_id, - loop_block: body_block, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - test_block, - ); - - // Fill in the test block - if let Some(test_expr) = &for_stmt.test { - let test = lower_expression_to_temporary(builder, test_expr)?; - builder.terminate_with_continuation( - Terminal::Branch { - test, - consequent: body_block, - alternate: continuation_id, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - continuation_block, - ); - } else { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerStatement) Handle empty test in ForStatement" - .to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - // Treat `for(;;)` as `while(true)` to keep the builder state consistent - let true_val = InstructionValue::Primitive { - value: PrimitiveValue::Boolean(true), - loc: loc.clone(), - }; - let test = lower_value_to_temporary(builder, true_val)?; - builder.terminate_with_continuation( - Terminal::Branch { - test, - consequent: body_block, - alternate: continuation_id, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc, - }, - continuation_block, - ); - } - } - Statement::WhileStatement(while_stmt) => { - let loc = convert_opt_loc(&while_stmt.base.loc); - // Block used to evaluate whether to (re)enter or exit the loop - let conditional_block = builder.reserve(BlockKind::Loop); - let conditional_id = conditional_block.id; - // Block for code following the loop - let continuation_block = builder.reserve(BlockKind::Block); - let continuation_id = continuation_block.id; - - // Loop body - let body_loc = statement_loc(&while_stmt.body); - let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| { - builder.loop_scope( - label.map(|s| s.to_string()), - conditional_id, - continuation_id, - |builder| { - lower_statement(builder, &while_stmt.body, None, parent_scope)?; - Ok(Terminal::Goto { - block: conditional_id, - variant: GotoVariant::Continue, - id: EvaluationOrder(0), - loc: body_loc, - }) - }, - ) - })?; - - // Emit While terminal, jumping to the conditional block - builder.terminate_with_continuation( - Terminal::While { - test: conditional_id, - loop_block, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - conditional_block, - ); - - // Fill in the conditional block: lower test, branch - let test = lower_expression_to_temporary(builder, &while_stmt.test)?; - builder.terminate_with_continuation( - Terminal::Branch { - test, - consequent: loop_block, - alternate: continuation_id, - fallthrough: conditional_id, - id: EvaluationOrder(0), - loc, - }, - continuation_block, - ); - } - Statement::DoWhileStatement(do_while_stmt) => { - let loc = convert_opt_loc(&do_while_stmt.base.loc); - // Block used to evaluate whether to (re)enter or exit the loop - let conditional_block = builder.reserve(BlockKind::Loop); - let conditional_id = conditional_block.id; - // Block for code following the loop - let continuation_block = builder.reserve(BlockKind::Block); - let continuation_id = continuation_block.id; - - // Loop body, executed at least once unconditionally prior to exit - let body_loc = statement_loc(&do_while_stmt.body); - let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| { - builder.loop_scope( - label.map(|s| s.to_string()), - conditional_id, - continuation_id, - |builder| { - lower_statement(builder, &do_while_stmt.body, None, parent_scope)?; - Ok(Terminal::Goto { - block: conditional_id, - variant: GotoVariant::Continue, - id: EvaluationOrder(0), - loc: body_loc, - }) - }, - ) - })?; - - // Jump to the conditional block - builder.terminate_with_continuation( - Terminal::DoWhile { - loop_block, - test: conditional_id, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - conditional_block, - ); - - // Fill in the conditional block: lower test, branch - let test = lower_expression_to_temporary(builder, &do_while_stmt.test)?; - builder.terminate_with_continuation( - Terminal::Branch { - test, - consequent: loop_block, - alternate: continuation_id, - fallthrough: conditional_id, - id: EvaluationOrder(0), - loc, - }, - continuation_block, - ); - } - Statement::ForInStatement(for_in) => { - let loc = convert_opt_loc(&for_in.base.loc); - let continuation_block = builder.reserve(BlockKind::Block); - let continuation_id = continuation_block.id; - let init_block = builder.reserve(BlockKind::Loop); - let init_block_id = init_block.id; - - let body_loc = statement_loc(&for_in.body); - let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| { - builder.loop_scope( - label.map(|s| s.to_string()), - init_block_id, - continuation_id, - |builder| { - lower_statement(builder, &for_in.body, None, parent_scope)?; - Ok(Terminal::Goto { - block: init_block_id, - variant: GotoVariant::Continue, - id: EvaluationOrder(0), - loc: body_loc, - }) - }, - ) - })?; - - let value = lower_expression_to_temporary(builder, &for_in.right)?; - builder.terminate_with_continuation( - Terminal::ForIn { - init: init_block_id, - loop_block, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - init_block, - ); - - // Lower the init: NextPropertyOf + assignment - let left_loc = match for_in.left.as_ref() { - react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => { - convert_opt_loc(&var_decl.base.loc).or(loc.clone()) - } - react_compiler_ast::statements::ForInOfLeft::Pattern(pat) => { - pattern_like_hir_loc(pat).or(loc.clone()) - } - }; - let next_property = lower_value_to_temporary( - builder, - InstructionValue::NextPropertyOf { - value, - loc: left_loc.clone(), - }, - )?; - - let assign_result = match for_in.left.as_ref() { - react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => { - if var_decl.declarations.len() != 1 { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Invariant, - reason: format!( - "Expected only one declaration in ForInStatement init, got {}", - var_decl.declarations.len() - ), - description: None, - loc: left_loc.clone(), - suggestions: None, - })?; - } - if let Some(declarator) = var_decl.declarations.first() { - lower_assignment( - builder, - left_loc.clone(), - InstructionKind::Let, - &declarator.id, - next_property.clone(), - AssignmentStyle::Assignment, - )? - } else { - None - } - } - react_compiler_ast::statements::ForInOfLeft::Pattern(pattern) => lower_assignment( - builder, - left_loc.clone(), - InstructionKind::Reassign, - pattern, - next_property.clone(), - AssignmentStyle::Assignment, - )?, - }; - // Use the assign result (StoreLocal temp) as the test, matching TS behavior - let test_value = assign_result.unwrap_or(next_property); - let test = lower_value_to_temporary( - builder, - InstructionValue::LoadLocal { - place: test_value, - loc: left_loc.clone(), - }, - )?; - builder.terminate_with_continuation( - Terminal::Branch { - test, - consequent: loop_block, - alternate: continuation_id, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - continuation_block, - ); - } - Statement::ForOfStatement(for_of) => { - let loc = convert_opt_loc(&for_of.base.loc); - let continuation_block = builder.reserve(BlockKind::Block); - let continuation_id = continuation_block.id; - let init_block = builder.reserve(BlockKind::Loop); - let init_block_id = init_block.id; - let test_block = builder.reserve(BlockKind::Loop); - let test_block_id = test_block.id; - - if for_of.is_await { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerStatement) Handle for-await loops".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(()); - } - - let body_loc = statement_loc(&for_of.body); - let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| { - builder.loop_scope( - label.map(|s| s.to_string()), - init_block_id, - continuation_id, - |builder| { - lower_statement(builder, &for_of.body, None, parent_scope)?; - Ok(Terminal::Goto { - block: init_block_id, - variant: GotoVariant::Continue, - id: EvaluationOrder(0), - loc: body_loc, - }) - }, - ) - })?; - - let value = lower_expression_to_temporary(builder, &for_of.right)?; - builder.terminate_with_continuation( - Terminal::ForOf { - init: init_block_id, - test: test_block_id, - loop_block, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - init_block, - ); - - // Init block: GetIterator, goto test - let iterator = lower_value_to_temporary( - builder, - InstructionValue::GetIterator { - collection: value.clone(), - loc: value.loc.clone(), - }, - )?; - builder.terminate_with_continuation( - Terminal::Goto { - block: test_block_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - test_block, - ); - - // Test block: IteratorNext, assign, branch - let left_loc = match for_of.left.as_ref() { - react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => { - convert_opt_loc(&var_decl.base.loc).or(loc.clone()) - } - react_compiler_ast::statements::ForInOfLeft::Pattern(pat) => { - pattern_like_hir_loc(pat).or(loc.clone()) - } - }; - let advance_iterator = lower_value_to_temporary( - builder, - InstructionValue::IteratorNext { - iterator: iterator.clone(), - collection: value.clone(), - loc: left_loc.clone(), - }, - )?; - - let assign_result = match for_of.left.as_ref() { - react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => { - if var_decl.declarations.len() != 1 { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Invariant, - reason: format!( - "Expected only one declaration in ForOfStatement init, got {}", - var_decl.declarations.len() - ), - description: None, - loc: left_loc.clone(), - suggestions: None, - })?; - } - if let Some(declarator) = var_decl.declarations.first() { - lower_assignment( - builder, - left_loc.clone(), - InstructionKind::Let, - &declarator.id, - advance_iterator.clone(), - AssignmentStyle::Assignment, - )? - } else { - None - } - } - react_compiler_ast::statements::ForInOfLeft::Pattern(pattern) => lower_assignment( - builder, - left_loc.clone(), - InstructionKind::Reassign, - pattern, - advance_iterator.clone(), - AssignmentStyle::Assignment, - )?, - }; - // Use the assign result (StoreLocal temp) as the test, matching TS behavior - let test_value = assign_result.unwrap_or(advance_iterator); - let test = lower_value_to_temporary( - builder, - InstructionValue::LoadLocal { - place: test_value, - loc: left_loc.clone(), - }, - )?; - builder.terminate_with_continuation( - Terminal::Branch { - test, - consequent: loop_block, - alternate: continuation_id, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - continuation_block, - ); - } - Statement::SwitchStatement(switch_stmt) => { - let loc = convert_opt_loc(&switch_stmt.base.loc); - let continuation_block = builder.reserve(BlockKind::Block); - let continuation_id = continuation_block.id; - - // Iterate through cases in reverse order so that previous blocks can - // fallthrough to successors - let mut fallthrough = continuation_id; - let mut cases: Vec<Case> = Vec::new(); - let mut has_default = false; - - for ii in (0..switch_stmt.cases.len()).rev() { - let case = &switch_stmt.cases[ii]; - let case_loc = convert_opt_loc(&case.base.loc); - - if case.test.is_none() { - if has_default { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Syntax, - reason: "Expected at most one `default` branch in a switch statement" - .to_string(), - description: None, - loc: case_loc.clone(), - suggestions: None, - })?; - break; - } - has_default = true; - } - - let fallthrough_target = fallthrough; - let block = builder.try_enter(BlockKind::Block, |builder, _block_id| { - builder.switch_scope(label.map(|s| s.to_string()), continuation_id, |builder| { - for consequent in &case.consequent { - lower_statement(builder, consequent, None, parent_scope)?; - } - Ok(Terminal::Goto { - block: fallthrough_target, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: case_loc.clone(), - }) - }) - })?; - - let test = if let Some(test_expr) = &case.test { - Some(lower_reorderable_expression(builder, test_expr)?) - } else { - None - }; - - cases.push(Case { test, block }); - fallthrough = block; - } - - // Reverse back to original order - cases.reverse(); - - // If no default case, add one that jumps to continuation - if !has_default { - cases.push(Case { - test: None, - block: continuation_id, - }); - } - - let test = lower_expression_to_temporary(builder, &switch_stmt.discriminant)?; - builder.terminate_with_continuation( - Terminal::Switch { - test, - cases, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc, - }, - continuation_block, - ); - } - Statement::TryStatement(try_stmt) => { - let loc = convert_opt_loc(&try_stmt.base.loc); - let continuation_block = builder.reserve(BlockKind::Block); - let continuation_id = continuation_block.id; - - let handler_clause = match &try_stmt.handler { - Some(h) => h, - None => { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: - "(BuildHIR::lowerStatement) Handle TryStatement without a catch clause" - .to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(()); - } - }; - - if try_stmt.finalizer.is_some() { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - } - - // Set up handler binding if catch has a param - let handler_binding_info: Option<(Place, react_compiler_ast::patterns::PatternLike)> = - if let Some(param) = &handler_clause.param { - // Check for destructuring in catch clause params. - // Match TS behavior: Babel doesn't register destructured catch bindings - // in its scope, so resolveIdentifier fails and records an invariant error. - let is_destructuring = matches!( - param, - react_compiler_ast::patterns::PatternLike::ObjectPattern(_) - | react_compiler_ast::patterns::PatternLike::ArrayPattern(_) - ); - if is_destructuring { - // Iterate the pattern to find all identifier locs for error reporting - fn collect_identifier_locs( - pat: &react_compiler_ast::patterns::PatternLike, - locs: &mut Vec<Option<SourceLocation>>, - ) { - match pat { - react_compiler_ast::patterns::PatternLike::Identifier(id) => { - locs.push(convert_opt_loc(&id.base.loc)); - } - react_compiler_ast::patterns::PatternLike::ObjectPattern(obj) => { - for prop in &obj.properties { - match prop { - react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => { - collect_identifier_locs(&p.value, locs); - } - react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => { - collect_identifier_locs(&r.argument, locs); - } - } - } - } - react_compiler_ast::patterns::PatternLike::ArrayPattern(arr) => { - for elem in &arr.elements { - if let Some(e) = elem { - collect_identifier_locs(e, locs); - } - } - } - _ => {} - } - } - let mut id_locs = Vec::new(); - collect_identifier_locs(param, &mut id_locs); - for id_loc in id_locs { - builder.record_error(CompilerErrorDetail { - reason: "(BuildHIR::lowerAssignment) Could not find binding for declaration.".to_string(), - category: ErrorCategory::Invariant, - loc: id_loc, - description: None, - suggestions: None, - })?; - } - None - } else { - let param_loc = convert_opt_loc(&pattern_like_loc(param)); - let id = builder.make_temporary(param_loc.clone()); - promote_temporary(builder, id); - let place = Place { - identifier: id, - effect: Effect::Unknown, - reactive: false, - loc: param_loc.clone(), - }; - // Emit DeclareLocal for the catch binding - lower_value_to_temporary( - builder, - InstructionValue::DeclareLocal { - lvalue: LValue { - kind: InstructionKind::Catch, - place: place.clone(), - }, - type_annotation: None, - loc: param_loc, - }, - )?; - Some((place, param.clone())) - } - } else { - None - }; - - // Create the handler (catch) block - let handler_binding_for_block = handler_binding_info.clone(); - let handler_loc = convert_opt_loc(&handler_clause.base.loc); - // Use the catch param's loc for the assignment, matching TS: handlerBinding.path.node.loc - let handler_param_loc = handler_clause - .param - .as_ref() - .and_then(|p| convert_opt_loc(&pattern_like_loc(p))); - let handler_block = builder.try_enter(BlockKind::Catch, |builder, _block_id| { - if let Some((ref place, ref pattern)) = handler_binding_for_block { - lower_assignment( - builder, - handler_param_loc.clone().or_else(|| handler_loc.clone()), - InstructionKind::Catch, - pattern, - place.clone(), - AssignmentStyle::Assignment, - )?; - } - // Lower the catch body using lower_block_statement to get hoisting support. - // Match TS behavior where `lowerStatement(builder, handlerPath.get('body'))` - // processes the catch body as a BlockStatement (with hoisting). - // Use the catch clause's scope since the catch body block shares - // the CatchClause scope in Babel (contains the catch param binding). - // Use the catch clause's scope (which contains the catch param binding). - // Fall back to the body block's own scope if the catch clause scope is missing. - let catch_scope = builder - .scope_info() - .resolve_scope_for_node(handler_clause.base.node_id) - .or_else(|| { - builder - .scope_info() - .resolve_scope_for_node(handler_clause.body.base.node_id) - }); - if let Some(scope_id) = catch_scope { - lower_block_statement_with_scope(builder, &handler_clause.body, scope_id)?; - } else { - // No scope found — this shouldn't happen with well-formed Babel output. - // Fall back to plain block lowering (no hoisting) rather than panicking, - // since this is a non-critical degradation. - lower_block_statement(builder, &handler_clause.body, parent_scope)?; - } - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: handler_loc.clone(), - }) - })?; - - // Create the try block - // Use lower_block_statement to get hoisting support for bindings - // declared inside the try body. This matches the catch block's use of - // lower_block_statement_with_scope and ensures self-referencing function - // declarations (e.g., `const loop = () => { loop(); }`) inside try blocks - // are correctly promoted to context variables. - let try_body_loc = convert_opt_loc(&try_stmt.block.base.loc); - let try_block = builder.try_enter(BlockKind::Block, |builder, _block_id| { - builder.try_enter_try_catch(handler_block, |builder| { - lower_block_statement(builder, &try_stmt.block, parent_scope)?; - Ok(()) - })?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Try, - id: EvaluationOrder(0), - loc: try_body_loc.clone(), - }) - })?; - - builder.terminate_with_continuation( - Terminal::Try { - block: try_block, - handler_binding: handler_binding_info.map(|(place, _)| place), - handler: handler_block, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc, - }, - continuation_block, - ); - } - Statement::LabeledStatement(labeled_stmt) => { - let label_name = &labeled_stmt.label.name; - let loc = convert_opt_loc(&labeled_stmt.base.loc); - - // Check if the body is a loop statement - if so, delegate with label - match labeled_stmt.body.as_ref() { - Statement::ForStatement(_) - | Statement::WhileStatement(_) - | Statement::DoWhileStatement(_) - | Statement::ForInStatement(_) - | Statement::ForOfStatement(_) => { - // Labeled loops are special because of continue, push the label down - lower_statement(builder, &labeled_stmt.body, Some(label_name), parent_scope)?; - } - _ => { - // All other statements create a continuation block to allow `break` - let continuation_block = builder.reserve(BlockKind::Block); - let continuation_id = continuation_block.id; - let body_loc = statement_loc(&labeled_stmt.body); - - let block = builder.try_enter(BlockKind::Block, |builder, _block_id| { - builder.label_scope(label_name.clone(), continuation_id, |builder| { - lower_statement(builder, &labeled_stmt.body, None, parent_scope)?; - Ok(()) - })?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: body_loc, - }) - })?; - - builder.terminate_with_continuation( - Terminal::Label { - block, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc, - }, - continuation_block, - ); - } - } - } - Statement::WithStatement(with_stmt) => { - let loc = convert_opt_loc(&with_stmt.base.loc); - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::UnsupportedSyntax, - reason: "JavaScript 'with' syntax is not supported".to_string(), - description: Some("'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives".to_string()), - loc: loc.clone(), - suggestions: None, - })?; - lower_value_to_temporary( - builder, - InstructionValue::UnsupportedNode { - node_type: Some("WithStatement".to_string()), - original_node: serialize_statement(stmt), - loc, - }, - )?; - } - Statement::FunctionDeclaration(func_decl) => { - lower_function_declaration(builder, func_decl)?; - } - Statement::ClassDeclaration(cls) => { - let loc = convert_opt_loc(&cls.base.loc); - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::UnsupportedSyntax, - reason: "Inline `class` declarations are not supported".to_string(), - description: Some( - "Move class declarations outside of components/hooks".to_string(), - ), - loc: loc.clone(), - suggestions: None, - })?; - lower_value_to_temporary( - builder, - InstructionValue::UnsupportedNode { - node_type: Some("ClassDeclaration".to_string()), - original_node: serialize_statement(stmt), - loc, - }, - )?; - } - Statement::ImportDeclaration(_) - | Statement::ExportNamedDeclaration(_) - | Statement::ExportDefaultDeclaration(_) - | Statement::ExportAllDeclaration(_) => { - let (loc, node_type_name) = match stmt { - Statement::ImportDeclaration(s) => { - (convert_opt_loc(&s.base.loc), "ImportDeclaration") - } - Statement::ExportNamedDeclaration(s) => { - (convert_opt_loc(&s.base.loc), "ExportNamedDeclaration") - } - Statement::ExportDefaultDeclaration(s) => { - (convert_opt_loc(&s.base.loc), "ExportDefaultDeclaration") - } - Statement::ExportAllDeclaration(s) => { - (convert_opt_loc(&s.base.loc), "ExportAllDeclaration") - } - _ => unreachable!(), - }; - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Syntax, - reason: "JavaScript `import` and `export` statements may only appear at the top level of a module".to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - lower_value_to_temporary( - builder, - InstructionValue::UnsupportedNode { - node_type: Some(node_type_name.to_string()), - original_node: serialize_statement(stmt), - loc, - }, - )?; - } - // TypeScript/Flow declarations are type-only, skip them - Statement::TSEnumDeclaration(e) => { - let loc = convert_opt_loc(&e.base.loc); - let original_node = serde_json::to_value( - &react_compiler_ast::statements::Statement::TSEnumDeclaration(e.clone()), - ) - .ok(); - lower_value_to_temporary( - builder, - InstructionValue::UnsupportedNode { - node_type: Some("TSEnumDeclaration".to_string()), - original_node, - loc, - }, - )?; - } - Statement::EnumDeclaration(e) => { - let loc = convert_opt_loc(&e.base.loc); - let original_node = serde_json::to_value( - &react_compiler_ast::statements::Statement::EnumDeclaration(e.clone()), - ) - .ok(); - lower_value_to_temporary( - builder, - InstructionValue::UnsupportedNode { - node_type: Some("EnumDeclaration".to_string()), - original_node, - loc, - }, - )?; - } - // TypeScript/Flow type declarations are type-only, skip them - Statement::TSTypeAliasDeclaration(_) - | Statement::TSInterfaceDeclaration(_) - | Statement::TSModuleDeclaration(_) - | Statement::TSDeclareFunction(_) - | Statement::TypeAlias(_) - | Statement::OpaqueType(_) - | Statement::InterfaceDeclaration(_) - | Statement::DeclareVariable(_) - | Statement::DeclareFunction(_) - | Statement::DeclareClass(_) - | Statement::DeclareModule(_) - | Statement::DeclareModuleExports(_) - | Statement::DeclareExportDeclaration(_) - | Statement::DeclareExportAllDeclaration(_) - | Statement::DeclareInterface(_) - | Statement::DeclareTypeAlias(_) - | Statement::DeclareOpaqueType(_) => {} - // The TS reference can only reach its equivalent default case via - // assertExhaustive (Babel's closed Statement type), so it crashes; - // here unmodeled syntax is reachable by construction and degrades - // like the other unsupported-statement arms instead. - Statement::Unknown(unknown) => { - let loc = convert_opt_loc(&unknown.base().loc); - let node_type = unknown.node_type().to_string(); - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::UnsupportedSyntax, - reason: format!("Unsupported statement kind '{node_type}'"), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - lower_value_to_temporary( - builder, - InstructionValue::UnsupportedNode { - node_type: Some(node_type), - original_node: Some(unknown.raw().parse_value()), - loc, - }, - )?; - } - } - Ok(()) -} - -// ============================================================================= -// lower() entry point -// ============================================================================= - -enum FunctionBody<'a> { - Block(&'a react_compiler_ast::statements::BlockStatement), - Expression(&'a react_compiler_ast::expressions::Expression), -} - -/// Main entry point: lower a function AST node into HIR. -/// -/// Receives a `FunctionNode` (discovered by the entrypoint) and lowers it to HIR. -/// The `id` parameter provides the function name (which may come from the variable -/// declarator rather than the function node itself, e.g. `const Foo = () => {}`). -pub fn lower( - func: &FunctionNode<'_>, - _id: Option<&str>, - scope_info: &ScopeInfo, - env: &mut Environment, -) -> Result<HirFunction, CompilerError> { - // Extract params, body, generator, is_async, loc, scope_id, and the AST function's own id - // Note: `id` param may include inferred names (e.g., from `const Foo = () => {}`), - // but the HIR function's `id` field should only include the function's own AST id - // (FunctionDeclaration.id or FunctionExpression.id, NOT arrow functions). - let (params, body, generator, is_async, loc, start, end, ast_id) = match func { - FunctionNode::FunctionDeclaration(decl) => ( - &decl.params[..], - FunctionBody::Block(&decl.body), - decl.generator, - decl.is_async, - convert_opt_loc(&decl.base.loc), - decl.base.start.unwrap_or(0), - decl.base.end.unwrap_or(0), - decl.id.as_ref().map(|id| id.name.as_str()), - ), - FunctionNode::FunctionExpression(expr) => ( - &expr.params[..], - FunctionBody::Block(&expr.body), - expr.generator, - expr.is_async, - convert_opt_loc(&expr.base.loc), - expr.base.start.unwrap_or(0), - expr.base.end.unwrap_or(0), - expr.id.as_ref().map(|id| id.name.as_str()), - ), - FunctionNode::ArrowFunctionExpression(arrow) => { - let body = match arrow.body.as_ref() { - react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => { - FunctionBody::Block(block) - } - react_compiler_ast::expressions::ArrowFunctionBody::Expression(expr) => { - FunctionBody::Expression(expr) - } - }; - ( - &arrow.params[..], - body, - arrow.generator, - arrow.is_async, - convert_opt_loc(&arrow.base.loc), - arrow.base.start.unwrap_or(0), - arrow.base.end.unwrap_or(0), - None, // Arrow functions never have an AST id - ) - } - }; - - let scope_id = scope_info - .resolve_scope_for_node(func.node_id()) - .unwrap_or(scope_info.program_scope); - - validate_ts_this_parameters_in_function_range(scope_info, start, end)?; - - // Build identifier location index from the AST (replaces serialized referenceLocs/jsxReferencePositions) - let identifier_locs = build_identifier_loc_index(func, scope_info); - - // Pre-compute context identifiers: variables captured across function boundaries - let context_identifiers = find_context_identifiers(func, scope_info, env, &identifier_locs)?; - - // For top-level functions, context is empty (no captured refs) - let context_map: IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>> = - IndexMap::new(); - - let (hir_func, _used_names, _child_bindings) = lower_inner( - params, - body, - ast_id, - generator, - is_async, - loc, - scope_info, - env, - None, // no pre-existing bindings for top-level - None, // no pre-existing used_names for top-level - context_map, - scope_id, - scope_id, // component_scope = function_scope for top-level - &context_identifiers, - true, // is_top_level - &identifier_locs, - )?; - - Ok(hir_func) -} - -// ============================================================================= -// Stubs for future milestones -// ============================================================================= - -/// Result of resolving an identifier for assignment. -enum IdentifierForAssignment { - /// A local place (identifier binding) - Place(Place), - /// A global variable (non-local, non-import) - Global { name: String }, -} - -/// Resolve an identifier for use as an assignment target. -/// Returns None if the binding could not be found (error recorded). -fn lower_identifier_for_assignment( - builder: &mut HirBuilder, - loc: Option<SourceLocation>, - ident_loc: Option<SourceLocation>, - kind: InstructionKind, - name: &str, - start: u32, - node_id: Option<u32>, -) -> Result<Option<IdentifierForAssignment>, CompilerError> { - let mut binding = builder.resolve_identifier(name, start, ident_loc.clone(), node_id)?; - if !matches!(binding, VariableBinding::Identifier { .. }) && kind != InstructionKind::Reassign { - if let Some((binding_id, binding_data)) = builder - .scope_info() - .find_binding_id_in_descendants(name, builder.function_scope()) - { - let bk = crate::convert_binding_kind(&binding_data.kind); - let identifier = - builder.resolve_binding_with_loc(name, binding_id, ident_loc.clone())?; - binding = VariableBinding::Identifier { - identifier, - binding_kind: bk, - }; - } - } - match binding { - VariableBinding::Identifier { - identifier, - binding_kind, - .. - } => { - // Set the identifier's loc from the declaration site (not for reassignments, - // which should keep the original declaration loc) - if kind != InstructionKind::Reassign { - builder.set_identifier_declaration_loc(identifier, &ident_loc); - } - if binding_kind == BindingKind::Const && kind == InstructionKind::Reassign { - builder.record_error(CompilerErrorDetail { - reason: "Cannot reassign a `const` variable".to_string(), - category: ErrorCategory::Syntax, - loc: loc.clone(), - description: Some(format!("`{}` is declared as const", name)), - suggestions: None, - })?; - return Ok(None); - } - Ok(Some(IdentifierForAssignment::Place(Place { - identifier, - effect: Effect::Unknown, - reactive: false, - loc, - }))) - } - VariableBinding::Global { name: gname } => { - if kind == InstructionKind::Reassign { - Ok(Some(IdentifierForAssignment::Global { name: gname })) - } else { - builder.record_error(CompilerErrorDetail { - reason: "Could not find binding for declaration".to_string(), - category: ErrorCategory::Invariant, - loc, - description: None, - suggestions: None, - })?; - Ok(None) - } - } - _ => { - // Import bindings can't be assigned to - if kind == InstructionKind::Reassign { - Ok(Some(IdentifierForAssignment::Global { - name: name.to_string(), - })) - } else { - builder.record_error(CompilerErrorDetail { - reason: "Could not find binding for declaration".to_string(), - category: ErrorCategory::Invariant, - loc, - description: None, - suggestions: None, - })?; - Ok(None) - } - } - } -} - -fn lower_assignment( - builder: &mut HirBuilder, - loc: Option<SourceLocation>, - kind: InstructionKind, - target: &react_compiler_ast::patterns::PatternLike, - value: Place, - assignment_style: AssignmentStyle, -) -> Result<Option<Place>, CompilerError> { - use react_compiler_ast::patterns::PatternLike; - - match target { - PatternLike::Identifier(id) => { - let id_loc = convert_opt_loc(&id.base.loc); - let result = lower_identifier_for_assignment( - builder, - loc.clone(), - id_loc, - kind, - &id.name, - id.base.start.unwrap_or(0), - id.base.node_id, - )?; - match result { - None => { - // Error already recorded - return Ok(None); - } - Some(IdentifierForAssignment::Global { name }) => { - let temp = lower_value_to_temporary( - builder, - InstructionValue::StoreGlobal { name, value, loc }, - )?; - return Ok(Some(temp)); - } - Some(IdentifierForAssignment::Place(place)) => { - let start = id.base.start.unwrap_or(0); - if builder.is_context_identifier(&id.name, start, id.base.node_id) { - // Check if the binding is hoisted before flagging const reassignment - let is_hoisted = builder - .scope_info() - .resolve_reference_for_node(id.base.node_id) - .map(|b| builder.environment().is_hoisted_identifier(b.id.0)) - .unwrap_or(false); - if kind == InstructionKind::Const && !is_hoisted { - builder.record_error(CompilerErrorDetail { - reason: "Expected `const` declaration not to be reassigned" - .to_string(), - category: ErrorCategory::Syntax, - loc: loc.clone(), - suggestions: None, - description: None, - })?; - } - if kind != InstructionKind::Const - && kind != InstructionKind::Reassign - && kind != InstructionKind::Let - && kind != InstructionKind::Function - { - builder.record_error(CompilerErrorDetail { - reason: "Unexpected context variable kind".to_string(), - category: ErrorCategory::Syntax, - loc: loc.clone(), - suggestions: None, - description: None, - })?; - let temp = lower_value_to_temporary( - builder, - InstructionValue::UnsupportedNode { - node_type: Some("Identifier".to_string()), - original_node: serialize_pattern(target), - loc, - }, - )?; - return Ok(Some(temp)); - } - let temp = lower_value_to_temporary( - builder, - InstructionValue::StoreContext { - lvalue: LValue { place, kind }, - value, - loc, - }, - )?; - return Ok(Some(temp)); - } else { - let type_annotation = extract_type_annotation_name(&id.type_annotation); - let temp = lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { place, kind }, - value, - type_annotation, - loc, - }, - )?; - return Ok(Some(temp)); - } - } - } - } - - PatternLike::MemberExpression(member) => { - // MemberExpression may only appear in an assignment expression (Reassign) - if kind != InstructionKind::Reassign { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Invariant, - reason: "MemberExpression may only appear in an assignment expression" - .to_string(), - description: None, - loc: loc.clone(), - suggestions: None, - })?; - return Ok(None); - } - let object = lower_expression_to_temporary(builder, &member.object)?; - let temp = if !member.computed - || matches!( - &*member.property, - react_compiler_ast::expressions::Expression::NumericLiteral(_) - ) { - match &*member.property { - react_compiler_ast::expressions::Expression::Identifier(prop_id) => { - lower_value_to_temporary( - builder, - InstructionValue::PropertyStore { - object, - property: PropertyLiteral::String(prop_id.name.clone()), - value, - loc, - }, - )? - } - react_compiler_ast::expressions::Expression::NumericLiteral(num) => { - lower_value_to_temporary( - builder, - InstructionValue::PropertyStore { - object, - property: PropertyLiteral::Number(FloatValue::new( - num.precise_value(), - )), - value, - loc, - }, - )? - } - _ => { - builder.record_error(CompilerErrorDetail { - reason: format!("(BuildHIR::lowerAssignment) Handle {} properties in MemberExpression", expression_type_name(&member.property)), - category: ErrorCategory::Todo, - loc: expression_loc(&member.property), - description: None, - suggestions: None, - })?; - lower_value_to_temporary( - builder, - InstructionValue::UnsupportedNode { - node_type: Some("MemberExpression".to_string()), - original_node: serialize_pattern(target), - loc, - }, - )? - } - } - } else { - if matches!( - &*member.property, - react_compiler_ast::expressions::Expression::PrivateName(_) - ) { - builder.record_error(CompilerErrorDetail { - reason: "(BuildHIR::lowerAssignment) Expected private name to appear as a non-computed property".to_string(), - category: ErrorCategory::Todo, - loc: expression_loc(&member.property), - description: None, - suggestions: None, - })?; - lower_value_to_temporary( - builder, - InstructionValue::UnsupportedNode { - node_type: Some("MemberExpression".to_string()), - original_node: serialize_pattern(target), - loc, - }, - )? - } else { - let property_place = lower_expression_to_temporary(builder, &member.property)?; - lower_value_to_temporary( - builder, - InstructionValue::ComputedStore { - object, - property: property_place, - value, - loc, - }, - )? - } - }; - Ok(Some(temp)) - } - - PatternLike::ArrayPattern(pattern) => { - let mut items: Vec<ArrayPatternElement> = Vec::new(); - let mut followups: Vec<(Place, &PatternLike)> = Vec::new(); - - // Compute forceTemporaries: when kind is Reassign and any element is - // non-identifier, a context variable, or a non-local binding - let force_temporaries = if kind == InstructionKind::Reassign { - let mut found = false; - for elem in &pattern.elements { - match elem { - Some(PatternLike::Identifier(id)) => { - let start = id.base.start.unwrap_or(0); - if builder.is_context_identifier(&id.name, start, id.base.node_id) { - found = true; - break; - } - let ident_loc = convert_opt_loc(&id.base.loc); - match builder.resolve_identifier( - &id.name, - start, - ident_loc, - id.base.node_id, - )? { - VariableBinding::Identifier { .. } => {} - _ => { - found = true; - break; - } - } - } - _ => { - // Non-identifier elements (including None/holes and RestElements) - // trigger forceTemporaries, matching TS where `!element.isIdentifier()` - // returns true for null elements - found = true; - break; - } - } - } - found - } else { - false - }; - - for element in &pattern.elements { - match element { - None => { - items.push(ArrayPatternElement::Hole); - } - Some(PatternLike::RestElement(rest)) => { - match &*rest.argument { - PatternLike::Identifier(id) => { - let start = id.base.start.unwrap_or(0); - let is_context = - builder.is_context_identifier(&id.name, start, id.base.node_id); - let can_use_direct = !force_temporaries - && (matches!(assignment_style, AssignmentStyle::Assignment) - || !is_context); - if can_use_direct { - match lower_identifier_for_assignment( - builder, - convert_opt_loc(&rest.base.loc), - convert_opt_loc(&id.base.loc), - kind, - &id.name, - start, - id.base.node_id, - )? { - Some(IdentifierForAssignment::Place(place)) => { - items.push(ArrayPatternElement::Spread( - SpreadPattern { place }, - )); - } - Some(IdentifierForAssignment::Global { .. }) => { - let temp = build_temporary_place( - builder, - convert_opt_loc(&rest.base.loc), - ); - promote_temporary(builder, temp.identifier); - items.push(ArrayPatternElement::Spread( - SpreadPattern { - place: temp.clone(), - }, - )); - followups.push((temp, &rest.argument)); - } - None => { - // Error already recorded - } - } - } else { - let temp = build_temporary_place( - builder, - convert_opt_loc(&rest.base.loc), - ); - promote_temporary(builder, temp.identifier); - items.push(ArrayPatternElement::Spread(SpreadPattern { - place: temp.clone(), - })); - followups.push((temp, &rest.argument)); - } - } - _ => { - let temp = - build_temporary_place(builder, convert_opt_loc(&rest.base.loc)); - promote_temporary(builder, temp.identifier); - items.push(ArrayPatternElement::Spread(SpreadPattern { - place: temp.clone(), - })); - followups.push((temp, &rest.argument)); - } - } - } - Some(PatternLike::Identifier(id)) => { - let start = id.base.start.unwrap_or(0); - let is_context = - builder.is_context_identifier(&id.name, start, id.base.node_id); - let can_use_direct = !force_temporaries - && (matches!(assignment_style, AssignmentStyle::Assignment) - || !is_context); - if can_use_direct { - match lower_identifier_for_assignment( - builder, - convert_opt_loc(&id.base.loc), - convert_opt_loc(&id.base.loc), - kind, - &id.name, - start, - id.base.node_id, - )? { - Some(IdentifierForAssignment::Place(place)) => { - items.push(ArrayPatternElement::Place(place)); - } - Some(IdentifierForAssignment::Global { .. }) => { - let temp = build_temporary_place( - builder, - convert_opt_loc(&id.base.loc), - ); - promote_temporary(builder, temp.identifier); - items.push(ArrayPatternElement::Place(temp.clone())); - followups.push((temp, element.as_ref().unwrap())); - } - None => { - items.push(ArrayPatternElement::Hole); - } - } - } else { - // Context variable or force_temporaries: use promoted temporary - let temp = - build_temporary_place(builder, convert_opt_loc(&id.base.loc)); - promote_temporary(builder, temp.identifier); - items.push(ArrayPatternElement::Place(temp.clone())); - followups.push((temp, element.as_ref().unwrap())); - } - } - Some(other) => { - // Nested pattern: use temporary + followup - let elem_loc = pattern_like_hir_loc(other); - let temp = build_temporary_place(builder, elem_loc); - promote_temporary(builder, temp.identifier); - items.push(ArrayPatternElement::Place(temp.clone())); - followups.push((temp, other)); - } - } - } - - let temporary = lower_value_to_temporary( - builder, - InstructionValue::Destructure { - lvalue: LValuePattern { - pattern: Pattern::Array(ArrayPattern { - items, - loc: convert_opt_loc(&pattern.base.loc), - }), - kind, - }, - value: value.clone(), - loc: loc.clone(), - }, - )?; - - for (place, path) in followups { - let followup_loc = pattern_like_hir_loc(path).or(loc.clone()); - lower_assignment(builder, followup_loc, kind, path, place, assignment_style)?; - } - Ok(Some(temporary)) - } - - PatternLike::ObjectPattern(pattern) => { - let mut properties: Vec<ObjectPropertyOrSpread> = Vec::new(); - let mut followups: Vec<(Place, &PatternLike)> = Vec::new(); - - // Compute forceTemporaries for ObjectPattern - let force_temporaries = if kind == InstructionKind::Reassign { - use react_compiler_ast::patterns::ObjectPatternProperty; - let mut found = false; - for prop in &pattern.properties { - match prop { - ObjectPatternProperty::RestElement(_) => { - found = true; - break; - } - ObjectPatternProperty::ObjectProperty(obj_prop) => match &*obj_prop.value { - PatternLike::Identifier(id) => { - let start = id.base.start.unwrap_or(0); - let ident_loc = convert_opt_loc(&id.base.loc); - match builder.resolve_identifier( - &id.name, - start, - ident_loc, - id.base.node_id, - )? { - VariableBinding::Identifier { .. } => {} - _ => { - found = true; - break; - } - } - } - _ => { - found = true; - break; - } - }, - } - } - found - } else { - false - }; - - for prop in &pattern.properties { - match prop { - react_compiler_ast::patterns::ObjectPatternProperty::RestElement(rest) => { - match &*rest.argument { - PatternLike::Identifier(id) => { - let start = id.base.start.unwrap_or(0); - let is_context = - builder.is_context_identifier(&id.name, start, id.base.node_id); - let can_use_direct = !force_temporaries - && (matches!(assignment_style, AssignmentStyle::Assignment) - || !is_context); - if can_use_direct { - match lower_identifier_for_assignment( - builder, - convert_opt_loc(&rest.base.loc), - convert_opt_loc(&id.base.loc), - kind, - &id.name, - start, - id.base.node_id, - )? { - Some(IdentifierForAssignment::Place(place)) => { - properties.push(ObjectPropertyOrSpread::Spread( - SpreadPattern { place }, - )); - } - Some(IdentifierForAssignment::Global { .. }) => { - builder.record_error(CompilerErrorDetail { - reason: "Expected reassignment of globals to enable forceTemporaries".to_string(), - category: ErrorCategory::Todo, - loc: convert_opt_loc(&rest.base.loc), - description: None, - suggestions: None, - })?; - } - None => {} - } - } else { - let temp = build_temporary_place( - builder, - convert_opt_loc(&rest.base.loc), - ); - promote_temporary(builder, temp.identifier); - properties.push(ObjectPropertyOrSpread::Spread( - SpreadPattern { - place: temp.clone(), - }, - )); - followups.push((temp, &rest.argument)); - } - } - _ => { - builder.record_error(CompilerErrorDetail { - reason: format!("(BuildHIR::lowerAssignment) Handle {} rest element in ObjectPattern", - match &*rest.argument { - PatternLike::ObjectPattern(_) => "ObjectPattern", - PatternLike::ArrayPattern(_) => "ArrayPattern", - PatternLike::AssignmentPattern(_) => "AssignmentPattern", - PatternLike::MemberExpression(_) => "MemberExpression", - _ => "unknown", - }), - category: ErrorCategory::Todo, - loc: convert_opt_loc(&rest.base.loc), - description: None, - suggestions: None, - })?; - } - } - } - react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty( - obj_prop, - ) => { - if obj_prop.computed { - builder.record_error(CompilerErrorDetail { - reason: "(BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern".to_string(), - category: ErrorCategory::Todo, - loc: convert_opt_loc(&obj_prop.base.loc), - description: None, - suggestions: None, - })?; - continue; - } - - let key = match lower_object_property_key(builder, &obj_prop.key, false)? { - Some(k) => k, - None => continue, - }; - - match &*obj_prop.value { - PatternLike::Identifier(id) => { - let start = id.base.start.unwrap_or(0); - let is_context = - builder.is_context_identifier(&id.name, start, id.base.node_id); - let can_use_direct = !force_temporaries - && (matches!(assignment_style, AssignmentStyle::Assignment) - || !is_context); - if can_use_direct { - match lower_identifier_for_assignment( - builder, - convert_opt_loc(&id.base.loc), - convert_opt_loc(&id.base.loc), - kind, - &id.name, - start, - id.base.node_id, - )? { - Some(IdentifierForAssignment::Place(place)) => { - properties.push(ObjectPropertyOrSpread::Property( - ObjectProperty { - key, - property_type: ObjectPropertyType::Property, - place, - }, - )); - } - Some(IdentifierForAssignment::Global { .. }) => { - builder.record_error(CompilerErrorDetail { - reason: "Expected reassignment of globals to enable forceTemporaries".to_string(), - category: ErrorCategory::Todo, - loc: convert_opt_loc(&id.base.loc), - description: None, - suggestions: None, - })?; - } - None => { - continue; - } - } - } else { - // Context variable or force_temporaries: use promoted temporary - let temp = build_temporary_place( - builder, - convert_opt_loc(&id.base.loc), - ); - promote_temporary(builder, temp.identifier); - properties.push(ObjectPropertyOrSpread::Property( - ObjectProperty { - key, - property_type: ObjectPropertyType::Property, - place: temp.clone(), - }, - )); - followups.push((temp, &*obj_prop.value)); - } - } - other => { - // Nested pattern: use temporary + followup - let elem_loc = pattern_like_hir_loc(other); - let temp = build_temporary_place(builder, elem_loc); - promote_temporary(builder, temp.identifier); - properties.push(ObjectPropertyOrSpread::Property(ObjectProperty { - key, - property_type: ObjectPropertyType::Property, - place: temp.clone(), - })); - followups.push((temp, other)); - } - } - } - } - } - - let temporary = lower_value_to_temporary( - builder, - InstructionValue::Destructure { - lvalue: LValuePattern { - pattern: Pattern::Object(ObjectPattern { - properties, - loc: convert_opt_loc(&pattern.base.loc), - }), - kind, - }, - value: value.clone(), - loc: loc.clone(), - }, - )?; - - for (place, path) in followups { - let followup_loc = pattern_like_hir_loc(path).or(loc.clone()); - lower_assignment(builder, followup_loc, kind, path, place, assignment_style)?; - } - Ok(Some(temporary)) - } - - PatternLike::AssignmentPattern(pattern) => { - // Default value: if value === undefined, use default, else use value - let pat_loc = convert_opt_loc(&pattern.base.loc); - - let temp = build_temporary_place(builder, pat_loc.clone()); - - let test_block = builder.reserve(BlockKind::Value); - let continuation_block = builder.reserve(builder.current_block_kind()); - - // Consequent: use default value - let consequent = builder.try_enter(BlockKind::Value, |builder, _| { - let default_value = lower_reorderable_expression(builder, &pattern.right)?; - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - place: temp.clone(), - kind: InstructionKind::Const, - }, - value: default_value, - type_annotation: None, - loc: pat_loc.clone(), - }, - )?; - Ok(Terminal::Goto { - block: continuation_block.id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: pat_loc.clone(), - }) - }); - - // Alternate: use the original value - let alternate = builder.try_enter(BlockKind::Value, |builder, _| { - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - place: temp.clone(), - kind: InstructionKind::Const, - }, - value: value.clone(), - type_annotation: None, - loc: pat_loc.clone(), - }, - )?; - Ok(Terminal::Goto { - block: continuation_block.id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: pat_loc.clone(), - }) - }); - - // Ternary terminal - builder.terminate_with_continuation( - Terminal::Ternary { - test: test_block.id, - fallthrough: continuation_block.id, - id: EvaluationOrder(0), - loc: pat_loc.clone(), - }, - test_block, - ); - - // In test block: check if value === undefined - let undef = lower_value_to_temporary( - builder, - InstructionValue::Primitive { - value: PrimitiveValue::Undefined, - loc: pat_loc.clone(), - }, - )?; - let test = lower_value_to_temporary( - builder, - InstructionValue::BinaryExpression { - left: value, - operator: BinaryOperator::StrictEqual, - right: undef, - loc: pat_loc.clone(), - }, - )?; - builder.terminate_with_continuation( - Terminal::Branch { - test, - consequent: consequent?, - alternate: alternate?, - fallthrough: continuation_block.id, - id: EvaluationOrder(0), - loc: pat_loc.clone(), - }, - continuation_block, - ); - - // Recursively assign the resolved value to the left pattern - Ok(lower_assignment( - builder, - pat_loc, - kind, - &pattern.left, - temp, - assignment_style, - )?) - } - - PatternLike::RestElement(rest) => { - // Delegate to the argument pattern - Ok(lower_assignment( - builder, - loc, - kind, - &rest.argument, - value, - assignment_style, - )?) - } - - // TS assignment-target wrappers (e.g. `(x as T) = ...`) and the Flow - // analogue `TypeCastExpression`. For destructuring targets the - // TS-faithful Todo is recorded once in `find_context_identifiers`, so - // it is not recorded again here. `for (... of ...)` heads also reach - // this arm directly without that Todo; emitted code matches the TS - // reference there, but the recorded diagnostics do not yet. - PatternLike::TSAsExpression(_) - | PatternLike::TSSatisfiesExpression(_) - | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) - | PatternLike::TypeCastExpression(_) => Ok(None), - } -} - -/// Helper to extract HIR loc from a PatternLike (converts AST loc) -fn pattern_like_hir_loc(pat: &react_compiler_ast::patterns::PatternLike) -> Option<SourceLocation> { - convert_opt_loc(&pattern_like_loc(pat)) -} - -fn lower_optional_member_expression( - builder: &mut HirBuilder, - expr: &react_compiler_ast::expressions::OptionalMemberExpression, -) -> Result<InstructionValue, CompilerError> { - let place = lower_optional_member_expression_impl(builder, expr, None)?.1; - Ok(InstructionValue::LoadLocal { - loc: place.loc.clone(), - place, - }) -} - -/// Returns (object, value_place) pair. -/// The `value_place` is stored into a temporary; we also return it as an InstructionValue -/// via LoadLocal for the top-level call. -fn lower_optional_member_expression_impl( - builder: &mut HirBuilder, - expr: &react_compiler_ast::expressions::OptionalMemberExpression, - parent_alternate: Option<BlockId>, -) -> Result<(Place, Place), CompilerError> { - use react_compiler_ast::expressions::Expression; - let optional = expr.optional; - let loc = convert_opt_loc(&expr.base.loc); - let place = build_temporary_place(builder, loc.clone()); - let continuation_block = builder.reserve(builder.current_block_kind()); - let continuation_id = continuation_block.id; - let consequent = builder.reserve(BlockKind::Value); - - // Block to evaluate if the callee is null/undefined — sets result to undefined. - // Only create an alternate when first entering an optional subtree. - let alternate = if let Some(parent_alt) = parent_alternate { - Ok(parent_alt) - } else { - builder.try_enter(BlockKind::Value, |builder, _block_id| { - let temp = lower_value_to_temporary( - builder, - InstructionValue::Primitive { - value: PrimitiveValue::Undefined, - loc: loc.clone(), - }, - )?; - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Const, - place: place.clone(), - }, - value: temp, - type_annotation: None, - loc: loc.clone(), - }, - )?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: loc.clone(), - }) - }) - }?; - - let mut object: Option<Place> = None; - let test_block = builder.try_enter(BlockKind::Value, |builder, _block_id| { - match expr.object.as_ref() { - Expression::OptionalMemberExpression(opt_member) => { - let (_obj, value) = - lower_optional_member_expression_impl(builder, opt_member, Some(alternate))?; - object = Some(value); - } - Expression::OptionalCallExpression(opt_call) => { - let value = - lower_optional_call_expression_impl(builder, opt_call, Some(alternate))?; - let value_place = lower_value_to_temporary(builder, value)?; - object = Some(value_place); - } - other => { - object = Some(lower_expression_to_temporary(builder, other)?); - } - } - let test_place = object.as_ref().unwrap().clone(); - Ok(Terminal::Branch { - test: test_place, - consequent: consequent.id, - alternate, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }) - }); - - let obj = object.unwrap(); - - // Block to evaluate if the callee is non-null/undefined - builder.try_enter_reserved(consequent, |builder| { - let lowered = lower_member_expression_with_object(builder, expr, obj.clone())?; - let temp = lower_value_to_temporary(builder, lowered.value)?; - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Const, - place: place.clone(), - }, - value: temp, - type_annotation: None, - loc: loc.clone(), - }, - )?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: loc.clone(), - }) - })?; - - builder.terminate_with_continuation( - Terminal::Optional { - optional, - test: test_block?, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - continuation_block, - ); - - Ok((obj, place)) -} - -fn lower_optional_call_expression( - builder: &mut HirBuilder, - expr: &react_compiler_ast::expressions::OptionalCallExpression, -) -> Result<InstructionValue, CompilerError> { - Ok(lower_optional_call_expression_impl(builder, expr, None)?) -} - -fn lower_optional_call_expression_impl( - builder: &mut HirBuilder, - expr: &react_compiler_ast::expressions::OptionalCallExpression, - parent_alternate: Option<BlockId>, -) -> Result<InstructionValue, CompilerError> { - use react_compiler_ast::expressions::Expression; - let optional = expr.optional; - let loc = convert_opt_loc(&expr.base.loc); - let place = build_temporary_place(builder, loc.clone()); - let continuation_block = builder.reserve(builder.current_block_kind()); - let continuation_id = continuation_block.id; - let consequent = builder.reserve(BlockKind::Value); - - // Block to evaluate if the callee is null/undefined - let alternate = if let Some(parent_alt) = parent_alternate { - Ok(parent_alt) - } else { - builder.try_enter(BlockKind::Value, |builder, _block_id| { - let temp = lower_value_to_temporary( - builder, - InstructionValue::Primitive { - value: PrimitiveValue::Undefined, - loc: loc.clone(), - }, - )?; - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Const, - place: place.clone(), - }, - value: temp, - type_annotation: None, - loc: loc.clone(), - }, - )?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: loc.clone(), - }) - }) - }?; - - // Track callee info for building the call in the consequent block - enum CalleeInfo { - CallExpression { callee: Place }, - MethodCall { receiver: Place, property: Place }, - } - - let mut callee_info: Option<CalleeInfo> = None; - - let test_block = builder.try_enter(BlockKind::Value, |builder, _block_id| { - match expr.callee.as_ref() { - Expression::OptionalCallExpression(opt_call) => { - let value = - lower_optional_call_expression_impl(builder, opt_call, Some(alternate))?; - let value_place = lower_value_to_temporary(builder, value)?; - callee_info = Some(CalleeInfo::CallExpression { - callee: value_place, - }); - } - Expression::OptionalMemberExpression(opt_member) => { - let (obj, value) = - lower_optional_member_expression_impl(builder, opt_member, Some(alternate))?; - callee_info = Some(CalleeInfo::MethodCall { - receiver: obj, - property: value, - }); - } - Expression::MemberExpression(member) => { - let lowered = lower_member_expression(builder, member)?; - let property_place = lower_value_to_temporary(builder, lowered.value)?; - callee_info = Some(CalleeInfo::MethodCall { - receiver: lowered.object, - property: property_place, - }); - } - other => { - let callee_place = lower_expression_to_temporary(builder, other)?; - callee_info = Some(CalleeInfo::CallExpression { - callee: callee_place, - }); - } - } - - let test_place = match callee_info.as_ref().unwrap() { - CalleeInfo::CallExpression { callee } => callee.clone(), - CalleeInfo::MethodCall { property, .. } => property.clone(), - }; - - Ok(Terminal::Branch { - test: test_place, - consequent: consequent.id, - alternate, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }) - }); - - // Block to evaluate if the callee is non-null/undefined - builder.try_enter_reserved(consequent, |builder| { - let args = lower_arguments(builder, &expr.arguments)?; - let temp = build_temporary_place(builder, loc.clone()); - - match callee_info.as_ref().unwrap() { - CalleeInfo::CallExpression { callee } => { - builder.push(Instruction { - id: EvaluationOrder(0), - lvalue: temp.clone(), - value: InstructionValue::CallExpression { - callee: callee.clone(), - args, - loc: loc.clone(), - }, - loc: loc.clone(), - effects: None, - }); - } - CalleeInfo::MethodCall { receiver, property } => { - builder.push(Instruction { - id: EvaluationOrder(0), - lvalue: temp.clone(), - value: InstructionValue::MethodCall { - receiver: receiver.clone(), - property: property.clone(), - args, - loc: loc.clone(), - }, - loc: loc.clone(), - effects: None, - }); - } - } - - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Const, - place: place.clone(), - }, - value: temp, - type_annotation: None, - loc: loc.clone(), - }, - )?; - Ok(Terminal::Goto { - block: continuation_id, - variant: GotoVariant::Break, - id: EvaluationOrder(0), - loc: loc.clone(), - }) - })?; - - builder.terminate_with_continuation( - Terminal::Optional { - optional, - test: test_block?, - fallthrough: continuation_id, - id: EvaluationOrder(0), - loc: loc.clone(), - }, - continuation_block, - ); - - Ok(InstructionValue::LoadLocal { - place: place.clone(), - loc: place.loc, - }) -} - -fn lower_function_to_value( - builder: &mut HirBuilder, - expr: &react_compiler_ast::expressions::Expression, - expr_type: FunctionExpressionType, -) -> Result<InstructionValue, CompilerDiagnostic> { - use react_compiler_ast::expressions::Expression; - let loc = match expr { - Expression::ArrowFunctionExpression(arrow) => convert_opt_loc(&arrow.base.loc), - Expression::FunctionExpression(func) => convert_opt_loc(&func.base.loc), - _ => None, - }; - let name = match expr { - Expression::FunctionExpression(func) => func.id.as_ref().map(|id| id.name.clone()), - _ => None, - }; - let lowered_func = lower_function(builder, expr)?; - Ok(InstructionValue::FunctionExpression { - name, - name_hint: None, - lowered_func, - expr_type, - loc, - }) -} - -fn lower_function( - builder: &mut HirBuilder, - expr: &react_compiler_ast::expressions::Expression, -) -> Result<LoweredFunction, CompilerDiagnostic> { - use react_compiler_ast::expressions::Expression; - - // Extract function parts from the AST node - let (params, body, id, generator, is_async, func_start, func_end, func_loc, func_node_id) = - match expr { - Expression::ArrowFunctionExpression(arrow) => { - let body = match arrow.body.as_ref() { - react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => { - FunctionBody::Block(block) - } - react_compiler_ast::expressions::ArrowFunctionBody::Expression(expr) => { - FunctionBody::Expression(expr) - } - }; - ( - &arrow.params[..], - body, - None::<&str>, - arrow.generator, - arrow.is_async, - arrow.base.start.unwrap_or(0), - arrow.base.end.unwrap_or(0), - convert_opt_loc(&arrow.base.loc), - arrow.base.node_id, - ) - } - Expression::FunctionExpression(func) => ( - &func.params[..], - FunctionBody::Block(&func.body), - func.id.as_ref().map(|id| id.name.as_str()), - func.generator, - func.is_async, - func.base.start.unwrap_or(0), - func.base.end.unwrap_or(0), - convert_opt_loc(&func.base.loc), - func.base.node_id, - ), - _ => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "lower_function called with non-function expression", - None, - )); - } - }; - - // Find the function's scope. For synthetic zero-width functions (e.g., desugared - // match IIFEs from Hermes with start=end=0), node_id_to_scope won't have an entry. - let function_scope = - if let Some(scope) = builder.scope_info().resolve_scope_for_node(func_node_id) { - scope - } else if func_start < func_end { - builder.scope_info().program_scope - } else { - let parent = builder.function_scope(); - let scope_info = builder.scope_info(); - let mapped: std::collections::HashSet<react_compiler_ast::scope::ScopeId> = - scope_info.node_id_to_scope.values().copied().collect(); - let param_names: Vec<String> = params - .iter() - .filter_map(|p| { - if let react_compiler_ast::patterns::PatternLike::Identifier(id) = p { - Some(id.name.clone()) - } else { - None - } - }) - .collect(); - let mut descendants = std::collections::HashSet::new(); - descendants.insert(parent); - let mut changed = true; - while changed { - changed = false; - for (i, scope) in scope_info.scopes.iter().enumerate() { - let sid = react_compiler_ast::scope::ScopeId(i as u32); - if let Some(p) = scope.parent { - if descendants.contains(&p) && !descendants.contains(&sid) { - descendants.insert(sid); - changed = true; - } - } - } - } - let mut found = scope_info.program_scope; - for (i, scope) in scope_info.scopes.iter().enumerate() { - let sid = react_compiler_ast::scope::ScopeId(i as u32); - if let Some(p) = scope.parent { - if descendants.contains(&p) - && matches!(scope.kind, ScopeKind::Function) - && !mapped.contains(&sid) - && !builder.is_synthetic_scope_claimed(sid) - { - if !param_names.is_empty() { - let all_match = param_names - .iter() - .all(|name| scope.bindings.contains_key(name)); - if !all_match { - continue; - } - } - found = sid; - break; - } - } - } - builder.claim_synthetic_scope(found); - found - }; - - let component_scope = builder.component_scope(); - let scope_info = builder.scope_info(); - - let parent_bindings = builder.bindings().clone(); - let parent_used_names = builder.used_names().clone(); - let context_ids = builder.context_identifiers().clone(); - let ident_locs = builder.identifier_locs(); - - // For synthetic functions with zero-width position ranges, position-based - // reference filtering fails. Walk the body AST to collect actual positions. - let ref_override = if func_start >= func_end { - Some(collect_identifier_node_ids_from_body(&body)) - } else { - None - }; - - // Gather captured context - let captured_context = gather_captured_context( - scope_info, - function_scope, - component_scope, - func_start, - func_end, - ident_locs, - ref_override.as_ref(), - ); - let merged_context: IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>> = { - let parent_context = builder.context().clone(); - let mut merged = parent_context; - for (k, v) in captured_context { - merged.insert(k, v); - } - merged - }; - - // Use scope_info_and_env_mut to avoid conflicting borrows - let (scope_info, env) = builder.scope_info_and_env_mut(); - let (hir_func, child_used_names, child_bindings) = lower_inner( - params, - body, - id, - generator, - is_async, - func_loc, - scope_info, - env, - Some(parent_bindings), - Some(parent_used_names), - merged_context, - function_scope, - component_scope, - &context_ids, - false, // nested function - ident_locs, - )?; - - builder.merge_used_names(child_used_names); - builder.merge_bindings(child_bindings); - - let func_id = builder.environment_mut().add_function(hir_func); - Ok(LoweredFunction { func: func_id }) -} - -/// Lower a function declaration statement to a FunctionExpression + StoreLocal. -fn lower_function_declaration( - builder: &mut HirBuilder, - func_decl: &react_compiler_ast::statements::FunctionDeclaration, -) -> Result<(), CompilerError> { - let loc = convert_opt_loc(&func_decl.base.loc); - let func_start = func_decl.base.start.unwrap_or(0); - let func_end = func_decl.base.end.unwrap_or(0); - - let func_name = func_decl.id.as_ref().map(|id| id.name.clone()); - - // Find the function's scope - let function_scope = builder - .scope_info() - .resolve_scope_for_node(func_decl.base.node_id) - .unwrap_or(builder.scope_info().program_scope); - - let component_scope = builder.component_scope(); - let scope_info = builder.scope_info(); - - let parent_bindings = builder.bindings().clone(); - let parent_used_names = builder.used_names().clone(); - let context_ids = builder.context_identifiers().clone(); - let ident_locs = builder.identifier_locs(); - - // Gather captured context - let captured_context = gather_captured_context( - scope_info, - function_scope, - component_scope, - func_start, - func_end, - ident_locs, - None, - ); - let merged_context: IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>> = { - let parent_context = builder.context().clone(); - let mut merged = parent_context; - for (k, v) in captured_context { - merged.insert(k, v); - } - merged - }; - - let (scope_info, env) = builder.scope_info_and_env_mut(); - let (hir_func, child_used_names, child_bindings) = lower_inner( - &func_decl.params, - FunctionBody::Block(&func_decl.body), - func_decl.id.as_ref().map(|id| id.name.as_str()), - func_decl.generator, - func_decl.is_async, - loc.clone(), - scope_info, - env, - Some(parent_bindings), - Some(parent_used_names), - merged_context, - function_scope, - component_scope, - &context_ids, - false, // nested function - ident_locs, - )?; - - builder.merge_used_names(child_used_names); - builder.merge_bindings(child_bindings); - - let func_id = builder.environment_mut().add_function(hir_func); - let lowered_func = LoweredFunction { func: func_id }; - - // Emit FunctionExpression instruction - let fn_value = InstructionValue::FunctionExpression { - name: func_name.clone(), - name_hint: None, - lowered_func, - expr_type: FunctionExpressionType::FunctionDeclaration, - loc: loc.clone(), - }; - let fn_place = lower_value_to_temporary(builder, fn_value)?; - - // Resolve the binding for the function name and store. TS resolves the id - // via Babel's `path.scope.getBinding(name)`, which starts at the function's - // OWN scope: a body-level local that shadows the function's name resolves - // to that inner binding — storing the function into the shadow while - // references elsewhere resolve to the hoisted binding in the parent scope. - // This is a known TS quirk that we reproduce for parity (see - // todo-repro-named-function-with-shadowed-local-same-name). Fall back to - // node-based resolution when the scope walk fails (degraded scope info, - // e.g. synthetic scopes, or backends that split function-body scopes). - if let Some(ref name) = func_name { - if let Some(id_node) = &func_decl.id { - let start = id_node.base.start.unwrap_or(0); - let ident_loc = convert_opt_loc(&id_node.base.loc); - let scope_binding = builder.get_function_declaration_binding(function_scope, name); - let mut is_context = false; - let binding = match scope_binding { - Some(binding_id) => { - is_context = builder.is_context_binding(binding_id); - let binding_kind = crate::convert_binding_kind( - &builder.scope_info().bindings[binding_id.0 as usize].kind, - ); - let identifier = - builder.resolve_binding_with_loc(name, binding_id, ident_loc.clone())?; - VariableBinding::Identifier { - identifier, - binding_kind, - } - } - None => { - let mut binding = builder.resolve_identifier( - name, - start, - ident_loc.clone(), - id_node.base.node_id, - )?; - if matches!(&binding, VariableBinding::Global { .. }) { - // For function redeclarations (e.g., `function x() {} function x() {}`), - // the redeclaration's identifier may not be in ref_node_id_to_binding - // (OXC/SWC don't map constant violations). Retry using the first - // declaration's node_id from the scope chain. - let fallback = { - let si = builder.scope_info(); - let scope_id = si - .resolve_scope_for_node(func_decl.base.node_id) - .unwrap_or(si.program_scope); - si.get_binding(scope_id, name).map(|bid| { - let b = &si.bindings[bid.0 as usize]; - (b.declaration_start.unwrap_or(0), b.declaration_node_id) - }) - }; - if let Some((ds, ds_node_id)) = fallback { - binding = builder.resolve_identifier( - name, - ds, - ident_loc.clone(), - ds_node_id, - )?; - } - } - if matches!(&binding, VariableBinding::Identifier { .. }) { - is_context = - builder.is_context_identifier(name, start, id_node.base.node_id); - } - binding - } - }; - match binding { - VariableBinding::Identifier { identifier, .. } => { - // Don't override the identifier's declaration loc here. - // For function redeclarations (e.g., `function x() {} function x() {}`), - // the identifier's loc should remain the first declaration's loc, - // which was already set during define_binding. - // Use the full function declaration loc for the Place, - // matching the TS behavior where lowerAssignment uses stmt.node.loc - let place = Place { - identifier, - reactive: false, - effect: Effect::Unknown, - loc: loc.clone(), - }; - if is_context { - lower_value_to_temporary( - builder, - InstructionValue::StoreContext { - lvalue: LValue { - kind: InstructionKind::Function, - place, - }, - value: fn_place, - loc, - }, - )?; - } else { - lower_value_to_temporary( - builder, - InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Function, - place, - }, - value: fn_place, - type_annotation: None, - loc, - }, - )?; - } - } - _ => { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Invariant, - reason: format!( - "Could not find binding for function declaration `{}`", - name - ), - description: None, - loc, - suggestions: None, - })?; - } - } - } - } - Ok(()) -} - -/// Lower a function expression used as an object method. -fn lower_function_for_object_method( - builder: &mut HirBuilder, - method: &react_compiler_ast::expressions::ObjectMethod, -) -> Result<LoweredFunction, CompilerError> { - let func_start = method.base.start.unwrap_or(0); - let func_end = method.base.end.unwrap_or(0); - let func_loc = convert_opt_loc(&method.base.loc); - - let function_scope = builder - .scope_info() - .resolve_scope_for_node(method.base.node_id) - .unwrap_or(builder.scope_info().program_scope); - - let component_scope = builder.component_scope(); - let scope_info = builder.scope_info(); - - let parent_bindings = builder.bindings().clone(); - let parent_used_names = builder.used_names().clone(); - let context_ids = builder.context_identifiers().clone(); - let ident_locs = builder.identifier_locs(); - - let captured_context = gather_captured_context( - scope_info, - function_scope, - component_scope, - func_start, - func_end, - ident_locs, - None, - ); - let merged_context: IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>> = { - let parent_context = builder.context().clone(); - let mut merged = parent_context; - for (k, v) in captured_context { - merged.insert(k, v); - } - merged - }; - - let (scope_info, env) = builder.scope_info_and_env_mut(); - let (hir_func, child_used_names, child_bindings) = lower_inner( - &method.params, - FunctionBody::Block(&method.body), - None, - method.generator, - method.is_async, - func_loc, - scope_info, - env, - Some(parent_bindings), - Some(parent_used_names), - merged_context, - function_scope, - component_scope, - &context_ids, - false, // nested function - ident_locs, - )?; - - builder.merge_used_names(child_used_names); - builder.merge_bindings(child_bindings); - - let func_id = builder.environment_mut().add_function(hir_func); - Ok(LoweredFunction { func: func_id }) -} - -/// Internal helper: lower a function given its extracted parts. -/// Used by both the top-level `lower()` and nested `lower_function()`. -fn lower_inner( - params: &[react_compiler_ast::patterns::PatternLike], - body: FunctionBody<'_>, - id: Option<&str>, - generator: bool, - is_async: bool, - loc: Option<SourceLocation>, - scope_info: &ScopeInfo, - env: &mut Environment, - parent_bindings: Option<IndexMap<react_compiler_ast::scope::BindingId, IdentifierId>>, - parent_used_names: Option<IndexMap<String, react_compiler_ast::scope::BindingId>>, - context_map: IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>>, - function_scope: react_compiler_ast::scope::ScopeId, - component_scope: react_compiler_ast::scope::ScopeId, - context_identifiers: &HashSet<react_compiler_ast::scope::BindingId>, - is_top_level: bool, - identifier_locs: &IdentifierLocIndex, -) -> Result< - ( - HirFunction, - IndexMap<String, react_compiler_ast::scope::BindingId>, - IndexMap<react_compiler_ast::scope::BindingId, IdentifierId>, - ), - CompilerError, -> { - validate_ts_this_parameter(scope_info, function_scope)?; - - let mut builder = HirBuilder::new( - env, - scope_info, - function_scope, - component_scope, - context_identifiers.clone(), - parent_bindings, - Some(context_map.clone()), - None, - parent_used_names, - identifier_locs, - ); - - // Build context places from the captured refs - let mut context: Vec<Place> = Vec::new(); - for (&binding_id, ctx_loc) in &context_map { - let binding = &scope_info.bindings[binding_id.0 as usize]; - let identifier = builder.resolve_binding(&binding.name, binding_id)?; - context.push(Place { - identifier, - effect: Effect::Unknown, - reactive: false, - loc: ctx_loc.clone(), - }); - } - - // Process parameters - let mut hir_params: Vec<ParamPattern> = Vec::new(); - for param in params { - match param { - react_compiler_ast::patterns::PatternLike::Identifier(ident) => { - if is_always_reserved_word(&ident.name) { - return Err(CompilerError::from(reserved_identifier_diagnostic( - &ident.name, - ))); - } - let start = ident.base.start.unwrap_or(0); - let param_loc = convert_opt_loc(&ident.base.loc); - let mut binding = builder.resolve_identifier( - &ident.name, - start, - param_loc.clone(), - ident.base.node_id, - )?; - if !matches!(binding, VariableBinding::Identifier { .. }) { - // Position-based resolution failed (common for synthetic params - // like $$gen$m0 at position 0). Try lookup in function scope - // and descendants. - if let Some((binding_id, binding_data)) = builder - .scope_info() - .find_binding_id_in_descendants(&ident.name, builder.function_scope()) - { - let binding_kind = crate::convert_binding_kind(&binding_data.kind); - let identifier = builder.resolve_binding_with_loc( - &ident.name, - binding_id, - param_loc.clone(), - )?; - binding = VariableBinding::Identifier { - identifier, - binding_kind, - }; - } - } - match binding { - VariableBinding::Identifier { identifier, .. } => { - builder.set_identifier_declaration_loc(identifier, ¶m_loc); - let place = Place { - identifier, - effect: Effect::Unknown, - reactive: false, - loc: param_loc, - }; - hir_params.push(ParamPattern::Place(place)); - } - _ => { - builder.record_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Could not find binding", - Some(format!( - "[BuildHIR] Could not find binding for param `{}`", - ident.name - )), - ) - .with_detail( - CompilerDiagnosticDetail::Error { - loc: convert_opt_loc(&ident.base.loc), - message: Some("Could not find binding".to_string()), - identifier_name: None, - }, - ), - ); - } - } - } - react_compiler_ast::patterns::PatternLike::RestElement(rest) => { - let rest_loc = convert_opt_loc(&rest.base.loc); - // Create a temporary place for the spread param - let place = build_temporary_place(&mut builder, rest_loc.clone()); - hir_params.push(ParamPattern::Spread(SpreadPattern { - place: place.clone(), - })); - // Delegate the assignment of the rest argument - lower_assignment( - &mut builder, - rest_loc, - InstructionKind::Let, - &rest.argument, - place, - AssignmentStyle::Assignment, - )?; - } - react_compiler_ast::patterns::PatternLike::ObjectPattern(_) - | react_compiler_ast::patterns::PatternLike::ArrayPattern(_) - | react_compiler_ast::patterns::PatternLike::AssignmentPattern(_) => { - let param_loc = convert_opt_loc(&pattern_like_loc(param)); - let place = build_temporary_place(&mut builder, param_loc.clone()); - promote_temporary(&mut builder, place.identifier); - hir_params.push(ParamPattern::Place(place.clone())); - lower_assignment( - &mut builder, - param_loc, - InstructionKind::Let, - param, - place, - AssignmentStyle::Assignment, - )?; - } - react_compiler_ast::patterns::PatternLike::MemberExpression(member) => { - builder.record_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::Todo, - "Handle MemberExpression parameters", - Some("[BuildHIR] Add support for MemberExpression parameters".to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: convert_opt_loc(&member.base.loc), - message: Some("Unsupported parameter type".to_string()), - identifier_name: None, - }), - ); - } - react_compiler_ast::patterns::PatternLike::TSAsExpression(_) - | react_compiler_ast::patterns::PatternLike::TSSatisfiesExpression(_) - | react_compiler_ast::patterns::PatternLike::TSNonNullExpression(_) - | react_compiler_ast::patterns::PatternLike::TSTypeAssertion(_) - | react_compiler_ast::patterns::PatternLike::TypeCastExpression(_) => {} - } - } - - // Lower the body - let mut directives: Vec<String> = Vec::new(); - match body { - FunctionBody::Expression(expr) => { - let fallthrough = builder.reserve(BlockKind::Block); - let value = lower_expression_to_temporary(&mut builder, expr)?; - builder.terminate_with_continuation( - Terminal::Return { - value, - return_variant: ReturnVariant::Implicit, - id: EvaluationOrder(0), - loc: None, - effects: None, - }, - fallthrough, - ); - } - FunctionBody::Block(block) => { - directives = block - .directives - .iter() - .map(|d| d.value.value.clone()) - .collect(); - // Use lower_block_statement_with_scope to get hoisting support for the function body. - // Pass the function scope since in Babel, a function body BlockStatement shares - // the function's scope (node_to_scope maps the function node, not the block). - lower_block_statement_with_scope(&mut builder, block, function_scope)?; - } - } - - // Emit final Return(Void, undefined) - let undefined_value = InstructionValue::Primitive { - value: PrimitiveValue::Undefined, - loc: None, - }; - let return_value = lower_value_to_temporary(&mut builder, undefined_value)?; - builder.terminate( - Terminal::Return { - value: return_value, - return_variant: ReturnVariant::Void, - id: EvaluationOrder(0), - loc: None, - effects: None, - }, - None, - ); - - // Build the HIR - let (hir_body, instructions, used_names, child_bindings) = builder.build()?; - - // Create the returns place - let returns = crate::hir_builder::create_temporary_place(env, loc.clone()); - - Ok(( - HirFunction { - loc, - id: id.map(|s| s.to_string()), - name_hint: None, - fn_type: if is_top_level { - env.fn_type - } else { - ReactFunctionType::Other - }, - params: hir_params, - return_type_annotation: None, - returns, - context, - body: hir_body, - instructions, - generator, - is_async, - directives, - aliasing_effects: None, - }, - used_names, - child_bindings, - )) -} - -fn lower_jsx_element_name( - builder: &mut HirBuilder, - name: &react_compiler_ast::jsx::JSXElementName, -) -> Result<JsxTag, CompilerError> { - use react_compiler_ast::jsx::JSXElementName; - match name { - JSXElementName::JSXIdentifier(id) => { - let tag = &id.name; - let loc = convert_opt_loc(&id.base.loc); - let start = id.base.start.unwrap_or(0); - if tag.starts_with(|c: char| c.is_ascii_uppercase()) { - // Component tag: resolve as identifier and load - let place = lower_identifier(builder, tag, start, loc.clone(), id.base.node_id)?; - let load_value = if builder.is_context_identifier(tag, start, id.base.node_id) { - InstructionValue::LoadContext { place, loc } - } else { - InstructionValue::LoadLocal { place, loc } - }; - let temp = lower_value_to_temporary(builder, load_value)?; - Ok(JsxTag::Place(temp)) - } else { - // Builtin HTML tag - Ok(JsxTag::Builtin(BuiltinTag { - name: tag.clone(), - loc, - })) - } - } - JSXElementName::JSXMemberExpression(member) => { - let place = lower_jsx_member_expression(builder, member)?; - Ok(JsxTag::Place(place)) - } - JSXElementName::JSXNamespacedName(ns) => { - let namespace = &ns.namespace.name; - let name = &ns.name.name; - let tag = format!("{}:{}", namespace, name); - let loc = convert_opt_loc(&ns.base.loc); - if namespace.contains(':') || name.contains(':') { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Syntax, - reason: "Expected JSXNamespacedName to have no colons in the namespace or name" - .to_string(), - description: Some(format!("Got `{}` : `{}`", namespace, name)), - loc: loc.clone(), - suggestions: None, - })?; - } - let place = lower_value_to_temporary( - builder, - InstructionValue::Primitive { - value: PrimitiveValue::String(tag), - loc: loc.clone(), - }, - )?; - Ok(JsxTag::Place(place)) - } - } -} - -fn lower_jsx_member_expression( - builder: &mut HirBuilder, - expr: &react_compiler_ast::jsx::JSXMemberExpression, -) -> Result<Place, CompilerError> { - use react_compiler_ast::jsx::JSXMemberExprObject; - // Use the full member expression's loc for instruction locs (matching TS: exprPath.node.loc) - let expr_loc = convert_opt_loc(&expr.base.loc); - let object = match &*expr.object { - JSXMemberExprObject::JSXIdentifier(id) => { - let id_loc = convert_opt_loc(&id.base.loc); - let start = id.base.start.unwrap_or(0); - // Use identifier's own loc for the place, but member expression's loc for the instruction - let place = lower_identifier(builder, &id.name, start, id_loc, id.base.node_id)?; - let load_value = if builder.is_context_identifier(&id.name, start, id.base.node_id) { - InstructionValue::LoadContext { - place, - loc: expr_loc.clone(), - } - } else { - InstructionValue::LoadLocal { - place, - loc: expr_loc.clone(), - } - }; - lower_value_to_temporary(builder, load_value)? - } - JSXMemberExprObject::JSXMemberExpression(inner) => { - lower_jsx_member_expression(builder, inner)? - } - }; - let prop_name = &expr.property.name; - let value = InstructionValue::PropertyLoad { - object, - property: PropertyLiteral::String(prop_name.clone()), - loc: expr_loc, - }; - Ok(lower_value_to_temporary(builder, value)?) -} - -fn lower_jsx_element( - builder: &mut HirBuilder, - child: &react_compiler_ast::jsx::JSXChild, -) -> Result<Option<Place>, CompilerError> { - use react_compiler_ast::jsx::JSXChild; - use react_compiler_ast::jsx::JSXExpressionContainerExpr; - match child { - JSXChild::JSXText(text) => { - // FBT whitespace normalization differs from standard JSX. - // Since the fbt transform runs after, preserve all whitespace - // in FBT subtrees as is. - let value = if builder.fbt_depth > 0 { - Some(text.value.clone()) - } else { - trim_jsx_text(&text.value) - }; - match value { - None => Ok(None), - Some(value) => { - let loc = convert_opt_loc(&text.base.loc); - let place = lower_value_to_temporary( - builder, - InstructionValue::JSXText { value, loc }, - )?; - Ok(Some(place)) - } - } - } - JSXChild::JSXElement(element) => { - let value = lower_expression( - builder, - &react_compiler_ast::expressions::Expression::JSXElement(element.clone()), - )?; - Ok(Some(lower_value_to_temporary(builder, value)?)) - } - JSXChild::JSXFragment(fragment) => { - let value = lower_expression( - builder, - &react_compiler_ast::expressions::Expression::JSXFragment(fragment.clone()), - )?; - Ok(Some(lower_value_to_temporary(builder, value)?)) - } - JSXChild::JSXExpressionContainer(container) => match &container.expression { - JSXExpressionContainerExpr::JSXEmptyExpression(_) => Ok(None), - JSXExpressionContainerExpr::Expression(expr) => { - Ok(Some(lower_expression_to_temporary(builder, expr)?)) - } - }, - JSXChild::JSXSpreadChild(spread) => Ok(Some(lower_expression_to_temporary( - builder, - &spread.expression, - )?)), - } -} - -/// Split a string on line endings, handling \r\n, \n, and \r. -fn split_line_endings(s: &str) -> Vec<&str> { - let mut lines = Vec::new(); - let mut start = 0; - let bytes = s.as_bytes(); - let mut i = 0; - while i < bytes.len() { - if bytes[i] == b'\r' { - lines.push(&s[start..i]); - if i + 1 < bytes.len() && bytes[i + 1] == b'\n' { - i += 2; - } else { - i += 1; - } - start = i; - } else if bytes[i] == b'\n' { - lines.push(&s[start..i]); - i += 1; - start = i; - } else { - i += 1; - } - } - lines.push(&s[start..]); - lines -} - -/// Trims whitespace according to the JSX spec. -/// Implementation ported from Babel's cleanJSXElementLiteralChild. -fn trim_jsx_text(original: &str) -> Option<String> { - // Split on \r\n, \n, or \r to handle all line ending styles (matching TS split(/\r\n|\n|\r/)) - let lines: Vec<&str> = split_line_endings(original); - - // NOTE: when builder.fbt_depth > 0, the TS skips whitespace trimming entirely. - // That check is handled by the caller (lower_jsx_element) before calling this function. - - let mut last_non_empty_line = 0; - for (i, line) in lines.iter().enumerate() { - if line.contains(|c: char| c != ' ' && c != '\t') { - last_non_empty_line = i; - } - } - - let mut str = String::new(); - - for (i, line) in lines.iter().enumerate() { - let is_first_line = i == 0; - let is_last_line = i == lines.len() - 1; - let is_last_non_empty_line = i == last_non_empty_line; - - // Replace rendered whitespace tabs with spaces - let mut trimmed_line = line.replace('\t', " "); - - // Trim whitespace touching a newline (leading whitespace on non-first lines) - if !is_first_line { - trimmed_line = trimmed_line.trim_start_matches(' ').to_string(); - } - - // Trim whitespace touching an endline (trailing whitespace on non-last lines) - if !is_last_line { - trimmed_line = trimmed_line.trim_end_matches(' ').to_string(); - } - - if !trimmed_line.is_empty() { - if !is_last_non_empty_line { - trimmed_line.push(' '); - } - str.push_str(&trimmed_line); - } - } - - if str.is_empty() { None } else { Some(str) } -} - -fn lower_object_method( - builder: &mut HirBuilder, - method: &react_compiler_ast::expressions::ObjectMethod, -) -> Result<Option<ObjectProperty>, CompilerError> { - use react_compiler_ast::expressions::ObjectMethodKind; - if !matches!(method.kind, ObjectMethodKind::Method) { - let kind_str = match method.kind { - ObjectMethodKind::Get => "get", - ObjectMethodKind::Set => "set", - ObjectMethodKind::Method => "method", - }; - builder.record_error(CompilerErrorDetail { - reason: format!( - "(BuildHIR::lowerExpression) Handle {} functions in ObjectExpression", - kind_str - ), - category: ErrorCategory::Todo, - loc: convert_opt_loc(&method.base.loc), - description: None, - suggestions: None, - })?; - return Ok(None); - } - let key = lower_object_property_key(builder, &method.key, method.computed)?.unwrap_or( - ObjectPropertyKey::String { - name: String::new(), - }, - ); - - let lowered_func = lower_function_for_object_method(builder, method)?; - - let loc = convert_opt_loc(&method.base.loc); - let method_value = InstructionValue::ObjectMethod { - loc: loc.clone(), - lowered_func, - }; - let method_place = lower_value_to_temporary(builder, method_value)?; - - Ok(Some(ObjectProperty { - key, - property_type: ObjectPropertyType::Method, - place: method_place, - })) -} - -fn lower_object_property_key( - builder: &mut HirBuilder, - key: &react_compiler_ast::expressions::Expression, - computed: bool, -) -> Result<Option<ObjectPropertyKey>, CompilerError> { - use react_compiler_ast::expressions::Expression; - match key { - Expression::StringLiteral(lit) => Ok(Some(ObjectPropertyKey::String { - name: lit.value.clone(), - })), - Expression::Identifier(ident) if !computed => Ok(Some(ObjectPropertyKey::Identifier { - name: ident.name.clone(), - })), - Expression::NumericLiteral(lit) if !computed => Ok(Some(ObjectPropertyKey::Identifier { - name: lit.value.to_string(), - })), - _ if computed => { - let place = lower_expression_to_temporary(builder, key)?; - Ok(Some(ObjectPropertyKey::Computed { name: place })) - } - _ => { - let loc = match key { - Expression::Identifier(i) => convert_opt_loc(&i.base.loc), - _ => None, - }; - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "Unsupported key type in ObjectExpression".to_string(), - description: None, - loc, - suggestions: None, - })?; - Ok(None) - } - } -} - -fn lower_reorderable_expression( - builder: &mut HirBuilder, - expr: &react_compiler_ast::expressions::Expression, -) -> Result<Place, CompilerError> { - if !is_reorderable_expression(builder, expr, true) { - builder.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: format!( - "(BuildHIR::node.lowerReorderableExpression) Expression type `{}` cannot be safely reordered", - expression_type_name(expr) - ), - description: None, - loc: expression_loc(expr), - suggestions: None, - })?; - } - Ok(lower_expression_to_temporary(builder, expr)?) -} - -fn is_reorderable_expression( - builder: &HirBuilder, - expr: &react_compiler_ast::expressions::Expression, - allow_local_identifiers: bool, -) -> bool { - use react_compiler_ast::expressions::Expression; - match expr { - Expression::Identifier(ident) => { - let binding = builder - .scope_info() - .resolve_reference_for_node(ident.base.node_id); - match binding { - None => { - // global, safe to reorder - true - } - Some(b) => { - if b.scope == builder.scope_info().program_scope { - // Module-scope binding (ModuleLocal, imports), safe to reorder - true - } else { - allow_local_identifiers - } - } - } - } - Expression::RegExpLiteral(_) - | Expression::StringLiteral(_) - | Expression::NumericLiteral(_) - | Expression::NullLiteral(_) - | Expression::BooleanLiteral(_) - | Expression::BigIntLiteral(_) => true, - Expression::UnaryExpression(unary) => { - use react_compiler_ast::operators::UnaryOperator; - matches!( - unary.operator, - UnaryOperator::Not | UnaryOperator::Plus | UnaryOperator::Neg - ) && is_reorderable_expression(builder, &unary.argument, allow_local_identifiers) - } - Expression::LogicalExpression(logical) => { - is_reorderable_expression(builder, &logical.left, allow_local_identifiers) - && is_reorderable_expression(builder, &logical.right, allow_local_identifiers) - } - Expression::ConditionalExpression(cond) => { - is_reorderable_expression(builder, &cond.test, allow_local_identifiers) - && is_reorderable_expression(builder, &cond.consequent, allow_local_identifiers) - && is_reorderable_expression(builder, &cond.alternate, allow_local_identifiers) - } - Expression::ArrayExpression(arr) => { - arr.elements.iter().all(|element| { - match element { - Some(e) => is_reorderable_expression(builder, e, allow_local_identifiers), - None => false, // holes are not reorderable - } - }) - } - Expression::ObjectExpression(obj) => obj.properties.iter().all(|prop| match prop { - react_compiler_ast::expressions::ObjectExpressionProperty::ObjectProperty(p) => { - !p.computed && is_reorderable_expression(builder, &p.value, allow_local_identifiers) - } - _ => false, - }), - Expression::MemberExpression(member) => { - // Allow member expressions where the innermost object is a global or module-local - let mut inner = member.object.as_ref(); - while let Expression::MemberExpression(m) = inner { - inner = m.object.as_ref(); - } - if let Expression::Identifier(ident) = inner { - match builder - .scope_info() - .resolve_reference_for_node(ident.base.node_id) - { - None => true, // global - Some(binding) => { - // Module-scope bindings (ModuleLocal, imports) are safe to reorder - binding.scope == builder.scope_info().program_scope - } - } - } else { - false - } - } - Expression::ArrowFunctionExpression(arrow) => { - use react_compiler_ast::expressions::ArrowFunctionBody; - match arrow.body.as_ref() { - ArrowFunctionBody::BlockStatement(block) => block.body.is_empty(), - ArrowFunctionBody::Expression(body_expr) => { - is_reorderable_expression(builder, body_expr, false) - } - } - } - Expression::CallExpression(call) => { - is_reorderable_expression(builder, &call.callee, allow_local_identifiers) - && call - .arguments - .iter() - .all(|arg| is_reorderable_expression(builder, arg, allow_local_identifiers)) - } - Expression::NewExpression(new_expr) => { - is_reorderable_expression(builder, &new_expr.callee, allow_local_identifiers) - && new_expr - .arguments - .iter() - .all(|arg| is_reorderable_expression(builder, arg, allow_local_identifiers)) - } - // TypeScript/Flow type wrappers: recurse into the inner expression - Expression::TSAsExpression(ts) => { - is_reorderable_expression(builder, &ts.expression, allow_local_identifiers) - } - Expression::TSSatisfiesExpression(ts) => { - is_reorderable_expression(builder, &ts.expression, allow_local_identifiers) - } - Expression::TSNonNullExpression(ts) => { - is_reorderable_expression(builder, &ts.expression, allow_local_identifiers) - } - Expression::TSInstantiationExpression(ts) => { - is_reorderable_expression(builder, &ts.expression, allow_local_identifiers) - } - Expression::TypeCastExpression(tc) => { - is_reorderable_expression(builder, &tc.expression, allow_local_identifiers) - } - Expression::TSTypeAssertion(ts) => { - is_reorderable_expression(builder, &ts.expression, allow_local_identifiers) - } - Expression::ParenthesizedExpression(p) => { - is_reorderable_expression(builder, &p.expression, allow_local_identifiers) - } - _ => false, - } -} - -/// Extract the type name from a type annotation serde_json::Value. -/// Returns the "type" field value, e.g. "TSTypeReference", "GenericTypeAnnotation". -fn get_type_annotation_name(val: &serde_json::Value) -> Option<String> { - val.get("type") - .and_then(|v| v.as_str()) - .map(|s| s.to_string()) -} - -/// Lower a type annotation JSON value to an HIR Type. -/// Mirrors the TS `lowerType` function. -fn lower_type_annotation(val: &serde_json::Value, builder: &mut HirBuilder) -> Type { - let type_name = match val.get("type").and_then(|v| v.as_str()) { - Some(name) => name, - None => return builder.make_type(), - }; - match type_name { - "GenericTypeAnnotation" => { - // Check if it's Array - if let Some(id) = val.get("id") { - if id.get("type").and_then(|v| v.as_str()) == Some("Identifier") { - if id.get("name").and_then(|v| v.as_str()) == Some("Array") { - return Type::Object { - shape_id: Some("BuiltInArray".to_string()), - }; - } - } - } - builder.make_type() - } - "TSTypeReference" => { - if let Some(type_name_val) = val.get("typeName") { - if type_name_val.get("type").and_then(|v| v.as_str()) == Some("Identifier") { - if type_name_val.get("name").and_then(|v| v.as_str()) == Some("Array") { - return Type::Object { - shape_id: Some("BuiltInArray".to_string()), - }; - } - } - } - builder.make_type() - } - "ArrayTypeAnnotation" | "TSArrayType" => Type::Object { - shape_id: Some("BuiltInArray".to_string()), - }, - "BooleanLiteralTypeAnnotation" - | "BooleanTypeAnnotation" - | "NullLiteralTypeAnnotation" - | "NumberLiteralTypeAnnotation" - | "NumberTypeAnnotation" - | "StringLiteralTypeAnnotation" - | "StringTypeAnnotation" - | "TSBooleanKeyword" - | "TSNullKeyword" - | "TSNumberKeyword" - | "TSStringKeyword" - | "TSSymbolKeyword" - | "TSUndefinedKeyword" - | "TSVoidKeyword" - | "VoidTypeAnnotation" => Type::Primitive, - _ => builder.make_type(), - } -} - -/// Gather captured context variables for a nested function. -/// -/// Walks through all identifier references (via `reference_to_binding`) and checks -/// which ones resolve to bindings declared in scopes between the function's parent scope -/// and the component scope. These are "free variables" that become the function's `context`. -fn gather_captured_context( - scope_info: &ScopeInfo, - function_scope: react_compiler_ast::scope::ScopeId, - component_scope: react_compiler_ast::scope::ScopeId, - func_start: u32, - func_end: u32, - identifier_locs: &IdentifierLocIndex, - ref_node_ids_override: Option<&IndexSet<u32>>, -) -> IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>> { - let parent_scope = scope_info.scopes[function_scope.0 as usize].parent; - let pure_scopes = match parent_scope { - Some(parent) => capture_scopes(scope_info, parent, component_scope), - None => IndexSet::new(), - }; - - // Collect the earliest (lowest source position) reference location for each - // captured binding. Using the minimum position makes the result independent of - // ref_node_id_to_binding iteration order, matching the behavior the TS compiler - // gets from Babel's position-ordered traversal. - let mut captured: std::collections::HashMap< - react_compiler_ast::scope::BindingId, - (u32, Option<SourceLocation>), // (min_position, loc) - > = std::collections::HashMap::new(); - - for (&ref_nid, &binding_id) in &scope_info.ref_node_id_to_binding { - if let Some(allowed) = ref_node_ids_override { - if !allowed.contains(&ref_nid) { - continue; - } - } else { - // Range check: use the position stored in identifier_locs - let ref_start = identifier_locs.get(&ref_nid).map(|e| e.start).unwrap_or(0); - if ref_start < func_start || ref_start >= func_end { - continue; - } - } - let binding = &scope_info.bindings[binding_id.0 as usize]; - // Skip references that are actually the binding's own declaration site - if binding.declaration_node_id == Some(ref_nid) { - continue; - } - // Skip function/class declaration names that are not expression references. - // Skip type-annotation references: TS's gatherCapturedContext traverse - // skips TypeAnnotation/TSTypeAnnotation/TypeAlias/TSTypeAliasDeclaration - // subtrees, so identifiers there never become captures (they DO still - // feed FindContextIdentifiers and the hoisting analysis, which have no - // such skip in TS). - if let Some(entry) = identifier_locs.get(&ref_nid) { - if entry.is_declaration_name || entry.in_type_annotation { - continue; - } - } - // Skip type-only bindings - if binding.declaration_type == "TypeAlias" - || binding.declaration_type == "OpaqueType" - || binding.declaration_type == "InterfaceDeclaration" - || binding.declaration_type == "TSTypeAliasDeclaration" - || binding.declaration_type == "TSInterfaceDeclaration" - || binding.declaration_type == "TSEnumDeclaration" - { - continue; - } - if pure_scopes.contains(&binding.scope) { - let ref_start = identifier_locs.get(&ref_nid).map(|e| e.start).unwrap_or(0); - // Skip references whose start offset aliases the binding's own - // declaration offset. Hermes desugars (component syntax) reuse the - // original source offsets for generated nodes, so a sibling - // reference structurally OUTSIDE this function (e.g. the forwardRef - // argument naming the desugared inner function) can fall inside the - // function's position range and alias the declaration position. In - // real source a non-declaration reference can never share its - // declaration's offset, so this only filters desugared aliases. - if binding.declaration_start == Some(ref_start) { - continue; - } - let loc = identifier_locs.get(&ref_nid).map(|entry| { - if let Some(oe_loc) = &entry.opening_element_loc { - oe_loc.clone() - } else { - entry.loc.clone() - } - }); - captured - .entry(binding.id) - .and_modify(|(min_pos, existing_loc)| { - if ref_start < *min_pos { - *min_pos = ref_start; - *existing_loc = loc.clone(); - } - }) - .or_insert((ref_start, loc)); - } - } - - // Sort captured entries by source position so context declarations appear - // in source order, matching the TS compiler's position-ordered traversal. - let mut sorted: Vec<_> = captured.into_iter().collect(); - sorted.sort_by_key(|(_, (pos, _))| *pos); - - sorted - .into_iter() - .map(|(bid, (_, loc))| (bid, loc)) - .collect() -} - -fn capture_scopes( - scope_info: &ScopeInfo, - from: react_compiler_ast::scope::ScopeId, - to: react_compiler_ast::scope::ScopeId, -) -> IndexSet<react_compiler_ast::scope::ScopeId> { - let mut result = IndexSet::new(); - let mut current = Some(from); - while let Some(scope_id) = current { - result.insert(scope_id); - if scope_id == to { - break; - } - current = scope_info.scopes[scope_id.0 as usize].parent; - } - result -} - -/// The style of assignment (used internally by lower_assignment). -#[derive(Clone, Copy)] -pub enum AssignmentStyle { - /// Assignment via `=` - Assignment, - /// Destructuring assignment - Destructure, -} - -/// Collect locations of fbt:enum, fbt:plural, fbt:pronoun sub-tags -/// within the children of an fbt/fbs JSX element. -fn collect_fbt_sub_tags( - children: &[react_compiler_ast::jsx::JSXChild], - tag_name: &str, - enum_locs: &mut Vec<Option<SourceLocation>>, - plural_locs: &mut Vec<Option<SourceLocation>>, - pronoun_locs: &mut Vec<Option<SourceLocation>>, -) { - use react_compiler_ast::jsx::JSXChild; - for child in children { - match child { - JSXChild::JSXElement(el) => { - collect_fbt_sub_tags_from_element( - el, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - JSXChild::JSXFragment(frag) => { - collect_fbt_sub_tags( - &frag.children, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - JSXChild::JSXExpressionContainer(container) => { - if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(expr) = - &container.expression - { - collect_fbt_sub_tags_from_expr( - expr, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - } - _ => {} - } - } -} - -fn collect_fbt_sub_tags_from_element( - el: &react_compiler_ast::jsx::JSXElement, - tag_name: &str, - enum_locs: &mut Vec<Option<SourceLocation>>, - plural_locs: &mut Vec<Option<SourceLocation>>, - pronoun_locs: &mut Vec<Option<SourceLocation>>, -) { - use react_compiler_ast::jsx::JSXElementName; - if let JSXElementName::JSXNamespacedName(ns) = &el.opening_element.name { - if ns.namespace.name == tag_name { - let loc = convert_opt_loc(&ns.base.loc); - match ns.name.name.as_str() { - "enum" => enum_locs.push(loc), - "plural" => plural_locs.push(loc), - "pronoun" => pronoun_locs.push(loc), - _ => {} - } - } - } - collect_fbt_sub_tags(&el.children, tag_name, enum_locs, plural_locs, pronoun_locs); - // Also traverse JSX attributes (matching TS expr.traverse which visits all nodes) - for attr in &el.opening_element.attributes { - if let react_compiler_ast::jsx::JSXAttributeItem::JSXAttribute(a) = attr { - if let Some(val) = &a.value { - if let react_compiler_ast::jsx::JSXAttributeValue::JSXExpressionContainer( - container, - ) = val - { - if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(expr) = - &container.expression - { - collect_fbt_sub_tags_from_expr( - expr, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - } else if let react_compiler_ast::jsx::JSXAttributeValue::JSXElement(nested) = val { - collect_fbt_sub_tags_from_element( - nested, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - } - } - } -} - -fn collect_fbt_sub_tags_from_expr( - expr: &react_compiler_ast::expressions::Expression, - tag_name: &str, - enum_locs: &mut Vec<Option<SourceLocation>>, - plural_locs: &mut Vec<Option<SourceLocation>>, - pronoun_locs: &mut Vec<Option<SourceLocation>>, -) { - use react_compiler_ast::expressions::Expression; - match expr { - Expression::JSXElement(el) => { - collect_fbt_sub_tags_from_element(el, tag_name, enum_locs, plural_locs, pronoun_locs); - } - Expression::JSXFragment(frag) => { - collect_fbt_sub_tags( - &frag.children, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - Expression::ConditionalExpression(cond) => { - collect_fbt_sub_tags_from_expr( - &cond.consequent, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - collect_fbt_sub_tags_from_expr( - &cond.alternate, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - Expression::LogicalExpression(log) => { - collect_fbt_sub_tags_from_expr( - &log.left, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - collect_fbt_sub_tags_from_expr( - &log.right, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - Expression::ParenthesizedExpression(paren) => { - collect_fbt_sub_tags_from_expr( - &paren.expression, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - Expression::ArrowFunctionExpression(arrow) => match arrow.body.as_ref() { - react_compiler_ast::expressions::ArrowFunctionBody::Expression(body_expr) => { - collect_fbt_sub_tags_from_expr( - body_expr, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => { - collect_fbt_sub_tags_from_stmts( - &block.body, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - }, - Expression::CallExpression(call) => { - for arg in &call.arguments { - collect_fbt_sub_tags_from_expr(arg, tag_name, enum_locs, plural_locs, pronoun_locs); - } - } - _ => {} - } -} - -fn collect_fbt_sub_tags_from_stmts( - stmts: &[react_compiler_ast::statements::Statement], - tag_name: &str, - enum_locs: &mut Vec<Option<SourceLocation>>, - plural_locs: &mut Vec<Option<SourceLocation>>, - pronoun_locs: &mut Vec<Option<SourceLocation>>, -) { - for stmt in stmts { - if let react_compiler_ast::statements::Statement::ReturnStatement(ret) = stmt { - if let Some(arg) = &ret.argument { - collect_fbt_sub_tags_from_expr(arg, tag_name, enum_locs, plural_locs, pronoun_locs); - } - } else if let react_compiler_ast::statements::Statement::ExpressionStatement(expr_stmt) = - stmt - { - collect_fbt_sub_tags_from_expr( - &expr_stmt.expression, - tag_name, - enum_locs, - plural_locs, - pronoun_locs, - ); - } - } -} - -fn collect_identifier_node_ids_from_body(body: &FunctionBody) -> IndexSet<u32> { - let mut positions = IndexSet::new(); - match body { - FunctionBody::Block(block) => { - for stmt in &block.body { - collect_identifier_node_ids_from_stmt(stmt, &mut positions); - } - } - FunctionBody::Expression(expr) => { - collect_identifier_node_ids_from_expr(expr, &mut positions); - } - } - positions -} - -fn collect_identifier_node_ids_from_stmt( - stmt: &react_compiler_ast::statements::Statement, - positions: &mut IndexSet<u32>, -) { - use react_compiler_ast::statements::Statement; - match stmt { - Statement::ExpressionStatement(s) => { - collect_identifier_node_ids_from_expr(&s.expression, positions) - } - Statement::ReturnStatement(s) => { - if let Some(arg) = &s.argument { - collect_identifier_node_ids_from_expr(arg, positions); - } - } - Statement::ThrowStatement(s) => { - collect_identifier_node_ids_from_expr(&s.argument, positions) - } - Statement::BlockStatement(s) => { - for stmt in &s.body { - collect_identifier_node_ids_from_stmt(stmt, positions); - } - } - Statement::IfStatement(s) => { - collect_identifier_node_ids_from_expr(&s.test, positions); - collect_identifier_node_ids_from_stmt(&s.consequent, positions); - if let Some(alt) = &s.alternate { - collect_identifier_node_ids_from_stmt(alt, positions); - } - } - Statement::VariableDeclaration(s) => { - for decl in &s.declarations { - if let Some(init) = &decl.init { - collect_identifier_node_ids_from_expr(init, positions); - } - } - } - _ => {} - } -} - -fn collect_identifier_node_ids_from_expr( - expr: &react_compiler_ast::expressions::Expression, - positions: &mut IndexSet<u32>, -) { - use react_compiler_ast::expressions::Expression; - match expr { - Expression::Identifier(id) => { - if let Some(nid) = id.base.node_id { - positions.insert(nid); - } - } - Expression::CallExpression(call) => { - collect_identifier_node_ids_from_expr(&call.callee, positions); - for arg in &call.arguments { - collect_identifier_node_ids_from_expr(arg, positions); - } - } - Expression::BinaryExpression(e) => { - collect_identifier_node_ids_from_expr(&e.left, positions); - collect_identifier_node_ids_from_expr(&e.right, positions); - } - Expression::ConditionalExpression(e) => { - collect_identifier_node_ids_from_expr(&e.test, positions); - collect_identifier_node_ids_from_expr(&e.consequent, positions); - collect_identifier_node_ids_from_expr(&e.alternate, positions); - } - Expression::LogicalExpression(e) => { - collect_identifier_node_ids_from_expr(&e.left, positions); - collect_identifier_node_ids_from_expr(&e.right, positions); - } - Expression::MemberExpression(e) => { - collect_identifier_node_ids_from_expr(&e.object, positions); - } - Expression::OptionalMemberExpression(e) => { - collect_identifier_node_ids_from_expr(&e.object, positions); - } - Expression::OptionalCallExpression(e) => { - collect_identifier_node_ids_from_expr(&e.callee, positions); - for arg in &e.arguments { - collect_identifier_node_ids_from_expr(arg, positions); - } - } - Expression::UpdateExpression(e) => { - collect_identifier_node_ids_from_expr(&e.argument, positions); - } - Expression::FunctionExpression(func) => { - for stmt in &func.body.body { - collect_identifier_node_ids_from_stmt(stmt, positions); - } - } - Expression::UnaryExpression(e) => { - collect_identifier_node_ids_from_expr(&e.argument, positions); - } - Expression::ParenthesizedExpression(e) => { - collect_identifier_node_ids_from_expr(&e.expression, positions); - } - Expression::TypeCastExpression(e) => { - collect_identifier_node_ids_from_expr(&e.expression, positions); - } - Expression::ArrowFunctionExpression(arrow) => match arrow.body.as_ref() { - react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => { - for stmt in &block.body { - collect_identifier_node_ids_from_stmt(stmt, positions); - } - } - react_compiler_ast::expressions::ArrowFunctionBody::Expression(e) => { - collect_identifier_node_ids_from_expr(e, positions); - } - }, - Expression::JSXElement(el) => { - if let react_compiler_ast::jsx::JSXElementName::JSXIdentifier(id) = - &el.opening_element.name - { - if let Some(nid) = id.base.node_id { - positions.insert(nid); - } - } - for attr in &el.opening_element.attributes { - match attr { - react_compiler_ast::jsx::JSXAttributeItem::JSXAttribute(a) => { - if let Some(val) = &a.value { - match val { - react_compiler_ast::jsx::JSXAttributeValue::JSXExpressionContainer(c) => { - if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(e) = &c.expression { - collect_identifier_node_ids_from_expr(e, positions); - } - } - _ => {} - } - } - } - react_compiler_ast::jsx::JSXAttributeItem::JSXSpreadAttribute(a) => { - collect_identifier_node_ids_from_expr(&a.argument, positions); - } - } - } - for child in &el.children { - match child { - react_compiler_ast::jsx::JSXChild::JSXExpressionContainer(c) => { - if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(e) = - &c.expression - { - collect_identifier_node_ids_from_expr(e, positions); - } - } - react_compiler_ast::jsx::JSXChild::JSXElement(child_el) => { - collect_identifier_node_ids_from_expr( - &Expression::JSXElement(child_el.clone()), - positions, - ); - } - react_compiler_ast::jsx::JSXChild::JSXSpreadChild(s) => { - collect_identifier_node_ids_from_expr(&s.expression, positions); - } - _ => {} - } - } - } - Expression::JSXFragment(frag) => { - for child in &frag.children { - match child { - react_compiler_ast::jsx::JSXChild::JSXExpressionContainer(c) => { - if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(e) = - &c.expression - { - collect_identifier_node_ids_from_expr(e, positions); - } - } - react_compiler_ast::jsx::JSXChild::JSXElement(child_el) => { - collect_identifier_node_ids_from_expr( - &Expression::JSXElement(child_el.clone()), - positions, - ); - } - _ => {} - } - } - } - Expression::ArrayExpression(arr) => { - for elem in &arr.elements { - if let Some(e) = elem { - collect_identifier_node_ids_from_expr(e, positions); - } - } - } - Expression::ObjectExpression(obj) => { - for prop in &obj.properties { - match prop { - react_compiler_ast::expressions::ObjectExpressionProperty::ObjectProperty( - p, - ) => { - collect_identifier_node_ids_from_expr(&p.value, positions); - } - react_compiler_ast::expressions::ObjectExpressionProperty::SpreadElement(s) => { - collect_identifier_node_ids_from_expr(&s.argument, positions); - } - _ => {} - } - } - } - Expression::NewExpression(e) => { - collect_identifier_node_ids_from_expr(&e.callee, positions); - for arg in &e.arguments { - collect_identifier_node_ids_from_expr(arg, positions); - } - } - Expression::AssignmentExpression(e) => { - collect_identifier_node_ids_from_expr(&e.right, positions); - } - Expression::TemplateLiteral(e) => { - for expr in &e.expressions { - collect_identifier_node_ids_from_expr(expr, positions); - } - } - Expression::SpreadElement(e) => { - collect_identifier_node_ids_from_expr(&e.argument, positions); - } - Expression::SequenceExpression(e) => { - for expr in &e.expressions { - collect_identifier_node_ids_from_expr(expr, positions); - } - } - _ => {} - } -} diff --git a/compiler/crates/react_compiler_lowering/src/find_context_identifiers.rs b/compiler/crates/react_compiler_lowering/src/find_context_identifiers.rs deleted file mode 100644 index b2d53ecd643c..000000000000 --- a/compiler/crates/react_compiler_lowering/src/find_context_identifiers.rs +++ /dev/null @@ -1,447 +0,0 @@ -//! Rust equivalent of the TypeScript `FindContextIdentifiers` pass. -//! -//! Determines which bindings need StoreContext/LoadContext semantics by -//! walking the AST with scope tracking to find variables that cross -//! function boundaries. - -use std::collections::HashMap; -use std::collections::HashSet; - -use react_compiler_ast::expressions::*; -use react_compiler_ast::patterns::*; -use react_compiler_ast::scope::*; -use react_compiler_ast::statements::FunctionDeclaration; -use react_compiler_ast::visitor::AstWalker; -use react_compiler_ast::visitor::Visitor; -use react_compiler_diagnostics::CompilerError; -use react_compiler_diagnostics::CompilerErrorDetail; -use react_compiler_diagnostics::ErrorCategory; -use react_compiler_diagnostics::Position; -use react_compiler_diagnostics::SourceLocation; -use react_compiler_hir::environment::Environment; - -use crate::FunctionNode; - -#[derive(Default)] -struct BindingInfo { - reassigned: bool, - reassigned_by_inner_fn: bool, - referenced_by_inner_fn: bool, -} - -struct ContextIdentifierVisitor<'a> { - scope_info: &'a ScopeInfo, - env: &'a mut Environment, - /// Stack of inner function scopes encountered during traversal. - /// Empty when at the top level of the function being compiled. - function_stack: Vec<ScopeId>, - binding_info: HashMap<BindingId, BindingInfo>, - error: Option<CompilerError>, -} - -impl<'a> ContextIdentifierVisitor<'a> { - fn push_function_scope(&mut self, _start: Option<u32>, node_id: Option<u32>) { - let scope = self.scope_info.resolve_scope_for_node(node_id); - if let Some(scope) = scope { - self.function_stack.push(scope); - } - } - - fn pop_function_scope(&mut self, _start: Option<u32>, node_id: Option<u32>) { - let has_scope = self.scope_info.resolve_scope_for_node(node_id); - if has_scope.is_some() { - self.function_stack.pop(); - } - } - - fn check_captured_reference(&mut self, _start: Option<u32>, node_id: Option<u32>) { - let binding_id = match self.scope_info.resolve_reference_id_for_node(node_id) { - Some(id) => id, - None => return, - }; - let &fn_scope = match self.function_stack.last() { - Some(s) => s, - None => return, - }; - let binding = &self.scope_info.bindings[binding_id.0 as usize]; - if is_captured_by_function(self.scope_info, binding.scope, fn_scope) { - let info = self.binding_info.entry(binding_id).or_default(); - info.referenced_by_inner_fn = true; - } - } - - fn handle_reassignment_identifier(&mut self, name: &str, current_scope: ScopeId) { - if let Some(binding_id) = self.scope_info.get_binding(current_scope, name) { - let info = self.binding_info.entry(binding_id).or_default(); - info.reassigned = true; - if let Some(&fn_scope) = self.function_stack.last() { - let binding = &self.scope_info.bindings[binding_id.0 as usize]; - if is_captured_by_function(self.scope_info, binding.scope, fn_scope) { - info.reassigned_by_inner_fn = true; - } - } - } - } -} - -impl<'ast> Visitor<'ast> for ContextIdentifierVisitor<'_> { - fn enter_function_declaration(&mut self, node: &'ast FunctionDeclaration, _: &[ScopeId]) { - self.push_function_scope(node.base.start, node.base.node_id); - } - fn leave_function_declaration(&mut self, node: &'ast FunctionDeclaration, _: &[ScopeId]) { - self.pop_function_scope(node.base.start, node.base.node_id); - } - fn enter_function_expression(&mut self, node: &'ast FunctionExpression, _: &[ScopeId]) { - self.push_function_scope(node.base.start, node.base.node_id); - } - fn leave_function_expression(&mut self, node: &'ast FunctionExpression, _: &[ScopeId]) { - self.pop_function_scope(node.base.start, node.base.node_id); - } - fn enter_arrow_function_expression( - &mut self, - node: &'ast ArrowFunctionExpression, - _: &[ScopeId], - ) { - self.push_function_scope(node.base.start, node.base.node_id); - } - fn leave_arrow_function_expression( - &mut self, - node: &'ast ArrowFunctionExpression, - _: &[ScopeId], - ) { - self.pop_function_scope(node.base.start, node.base.node_id); - } - fn enter_object_method(&mut self, node: &'ast ObjectMethod, _: &[ScopeId]) { - self.push_function_scope(node.base.start, node.base.node_id); - } - fn leave_object_method(&mut self, node: &'ast ObjectMethod, _: &[ScopeId]) { - self.pop_function_scope(node.base.start, node.base.node_id); - } - - fn enter_identifier(&mut self, node: &'ast Identifier, _scope_stack: &[ScopeId]) { - self.check_captured_reference(node.base.start, node.base.node_id); - } - - fn enter_jsx_identifier( - &mut self, - node: &'ast react_compiler_ast::jsx::JSXIdentifier, - _scope_stack: &[ScopeId], - ) { - self.check_captured_reference(node.base.start, node.base.node_id); - } - - fn enter_assignment_expression( - &mut self, - node: &'ast AssignmentExpression, - scope_stack: &[ScopeId], - ) { - let current_scope = scope_stack - .last() - .copied() - .unwrap_or(self.scope_info.program_scope); - if self.error.is_none() { - if let Err(error) = walk_lval_for_reassignment(self, &node.left, current_scope) { - self.error = Some(error); - } - } - } - - fn enter_update_expression(&mut self, node: &'ast UpdateExpression, scope_stack: &[ScopeId]) { - if let Expression::Identifier(ident) = node.argument.as_ref() { - let current_scope = scope_stack - .last() - .copied() - .unwrap_or(self.scope_info.program_scope); - self.handle_reassignment_identifier(&ident.name, current_scope); - } - } -} - -/// Recursively walk an LVal pattern to find all reassignment target identifiers. -fn walk_lval_for_reassignment( - visitor: &mut ContextIdentifierVisitor<'_>, - pattern: &PatternLike, - current_scope: ScopeId, -) -> Result<(), CompilerError> { - match pattern { - PatternLike::Identifier(ident) => { - visitor.handle_reassignment_identifier(&ident.name, current_scope); - } - PatternLike::ArrayPattern(pat) => { - for element in &pat.elements { - if let Some(el) = element { - walk_lval_for_reassignment(visitor, el, current_scope)?; - } - } - } - PatternLike::ObjectPattern(pat) => { - for prop in &pat.properties { - match prop { - ObjectPatternProperty::ObjectProperty(p) => { - walk_lval_for_reassignment(visitor, &p.value, current_scope)?; - } - ObjectPatternProperty::RestElement(p) => { - walk_lval_for_reassignment(visitor, &p.argument, current_scope)?; - } - } - } - } - PatternLike::AssignmentPattern(pat) => { - walk_lval_for_reassignment(visitor, &pat.left, current_scope)?; - } - PatternLike::RestElement(pat) => { - walk_lval_for_reassignment(visitor, &pat.argument, current_scope)?; - } - PatternLike::MemberExpression(_) => { - // Interior mutability - not a variable reassignment - } - PatternLike::TSAsExpression(node) => { - record_unsupported_lval( - visitor.env, - "TSAsExpression", - convert_opt_loc(&node.base.loc), - )?; - } - PatternLike::TSSatisfiesExpression(node) => { - record_unsupported_lval( - visitor.env, - "TSSatisfiesExpression", - convert_opt_loc(&node.base.loc), - )?; - } - PatternLike::TSNonNullExpression(node) => { - record_unsupported_lval( - visitor.env, - "TSNonNullExpression", - convert_opt_loc(&node.base.loc), - )?; - } - PatternLike::TSTypeAssertion(node) => { - record_unsupported_lval( - visitor.env, - "TSTypeAssertion", - convert_opt_loc(&node.base.loc), - )?; - } - PatternLike::TypeCastExpression(node) => { - record_unsupported_lval( - visitor.env, - "TypeCastExpression", - convert_opt_loc(&node.base.loc), - )?; - } - } - Ok(()) -} - -fn convert_loc(loc: &react_compiler_ast::common::SourceLocation) -> SourceLocation { - SourceLocation { - start: Position { - line: loc.start.line, - column: loc.start.column, - index: loc.start.index, - }, - end: Position { - line: loc.end.line, - column: loc.end.column, - index: loc.end.index, - }, - } -} - -fn convert_opt_loc( - loc: &Option<react_compiler_ast::common::SourceLocation>, -) -> Option<SourceLocation> { - loc.as_ref().map(convert_loc) -} - -/// Record the TS-faithful Todo for an unsupported assignment-target wrapper -/// node, mirroring the TypeScript `FindContextIdentifiers` pass. TS throws -/// immediately (CompilerError.throwTodo in handleAssignment's default case), -/// aborting before BuildHIR ever runs or logs, so this must return Err rather -/// than record-and-continue: otherwise Rust emits HIR debug entries for a -/// function TS never lowered. -fn record_unsupported_lval( - env: &mut Environment, - type_name: &str, - loc: Option<SourceLocation>, -) -> Result<(), CompilerError> { - let _ = env; - let mut err = CompilerError::new(); - err.push_error_detail(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: format!( - "[FindContextIdentifiers] Cannot handle Object destructuring assignment target {type_name}" - ), - description: None, - loc, - suggestions: None, - }); - Err(err) -} - -/// Check if a binding declared at `binding_scope` is captured by a function at `function_scope`. -/// Returns true if the binding is declared above the function (in the parent scope or higher). -fn is_captured_by_function( - scope_info: &ScopeInfo, - binding_scope: ScopeId, - function_scope: ScopeId, -) -> bool { - let fn_parent = match scope_info.scopes[function_scope.0 as usize].parent { - Some(p) => p, - None => return false, - }; - if binding_scope == fn_parent { - return true; - } - // Walk up from fn_parent to see if binding_scope is an ancestor - let mut current = scope_info.scopes[fn_parent.0 as usize].parent; - while let Some(scope_id) = current { - if scope_id == binding_scope { - return true; - } - current = scope_info.scopes[scope_id.0 as usize].parent; - } - false -} - -/// Build a set of `(BindingId, position)` pairs that are declaration sites -/// in `reference_to_binding`, not true references. Uses node-ID comparison -/// when available (from `ref_node_id_to_binding` + `declaration_node_id`), -/// falling back to position comparison otherwise. -/// Build a set of (BindingId, node_id) pairs for declaration sites in -/// ref_node_id_to_binding. These are entries where the reference's node_id -/// matches the binding's declaration_node_id — i.e., the "reference" is -/// actually the declaration itself. -fn build_declaration_node_ids(scope_info: &ScopeInfo) -> HashSet<(BindingId, u32)> { - let mut result = HashSet::new(); - for (&ref_nid, &binding_id) in &scope_info.ref_node_id_to_binding { - let binding = &scope_info.bindings[binding_id.0 as usize]; - if binding.declaration_node_id == Some(ref_nid) { - result.insert((binding_id, ref_nid)); - } - } - result -} - -/// Find context identifiers for a function: variables that are captured across -/// function boundaries and need StoreContext/LoadContext semantics. -/// -/// A binding is a context identifier if: -/// - It is reassigned from inside a nested function (`reassignedByInnerFn`), OR -/// - It is reassigned AND referenced from inside a nested function -/// (`reassigned && referencedByInnerFn`) -/// -/// This is the Rust equivalent of the TypeScript `FindContextIdentifiers` pass. -pub fn find_context_identifiers( - func: &FunctionNode<'_>, - scope_info: &ScopeInfo, - env: &mut Environment, - identifier_locs: &crate::identifier_loc_index::IdentifierLocIndex, -) -> Result<HashSet<BindingId>, CompilerError> { - let func_scope = scope_info - .resolve_scope_for_node(func.node_id()) - .unwrap_or(scope_info.program_scope); - - let mut visitor = ContextIdentifierVisitor { - scope_info, - env, - function_stack: Vec::new(), - binding_info: HashMap::new(), - error: None, - }; - let mut walker = AstWalker::with_initial_scope(scope_info, func_scope); - - // Walk params and body (like Babel's func.traverse()) - match func { - FunctionNode::FunctionDeclaration(d) => { - for param in &d.params { - walker.walk_pattern(&mut visitor, param); - } - walker.walk_block_statement(&mut visitor, &d.body); - } - FunctionNode::FunctionExpression(e) => { - for param in &e.params { - walker.walk_pattern(&mut visitor, param); - } - walker.walk_block_statement(&mut visitor, &e.body); - } - FunctionNode::ArrowFunctionExpression(a) => { - for param in &a.params { - walker.walk_pattern(&mut visitor, param); - } - match a.body.as_ref() { - ArrowFunctionBody::BlockStatement(block) => { - walker.walk_block_statement(&mut visitor, block); - } - ArrowFunctionBody::Expression(expr) => { - walker.walk_expression(&mut visitor, expr); - } - } - } - } - - if let Some(error) = visitor.error { - return Err(error); - } - - // Supplement the walker-based analysis with referenceToBinding data. - // The AST walker doesn't visit identifiers inside type annotations, - // but Babel's traverse (used by TS findContextIdentifiers) does. - // After scope extraction includes type annotation references, - // we check if any reassigned binding has references in nested function scopes - // via referenceToBinding — matching the TS behavior. - // - // We must skip declaration sites (e.g., the `x` in `function x() {}`), - // which are included in reference_to_binding but are not true references. - // Prefer node-ID comparison (immune to position-0 collisions from synthetic - // nodes), falling back to position when node-IDs are unavailable. - let declaration_node_ids = build_declaration_node_ids(scope_info); - for (&ref_nid, &binding_id) in &scope_info.ref_node_id_to_binding { - let info = match visitor.binding_info.get(&binding_id) { - Some(info) if info.reassigned && !info.referenced_by_inner_fn => info, - _ => continue, - }; - let _ = info; - if declaration_node_ids.contains(&(binding_id, ref_nid)) { - continue; - } - // Get the reference's position from identifier_locs for range checks - let ref_pos = match identifier_locs.get(&ref_nid) { - Some(entry) => entry.start, - None => continue, - }; - let binding = &scope_info.bindings[binding_id.0 as usize]; - // Check if ref_pos is inside a nested function scope - for (&scope_start, &scope_id) in &scope_info.node_to_scope { - if scope_start <= ref_pos { - if let Some(&scope_end) = scope_info.node_to_scope_end.get(&scope_start) { - if ref_pos < scope_end - && matches!( - scope_info.scopes[scope_id.0 as usize].kind, - ScopeKind::Function - ) - && is_captured_by_function(scope_info, binding.scope, scope_id) - { - visitor - .binding_info - .get_mut(&binding_id) - .unwrap() - .referenced_by_inner_fn = true; - break; - } - } - } - } - } - - // Collect results - Ok(visitor - .binding_info - .into_iter() - .filter(|(_, info)| { - info.reassigned_by_inner_fn || (info.reassigned && info.referenced_by_inner_fn) - }) - .map(|(id, _)| id) - .collect()) -} diff --git a/compiler/crates/react_compiler_lowering/src/hir_builder.rs b/compiler/crates/react_compiler_lowering/src/hir_builder.rs deleted file mode 100644 index 84bd1e0d45b1..000000000000 --- a/compiler/crates/react_compiler_lowering/src/hir_builder.rs +++ /dev/null @@ -1,1431 +0,0 @@ -use indexmap::IndexMap; -use indexmap::IndexSet; -use react_compiler_ast::scope::BindingId; -use react_compiler_ast::scope::ImportBindingKind; -use react_compiler_ast::scope::ScopeId; -use react_compiler_ast::scope::ScopeInfo; -use react_compiler_diagnostics::CompilerDiagnostic; -use react_compiler_diagnostics::CompilerDiagnosticDetail; -use react_compiler_diagnostics::CompilerError; -use react_compiler_diagnostics::CompilerErrorDetail; -use react_compiler_diagnostics::ErrorCategory; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors::each_terminal_successor; -use react_compiler_hir::visitors::terminal_fallthrough; -use react_compiler_hir::*; - -use crate::identifier_loc_index::IdentifierLocIndex; - -// --------------------------------------------------------------------------- -// Reserved word check (matches TS isReservedWord) -// --------------------------------------------------------------------------- - -pub(crate) fn is_always_reserved_word(s: &str) -> bool { - matches!( - s, - "break" - | "case" - | "catch" - | "continue" - | "debugger" - | "default" - | "do" - | "else" - | "finally" - | "for" - | "function" - | "if" - | "in" - | "instanceof" - | "new" - | "return" - | "switch" - | "this" - | "throw" - | "try" - | "typeof" - | "var" - | "void" - | "while" - | "with" - | "class" - | "const" - | "enum" - | "export" - | "extends" - | "import" - | "super" - | "null" - | "true" - | "false" - | "delete" - ) -} - -pub(crate) fn reserved_identifier_diagnostic(name: &str) -> CompilerDiagnostic { - CompilerDiagnostic::new( - ErrorCategory::Syntax, - "Expected a non-reserved identifier name", - Some(format!( - "`{}` is a reserved word in JavaScript and cannot be used as an identifier name", - name - )), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: None, // GeneratedSource in TS - message: Some("reserved word".to_string()), - identifier_name: None, - }) -} - -// --------------------------------------------------------------------------- -// Scope types for tracking break/continue targets -// --------------------------------------------------------------------------- - -enum Scope { - Loop { - label: Option<String>, - continue_block: BlockId, - break_block: BlockId, - }, - Label { - label: String, - break_block: BlockId, - }, - Switch { - label: Option<String>, - break_block: BlockId, - }, -} - -impl Scope { - fn label(&self) -> Option<&str> { - match self { - Scope::Loop { label, .. } => label.as_deref(), - Scope::Label { label, .. } => Some(label.as_str()), - Scope::Switch { label, .. } => label.as_deref(), - } - } - - fn break_block(&self) -> BlockId { - match self { - Scope::Loop { break_block, .. } => *break_block, - Scope::Label { break_block, .. } => *break_block, - Scope::Switch { break_block, .. } => *break_block, - } - } -} - -// --------------------------------------------------------------------------- -// WipBlock: a block under construction that does not yet have a terminal -// --------------------------------------------------------------------------- - -pub struct WipBlock { - pub id: BlockId, - pub instructions: Vec<InstructionId>, - pub kind: BlockKind, -} - -fn new_block(id: BlockId, kind: BlockKind) -> WipBlock { - WipBlock { - id, - kind, - instructions: Vec::new(), - } -} - -// --------------------------------------------------------------------------- -// HirBuilder: helper struct for constructing a CFG -// --------------------------------------------------------------------------- - -pub struct HirBuilder<'a> { - completed: IndexMap<BlockId, BasicBlock>, - current: WipBlock, - entry: BlockId, - scopes: Vec<Scope>, - /// Context identifiers: variables captured from an outer scope. - /// Maps the outer scope's BindingId to the source location where it was referenced. - context: IndexMap<BindingId, Option<SourceLocation>>, - /// Resolved bindings: maps a BindingId to the HIR IdentifierId created for it. - bindings: IndexMap<BindingId, IdentifierId>, - /// Names already used by bindings, for collision avoidance. - /// Maps name string -> how many times it has been used (for appending _0, _1, ...). - used_names: IndexMap<String, BindingId>, - env: &'a mut Environment, - scope_info: &'a ScopeInfo, - exception_handler_stack: Vec<BlockId>, - /// Flat instruction table being built up. - instruction_table: Vec<Instruction>, - /// Traversal context: counts the number of `fbt` tag parents - /// of the current babel node. - pub fbt_depth: u32, - /// The scope of the function being compiled (for context identifier checks). - function_scope: ScopeId, - /// The scope of the outermost component/hook function (for gather_captured_context). - component_scope: ScopeId, - /// Set of BindingIds for variables declared in scopes between component_scope - /// and any inner function scope, that are referenced from an inner function scope. - /// These need StoreContext/LoadContext instead of StoreLocal/LoadLocal. - context_identifiers: std::collections::HashSet<BindingId>, - /// Set of ScopeIds that have been matched to synthetic blocks/functions. - /// Prevents the same scope from being reused for different synthetic nodes. - claimed_synthetic_scopes: std::collections::HashSet<ScopeId>, - /// Index mapping identifier byte offsets to source locations and JSX status. - identifier_locs: &'a IdentifierLocIndex, -} - -impl<'a> HirBuilder<'a> { - // ----------------------------------------------------------------------- - // M2: Core methods - // ----------------------------------------------------------------------- - - /// Create a new HirBuilder. - /// - /// - `env`: the shared environment (counters, arenas, error accumulator) - /// - `scope_info`: the scope information from the AST - /// - `function_scope`: the ScopeId of the function being compiled - /// - `bindings`: optional pre-existing bindings (e.g., from a parent function) - /// - `context`: optional pre-existing captured context map - /// - `entry_block_kind`: the kind of the entry block (defaults to `Block`) - pub fn new( - env: &'a mut Environment, - scope_info: &'a ScopeInfo, - function_scope: ScopeId, - component_scope: ScopeId, - context_identifiers: std::collections::HashSet<BindingId>, - bindings: Option<IndexMap<BindingId, IdentifierId>>, - context: Option<IndexMap<BindingId, Option<SourceLocation>>>, - entry_block_kind: Option<BlockKind>, - used_names: Option<IndexMap<String, BindingId>>, - identifier_locs: &'a IdentifierLocIndex, - ) -> Self { - let entry = env.next_block_id(); - let kind = entry_block_kind.unwrap_or(BlockKind::Block); - HirBuilder { - completed: IndexMap::new(), - current: new_block(entry, kind), - entry, - scopes: Vec::new(), - context: context.unwrap_or_default(), - bindings: bindings.unwrap_or_default(), - used_names: used_names.unwrap_or_default(), - env, - scope_info, - exception_handler_stack: Vec::new(), - instruction_table: Vec::new(), - fbt_depth: 0, - function_scope, - component_scope, - context_identifiers, - claimed_synthetic_scopes: std::collections::HashSet::new(), - identifier_locs, - } - } - - /// Check if a scope is the component scope or a descendant of it. - /// Used to determine whether a binding is local to the compiled function - /// or belongs to an ancestor function scope (e.g., a factory function - /// wrapping a nested component declaration). - /// Uses component_scope (the outermost compiled function's scope) rather - /// than function_scope because inner function expressions within the - /// compiled function have their own function_scope but still consider - /// the outer component's variables as local. - fn is_scope_within_compiled_function(&self, scope_id: ScopeId) -> bool { - let mut current = Some(scope_id); - while let Some(id) = current { - if id == self.component_scope { - return true; - } - current = self.scope_info.scopes[id.0 as usize].parent; - } - false - } - - /// Access the environment. - pub fn environment(&self) -> &Environment { - self.env - } - - /// Access the environment mutably. - pub fn environment_mut(&mut self) -> &mut Environment { - self.env - } - - /// Create a new unique TypeVar type, allocated from the environment's type arena - /// so that TypeIds are consistent with identifier type slots. - pub fn make_type(&mut self) -> Type { - let type_id = self.env.make_type(); - Type::TypeVar { id: type_id } - } - - /// Access the scope info. - pub fn scope_info(&self) -> &ScopeInfo { - self.scope_info - } - - /// Look up the source location of an identifier by its node_id. - pub fn get_identifier_loc(&self, node_id: u32) -> Option<SourceLocation> { - self.identifier_locs - .get(&node_id) - .map(|entry| entry.loc.clone()) - } - - /// Check whether a reference at the given byte offset corresponds to a - /// JSXIdentifier. Scans the node_id-keyed index for an entry whose stored - /// `start` matches the offset. - pub fn is_jsx_identifier_at_pos(&self, offset: u32) -> bool { - self.identifier_locs - .values() - .any(|entry| entry.start == offset && entry.is_jsx) - } - - /// Access the function scope (the scope of the function being compiled). - pub fn function_scope(&self) -> ScopeId { - self.function_scope - } - - /// Access the component scope. - pub fn component_scope(&self) -> ScopeId { - self.component_scope - } - - /// Access the context map. - pub fn context(&self) -> &IndexMap<BindingId, Option<SourceLocation>> { - &self.context - } - - /// Access the pre-computed context identifiers set. - pub fn context_identifiers(&self) -> &std::collections::HashSet<BindingId> { - &self.context_identifiers - } - - /// Add a binding to the context identifiers set (used by hoisting). - pub fn add_context_identifier(&mut self, binding_id: BindingId) { - self.context_identifiers.insert(binding_id); - } - - pub fn claim_synthetic_scope(&mut self, scope_id: ScopeId) { - self.claimed_synthetic_scopes.insert(scope_id); - } - - pub fn is_synthetic_scope_claimed(&self, scope_id: ScopeId) -> bool { - self.claimed_synthetic_scopes.contains(&scope_id) - } - - /// Access scope_info and environment mutably at the same time. - /// This is safe because they are disjoint fields, but Rust's borrow checker - /// can't prove this through method calls alone. - pub fn scope_info_and_env_mut(&mut self) -> (&ScopeInfo, &mut Environment) { - (self.scope_info, self.env) - } - - /// Access the identifier location index. - /// Returns the 'a reference to avoid conflicts with mutable borrows on self. - pub fn identifier_locs(&self) -> &'a IdentifierLocIndex { - self.identifier_locs - } - - /// Access the bindings map. - pub fn bindings(&self) -> &IndexMap<BindingId, IdentifierId> { - &self.bindings - } - - /// Access the used names map. - pub fn used_names(&self) -> &IndexMap<String, BindingId> { - &self.used_names - } - - /// Merge used names from a child builder back into this builder. - /// This ensures name deduplication works across function scopes. - pub fn merge_used_names(&mut self, child_used_names: IndexMap<String, BindingId>) { - for (name, binding_id) in child_used_names { - self.used_names.entry(name).or_insert(binding_id); - } - } - - /// Merge bindings (binding_id -> IdentifierId) from a child builder back into this builder. - /// This matches TS behavior where parent and child share the same #bindings map by reference, - /// so bindings resolved by the child are automatically visible to the parent. - pub fn merge_bindings(&mut self, child_bindings: IndexMap<BindingId, IdentifierId>) { - for (binding_id, identifier_id) in child_bindings { - self.bindings.entry(binding_id).or_insert(identifier_id); - } - } - - /// Push an instruction onto the current block. - /// - /// Adds the instruction to the flat instruction table and records - /// its InstructionId in the current block's instruction list. - /// - /// If an exception handler is active, also emits a MaybeThrow terminal - /// after the instruction to model potential control flow to the handler, - /// then continues in a new block. - pub fn push(&mut self, instruction: Instruction) { - let loc = instruction.loc.clone(); - let instr_id = InstructionId(self.instruction_table.len() as u32); - self.instruction_table.push(instruction); - self.current.instructions.push(instr_id); - - if let Some(&handler) = self.exception_handler_stack.last() { - let continuation = self.reserve(self.current_block_kind()); - self.terminate_with_continuation( - Terminal::MaybeThrow { - continuation: continuation.id, - handler: Some(handler), - id: EvaluationOrder(0), - loc, - effects: None, - }, - continuation, - ); - } - } - - /// Terminate the current block with the given terminal and start a new block. - /// - /// If `next_block_kind` is `Some`, a new current block is created with that kind. - /// Returns the BlockId of the completed block. - pub fn terminate(&mut self, terminal: Terminal, next_block_kind: Option<BlockKind>) -> BlockId { - // The placeholder block created here (BlockId(u32::MAX)) is only used when - // next_block_kind is None, meaning this is the final terminate() call. - // It will never be read or completed because build() consumes self - // immediately after, and no further operations should occur on the builder. - let wip = std::mem::replace( - &mut self.current, - new_block(BlockId(u32::MAX), BlockKind::Block), - ); - let block_id = wip.id; - - self.completed.insert( - block_id, - BasicBlock { - kind: wip.kind, - id: block_id, - instructions: wip.instructions, - terminal, - preds: IndexSet::new(), - phis: Vec::new(), - }, - ); - - if let Some(kind) = next_block_kind { - let next_id = self.env.next_block_id(); - self.current = new_block(next_id, kind); - } - block_id - } - - /// Terminate the current block with the given terminal, and set - /// a previously reserved block as the new current block. - pub fn terminate_with_continuation(&mut self, terminal: Terminal, continuation: WipBlock) { - let wip = std::mem::replace(&mut self.current, continuation); - let block_id = wip.id; - self.completed.insert( - block_id, - BasicBlock { - kind: wip.kind, - id: block_id, - instructions: wip.instructions, - terminal, - preds: IndexSet::new(), - phis: Vec::new(), - }, - ); - } - - /// Reserve a new block so it can be referenced before construction. - /// Use `terminate_with_continuation()` to make it current, or `complete()` to - /// save it directly. - pub fn reserve(&mut self, kind: BlockKind) -> WipBlock { - let id = self.env.next_block_id(); - new_block(id, kind) - } - - /// Save a previously reserved block as completed with the given terminal. - pub fn complete(&mut self, block: WipBlock, terminal: Terminal) { - let block_id = block.id; - self.completed.insert( - block_id, - BasicBlock { - kind: block.kind, - id: block_id, - instructions: block.instructions, - terminal, - preds: IndexSet::new(), - phis: Vec::new(), - }, - ); - } - - /// Sets the given wip block as current, executes the closure to populate - /// it and obtain its terminal, then completes the block and restores the - /// previous current block. - pub fn enter_reserved(&mut self, wip: WipBlock, f: impl FnOnce(&mut Self) -> Terminal) { - let prev = std::mem::replace(&mut self.current, wip); - let terminal = f(self); - let completed_wip = std::mem::replace(&mut self.current, prev); - self.completed.insert( - completed_wip.id, - BasicBlock { - kind: completed_wip.kind, - id: completed_wip.id, - instructions: completed_wip.instructions, - terminal, - preds: IndexSet::new(), - phis: Vec::new(), - }, - ); - } - - /// Like `enter_reserved`, but the closure returns a `Result<Terminal, CompilerDiagnostic>`. - pub fn try_enter_reserved( - &mut self, - wip: WipBlock, - f: impl FnOnce(&mut Self) -> Result<Terminal, CompilerDiagnostic>, - ) -> Result<(), CompilerDiagnostic> { - let prev = std::mem::replace(&mut self.current, wip); - let terminal = f(self)?; - let completed_wip = std::mem::replace(&mut self.current, prev); - self.completed.insert( - completed_wip.id, - BasicBlock { - kind: completed_wip.kind, - id: completed_wip.id, - instructions: completed_wip.instructions, - terminal, - preds: IndexSet::new(), - phis: Vec::new(), - }, - ); - Ok(()) - } - - /// Create a new block, set it as current, run the closure to populate it - /// and obtain its terminal, complete the block, and restore the previous - /// current block. Returns the new block's BlockId. - pub fn enter( - &mut self, - kind: BlockKind, - f: impl FnOnce(&mut Self, BlockId) -> Terminal, - ) -> BlockId { - let wip = self.reserve(kind); - let wip_id = wip.id; - self.enter_reserved(wip, |this| f(this, wip_id)); - wip_id - } - - /// Like `enter`, but the closure returns a `Result<Terminal, CompilerDiagnostic>`. - pub fn try_enter( - &mut self, - kind: BlockKind, - f: impl FnOnce(&mut Self, BlockId) -> Result<Terminal, CompilerDiagnostic>, - ) -> Result<BlockId, CompilerDiagnostic> { - let wip = self.reserve(kind); - let wip_id = wip.id; - self.try_enter_reserved(wip, |this| f(this, wip_id))?; - Ok(wip_id) - } - - /// Push an exception handler, run the closure, then pop the handler. - pub fn enter_try_catch(&mut self, handler: BlockId, f: impl FnOnce(&mut Self)) { - self.exception_handler_stack.push(handler); - f(self); - self.exception_handler_stack.pop(); - } - - /// Like `enter_try_catch`, but the closure returns a `Result`. - pub fn try_enter_try_catch( - &mut self, - handler: BlockId, - f: impl FnOnce(&mut Self) -> Result<(), CompilerDiagnostic>, - ) -> Result<(), CompilerDiagnostic> { - self.exception_handler_stack.push(handler); - let result = f(self); - self.exception_handler_stack.pop(); - result - } - - /// Return the top of the exception handler stack, or None. - pub fn resolve_throw_handler(&self) -> Option<BlockId> { - self.exception_handler_stack.last().copied() - } - - /// Push a Loop scope, run the closure, pop and verify. - pub fn loop_scope<T>( - &mut self, - label: Option<String>, - continue_block: BlockId, - break_block: BlockId, - f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>, - ) -> Result<T, CompilerDiagnostic> { - self.scopes.push(Scope::Loop { - label: label.clone(), - continue_block, - break_block, - }); - let value = f(self)?; - let last = self - .scopes - .pop() - .expect("Mismatched loop scope: stack empty"); - match &last { - Scope::Loop { - label: l, - continue_block: c, - break_block: b, - } => { - assert!( - *l == label && *c == continue_block && *b == break_block, - "Mismatched loop scope" - ); - } - _ => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Mismatched loop scope: expected Loop, got other", - None, - )); - } - } - Ok(value) - } - - /// Push a Label scope, run the closure, pop and verify. - pub fn label_scope<T>( - &mut self, - label: String, - break_block: BlockId, - f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>, - ) -> Result<T, CompilerDiagnostic> { - self.scopes.push(Scope::Label { - label: label.clone(), - break_block, - }); - let value = f(self)?; - let last = self - .scopes - .pop() - .expect("Mismatched label scope: stack empty"); - match &last { - Scope::Label { - label: l, - break_block: b, - } => { - assert!(*l == label && *b == break_block, "Mismatched label scope"); - } - _ => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Mismatched label scope: expected Label, got other", - None, - )); - } - } - Ok(value) - } - - /// Push a Switch scope, run the closure, pop and verify. - pub fn switch_scope<T>( - &mut self, - label: Option<String>, - break_block: BlockId, - f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>, - ) -> Result<T, CompilerDiagnostic> { - self.scopes.push(Scope::Switch { - label: label.clone(), - break_block, - }); - let value = f(self)?; - let last = self - .scopes - .pop() - .expect("Mismatched switch scope: stack empty"); - match &last { - Scope::Switch { - label: l, - break_block: b, - } => { - assert!(*l == label && *b == break_block, "Mismatched switch scope"); - } - _ => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Mismatched switch scope: expected Switch, got other", - None, - )); - } - } - Ok(value) - } - - /// Look up the break target for the given label (or the innermost - /// loop/switch if label is None). - pub fn lookup_break(&self, label: Option<&str>) -> Result<BlockId, CompilerDiagnostic> { - for scope in self.scopes.iter().rev() { - match scope { - Scope::Loop { .. } | Scope::Switch { .. } if label.is_none() => { - return Ok(scope.break_block()); - } - _ if label.is_some() && scope.label() == label => { - return Ok(scope.break_block()); - } - _ => continue, - } - } - Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected a loop or switch to be in scope for break", - None, - )) - } - - /// Look up the continue target for the given label (or the innermost - /// loop if label is None). Only loops support continue. - pub fn lookup_continue(&self, label: Option<&str>) -> Result<BlockId, CompilerDiagnostic> { - for scope in self.scopes.iter().rev() { - match scope { - Scope::Loop { - label: scope_label, - continue_block, - .. - } => { - if label.is_none() || label == scope_label.as_deref() { - return Ok(*continue_block); - } - } - _ => { - if label.is_some() && scope.label() == label { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Continue may only refer to a labeled loop", - None, - )); - } - } - } - } - Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected a loop to be in scope for continue", - None, - )) - } - - /// Create a temporary identifier with a fresh id, returning its IdentifierId. - pub fn make_temporary(&mut self, loc: Option<SourceLocation>) -> IdentifierId { - let id = self.env.next_identifier_id(); - // Update the loc on the allocated identifier - self.env.identifiers[id.0 as usize].loc = loc; - id - } - - /// Set the source location for an identifier. - pub fn set_identifier_loc(&mut self, id: IdentifierId, loc: Option<SourceLocation>) { - self.env.identifiers[id.0 as usize].loc = loc; - } - - /// Record an error on the environment. - /// Returns `Err` for Invariant errors (matching TS throw behavior). - pub fn record_error(&mut self, error: CompilerErrorDetail) -> Result<(), CompilerError> { - self.env.record_error(error) - } - - /// Record a diagnostic on the environment. - pub fn record_diagnostic(&mut self, diagnostic: CompilerDiagnostic) { - self.env.record_diagnostic(diagnostic); - } - - /// Check if a name has a local binding (non-module-level). - /// This is used for checking if fbt/fbs JSX tags are local bindings - /// (which is not supported). - pub fn has_local_binding(&self, name: &str) -> bool { - if let Some(binding) = self - .scope_info - .find_binding_in_descendants(name, self.component_scope) - { - return binding.scope != self.scope_info.program_scope; - } - false - } - - /// Return the kind of the current block. - pub fn current_block_kind(&self) -> BlockKind { - self.current.kind - } - - /// Construct the final HIR and instruction table from the completed blocks. - /// - /// Performs these post-build passes: - /// 1. Reverse-postorder sort + unreachable block removal - /// 2. Check for unreachable blocks containing FunctionExpression instructions - /// 3. Remove unreachable for-loop updates - /// 4. Remove dead do-while statements - /// 5. Remove unnecessary try-catch - /// 6. Number all instructions and terminals - /// 7. Mark predecessor blocks - pub fn build( - mut self, - ) -> Result< - ( - HIR, - Vec<Instruction>, - IndexMap<String, BindingId>, - IndexMap<BindingId, IdentifierId>, - ), - CompilerError, - > { - let mut hir = HIR { - blocks: std::mem::take(&mut self.completed), - entry: self.entry, - }; - - let mut instructions = std::mem::take(&mut self.instruction_table); - - let rpo_blocks = get_reverse_postordered_blocks(&hir, &instructions); - - // Check for unreachable blocks that contain FunctionExpression instructions. - // These could contain hoisted declarations that we can't safely remove. - for (id, block) in &hir.blocks { - if !rpo_blocks.contains_key(id) { - let has_function_expr = block.instructions.iter().any(|&instr_id| { - matches!( - instructions[instr_id.0 as usize].value, - InstructionValue::FunctionExpression { .. } - ) - }); - if has_function_expr { - let loc = block - .instructions - .first() - .and_then(|&i| instructions[i.0 as usize].loc.clone()) - .or_else(|| block.terminal.loc().copied()); - self.env.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "Support functions with unreachable code that may contain hoisted declarations".to_string(), - description: None, - loc, - suggestions: None, - })?; - } - } - } - - hir.blocks = rpo_blocks; - - remove_unreachable_for_updates(&mut hir); - remove_dead_do_while_statements(&mut hir); - remove_unnecessary_try_catch(&mut hir); - mark_instruction_ids(&mut hir, &mut instructions); - mark_predecessors(&mut hir); - - let used_names = self.used_names; - let bindings = self.bindings; - Ok((hir, instructions, used_names, bindings)) - } - - // ----------------------------------------------------------------------- - // M3: Binding resolution methods - // ----------------------------------------------------------------------- - - /// Map a BindingId to an HIR IdentifierId. - /// - /// On first encounter, creates a new Identifier with the given name and a fresh id. - /// On subsequent encounters, returns the cached IdentifierId. - /// Handles name collisions by appending `_0`, `_1`, etc. - /// - /// Records errors for variables named 'fbt' or 'this'. - pub fn resolve_binding( - &mut self, - name: &str, - binding_id: BindingId, - ) -> Result<IdentifierId, CompilerError> { - self.resolve_binding_with_loc(name, binding_id, None) - } - - /// Map a BindingId to an HIR IdentifierId, with an optional source location. - pub fn resolve_binding_with_loc( - &mut self, - name: &str, - binding_id: BindingId, - loc: Option<SourceLocation>, - ) -> Result<IdentifierId, CompilerError> { - // Check for unsupported names BEFORE the cache check. - // In TS, resolveBinding records fbt errors when node.name === 'fbt'. After a name collision - // causes a rename (e.g., "fbt" -> "fbt_0"), TS's scope.rename changes the AST node's name, - // preventing subsequent fbt error recording. We simulate this by checking whether the - // resolved name for this binding is still "fbt" (not renamed to "fbt_0" etc.). - if name == "fbt" { - // Check if this binding was previously resolved to a renamed version - let should_record_fbt_error = - if let Some(&identifier_id) = self.bindings.get(&binding_id) { - // Already resolved - check if the resolved name is still "fbt" - match &self.env.identifiers[identifier_id.0 as usize].name { - Some(IdentifierName::Named(resolved_name)) => resolved_name == "fbt", - _ => false, - } - } else { - // First resolution - always record - true - }; - if should_record_fbt_error { - let error_loc = self.scope_info.bindings[binding_id.0 as usize] - .declaration_node_id - .and_then(|nid| self.get_identifier_loc(nid)) - .or_else(|| loc.clone()); - self.env.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "Support local variables named `fbt`".to_string(), - description: Some( - "Local variables named `fbt` may conflict with the fbt plugin and are not yet supported".to_string(), - ), - loc: error_loc, - suggestions: None, - })?; - } - } - - // If we've already resolved this binding, return the cached IdentifierId - if let Some(&identifier_id) = self.bindings.get(&binding_id) { - return Ok(identifier_id); - } - - if is_always_reserved_word(name) { - // Match TS behavior: makeIdentifierName throws for reserved words. - return Err(CompilerError::from(reserved_identifier_diagnostic(name))); - } - - // Find a unique name: start with the original name, then try name_0, name_1, ... - let mut candidate = name.to_string(); - let mut index = 0u32; - loop { - if let Some(&existing_binding_id) = self.used_names.get(&candidate) { - if existing_binding_id == binding_id { - // Same binding, use this name - break; - } - // Name collision with a different binding, try the next suffix - candidate = format!("{}_{}", name, index); - index += 1; - } else { - // Name is available - break; - } - } - - // Record rename if the candidate differs from the original name - if candidate != name { - let binding = &self.scope_info.bindings[binding_id.0 as usize]; - if let Some(decl_start) = binding.declaration_start { - self.env - .renames - .push(react_compiler_hir::environment::BindingRename { - original: name.to_string(), - renamed: candidate.clone(), - declaration_start: decl_start, - }); - } - } - - // Allocate identifier in the arena - let id = self.env.next_identifier_id(); - // Update the name and loc on the allocated identifier - self.env.identifiers[id.0 as usize].name = Some(IdentifierName::Named(candidate.clone())); - // Prefer the binding's declaration loc over the reference loc. - // This matches TS behavior where Babel's resolveBinding returns the - // binding identifier's original loc (the declaration site). - let binding = &self.scope_info.bindings[binding_id.0 as usize]; - let decl_loc = binding - .declaration_node_id - .and_then(|nid| self.get_identifier_loc(nid)); - if let Some(ref dl) = decl_loc { - self.env.identifiers[id.0 as usize].loc = Some(dl.clone()); - } else if let Some(ref loc) = loc { - self.env.identifiers[id.0 as usize].loc = Some(loc.clone()); - } - - self.used_names.insert(candidate, binding_id); - self.bindings.insert(binding_id, id); - Ok(id) - } - - /// Set the loc on an identifier to the declaration-site loc. - /// This overrides any previously-set loc (which may have come from a reference site). - pub fn set_identifier_declaration_loc( - &mut self, - id: IdentifierId, - loc: &Option<SourceLocation>, - ) { - if let Some(loc_val) = loc { - self.env.identifiers[id.0 as usize].loc = Some(loc_val.clone()); - } - } - - /// Resolve an identifier reference to a VariableBinding. - /// - /// Uses ScopeInfo to determine whether the reference is: - /// - Global (no binding found) - /// - ImportDefault, ImportSpecifier, ImportNamespace (program-scope import binding) - /// - ModuleLocal (program-scope non-import binding) - /// - Identifier (local binding, resolved via resolve_binding) - pub fn resolve_identifier( - &mut self, - name: &str, - _start_offset: u32, - loc: Option<SourceLocation>, - node_id: Option<u32>, - ) -> Result<VariableBinding, CompilerError> { - let binding_data = self.scope_info.resolve_reference_for_node(node_id); - - match binding_data { - None => { - // No binding found: this is a global - Ok(VariableBinding::Global { - name: name.to_string(), - }) - } - Some(binding) => { - // Treat type-only declarations as globals so the compiler - // doesn't try to create/initialize HIR bindings for them. - // TSEnumDeclaration is included because enums inside function - // bodies are lowered as UnsupportedNode and their binding - // is never initialized in HIR. - if matches!( - binding.declaration_type.as_str(), - "TSTypeAliasDeclaration" - | "TSInterfaceDeclaration" - | "TSEnumDeclaration" - | "TSModuleDeclaration" - ) { - return Ok(VariableBinding::Global { - name: name.to_string(), - }); - } - if binding.scope == self.scope_info.program_scope { - // Module-level binding: check import info - Ok(match &binding.import { - Some(import_info) => match import_info.kind { - ImportBindingKind::Default => VariableBinding::ImportDefault { - name: name.to_string(), - module: import_info.source.clone(), - }, - ImportBindingKind::Named => VariableBinding::ImportSpecifier { - name: name.to_string(), - module: import_info.source.clone(), - imported: import_info - .imported - .clone() - .unwrap_or_else(|| name.to_string()), - }, - ImportBindingKind::Namespace => VariableBinding::ImportNamespace { - name: name.to_string(), - module: import_info.source.clone(), - }, - }, - None => VariableBinding::ModuleLocal { - name: name.to_string(), - }, - }) - } else if !self.is_scope_within_compiled_function(binding.scope) { - Ok(VariableBinding::ModuleLocal { - name: name.to_string(), - }) - } else { - let binding_id = binding.id; - let binding_kind = crate::convert_binding_kind(&binding.kind); - let identifier_id = self.resolve_binding_with_loc(name, binding_id, loc)?; - Ok(VariableBinding::Identifier { - identifier: identifier_id, - binding_kind, - }) - } - } - } - } - - /// Check if an identifier reference resolves to a context identifier. - /// - /// A context identifier is a variable declared in an ancestor scope of the - /// current function's scope, but NOT in the program scope itself and NOT - /// in the function's own scope. These are "captured" variables from an - /// enclosing function. - pub fn is_context_identifier( - &self, - _name: &str, - _start_offset: u32, - node_id: Option<u32>, - ) -> bool { - let binding = self.scope_info.resolve_reference_for_node(node_id); - - match binding { - None => false, - Some(binding_data) => { - if binding_data.scope == self.scope_info.program_scope { - return false; - } - self.context_identifiers.contains(&binding_data.id) - } - } - } - - /// Like `is_context_identifier`, for callers that already resolved a - /// BindingId instead of going through a reference node. - pub fn is_context_binding(&self, binding_id: BindingId) -> bool { - let binding = &self.scope_info.bindings[binding_id.0 as usize]; - if binding.scope == self.scope_info.program_scope { - return false; - } - self.context_identifiers.contains(&binding_id) - } - - /// Resolve the binding for a function declaration's id the way TS does: - /// Babel's `path.scope.getBinding(name)` starts at the function's OWN - /// scope, so a body-level local (or parameter) that shadows the function's - /// name resolves to that inner binding rather than to the function's - /// hoisted binding in the parent scope. - /// - /// Babel's `scope.rename` re-keys a scope's bindings when the TS builder - /// renames a shadowed binding (e.g. `init` -> `init_0`), so a binding only - /// matches if its *current* name — the resolved HIR identifier name once - /// resolved — still equals `name`. A binding renamed *to* `name` overwrites - /// the original key in Babel and takes precedence over an unresolved - /// binding with that original name. - /// - /// Returns None when the walk resolves outside the compiled function - /// (degraded scope info); callers should fall back to node-based - /// resolution in that case. - pub fn get_function_declaration_binding( - &self, - function_scope: ScopeId, - name: &str, - ) -> Option<BindingId> { - // None = unresolved binding; Some(matches) = resolved, current name comparison - let resolved_name_matches = |bid: BindingId| -> Option<bool> { - let &identifier_id = self.bindings.get(&bid)?; - match &self.env.identifiers[identifier_id.0 as usize].name { - Some(IdentifierName::Named(n)) => Some(n == name), - _ => Some(false), - } - }; - let mut current = Some(function_scope); - while let Some(id) = current { - let scope = &self.scope_info.scopes[id.0 as usize]; - let mut found = scope - .bindings - .values() - .copied() - .find(|&bid| resolved_name_matches(bid) == Some(true)); - if found.is_none() { - if let Some(&bid) = scope.bindings.get(name) { - // Skip bindings that were renamed away from `name`. - if resolved_name_matches(bid) != Some(false) { - found = Some(bid); - } - } - } - if let Some(bid) = found { - let binding_scope = self.scope_info.bindings[bid.0 as usize].scope; - if !self.is_scope_within_compiled_function(binding_scope) { - return None; - } - return Some(bid); - } - current = scope.parent; - } - None - } -} - -// --------------------------------------------------------------------------- -// Post-build helper functions -// --------------------------------------------------------------------------- - -/// Compute a reverse-postorder of blocks reachable from the entry. -/// -/// Visits successors in reverse order so that when the postorder list is -/// reversed, sibling edges appear in program order. -/// -/// Blocks not reachable through successors are removed. Blocks that are -/// only reachable as fallthroughs (not through real successor edges) are -/// replaced with empty blocks that have an Unreachable terminal. -pub fn get_reverse_postordered_blocks( - hir: &HIR, - _instructions: &[Instruction], -) -> IndexMap<BlockId, BasicBlock> { - let mut visited: IndexSet<BlockId> = IndexSet::new(); - let mut used: IndexSet<BlockId> = IndexSet::new(); - let mut used_fallthroughs: IndexSet<BlockId> = IndexSet::new(); - let mut postorder: Vec<BlockId> = Vec::new(); - - fn visit( - hir: &HIR, - block_id: BlockId, - is_used: bool, - visited: &mut IndexSet<BlockId>, - used: &mut IndexSet<BlockId>, - used_fallthroughs: &mut IndexSet<BlockId>, - postorder: &mut Vec<BlockId>, - ) { - let was_used = used.contains(&block_id); - let was_visited = visited.contains(&block_id); - visited.insert(block_id); - if is_used { - used.insert(block_id); - } - if was_visited && (was_used || !is_used) { - return; - } - - let block = hir - .blocks - .get(&block_id) - .unwrap_or_else(|| panic!("[HIRBuilder] expected block {:?} to exist", block_id)); - - // Visit successors in reverse order so that when we reverse the - // postorder list, sibling edges come out in program order. - let mut successors = each_terminal_successor(&block.terminal); - successors.reverse(); - - let fallthrough = terminal_fallthrough(&block.terminal); - - // Visit fallthrough first (marking as not-yet-used) to ensure its - // block ID is emitted in the correct position. - if let Some(ft) = fallthrough { - if is_used { - used_fallthroughs.insert(ft); - } - visit(hir, ft, false, visited, used, used_fallthroughs, postorder); - } - for successor in successors { - visit( - hir, - successor, - is_used, - visited, - used, - used_fallthroughs, - postorder, - ); - } - - if !was_visited { - postorder.push(block_id); - } - } - - visit( - hir, - hir.entry, - true, - &mut visited, - &mut used, - &mut used_fallthroughs, - &mut postorder, - ); - - let mut blocks = IndexMap::new(); - for block_id in postorder.into_iter().rev() { - let block = hir.blocks.get(&block_id).unwrap(); - if used.contains(&block_id) { - blocks.insert(block_id, block.clone()); - } else if used_fallthroughs.contains(&block_id) { - blocks.insert( - block_id, - BasicBlock { - kind: block.kind, - id: block_id, - instructions: Vec::new(), - terminal: Terminal::Unreachable { - id: block.terminal.evaluation_order(), - loc: block.terminal.loc().copied(), - }, - preds: block.preds.clone(), - phis: Vec::new(), - }, - ); - } - // otherwise this block is unreachable and is dropped - } - - blocks -} - -/// For each block with a `For` terminal whose update block is not in the -/// blocks map, set update to None. -pub fn remove_unreachable_for_updates(hir: &mut HIR) { - let block_ids: IndexSet<BlockId> = hir.blocks.keys().copied().collect(); - for block in hir.blocks.values_mut() { - if let Terminal::For { update, .. } = &mut block.terminal { - if let Some(update_id) = *update { - if !block_ids.contains(&update_id) { - *update = None; - } - } - } - } -} - -/// For each block with a `DoWhile` terminal whose test block is not in -/// the blocks map, replace the terminal with a Goto to the loop block. -pub fn remove_dead_do_while_statements(hir: &mut HIR) { - let block_ids: IndexSet<BlockId> = hir.blocks.keys().copied().collect(); - for block in hir.blocks.values_mut() { - let should_replace = if let Terminal::DoWhile { test, .. } = &block.terminal { - !block_ids.contains(test) - } else { - false - }; - if should_replace { - if let Terminal::DoWhile { - loop_block, - id, - loc, - .. - } = std::mem::replace( - &mut block.terminal, - Terminal::Unreachable { - id: EvaluationOrder(0), - loc: None, - }, - ) { - block.terminal = Terminal::Goto { - block: loop_block, - variant: GotoVariant::Break, - id, - loc, - }; - } - } - } -} - -/// For each block with a `Try` terminal whose handler block is not in -/// the blocks map, replace the terminal with a Goto to the try block. -/// -/// Also cleans up the fallthrough block's predecessors if the handler -/// was the only path to it. -pub fn remove_unnecessary_try_catch(hir: &mut HIR) { - let block_ids: IndexSet<BlockId> = hir.blocks.keys().copied().collect(); - - // Collect the blocks that need replacement and their associated data - let replacements: Vec<(BlockId, BlockId, BlockId, BlockId, Option<SourceLocation>)> = hir - .blocks - .iter() - .filter_map(|(&block_id, block)| { - if let Terminal::Try { - block: try_block, - handler, - fallthrough, - loc, - .. - } = &block.terminal - { - if !block_ids.contains(handler) { - return Some((block_id, *try_block, *handler, *fallthrough, loc.clone())); - } - } - None - }) - .collect(); - - for (block_id, try_block, handler_id, fallthrough_id, loc) in replacements { - // Replace the terminal - if let Some(block) = hir.blocks.get_mut(&block_id) { - block.terminal = Terminal::Goto { - block: try_block, - id: EvaluationOrder(0), - loc, - variant: GotoVariant::Break, - }; - } - - // Clean up fallthrough predecessor info - if let Some(fallthrough) = hir.blocks.get_mut(&fallthrough_id) { - if fallthrough.preds.len() == 1 && fallthrough.preds.contains(&handler_id) { - // The handler was the only predecessor: remove the fallthrough block - hir.blocks.shift_remove(&fallthrough_id); - } else { - fallthrough.preds.shift_remove(&handler_id); - } - } - } -} - -/// Sequentially number all instructions and terminals starting from 1. -pub fn mark_instruction_ids(hir: &mut HIR, instructions: &mut [Instruction]) { - let mut order: u32 = 0; - for block in hir.blocks.values_mut() { - for &instr_id in &block.instructions { - order += 1; - instructions[instr_id.0 as usize].id = EvaluationOrder(order); - } - order += 1; - block.terminal.set_evaluation_order(EvaluationOrder(order)); - } -} - -/// DFS from entry, for each successor add the predecessor's id to -/// the successor's preds set. -/// -/// Note: This only visits direct successors (via `each_terminal_successor`), -/// not fallthrough blocks. Fallthrough blocks are reached indirectly via -/// Goto terminals from within branching blocks, matching the TypeScript -/// `markPredecessors` behavior. -pub fn mark_predecessors(hir: &mut HIR) { - // Clear all preds first - for block in hir.blocks.values_mut() { - block.preds.clear(); - } - - let mut visited: IndexSet<BlockId> = IndexSet::new(); - - fn visit( - hir: &mut HIR, - block_id: BlockId, - prev_block_id: Option<BlockId>, - visited: &mut IndexSet<BlockId>, - ) { - // Add predecessor - if let Some(prev_id) = prev_block_id { - if let Some(block) = hir.blocks.get_mut(&block_id) { - block.preds.insert(prev_id); - } else { - return; - } - } - - if visited.contains(&block_id) { - return; - } - visited.insert(block_id); - - // Get successors before mutating - let successors = if let Some(block) = hir.blocks.get(&block_id) { - each_terminal_successor(&block.terminal) - } else { - return; - }; - - for successor in successors { - visit(hir, successor, Some(block_id), visited); - } - } - - visit(hir, hir.entry, None, &mut visited); -} - -// --------------------------------------------------------------------------- -// Public helper functions -// --------------------------------------------------------------------------- - -/// Create a temporary Place with a fresh identifier allocated in the arena. -pub fn create_temporary_place(env: &mut Environment, loc: Option<SourceLocation>) -> Place { - let id = env.next_identifier_id(); - // Update the loc on the allocated identifier - env.identifiers[id.0 as usize].loc = loc; - Place { - identifier: id, - reactive: false, - effect: Effect::Unknown, - loc: None, - } -} diff --git a/compiler/crates/react_compiler_lowering/src/identifier_loc_index.rs b/compiler/crates/react_compiler_lowering/src/identifier_loc_index.rs deleted file mode 100644 index 4a8b5e024675..000000000000 --- a/compiler/crates/react_compiler_lowering/src/identifier_loc_index.rs +++ /dev/null @@ -1,330 +0,0 @@ -//! Builds an index mapping identifier node-IDs to source locations. -//! -//! Walks the function's AST to collect `(node_id, start, SourceLocation, is_jsx)` -//! for every Identifier and JSXIdentifier node. Keyed by node_id for identity -//! lookups; each entry also stores `start` (byte offset) for range-containment -//! checks in `gather_captured_context`. - -use std::collections::HashMap; - -use react_compiler_ast::expressions::*; -use react_compiler_ast::jsx::JSXIdentifier; -use react_compiler_ast::jsx::JSXOpeningElement; -use react_compiler_ast::scope::ScopeId; -use react_compiler_ast::scope::ScopeInfo; -use react_compiler_ast::statements::FunctionDeclaration; -use react_compiler_ast::visitor::AstWalker; -use react_compiler_ast::visitor::Visitor; -use react_compiler_hir::SourceLocation; - -use crate::FunctionNode; - -/// Source location and whether the identifier is a JSXIdentifier. -pub struct IdentifierLocEntry { - /// The byte offset of the identifier (base.start). Stored here so that - /// callers iterating by node_id can still do position-range containment - /// checks without a separate bridge map. - pub start: u32, - pub loc: SourceLocation, - pub is_jsx: bool, - /// For JSX identifiers that are the root name of a JSXOpeningElement, - /// stores the JSXOpeningElement's loc (which spans the full tag). - pub opening_element_loc: Option<SourceLocation>, - /// True if this identifier is the name of a function/class declaration - /// (not an expression reference). Used by `gather_captured_context` to - /// skip non-expression positions, matching the TS behavior where the - /// Expression visitor doesn't visit declaration names. - pub is_declaration_name: bool, - /// True if this identifier sits inside a type annotation subtree - /// (TypeAnnotation/TSTypeAnnotation/TypeAlias/TSTypeAliasDeclaration). - /// `gather_captured_context` skips these to match the TS - /// gatherCapturedContext traverse, which skips those subtrees; the - /// hoisting analysis and FindContextIdentifiers do NOT skip them in TS. - pub in_type_annotation: bool, -} - -/// Index mapping node_id → IdentifierLocEntry for all Identifier -/// and JSXIdentifier nodes in a function's AST. -pub type IdentifierLocIndex = HashMap<u32, IdentifierLocEntry>; - -struct IdentifierLocVisitor { - index: IdentifierLocIndex, - /// Tracks the current JSXOpeningElement's loc while walking its name. - current_opening_element_loc: Option<SourceLocation>, -} - -fn convert_loc(loc: &react_compiler_ast::common::SourceLocation) -> SourceLocation { - SourceLocation { - start: react_compiler_hir::Position { - line: loc.start.line, - column: loc.start.column, - index: loc.start.index, - }, - end: react_compiler_hir::Position { - line: loc.end.line, - column: loc.end.column, - index: loc.end.index, - }, - } -} - -impl IdentifierLocVisitor { - fn insert_identifier(&mut self, node: &Identifier, is_declaration_name: bool) { - if let (Some(nid), Some(start), Some(loc)) = - (node.base.node_id, node.base.start, &node.base.loc) - { - self.index.insert( - nid, - IdentifierLocEntry { - start, - loc: convert_loc(loc), - is_jsx: false, - opening_element_loc: None, - is_declaration_name, - in_type_annotation: false, - }, - ); - } - } - - /// Recursively walk a serde_json::Value tree to find and index all Identifier - /// and JSXIdentifier nodes. Used for class bodies which are stored as untyped - /// JSON and not walked by the typed AstWalker. This matches the TS behavior - /// where gatherCapturedContext's Babel traverse walks into class bodies. - /// - /// `in_annotation` is true once the walk has descended through a type - /// annotation container node; identifiers found there are flagged so - /// `gather_captured_context` can mirror TS's TypeAnnotation subtree skip. - fn walk_json_for_identifiers(&mut self, value: &serde_json::Value, in_annotation: bool) { - match value { - serde_json::Value::Object(obj) => { - let node_in_annotation = in_annotation - || matches!( - obj.get("type").and_then(|t| t.as_str()), - Some( - "TypeAnnotation" - | "TSTypeAnnotation" - | "TypeAlias" - | "TSTypeAliasDeclaration" - ) - ); - if let Some(serde_json::Value::String(ty)) = obj.get("type") { - if ty == "Identifier" || ty == "JSXIdentifier" { - if let (Some(nid), Some(start)) = ( - obj.get("_nodeId").and_then(|s| s.as_u64()), - obj.get("start").and_then(|s| s.as_u64()), - ) { - if let Some(loc) = Self::extract_loc_from_json(obj) { - let is_jsx = ty == "JSXIdentifier"; - self.index.entry(nid as u32).or_insert(IdentifierLocEntry { - start: start as u32, - loc, - is_jsx, - opening_element_loc: None, - is_declaration_name: false, - in_type_annotation: node_in_annotation, - }); - } - } - } - } - for (_, v) in obj { - self.walk_json_for_identifiers(v, node_in_annotation); - } - } - serde_json::Value::Array(arr) => { - for v in arr { - self.walk_json_for_identifiers(v, in_annotation); - } - } - _ => {} - } - } - - fn extract_loc_from_json( - obj: &serde_json::Map<String, serde_json::Value>, - ) -> Option<SourceLocation> { - let loc = obj.get("loc")?.as_object()?; - let start = loc.get("start")?.as_object()?; - let end = loc.get("end")?.as_object()?; - Some(SourceLocation { - start: react_compiler_hir::Position { - line: start.get("line")?.as_u64()? as u32, - column: start.get("column")?.as_u64()? as u32, - index: start - .get("index") - .and_then(|i| i.as_u64()) - .map(|i| i as u32), - }, - end: react_compiler_hir::Position { - line: end.get("line")?.as_u64()? as u32, - column: end.get("column")?.as_u64()? as u32, - index: end.get("index").and_then(|i| i.as_u64()).map(|i| i as u32), - }, - }) - } -} - -impl<'ast> Visitor<'ast> for IdentifierLocVisitor { - fn enter_identifier(&mut self, node: &'ast Identifier, _scope_stack: &[ScopeId]) { - self.insert_identifier(node, false); - } - - fn enter_jsx_identifier(&mut self, node: &'ast JSXIdentifier, _scope_stack: &[ScopeId]) { - if let (Some(nid), Some(start), Some(loc)) = - (node.base.node_id, node.base.start, &node.base.loc) - { - self.index.insert( - nid, - IdentifierLocEntry { - start, - loc: convert_loc(loc), - is_jsx: true, - opening_element_loc: self.current_opening_element_loc.clone(), - is_declaration_name: false, - in_type_annotation: false, - }, - ); - } - } - - fn enter_jsx_opening_element( - &mut self, - node: &'ast JSXOpeningElement, - _scope_stack: &[ScopeId], - ) { - self.current_opening_element_loc = node.base.loc.as_ref().map(|loc| convert_loc(loc)); - } - - fn leave_jsx_opening_element( - &mut self, - _node: &'ast JSXOpeningElement, - _scope_stack: &[ScopeId], - ) { - self.current_opening_element_loc = None; - } - - // Visit function/class declaration and expression name identifiers, - // which are not walked by the generic walker (to avoid affecting - // other Visitor consumers like find_context_identifiers). - fn enter_function_declaration( - &mut self, - node: &'ast FunctionDeclaration, - _scope_stack: &[ScopeId], - ) { - if let Some(id) = &node.id { - self.insert_identifier(id, true); - } - } - - fn enter_function_expression( - &mut self, - node: &'ast FunctionExpression, - _scope_stack: &[ScopeId], - ) { - if let Some(id) = &node.id { - self.insert_identifier(id, true); - } - } - - fn enter_class_declaration( - &mut self, - node: &'ast react_compiler_ast::statements::ClassDeclaration, - _scope_stack: &[ScopeId], - ) { - if let Some(id) = &node.id { - self.insert_identifier(id, true); - } - // Walk class body JSON to index identifiers inside class methods. - // The typed AstWalker skips class bodies (stored as Vec<serde_json::Value>), - // but gatherCapturedContext in TS traverses them via Babel's traverse. - for member in &node.body.body { - self.walk_json_for_identifiers(&member.parse_value(), false); - } - } - - fn enter_class_expression( - &mut self, - node: &'ast react_compiler_ast::expressions::ClassExpression, - _scope_stack: &[ScopeId], - ) { - if let Some(id) = &node.id { - self.insert_identifier(id, true); - } - // Walk class body JSON to index identifiers inside class methods - for member in &node.body.body { - self.walk_json_for_identifiers(&member.parse_value(), false); - } - } -} - -/// Build an index of all Identifier and JSXIdentifier positions in a function's AST. -pub fn build_identifier_loc_index( - func: &FunctionNode<'_>, - scope_info: &ScopeInfo, -) -> IdentifierLocIndex { - let func_scope = scope_info - .resolve_scope_for_node(func.node_id()) - .unwrap_or(scope_info.program_scope); - - let mut visitor = IdentifierLocVisitor { - index: HashMap::new(), - current_opening_element_loc: None, - }; - let mut walker = AstWalker::with_initial_scope(scope_info, func_scope); - - // Visit the top-level function's own name identifier (if any), - // since the walker only walks params + body, not the function node itself. - match func { - FunctionNode::FunctionDeclaration(d) => { - if let Some(id) = &d.id { - visitor.enter_identifier(id, &[]); - } - for param in &d.params { - walker.walk_pattern(&mut visitor, param); - } - walker.walk_block_statement(&mut visitor, &d.body); - } - FunctionNode::FunctionExpression(e) => { - if let Some(id) = &e.id { - visitor.enter_identifier(id, &[]); - } - for param in &e.params { - walker.walk_pattern(&mut visitor, param); - } - walker.walk_block_statement(&mut visitor, &e.body); - } - FunctionNode::ArrowFunctionExpression(a) => { - for param in &a.params { - walker.walk_pattern(&mut visitor, param); - } - match a.body.as_ref() { - ArrowFunctionBody::BlockStatement(block) => { - walker.walk_block_statement(&mut visitor, block); - } - ArrowFunctionBody::Expression(expr) => { - walker.walk_expression(&mut visitor, expr); - } - } - } - } - - // Walk type annotations that the AST walker skips. - // The walker skips TypeAlias, TSTypeAliasDeclaration, and similar statements, - // but Babel's isReferencedIdentifier() returns true for identifiers inside them - // (e.g., typeof x in `type T = ReturnType<typeof x>`). The TS compiler's - // FindContextIdentifiers includes these via its Identifier visitor. We match by - // serializing the function body to JSON and walking the full JSON tree. - // The walk_json_for_identifiers method uses entry().or_insert() so it won't - // overwrite entries already added by the typed walker above. - let body_json: Option<serde_json::Value> = match func { - FunctionNode::FunctionDeclaration(d) => serde_json::to_value(&d.body).ok(), - FunctionNode::FunctionExpression(e) => serde_json::to_value(&e.body).ok(), - FunctionNode::ArrowFunctionExpression(a) => serde_json::to_value(&a.body).ok(), - }; - if let Some(json) = body_json { - visitor.walk_json_for_identifiers(&json, false); - } - - visitor.index -} diff --git a/compiler/crates/react_compiler_lowering/src/lib.rs b/compiler/crates/react_compiler_lowering/src/lib.rs deleted file mode 100644 index de5554a6a128..000000000000 --- a/compiler/crates/react_compiler_lowering/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -pub mod build_hir; -pub mod find_context_identifiers; -pub mod hir_builder; -pub mod identifier_loc_index; - -use react_compiler_ast::expressions::ArrowFunctionExpression; -use react_compiler_ast::expressions::FunctionExpression; -use react_compiler_ast::statements::FunctionDeclaration; -use react_compiler_hir::BindingKind; - -/// Convert AST binding kind to HIR binding kind. -pub fn convert_binding_kind(kind: &react_compiler_ast::scope::BindingKind) -> BindingKind { - match kind { - react_compiler_ast::scope::BindingKind::Var => BindingKind::Var, - react_compiler_ast::scope::BindingKind::Let => BindingKind::Let, - react_compiler_ast::scope::BindingKind::Const => BindingKind::Const, - react_compiler_ast::scope::BindingKind::Param => BindingKind::Param, - react_compiler_ast::scope::BindingKind::Module => BindingKind::Module, - react_compiler_ast::scope::BindingKind::Hoisted => BindingKind::Hoisted, - react_compiler_ast::scope::BindingKind::Local => BindingKind::Local, - react_compiler_ast::scope::BindingKind::Unknown => BindingKind::Unknown, - } -} - -/// Represents a reference to a function AST node for lowering. -/// Analogous to TS's `NodePath<t.Function>` / `BabelFn`. -pub enum FunctionNode<'a> { - FunctionDeclaration(&'a FunctionDeclaration), - FunctionExpression(&'a FunctionExpression), - ArrowFunctionExpression(&'a ArrowFunctionExpression), -} - -impl<'a> FunctionNode<'a> { - /// Get the node_id of the function node. Panics if not set. - pub fn node_id(&self) -> Option<u32> { - match self { - FunctionNode::FunctionDeclaration(d) => d.base.node_id, - FunctionNode::FunctionExpression(e) => e.base.node_id, - FunctionNode::ArrowFunctionExpression(a) => a.base.node_id, - } - } -} - -// The main lower() function - delegates to build_hir -pub use build_hir::lower; -// Re-export post-build helper functions used by optimization passes -pub use hir_builder::{ - create_temporary_place, get_reverse_postordered_blocks, mark_instruction_ids, - mark_predecessors, remove_dead_do_while_statements, remove_unnecessary_try_catch, - remove_unreachable_for_updates, -}; -pub use react_compiler_hir::visitors::each_terminal_successor; -pub use react_compiler_hir::visitors::terminal_fallthrough; diff --git a/compiler/crates/react_compiler_lowering/tests/unknown_statement_lowering.rs b/compiler/crates/react_compiler_lowering/tests/unknown_statement_lowering.rs deleted file mode 100644 index 5901e79d2dc1..000000000000 --- a/compiler/crates/react_compiler_lowering/tests/unknown_statement_lowering.rs +++ /dev/null @@ -1,91 +0,0 @@ -use react_compiler_ast::scope::ScopeInfo; -use react_compiler_ast::statements::FunctionDeclaration; -use react_compiler_hir::InstructionValue; -use react_compiler_hir::environment::Environment; -use react_compiler_lowering::{FunctionNode, lower}; -use serde_json::json; - -/// An unknown statement inside a function body degrades like the other -/// unsupported-statement arms: an UnsupportedSyntax error is recorded and an -/// UnsupportedNode instruction carries the raw node verbatim. -#[test] -fn unknown_statement_in_function_body_records_bailout() { - let unknown_node = json!({ - "type": "TSFutureStatement", - "start": 40, - "end": 52, - "payload": { "type": "Identifier", "name": "x" } - }); - let func: FunctionDeclaration = serde_json::from_value(json!({ - "type": "FunctionDeclaration", - "start": 0, - "end": 60, - "id": { "type": "Identifier", "name": "useValue", "start": 9, "end": 17 }, - "generator": false, - "async": false, - "params": [], - "body": { - "type": "BlockStatement", - "start": 20, - "end": 60, - "body": [unknown_node.clone()], - "directives": [] - } - })) - .unwrap(); - - let scope_info: ScopeInfo = serde_json::from_value(json!({ - "scopes": [ - { "id": 0, "parent": null, "kind": "program", "bindings": { "useValue": 0 } }, - { "id": 1, "parent": 0, "kind": "function", "bindings": {} } - ], - "bindings": [ - { - "id": 0, - "name": "useValue", - "kind": "hoisted", - "scope": 0, - "declarationType": "FunctionDeclaration" - } - ], - "nodeToScope": { "0": 1 }, - "referenceToBinding": {}, - "programScope": 0 - })) - .unwrap(); - - let mut env = Environment::new(); - let result = lower( - &FunctionNode::FunctionDeclaration(&func), - None, - &scope_info, - &mut env, - ); - - assert!( - env.has_errors(), - "expected a recorded error, got result {result:?}" - ); - let rendered = format!("{:?}", env.errors()); - assert!( - rendered.contains("Unsupported statement kind 'TSFutureStatement'"), - "unexpected error payload: {rendered}" - ); - - let hir = result.expect("lowering degrades, it does not fail outright"); - let unsupported = hir - .instructions - .iter() - .find_map(|instr| match &instr.value { - InstructionValue::UnsupportedNode { - node_type, - original_node, - .. - } => Some((node_type.clone(), original_node.clone())), - _ => None, - }) - .expect("expected an UnsupportedNode instruction"); - - assert_eq!(unsupported.0.as_deref(), Some("TSFutureStatement")); - assert_eq!(unsupported.1, Some(unknown_node)); -} diff --git a/compiler/crates/react_compiler_optimization/Cargo.toml b/compiler/crates/react_compiler_optimization/Cargo.toml deleted file mode 100644 index bdbb4d527699..000000000000 --- a/compiler/crates/react_compiler_optimization/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "react_compiler_optimization" -version = "0.1.0" -edition = "2024" - -[dependencies] -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" } -react_compiler_hir = { path = "../react_compiler_hir" } -react_compiler_lowering = { path = "../react_compiler_lowering" } -react_compiler_ssa = { path = "../react_compiler_ssa" } -indexmap = "2" diff --git a/compiler/crates/react_compiler_optimization/src/constant_propagation.rs b/compiler/crates/react_compiler_optimization/src/constant_propagation.rs deleted file mode 100644 index 65165f7e73b1..000000000000 --- a/compiler/crates/react_compiler_optimization/src/constant_propagation.rs +++ /dev/null @@ -1,1103 +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. - -//! Constant propagation/folding pass. -//! -//! Applies Sparse Conditional Constant Propagation to the given function. -//! We use abstract interpretation to record known constant values for identifiers, -//! with lack of a value indicating that the identifier does not have a known -//! constant value. -//! -//! Instructions which can be compile-time evaluated *and* whose operands are known -//! constants are replaced with the resulting constant value. -//! -//! This pass also exploits SSA form, tracking constant values of local variables. -//! For example, in `let x = 4; let y = x + 1` we know that `x = 4` in the binary -//! expression and can replace it with `Constant 5`. -//! -//! This pass also visits conditionals (currently only IfTerminal) and can prune -//! unreachable branches when the condition is a known truthy/falsey constant. -//! The pass uses fixpoint iteration, looping until no additional updates can be -//! performed. -//! -//! Analogous to TS `Optimization/ConstantPropagation.ts`. - -use std::collections::HashMap; - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{ - BinaryOperator, BlockKind, FloatValue, FunctionId, GotoVariant, HirFunction, IdentifierId, - InstructionValue, NonLocalBinding, Phi, Place, PrimitiveValue, PropertyLiteral, SourceLocation, - Terminal, UnaryOperator, UpdateOperator, format_js_number, -}; -use react_compiler_lowering::{ - get_reverse_postordered_blocks, mark_instruction_ids, mark_predecessors, - remove_dead_do_while_statements, remove_unnecessary_try_catch, remove_unreachable_for_updates, -}; -use react_compiler_ssa::enter_ssa::placeholder_function; - -use crate::merge_consecutive_blocks::merge_consecutive_blocks; - -// ============================================================================= -// Constant type — mirrors TS `type Constant = Primitive | LoadGlobal` -// The loc is preserved so that when we replace an instruction value with the -// constant, we use the loc from the original definition site (matching TS). -// ============================================================================= - -#[derive(Debug, Clone)] -enum Constant { - Primitive { - value: PrimitiveValue, - loc: Option<SourceLocation>, - }, - LoadGlobal { - binding: NonLocalBinding, - loc: Option<SourceLocation>, - }, -} - -impl Constant { - fn into_instruction_value(self) -> InstructionValue { - match self { - Constant::Primitive { value, loc } => InstructionValue::Primitive { value, loc }, - Constant::LoadGlobal { binding, loc } => InstructionValue::LoadGlobal { binding, loc }, - } - } -} - -/// Map of known constant values. Uses HashMap (not IndexMap) since iteration -/// order does not affect correctness — this map is only used for lookups. -type Constants = HashMap<IdentifierId, Constant>; - -// ============================================================================= -// Public entry point -// ============================================================================= - -pub fn constant_propagation(func: &mut HirFunction, env: &mut Environment) { - let mut constants: Constants = HashMap::new(); - constant_propagation_impl(func, env, &mut constants); -} - -fn constant_propagation_impl( - func: &mut HirFunction, - env: &mut Environment, - constants: &mut Constants, -) { - loop { - let have_terminals_changed = apply_constant_propagation(func, env, constants); - if !have_terminals_changed { - break; - } - /* - * If terminals have changed then blocks may have become newly unreachable. - * Re-run minification of the graph (incl reordering instruction ids) - */ - func.body.blocks = get_reverse_postordered_blocks(&func.body, &func.instructions); - remove_unreachable_for_updates(&mut func.body); - remove_dead_do_while_statements(&mut func.body); - remove_unnecessary_try_catch(&mut func.body); - mark_instruction_ids(&mut func.body, &mut func.instructions); - mark_predecessors(&mut func.body); - - // Now that predecessors are updated, prune phi operands that can never be reached - for (_block_id, block) in func.body.blocks.iter_mut() { - for phi in &mut block.phis { - phi.operands - .retain(|pred, _operand| block.preds.contains(pred)); - } - } - - /* - * By removing some phi operands, there may be phis that were not previously - * redundant but now are - */ - react_compiler_ssa::eliminate_redundant_phi(func, env); - - /* - * Finally, merge together any blocks that are now guaranteed to execute - * consecutively - */ - merge_consecutive_blocks(func, &mut env.functions); - - // TODO: port assertConsistentIdentifiers(fn) and assertTerminalSuccessorsExist(fn) - // from TS HIR validation. These are debug assertions that verify structural - // invariants after the CFG cleanup helpers run. - } -} - -fn apply_constant_propagation( - func: &mut HirFunction, - env: &mut Environment, - constants: &mut Constants, -) -> bool { - let mut has_changes = false; - - let block_ids: Vec<_> = func.body.blocks.keys().copied().collect(); - for block_id in block_ids { - let block = &func.body.blocks[&block_id]; - - // Initialize phi values if all operands have the same known constant value - let phi_updates: Vec<(IdentifierId, Constant)> = block - .phis - .iter() - .filter_map(|phi| { - let value = evaluate_phi(phi, constants)?; - Some((phi.place.identifier, value)) - }) - .collect(); - for (id, value) in phi_updates { - constants.insert(id, value); - } - - let block = &func.body.blocks[&block_id]; - let instr_ids = block.instructions.clone(); - let block_kind = block.kind; - let instr_count = instr_ids.len(); - - for (i, instr_id) in instr_ids.iter().enumerate() { - if block_kind == BlockKind::Sequence && i == instr_count - 1 { - /* - * evaluating the last value of a value block can break order of evaluation, - * skip these instructions - */ - continue; - } - let result = evaluate_instruction(constants, func, env, *instr_id); - if let Some(value) = result { - let lvalue_id = func.instructions[instr_id.0 as usize].lvalue.identifier; - constants.insert(lvalue_id, value); - } - } - - let block = &func.body.blocks[&block_id]; - match &block.terminal { - Terminal::If { - test, - consequent, - alternate, - id, - loc, - .. - } => { - let test_value = read(constants, test); - if let Some(Constant::Primitive { - value: ref prim, .. - }) = test_value - { - has_changes = true; - let target_block_id = if is_truthy(prim) { - *consequent - } else { - *alternate - }; - let terminal = Terminal::Goto { - variant: GotoVariant::Break, - block: target_block_id, - id: *id, - loc: *loc, - }; - func.body.blocks.get_mut(&block_id).unwrap().terminal = terminal; - } - } - Terminal::Unsupported { .. } - | Terminal::Unreachable { .. } - | Terminal::Throw { .. } - | Terminal::Return { .. } - | Terminal::Goto { .. } - | Terminal::Branch { .. } - | Terminal::Switch { .. } - | Terminal::DoWhile { .. } - | Terminal::While { .. } - | Terminal::For { .. } - | Terminal::ForOf { .. } - | Terminal::ForIn { .. } - | Terminal::Logical { .. } - | Terminal::Ternary { .. } - | Terminal::Optional { .. } - | Terminal::Label { .. } - | Terminal::Sequence { .. } - | Terminal::MaybeThrow { .. } - | Terminal::Try { .. } - | Terminal::Scope { .. } - | Terminal::PrunedScope { .. } => { - // no-op - } - } - } - - has_changes -} - -// ============================================================================= -// Phi evaluation -// ============================================================================= - -fn evaluate_phi(phi: &Phi, constants: &Constants) -> Option<Constant> { - let mut value: Option<Constant> = None; - for (_pred, operand) in &phi.operands { - let operand_value = constants.get(&operand.identifier)?; - - match &value { - None => { - // first iteration of the loop - value = Some(operand_value.clone()); - continue; - } - Some(current) => match (current, operand_value) { - (Constant::Primitive { value: a, .. }, Constant::Primitive { value: b, .. }) => { - // Use JS strict equality semantics: NaN !== NaN - if !js_strict_equal(a, b) { - return None; - } - } - ( - Constant::LoadGlobal { binding: a, .. }, - Constant::LoadGlobal { binding: b, .. }, - ) => { - // different global values, can't constant propagate - if a.name() != b.name() { - return None; - } - } - // found different kinds of constants, can't constant propagate - (Constant::Primitive { .. }, Constant::LoadGlobal { .. }) - | (Constant::LoadGlobal { .. }, Constant::Primitive { .. }) => { - return None; - } - }, - } - } - value -} - -// ============================================================================= -// Instruction evaluation -// ============================================================================= - -fn evaluate_instruction( - constants: &mut Constants, - func: &mut HirFunction, - env: &mut Environment, - instr_id: react_compiler_hir::InstructionId, -) -> Option<Constant> { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::Primitive { value, loc } => Some(Constant::Primitive { - value: value.clone(), - loc: *loc, - }), - InstructionValue::LoadGlobal { binding, loc } => Some(Constant::LoadGlobal { - binding: binding.clone(), - loc: *loc, - }), - InstructionValue::ComputedLoad { - object, - property, - loc, - } => { - let prop_value = read(constants, property); - if let Some(Constant::Primitive { - value: ref prim, .. - }) = prop_value - { - match prim { - PrimitiveValue::String(s) if is_valid_identifier(s) => { - let object = object.clone(); - let loc = *loc; - let new_property = PropertyLiteral::String(s.clone()); - func.instructions[instr_id.0 as usize].value = - InstructionValue::PropertyLoad { - object, - property: new_property, - loc, - }; - } - PrimitiveValue::Number(n) => { - let object = object.clone(); - let loc = *loc; - let new_property = PropertyLiteral::Number(*n); - func.instructions[instr_id.0 as usize].value = - InstructionValue::PropertyLoad { - object, - property: new_property, - loc, - }; - } - PrimitiveValue::Null - | PrimitiveValue::Undefined - | PrimitiveValue::Boolean(_) - | PrimitiveValue::String(_) => {} - } - } - None - } - InstructionValue::ComputedStore { - object, - property, - value, - loc, - } => { - let prop_value = read(constants, property); - if let Some(Constant::Primitive { - value: ref prim, .. - }) = prop_value - { - match prim { - PrimitiveValue::String(s) if is_valid_identifier(s) => { - let object = object.clone(); - let store_value = value.clone(); - let loc = *loc; - let new_property = PropertyLiteral::String(s.clone()); - func.instructions[instr_id.0 as usize].value = - InstructionValue::PropertyStore { - object, - property: new_property, - value: store_value, - loc, - }; - } - PrimitiveValue::Number(n) => { - let object = object.clone(); - let store_value = value.clone(); - let loc = *loc; - let new_property = PropertyLiteral::Number(*n); - func.instructions[instr_id.0 as usize].value = - InstructionValue::PropertyStore { - object, - property: new_property, - value: store_value, - loc, - }; - } - PrimitiveValue::Null - | PrimitiveValue::Undefined - | PrimitiveValue::Boolean(_) - | PrimitiveValue::String(_) => {} - } - } - None - } - InstructionValue::PostfixUpdate { - lvalue, - operation, - value, - loc, - } => { - let previous = read(constants, value); - if let Some(Constant::Primitive { - value: PrimitiveValue::Number(n), - loc: prev_loc, - }) = previous - { - let prev_val = n.value(); - let next_val = match operation { - UpdateOperator::Increment => prev_val + 1.0, - UpdateOperator::Decrement => prev_val - 1.0, - }; - // Store the updated value for the lvalue - let lvalue_id = lvalue.identifier; - constants.insert( - lvalue_id, - Constant::Primitive { - value: PrimitiveValue::Number(FloatValue::new(next_val)), - loc: *loc, - }, - ); - // But return the value prior to the update (preserving its original loc) - return Some(Constant::Primitive { - value: PrimitiveValue::Number(n), - loc: prev_loc, - }); - } - None - } - InstructionValue::PrefixUpdate { - lvalue, - operation, - value, - loc, - } => { - let previous = read(constants, value); - if let Some(Constant::Primitive { - value: PrimitiveValue::Number(n), - .. - }) = previous - { - let prev_val = n.value(); - let next_val = match operation { - UpdateOperator::Increment => prev_val + 1.0, - UpdateOperator::Decrement => prev_val - 1.0, - }; - let result = Constant::Primitive { - value: PrimitiveValue::Number(FloatValue::new(next_val)), - loc: *loc, - }; - // Store and return the updated value - let lvalue_id = lvalue.identifier; - constants.insert(lvalue_id, result.clone()); - return Some(result); - } - None - } - InstructionValue::UnaryExpression { - operator, - value, - loc, - } => match operator { - UnaryOperator::Not => { - let operand = read(constants, value); - if let Some(Constant::Primitive { - value: ref prim, .. - }) = operand - { - let negated = !is_truthy(prim); - let loc = *loc; - let result = Constant::Primitive { - value: PrimitiveValue::Boolean(negated), - loc, - }; - func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive { - value: PrimitiveValue::Boolean(negated), - loc, - }; - return Some(result); - } - None - } - UnaryOperator::Minus => { - let operand = read(constants, value); - if let Some(Constant::Primitive { - value: PrimitiveValue::Number(n), - .. - }) = operand - { - let negated = n.value() * -1.0; - let loc = *loc; - let result = Constant::Primitive { - value: PrimitiveValue::Number(FloatValue::new(negated)), - loc, - }; - func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive { - value: PrimitiveValue::Number(FloatValue::new(negated)), - loc, - }; - return Some(result); - } - None - } - UnaryOperator::Plus - | UnaryOperator::BitwiseNot - | UnaryOperator::TypeOf - | UnaryOperator::Void => None, - }, - InstructionValue::BinaryExpression { - operator, - left, - right, - loc, - } => { - let lhs_value = read(constants, left); - let rhs_value = read(constants, right); - if let ( - Some(Constant::Primitive { value: lhs, .. }), - Some(Constant::Primitive { value: rhs, .. }), - ) = (&lhs_value, &rhs_value) - { - let result = evaluate_binary_op(*operator, lhs, rhs); - if let Some(ref prim) = result { - let loc = *loc; - func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive { - value: prim.clone(), - loc, - }; - return Some(Constant::Primitive { - value: prim.clone(), - loc, - }); - } - } - None - } - InstructionValue::PropertyLoad { - object, - property, - loc, - } => { - let object_value = read(constants, object); - if let Some(Constant::Primitive { - value: PrimitiveValue::String(ref s), - .. - }) = object_value - { - if let PropertyLiteral::String(prop_name) = property { - if prop_name == "length" { - // Use UTF-16 code unit count to match JS .length semantics - let len = s.encode_utf16().count() as f64; - let loc = *loc; - let result = Constant::Primitive { - value: PrimitiveValue::Number(FloatValue::new(len)), - loc, - }; - func.instructions[instr_id.0 as usize].value = - InstructionValue::Primitive { - value: PrimitiveValue::Number(FloatValue::new(len)), - loc, - }; - return Some(result); - } - } - } - None - } - InstructionValue::TemplateLiteral { - subexprs, - quasis, - loc, - } => { - if subexprs.is_empty() { - // No subexpressions: join all cooked quasis - let mut result_string = String::new(); - for q in quasis { - match &q.cooked { - Some(cooked) => result_string.push_str(cooked), - None => return None, - } - } - let loc = *loc; - let result = Constant::Primitive { - value: PrimitiveValue::String(result_string.clone()), - loc, - }; - func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive { - value: PrimitiveValue::String(result_string), - loc, - }; - return Some(result); - } - - if subexprs.len() != quasis.len() - 1 { - return None; - } - - if quasis.iter().any(|q| q.cooked.is_none()) { - return None; - } - - let mut quasi_index = 0usize; - let mut result_string = quasis[quasi_index].cooked.as_ref().unwrap().clone(); - quasi_index += 1; - - for sub_expr in subexprs { - let sub_expr_value = read(constants, sub_expr); - let sub_prim = match sub_expr_value { - Some(Constant::Primitive { ref value, .. }) => value, - _ => return None, - }; - - let expression_str = match sub_prim { - PrimitiveValue::Null => "null".to_string(), - PrimitiveValue::Boolean(b) => b.to_string(), - PrimitiveValue::Number(n) => format_js_number(n.value()), - PrimitiveValue::String(s) => s.clone(), - // TS rejects undefined subexpression values - PrimitiveValue::Undefined => return None, - }; - - let suffix = match &quasis[quasi_index].cooked { - Some(s) => s.clone(), - None => return None, - }; - quasi_index += 1; - - result_string.push_str(&expression_str); - result_string.push_str(&suffix); - } - - let loc = *loc; - let result = Constant::Primitive { - value: PrimitiveValue::String(result_string.clone()), - loc, - }; - func.instructions[instr_id.0 as usize].value = InstructionValue::Primitive { - value: PrimitiveValue::String(result_string), - loc, - }; - Some(result) - } - InstructionValue::LoadLocal { place, .. } => { - let place_value = read(constants, place); - if let Some(ref constant) = place_value { - // Replace the LoadLocal with the constant value (including the constant's original loc) - func.instructions[instr_id.0 as usize].value = - constant.clone().into_instruction_value(); - } - place_value - } - InstructionValue::StoreLocal { lvalue, value, .. } => { - let place_value = read(constants, value); - if let Some(ref constant) = place_value { - let lvalue_id = lvalue.place.identifier; - constants.insert(lvalue_id, constant.clone()); - } - place_value - } - InstructionValue::FunctionExpression { lowered_func, .. } => { - let func_id = lowered_func.func; - process_inner_function(func_id, env, constants); - None - } - InstructionValue::ObjectMethod { lowered_func, .. } => { - let func_id = lowered_func.func; - process_inner_function(func_id, env, constants); - None - } - InstructionValue::StartMemoize { deps, .. } => { - if let Some(deps) = deps { - // Two-phase: collect which deps are constant, then mutate - let const_dep_indices: Vec<usize> = deps - .iter() - .enumerate() - .filter_map(|(i, dep)| { - if let react_compiler_hir::ManualMemoDependencyRoot::NamedLocal { - value, - .. - } = &dep.root - { - let pv = read(constants, value); - if matches!(pv, Some(Constant::Primitive { .. })) { - return Some(i); - } - } - None - }) - .collect(); - for idx in const_dep_indices { - if let InstructionValue::StartMemoize { - deps: Some(ref mut deps), - .. - } = func.instructions[instr_id.0 as usize].value - { - if let react_compiler_hir::ManualMemoDependencyRoot::NamedLocal { - constant, - .. - } = &mut deps[idx].root - { - *constant = true; - } - } - } - } - None - } - // All other instruction kinds: no constant folding - InstructionValue::LoadContext { .. } - | InstructionValue::DeclareLocal { .. } - | InstructionValue::DeclareContext { .. } - | InstructionValue::StoreContext { .. } - | InstructionValue::Destructure { .. } - | InstructionValue::JSXText { .. } - | InstructionValue::NewExpression { .. } - | InstructionValue::CallExpression { .. } - | InstructionValue::MethodCall { .. } - | InstructionValue::TypeCastExpression { .. } - | InstructionValue::JsxExpression { .. } - | InstructionValue::ObjectExpression { .. } - | InstructionValue::ArrayExpression { .. } - | InstructionValue::JsxFragment { .. } - | InstructionValue::RegExpLiteral { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::PropertyStore { .. } - | InstructionValue::PropertyDelete { .. } - | InstructionValue::ComputedDelete { .. } - | InstructionValue::StoreGlobal { .. } - | InstructionValue::TaggedTemplateExpression { .. } - | InstructionValue::Await { .. } - | InstructionValue::GetIterator { .. } - | InstructionValue::IteratorNext { .. } - | InstructionValue::NextPropertyOf { .. } - | InstructionValue::Debugger { .. } - | InstructionValue::FinishMemoize { .. } - | InstructionValue::UnsupportedNode { .. } => None, - } -} - -// ============================================================================= -// Inner function processing -// ============================================================================= - -fn process_inner_function(func_id: FunctionId, env: &mut Environment, constants: &mut Constants) { - let mut inner = std::mem::replace( - &mut env.functions[func_id.0 as usize], - placeholder_function(), - ); - constant_propagation_impl(&mut inner, env, constants); - env.functions[func_id.0 as usize] = inner; -} - -// ============================================================================= -// Helper: read constant for a place -// ============================================================================= - -fn read(constants: &Constants, place: &Place) -> Option<Constant> { - constants.get(&place.identifier).cloned() -} - -// ============================================================================= -// Helper: is_valid_identifier -// ============================================================================= - -/// Check if a string is a valid JavaScript identifier. -/// Supports Unicode identifier characters per ECMAScript spec (ID_Start / ID_Continue). -/// Rejects JS reserved words (matching Babel's `isValidIdentifier` default behavior). -fn is_valid_identifier(s: &str) -> bool { - if s.is_empty() { - return false; - } - let mut chars = s.chars(); - match chars.next() { - Some(c) if is_id_start(c) => {} - _ => return false, - } - if !chars.all(is_id_continue) { - return false; - } - !is_reserved_word(s) -} - -/// JS reserved words that cannot be used as identifiers. -/// Includes keywords, future reserved words, and strict mode reserved words. -fn is_reserved_word(s: &str) -> bool { - matches!( - s, - "break" - | "case" - | "catch" - | "continue" - | "debugger" - | "default" - | "do" - | "else" - | "finally" - | "for" - | "function" - | "if" - | "in" - | "instanceof" - | "new" - | "return" - | "switch" - | "this" - | "throw" - | "try" - | "typeof" - | "var" - | "void" - | "while" - | "with" - | "class" - | "const" - | "enum" - | "export" - | "extends" - | "import" - | "super" - | "implements" - | "interface" - | "let" - | "package" - | "private" - | "protected" - | "public" - | "static" - | "yield" - | "await" - | "delete" - | "null" - | "true" - | "false" - ) -} - -/// Check if a character is valid as the start of a JS identifier (ID_Start + _ + $). -fn is_id_start(c: char) -> bool { - c == '_' || c == '$' || c.is_alphabetic() -} - -/// Check if a character is valid as a continuation of a JS identifier (ID_Continue + $ + \u200C + \u200D). -fn is_id_continue(c: char) -> bool { - c == '$' - || c == '_' - || c.is_alphanumeric() - || c == '\u{200C}' // ZWNJ - || c == '\u{200D}' // ZWJ -} - -// ============================================================================= -// Helper: is_truthy for PrimitiveValue -// ============================================================================= - -fn is_truthy(value: &PrimitiveValue) -> bool { - match value { - PrimitiveValue::Null => false, - PrimitiveValue::Undefined => false, - PrimitiveValue::Boolean(b) => *b, - PrimitiveValue::Number(n) => { - let v = n.value(); - v != 0.0 && !v.is_nan() - } - PrimitiveValue::String(s) => !s.is_empty(), - } -} - -// ============================================================================= -// Binary operation evaluation -// ============================================================================= - -fn evaluate_binary_op( - operator: BinaryOperator, - lhs: &PrimitiveValue, - rhs: &PrimitiveValue, -) -> Option<PrimitiveValue> { - match operator { - BinaryOperator::Add => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number( - FloatValue::new(l.value() + r.value()), - )), - (PrimitiveValue::String(l), PrimitiveValue::String(r)) => { - let mut s = l.clone(); - s.push_str(r); - Some(PrimitiveValue::String(s)) - } - _ => None, - }, - BinaryOperator::Subtract => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number( - FloatValue::new(l.value() - r.value()), - )), - _ => None, - }, - BinaryOperator::Multiply => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number( - FloatValue::new(l.value() * r.value()), - )), - _ => None, - }, - BinaryOperator::Divide => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number( - FloatValue::new(l.value() / r.value()), - )), - _ => None, - }, - BinaryOperator::Modulo => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number( - FloatValue::new(l.value() % r.value()), - )), - _ => None, - }, - BinaryOperator::Exponent => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => Some(PrimitiveValue::Number( - FloatValue::new(l.value().powf(r.value())), - )), - _ => None, - }, - BinaryOperator::BitwiseOr => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => { - let result = js_to_int32(l.value()) | js_to_int32(r.value()); - Some(PrimitiveValue::Number(FloatValue::new(result as f64))) - } - _ => None, - }, - BinaryOperator::BitwiseAnd => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => { - let result = js_to_int32(l.value()) & js_to_int32(r.value()); - Some(PrimitiveValue::Number(FloatValue::new(result as f64))) - } - _ => None, - }, - BinaryOperator::BitwiseXor => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => { - let result = js_to_int32(l.value()) ^ js_to_int32(r.value()); - Some(PrimitiveValue::Number(FloatValue::new(result as f64))) - } - _ => None, - }, - BinaryOperator::ShiftLeft => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => { - let result = js_to_int32(l.value()) << (js_to_uint32(r.value()) & 0x1f); - Some(PrimitiveValue::Number(FloatValue::new(result as f64))) - } - _ => None, - }, - BinaryOperator::ShiftRight => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => { - let result = js_to_int32(l.value()) >> (js_to_uint32(r.value()) & 0x1f); - Some(PrimitiveValue::Number(FloatValue::new(result as f64))) - } - _ => None, - }, - BinaryOperator::UnsignedShiftRight => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => { - let result = js_to_uint32(l.value()) >> (js_to_uint32(r.value()) & 0x1f); - Some(PrimitiveValue::Number(FloatValue::new(result as f64))) - } - _ => None, - }, - BinaryOperator::LessThan => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => { - Some(PrimitiveValue::Boolean(l.value() < r.value())) - } - _ => None, - }, - BinaryOperator::LessEqual => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => { - Some(PrimitiveValue::Boolean(l.value() <= r.value())) - } - _ => None, - }, - BinaryOperator::GreaterThan => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => { - Some(PrimitiveValue::Boolean(l.value() > r.value())) - } - _ => None, - }, - BinaryOperator::GreaterEqual => match (lhs, rhs) { - (PrimitiveValue::Number(l), PrimitiveValue::Number(r)) => { - Some(PrimitiveValue::Boolean(l.value() >= r.value())) - } - _ => None, - }, - BinaryOperator::StrictEqual => Some(PrimitiveValue::Boolean(js_strict_equal(lhs, rhs))), - BinaryOperator::StrictNotEqual => Some(PrimitiveValue::Boolean(!js_strict_equal(lhs, rhs))), - BinaryOperator::Equal => Some(PrimitiveValue::Boolean(js_abstract_equal(lhs, rhs))), - BinaryOperator::NotEqual => Some(PrimitiveValue::Boolean(!js_abstract_equal(lhs, rhs))), - BinaryOperator::In | BinaryOperator::InstanceOf => None, - } -} - -// ============================================================================= -// JavaScript equality semantics -// ============================================================================= - -fn js_strict_equal(lhs: &PrimitiveValue, rhs: &PrimitiveValue) -> bool { - match (lhs, rhs) { - (PrimitiveValue::Null, PrimitiveValue::Null) => true, - (PrimitiveValue::Undefined, PrimitiveValue::Undefined) => true, - (PrimitiveValue::Boolean(a), PrimitiveValue::Boolean(b)) => a == b, - (PrimitiveValue::Number(a), PrimitiveValue::Number(b)) => { - let av = a.value(); - let bv = b.value(); - // NaN !== NaN in JS - if av.is_nan() || bv.is_nan() { - return false; - } - av == bv - } - (PrimitiveValue::String(a), PrimitiveValue::String(b)) => a == b, - // Different types => false - _ => false, - } -} - -/// Convert a string to a number using JS `ToNumber` semantics. -/// In JS: `""` → 0, `" "` → 0, `" 42 "` → 42, `"0x1A"` → 26, `"Infinity"` → Infinity. -fn js_to_number(s: &str) -> f64 { - let trimmed = s.trim(); - if trimmed.is_empty() { - return 0.0; - } - if trimmed == "Infinity" || trimmed == "+Infinity" { - return f64::INFINITY; - } - if trimmed == "-Infinity" { - return f64::NEG_INFINITY; - } - // Handle hex literals (0x/0X) - if trimmed.starts_with("0x") || trimmed.starts_with("0X") { - return match u64::from_str_radix(&trimmed[2..], 16) { - Ok(v) => v as f64, - Err(_) => f64::NAN, - }; - } - // Handle octal literals (0o/0O) - if trimmed.starts_with("0o") || trimmed.starts_with("0O") { - return match u64::from_str_radix(&trimmed[2..], 8) { - Ok(v) => v as f64, - Err(_) => f64::NAN, - }; - } - // Handle binary literals (0b/0B) - if trimmed.starts_with("0b") || trimmed.starts_with("0B") { - return match u64::from_str_radix(&trimmed[2..], 2) { - Ok(v) => v as f64, - Err(_) => f64::NAN, - }; - } - trimmed.parse::<f64>().unwrap_or(f64::NAN) -} - -fn js_abstract_equal(lhs: &PrimitiveValue, rhs: &PrimitiveValue) -> bool { - match (lhs, rhs) { - (PrimitiveValue::Null, PrimitiveValue::Null) => true, - (PrimitiveValue::Undefined, PrimitiveValue::Undefined) => true, - (PrimitiveValue::Null, PrimitiveValue::Undefined) - | (PrimitiveValue::Undefined, PrimitiveValue::Null) => true, - (PrimitiveValue::Boolean(a), PrimitiveValue::Boolean(b)) => a == b, - (PrimitiveValue::Number(a), PrimitiveValue::Number(b)) => { - let av = a.value(); - let bv = b.value(); - if av.is_nan() || bv.is_nan() { - return false; - } - av == bv - } - (PrimitiveValue::String(a), PrimitiveValue::String(b)) => a == b, - // Cross-type coercions for primitives - (PrimitiveValue::Number(n), PrimitiveValue::String(s)) - | (PrimitiveValue::String(s), PrimitiveValue::Number(n)) => { - // String is coerced to number using JS ToNumber semantics - let sv = js_to_number(s); - let nv = n.value(); - if nv.is_nan() || sv.is_nan() { - false - } else { - nv == sv - } - } - (PrimitiveValue::Boolean(b), other) => { - let num = if *b { 1.0 } else { 0.0 }; - js_abstract_equal(&PrimitiveValue::Number(FloatValue::new(num)), other) - } - (other, PrimitiveValue::Boolean(b)) => { - let num = if *b { 1.0 } else { 0.0 }; - js_abstract_equal(other, &PrimitiveValue::Number(FloatValue::new(num))) - } - // null/undefined vs number/string => false - _ => false, - } -} - -// ============================================================================= -// JavaScript Number.toString() approximation -// ============================================================================= - -/// ECMAScript ToInt32: convert f64 to i32 with modular (wrapping) semantics. -fn js_to_int32(n: f64) -> i32 { - if n.is_nan() || n.is_infinite() || n == 0.0 { - return 0; - } - // Truncate, then wrap to 32 bits - let int64 = (n.trunc() as i64) & 0xFFFFFFFF; - // Reinterpret as signed i32 - if int64 >= 0x80000000 { - (int64 as u32) as i32 - } else { - int64 as i32 - } -} - -/// ECMAScript ToUint32: convert f64 to u32 with modular (wrapping) semantics. -fn js_to_uint32(n: f64) -> u32 { - js_to_int32(n) as u32 -} diff --git a/compiler/crates/react_compiler_optimization/src/dead_code_elimination.rs b/compiler/crates/react_compiler_optimization/src/dead_code_elimination.rs deleted file mode 100644 index f3a563674535..000000000000 --- a/compiler/crates/react_compiler_optimization/src/dead_code_elimination.rs +++ /dev/null @@ -1,422 +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. - -//! Dead code elimination pass. -//! -//! Eliminates instructions whose values are unused, reducing generated code size. -//! Performs mark-and-sweep analysis to identify and remove dead code while -//! preserving side effects and program semantics. -//! -//! Ported from TypeScript `src/Optimization/DeadCodeElimination.ts`. - -use std::collections::HashSet; - -use react_compiler_hir::environment::{Environment, OutputMode}; -use react_compiler_hir::object_shape::HookKind; -use react_compiler_hir::visitors; -use react_compiler_hir::{ - ArrayPatternElement, BlockId, BlockKind, HirFunction, IdentifierId, InstructionKind, - InstructionValue, ObjectPropertyOrSpread, Pattern, -}; - -/// Implements dead-code elimination, eliminating instructions whose values are unused. -/// -/// Note that unreachable blocks are already pruned during HIR construction. -/// -/// Corresponds to TS `deadCodeElimination(fn: HIRFunction): void`. -pub fn dead_code_elimination(func: &mut HirFunction, env: &Environment) { - // Phase 1: Find/mark all referenced identifiers - let state = find_referenced_identifiers(func, env); - - // Phase 2: Prune / sweep unreferenced identifiers and instructions - // Collect instructions to rewrite (two-phase: collect then apply to avoid borrow conflicts) - let mut instructions_to_rewrite: Vec<react_compiler_hir::InstructionId> = Vec::new(); - - for (_block_id, block) in &mut func.body.blocks { - // Remove unused phi nodes - block - .phis - .retain(|phi| is_id_or_name_used(&state, &env.identifiers, phi.place.identifier)); - - // Remove instructions with unused lvalues - block.instructions.retain(|instr_id| { - let instr = &func.instructions[instr_id.0 as usize]; - is_id_or_name_used(&state, &env.identifiers, instr.lvalue.identifier) - }); - - // Collect instructions that need rewriting (not the block value) - let retained_count = block.instructions.len(); - for i in 0..retained_count { - let is_block_value = block.kind != BlockKind::Block && i == retained_count - 1; - if !is_block_value { - instructions_to_rewrite.push(block.instructions[i]); - } - } - } - - // Apply rewrites - for instr_id in instructions_to_rewrite { - rewrite_instruction(func, instr_id, &state, env); - } - - // Remove unused context variables - func.context - .retain(|ctx_var| is_id_or_name_used(&state, &env.identifiers, ctx_var.identifier)); -} - -/// State for tracking referenced identifiers during mark phase. -struct State { - /// SSA-specific usages (by IdentifierId) - identifiers: HashSet<IdentifierId>, - /// Named variable usages (any version) - named: HashSet<String>, -} - -impl State { - fn new() -> Self { - State { - identifiers: HashSet::new(), - named: HashSet::new(), - } - } - - fn count(&self) -> usize { - self.identifiers.len() - } -} - -/// Mark an identifier as being referenced (not dead code). -fn reference( - state: &mut State, - identifiers: &[react_compiler_hir::Identifier], - identifier_id: IdentifierId, -) { - state.identifiers.insert(identifier_id); - let ident = &identifiers[identifier_id.0 as usize]; - if let Some(ref name) = ident.name { - state.named.insert(name.value().to_string()); - } -} - -/// Check if any version of the given identifier is used somewhere. -/// Checks both the specific SSA id and (for named identifiers) any usage of that name. -fn is_id_or_name_used( - state: &State, - identifiers: &[react_compiler_hir::Identifier], - identifier_id: IdentifierId, -) -> bool { - if state.identifiers.contains(&identifier_id) { - return true; - } - let ident = &identifiers[identifier_id.0 as usize]; - if let Some(ref name) = ident.name { - state.named.contains(name.value()) - } else { - false - } -} - -/// Check if this specific SSA id is used. -fn is_id_used(state: &State, identifier_id: IdentifierId) -> bool { - state.identifiers.contains(&identifier_id) -} - -/// Phase 1: Find all referenced identifiers via fixed-point iteration. -fn find_referenced_identifiers(func: &HirFunction, env: &Environment) -> State { - let has_loop = has_back_edge(func); - // Collect block ids in reverse order (postorder - successors before predecessors) - let reversed_block_ids: Vec<BlockId> = func.body.blocks.keys().rev().copied().collect(); - - let mut state = State::new(); - let mut size; - - loop { - size = state.count(); - - for &block_id in &reversed_block_ids { - let block = &func.body.blocks[&block_id]; - - // Mark terminal operands - for place in visitors::each_terminal_operand(&block.terminal) { - reference(&mut state, &env.identifiers, place.identifier); - } - - // Process instructions in reverse order - let instr_count = block.instructions.len(); - for i in (0..instr_count).rev() { - let instr_id = block.instructions[i]; - let instr = &func.instructions[instr_id.0 as usize]; - - let is_block_value = block.kind != BlockKind::Block && i == instr_count - 1; - - if is_block_value { - // Last instr of a value block is never eligible for pruning - reference(&mut state, &env.identifiers, instr.lvalue.identifier); - for place in visitors::each_instruction_value_operand(&instr.value, env) { - reference(&mut state, &env.identifiers, place.identifier); - } - } else if is_id_or_name_used(&state, &env.identifiers, instr.lvalue.identifier) - || !pruneable_value(&instr.value, &state, env) - { - reference(&mut state, &env.identifiers, instr.lvalue.identifier); - - if let InstructionValue::StoreLocal { lvalue, value, .. } = &instr.value { - // If this is a Let/Const declaration, mark the initializer as referenced - // only if the SSA'd lval is also referenced - if lvalue.kind == InstructionKind::Reassign - || is_id_used(&state, lvalue.place.identifier) - { - reference(&mut state, &env.identifiers, value.identifier); - } - } else { - for place in visitors::each_instruction_value_operand(&instr.value, env) { - reference(&mut state, &env.identifiers, place.identifier); - } - } - } - } - - // Mark phi operands if phi result is used - for phi in &block.phis { - if is_id_or_name_used(&state, &env.identifiers, phi.place.identifier) { - for (_pred, operand) in &phi.operands { - reference(&mut state, &env.identifiers, operand.identifier); - } - } - } - } - - if !(state.count() > size && has_loop) { - break; - } - } - - state -} - -/// Rewrite a retained instruction (destructuring cleanup, StoreLocal -> DeclareLocal). -fn rewrite_instruction( - func: &mut HirFunction, - instr_id: react_compiler_hir::InstructionId, - state: &State, - env: &Environment, -) { - let instr = &mut func.instructions[instr_id.0 as usize]; - - match &mut instr.value { - InstructionValue::Destructure { lvalue, .. } => { - match &mut lvalue.pattern { - Pattern::Array(arr) => { - // For arrays, replace unused items with holes, truncate trailing holes - let mut last_entry_index = 0; - for i in 0..arr.items.len() { - match &arr.items[i] { - ArrayPatternElement::Place(p) => { - if !is_id_or_name_used(state, &env.identifiers, p.identifier) { - arr.items[i] = ArrayPatternElement::Hole; - } else { - last_entry_index = i; - } - } - ArrayPatternElement::Spread(s) => { - if !is_id_or_name_used(state, &env.identifiers, s.place.identifier) - { - arr.items[i] = ArrayPatternElement::Hole; - } else { - last_entry_index = i; - } - } - ArrayPatternElement::Hole => {} - } - } - arr.items.truncate(last_entry_index + 1); - } - Pattern::Object(obj) => { - // For objects, prune unused properties if rest element is unused or absent - let mut next_properties: Option<Vec<ObjectPropertyOrSpread>> = None; - for prop in &obj.properties { - match prop { - ObjectPropertyOrSpread::Property(p) => { - if is_id_or_name_used(state, &env.identifiers, p.place.identifier) { - next_properties - .get_or_insert_with(Vec::new) - .push(prop.clone()); - } - } - ObjectPropertyOrSpread::Spread(s) => { - if is_id_or_name_used(state, &env.identifiers, s.place.identifier) { - // Rest element is used, can't prune anything - next_properties = None; - break; - } - } - } - } - if let Some(props) = next_properties { - obj.properties = props; - } - } - } - } - InstructionValue::StoreLocal { - lvalue, - type_annotation, - loc, - .. - } => { - if lvalue.kind != InstructionKind::Reassign - && !is_id_used(state, lvalue.place.identifier) - { - // This is a const/let declaration where the variable is accessed later, - // but where the value is always overwritten before being read. - // Rewrite to DeclareLocal so the initializer value can be DCE'd. - let new_lvalue = lvalue.clone(); - let new_type_annotation = type_annotation.clone(); - let new_loc = *loc; - instr.value = InstructionValue::DeclareLocal { - lvalue: new_lvalue, - type_annotation: new_type_annotation, - loc: new_loc, - }; - } - } - _ => {} - } -} - -/// Returns true if it is safe to prune an instruction with the given value. -fn pruneable_value(value: &InstructionValue, state: &State, env: &Environment) -> bool { - match value { - InstructionValue::DeclareLocal { lvalue, .. } => { - // Declarations are pruneable only if the named variable is never read later - !is_id_or_name_used(state, &env.identifiers, lvalue.place.identifier) - } - InstructionValue::StoreLocal { lvalue, .. } => { - if lvalue.kind == InstructionKind::Reassign { - // Reassignments can be pruned if the specific instance being assigned is never read - !is_id_used(state, lvalue.place.identifier) - } else { - // Declarations are pruneable only if the named variable is never read later - !is_id_or_name_used(state, &env.identifiers, lvalue.place.identifier) - } - } - InstructionValue::Destructure { lvalue, .. } => { - let mut is_id_or_name_used_flag = false; - let mut is_id_used_flag = false; - for place in visitors::each_pattern_operand(&lvalue.pattern) { - if is_id_used(state, place.identifier) { - is_id_or_name_used_flag = true; - is_id_used_flag = true; - } else if is_id_or_name_used(state, &env.identifiers, place.identifier) { - is_id_or_name_used_flag = true; - } - } - if lvalue.kind == InstructionKind::Reassign { - !is_id_used_flag - } else { - !is_id_or_name_used_flag - } - } - InstructionValue::PostfixUpdate { lvalue, .. } - | InstructionValue::PrefixUpdate { lvalue, .. } => { - // Updates are pruneable if the specific instance being assigned is never read - !is_id_used(state, lvalue.identifier) - } - InstructionValue::Debugger { .. } => { - // explicitly retain debugger statements - false - } - InstructionValue::CallExpression { callee, .. } => { - if env.output_mode == OutputMode::Ssr { - let callee_ty = - &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize]; - if let Some(hook_kind) = env.get_hook_kind_for_type(callee_ty).ok().flatten() { - match hook_kind { - HookKind::UseState | HookKind::UseReducer | HookKind::UseRef => { - return true; - } - _ => {} - } - } - } - false - } - InstructionValue::MethodCall { property, .. } => { - if env.output_mode == OutputMode::Ssr { - let callee_ty = - &env.types[env.identifiers[property.identifier.0 as usize].type_.0 as usize]; - if let Some(hook_kind) = env.get_hook_kind_for_type(callee_ty).ok().flatten() { - match hook_kind { - HookKind::UseState | HookKind::UseReducer | HookKind::UseRef => { - return true; - } - _ => {} - } - } - } - false - } - InstructionValue::Await { .. } - | InstructionValue::ComputedDelete { .. } - | InstructionValue::ComputedStore { .. } - | InstructionValue::PropertyDelete { .. } - | InstructionValue::PropertyStore { .. } - | InstructionValue::StoreGlobal { .. } => { - // Mutating instructions are not safe to prune - false - } - InstructionValue::NewExpression { .. } - | InstructionValue::UnsupportedNode { .. } - | InstructionValue::TaggedTemplateExpression { .. } => { - // Potentially safe to prune, but we conservatively keep them - false - } - InstructionValue::GetIterator { .. } - | InstructionValue::NextPropertyOf { .. } - | InstructionValue::IteratorNext { .. } => { - // Iterator operations are always used downstream - false - } - InstructionValue::LoadContext { .. } - | InstructionValue::DeclareContext { .. } - | InstructionValue::StoreContext { .. } => false, - InstructionValue::StartMemoize { .. } | InstructionValue::FinishMemoize { .. } => false, - InstructionValue::RegExpLiteral { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::LoadGlobal { .. } - | InstructionValue::ArrayExpression { .. } - | InstructionValue::BinaryExpression { .. } - | InstructionValue::ComputedLoad { .. } - | InstructionValue::ObjectMethod { .. } - | InstructionValue::FunctionExpression { .. } - | InstructionValue::LoadLocal { .. } - | InstructionValue::JsxExpression { .. } - | InstructionValue::JsxFragment { .. } - | InstructionValue::JSXText { .. } - | InstructionValue::ObjectExpression { .. } - | InstructionValue::Primitive { .. } - | InstructionValue::PropertyLoad { .. } - | InstructionValue::TemplateLiteral { .. } - | InstructionValue::TypeCastExpression { .. } - | InstructionValue::UnaryExpression { .. } => { - // Definitely safe to prune since they are read-only - true - } - } -} - -/// Check if the CFG has any back edges (indicating loops). -fn has_back_edge(func: &HirFunction) -> bool { - let mut visited: HashSet<BlockId> = HashSet::new(); - for (block_id, block) in &func.body.blocks { - for pred_id in &block.preds { - if !visited.contains(pred_id) { - return true; - } - } - visited.insert(*block_id); - } - false -} diff --git a/compiler/crates/react_compiler_optimization/src/drop_manual_memoization.rs b/compiler/crates/react_compiler_optimization/src/drop_manual_memoization.rs deleted file mode 100644 index d17793df3539..000000000000 --- a/compiler/crates/react_compiler_optimization/src/drop_manual_memoization.rs +++ /dev/null @@ -1,755 +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. - -//! Removes manual memoization using `useMemo` and `useCallback` APIs. -//! -//! For useMemo: replaces `Call useMemo(fn, deps)` with `Call fn()` -//! For useCallback: replaces `Call useCallback(fn, deps)` with `LoadLocal fn` -//! -//! When validation flags are set, inserts `StartMemoize`/`FinishMemoize` markers. -//! -//! Analogous to TS `Inference/DropManualMemoization.ts`. - -use std::collections::HashMap; -use std::collections::HashSet; - -use react_compiler_diagnostics::CompilerDiagnostic; -use react_compiler_diagnostics::CompilerDiagnosticDetail; -use react_compiler_diagnostics::ErrorCategory; -use react_compiler_hir::ArrayElement; -use react_compiler_hir::DependencyPathEntry; -use react_compiler_hir::Effect; -use react_compiler_hir::EvaluationOrder; -use react_compiler_hir::HirFunction; -use react_compiler_hir::IdentifierId; -use react_compiler_hir::IdentifierName; -use react_compiler_hir::Instruction; -use react_compiler_hir::InstructionId; -use react_compiler_hir::InstructionValue; -use react_compiler_hir::ManualMemoDependency; -use react_compiler_hir::ManualMemoDependencyRoot; -use react_compiler_hir::NonLocalBinding; -use react_compiler_hir::Place; -use react_compiler_hir::PlaceOrSpread; -use react_compiler_hir::PropertyLiteral; -use react_compiler_hir::SourceLocation; -use react_compiler_hir::environment::Environment; -use react_compiler_lowering::create_temporary_place; -use react_compiler_lowering::mark_instruction_ids; - -// ============================================================================= -// Types -// ============================================================================= - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ManualMemoKind { - UseMemo, - UseCallback, -} - -#[derive(Debug, Clone)] -struct ManualMemoCallee { - kind: ManualMemoKind, - /// InstructionId of the LoadGlobal or PropertyLoad that loaded the callee. - load_instr_id: InstructionId, -} - -struct IdentifierSidemap { - /// Maps identifier id -> InstructionId of FunctionExpression instructions - functions: HashSet<IdentifierId>, - /// Maps identifier id -> ManualMemoCallee for useMemo/useCallback callees - manual_memos: HashMap<IdentifierId, ManualMemoCallee>, - /// Set of identifier ids that loaded 'React' global - react: HashSet<IdentifierId>, - /// Maps identifier id -> deps list info for array expressions - maybe_deps_lists: HashMap<IdentifierId, MaybeDepsListInfo>, - /// Maps identifier id -> ManualMemoDependency for dependency tracking - maybe_deps: HashMap<IdentifierId, ManualMemoDependency>, - /// Set of identifier ids that are results of optional chains - optionals: HashSet<IdentifierId>, -} - -#[derive(Debug, Clone)] -struct MaybeDepsListInfo { - loc: Option<SourceLocation>, - deps: Vec<Place>, -} - -struct ExtractedMemoArgs { - fn_place: Place, - deps_list: Option<Vec<ManualMemoDependency>>, - deps_loc: Option<SourceLocation>, -} - -// ============================================================================= -// Main pass -// ============================================================================= - -/// Drop manual memoization (useMemo/useCallback calls), replacing them -/// with direct invocations/references. -pub fn drop_manual_memoization( - func: &mut HirFunction, - env: &mut Environment, -) -> Result<(), CompilerDiagnostic> { - let is_validation_enabled = env.validate_preserve_existing_memoization_guarantees - || env.validate_no_set_state_in_render - || env.enable_preserve_existing_memoization_guarantees; - - let optionals = find_optional_places(func)?; - let mut sidemap = IdentifierSidemap { - functions: HashSet::new(), - manual_memos: HashMap::new(), - react: HashSet::new(), - maybe_deps: HashMap::new(), - maybe_deps_lists: HashMap::new(), - optionals, - }; - let mut next_manual_memo_id: u32 = 0; - - // Phase 1: - // - Overwrite manual memoization CallExpression/MethodCall - // - (if validation is enabled) collect manual memoization markers - // - // queued_inserts maps InstructionId -> new Instruction to insert after that instruction - let mut queued_inserts: HashMap<InstructionId, Instruction> = HashMap::new(); - - // Collect all block instruction lists up front to avoid borrowing func immutably - // while needing to mutate it - let all_block_instructions: Vec<Vec<InstructionId>> = func - .body - .blocks - .values() - .map(|block| block.instructions.clone()) - .collect(); - - for block_instructions in &all_block_instructions { - for &instr_id in block_instructions { - let instr = &func.instructions[instr_id.0 as usize]; - - // Extract the identifier we need to look up, and whether it's a call/method - let lookup_id = match &instr.value { - InstructionValue::CallExpression { callee, .. } => Some(callee.identifier), - InstructionValue::MethodCall { property, .. } => Some(property.identifier), - _ => None, - }; - - let manual_memo = lookup_id.and_then(|id| sidemap.manual_memos.get(&id).cloned()); - - if let Some(manual_memo) = manual_memo { - process_manual_memo_call( - func, - env, - instr_id, - &manual_memo, - &mut sidemap, - is_validation_enabled, - &mut next_manual_memo_id, - &mut queued_inserts, - ); - } else { - collect_temporaries(func, env, instr_id, &mut sidemap); - } - } - } - - // Phase 2: Insert manual memoization markers as needed - if !queued_inserts.is_empty() { - let mut has_changes = false; - for block in func.body.blocks.values_mut() { - let mut next_instructions: Option<Vec<InstructionId>> = None; - for i in 0..block.instructions.len() { - let instr_id = block.instructions[i]; - if let Some(insert_instr) = queued_inserts.remove(&instr_id) { - if next_instructions.is_none() { - next_instructions = Some(block.instructions[..i].to_vec()); - } - let ni = next_instructions.as_mut().unwrap(); - ni.push(instr_id); - // Add the new instruction to the flat table and get its InstructionId - let new_instr_id = InstructionId(func.instructions.len() as u32); - func.instructions.push(insert_instr); - ni.push(new_instr_id); - } else if let Some(ni) = next_instructions.as_mut() { - ni.push(instr_id); - } - } - if let Some(ni) = next_instructions { - block.instructions = ni; - has_changes = true; - } - } - - if has_changes { - mark_instruction_ids(&mut func.body, &mut func.instructions); - } - } - - Ok(()) -} - -// ============================================================================= -// Phase 1 helpers -// ============================================================================= - -#[allow(clippy::too_many_arguments)] -fn process_manual_memo_call( - func: &mut HirFunction, - env: &mut Environment, - instr_id: InstructionId, - manual_memo: &ManualMemoCallee, - sidemap: &mut IdentifierSidemap, - is_validation_enabled: bool, - next_manual_memo_id: &mut u32, - queued_inserts: &mut HashMap<InstructionId, Instruction>, -) { - let instr = &func.instructions[instr_id.0 as usize]; - - let memo_details = extract_manual_memoization_args(instr, manual_memo.kind, sidemap, env); - - let Some(memo_details) = memo_details else { - return; - }; - - let ExtractedMemoArgs { - fn_place, - deps_list, - deps_loc, - } = memo_details; - - let loc = func.instructions[instr_id.0 as usize].value.loc().cloned(); - - // Replace the instruction value with the memoization replacement - let replacement = get_manual_memoization_replacement(&fn_place, loc.clone(), manual_memo.kind); - func.instructions[instr_id.0 as usize].value = replacement; - - if is_validation_enabled { - // Bail out when we encounter manual memoization without inline function expressions - if !sidemap.functions.contains(&fn_place.identifier) { - let mut diag = CompilerDiagnostic::new( - ErrorCategory::UseMemo, - "Expected the first argument to be an inline function expression", - Some("Expected the first argument to be an inline function expression".to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: fn_place.loc.clone(), - message: Some( - "Expected the first argument to be an inline function expression".to_string(), - ), - identifier_name: None, - }); - // Match TS behavior: suggestions is [] (empty array), not null - diag.suggestions = Some(vec![]); - env.record_diagnostic(diag); - return; - } - - let memo_decl: Place = if manual_memo.kind == ManualMemoKind::UseMemo { - func.instructions[instr_id.0 as usize].lvalue.clone() - } else { - Place { - identifier: fn_place.identifier, - effect: Effect::Unknown, - reactive: false, - loc: fn_place.loc.clone(), - } - }; - - let manual_memo_id = *next_manual_memo_id; - *next_manual_memo_id += 1; - - let (start_marker, finish_marker) = make_manual_memoization_markers( - &fn_place, - env, - deps_list, - deps_loc, - &memo_decl, - manual_memo_id, - ); - - queued_inserts.insert(manual_memo.load_instr_id, start_marker); - queued_inserts.insert(instr_id, finish_marker); - } -} - -fn collect_temporaries( - func: &HirFunction, - env: &Environment, - instr_id: InstructionId, - sidemap: &mut IdentifierSidemap, -) { - let instr = &func.instructions[instr_id.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - - match &instr.value { - InstructionValue::FunctionExpression { .. } => { - sidemap.functions.insert(lvalue_id); - } - InstructionValue::LoadGlobal { binding, .. } => { - let hook_name = get_hook_detection_name(binding); - let mut detected = false; - if let Some(name) = hook_name { - if name == "useMemo" { - sidemap.manual_memos.insert( - lvalue_id, - ManualMemoCallee { - kind: ManualMemoKind::UseMemo, - load_instr_id: instr_id, - }, - ); - detected = true; - } else if name == "useCallback" { - sidemap.manual_memos.insert( - lvalue_id, - ManualMemoCallee { - kind: ManualMemoKind::UseCallback, - load_instr_id: instr_id, - }, - ); - detected = true; - } - } - if !detected && binding.name() == "React" { - sidemap.react.insert(lvalue_id); - } - } - InstructionValue::PropertyLoad { - object, property, .. - } => { - if sidemap.react.contains(&object.identifier) { - if let PropertyLiteral::String(prop_name) = property { - if prop_name == "useMemo" { - sidemap.manual_memos.insert( - lvalue_id, - ManualMemoCallee { - kind: ManualMemoKind::UseMemo, - load_instr_id: instr_id, - }, - ); - } else if prop_name == "useCallback" { - sidemap.manual_memos.insert( - lvalue_id, - ManualMemoCallee { - kind: ManualMemoKind::UseCallback, - load_instr_id: instr_id, - }, - ); - } - } - } - } - InstructionValue::ArrayExpression { elements, .. } => { - // Check if all elements are Identifier (Place) - no spreads or holes - let all_places: Option<Vec<Place>> = elements - .iter() - .map(|e| match e { - ArrayElement::Place(p) => Some(p.clone()), - _ => None, - }) - .collect(); - - if let Some(deps) = all_places { - sidemap.maybe_deps_lists.insert( - lvalue_id, - MaybeDepsListInfo { - loc: instr.value.loc().cloned(), - deps, - }, - ); - } - } - _ => {} - } - - let is_optional = sidemap.optionals.contains(&lvalue_id); - let maybe_dep = - collect_maybe_memo_dependencies(&instr.value, &sidemap.maybe_deps, is_optional, env); - if let Some(dep) = maybe_dep { - // For StoreLocal, also insert under the StoreLocal's lvalue place identifier, - // matching the TS behavior where collectMaybeMemoDependencies inserts into - // maybeDeps directly for StoreLocal's target variable. - if let InstructionValue::StoreLocal { lvalue, .. } = &instr.value { - sidemap - .maybe_deps - .insert(lvalue.place.identifier, dep.clone()); - } - sidemap.maybe_deps.insert(lvalue_id, dep); - } -} - -// ============================================================================= -// collectMaybeMemoDependencies -// ============================================================================= - -/// Collect loads from named variables and property reads into `maybe_deps`. -/// Returns the variable + property reads represented by the instruction value. -pub fn collect_maybe_memo_dependencies( - value: &InstructionValue, - maybe_deps: &HashMap<IdentifierId, ManualMemoDependency>, - optional: bool, - env: &Environment, -) -> Option<ManualMemoDependency> { - match value { - InstructionValue::LoadGlobal { binding, loc, .. } => Some(ManualMemoDependency { - root: ManualMemoDependencyRoot::Global { - identifier_name: binding.name().to_string(), - }, - path: vec![], - loc: loc.clone(), - }), - InstructionValue::PropertyLoad { - object, - property, - loc, - .. - } => { - if let Some(object_dep) = maybe_deps.get(&object.identifier) { - Some(ManualMemoDependency { - root: object_dep.root.clone(), - path: { - let mut path = object_dep.path.clone(); - path.push(DependencyPathEntry { - property: property.clone(), - optional, - loc: loc.clone(), - }); - path - }, - loc: loc.clone(), - }) - } else { - None - } - } - InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => { - if let Some(source) = maybe_deps.get(&place.identifier) { - Some(source.clone()) - } else if matches!( - &env.identifiers[place.identifier.0 as usize].name, - Some(IdentifierName::Named(_)) - ) { - Some(ManualMemoDependency { - root: ManualMemoDependencyRoot::NamedLocal { - value: place.clone(), - constant: false, - }, - path: vec![], - loc: place.loc.clone(), - }) - } else { - None - } - } - InstructionValue::StoreLocal { - lvalue, value: val, .. - } => { - // Value blocks rely on StoreLocal to populate their return value. - // We need to track these as optional property chains are valid in - // source depslists - let lvalue_id = lvalue.place.identifier; - let rvalue_id = val.identifier; - if let Some(aliased) = maybe_deps.get(&rvalue_id) { - let lvalue_name = &env.identifiers[lvalue_id.0 as usize].name; - if !matches!(lvalue_name, Some(IdentifierName::Named(_))) { - // Note: we can't insert into maybe_deps here since we only have - // a shared reference. The caller handles insertion. - return Some(aliased.clone()); - } - } - None - } - _ => None, - } -} - -// ============================================================================= -// Replacement helpers -// ============================================================================= - -fn get_manual_memoization_replacement( - fn_place: &Place, - loc: Option<SourceLocation>, - kind: ManualMemoKind, -) -> InstructionValue { - if kind == ManualMemoKind::UseMemo { - // Replace with Call fn() - invoke the memo function directly - InstructionValue::CallExpression { - callee: fn_place.clone(), - args: vec![], - loc, - } - } else { - // Replace with LoadLocal fn - just reference the function - InstructionValue::LoadLocal { - place: Place { - identifier: fn_place.identifier, - effect: Effect::Unknown, - reactive: false, - loc: loc.clone(), - }, - loc, - } - } -} - -fn make_manual_memoization_markers( - fn_expr: &Place, - env: &mut Environment, - deps_list: Option<Vec<ManualMemoDependency>>, - deps_loc: Option<SourceLocation>, - memo_decl: &Place, - manual_memo_id: u32, -) -> (Instruction, Instruction) { - let start = Instruction { - id: EvaluationOrder(0), - lvalue: create_temporary_place(env, fn_expr.loc.clone()), - value: InstructionValue::StartMemoize { - manual_memo_id, - deps: deps_list, - deps_loc: Some(deps_loc), - has_invalid_deps: false, - loc: fn_expr.loc.clone(), - }, - loc: fn_expr.loc.clone(), - effects: None, - }; - let finish = Instruction { - id: EvaluationOrder(0), - lvalue: create_temporary_place(env, fn_expr.loc.clone()), - value: InstructionValue::FinishMemoize { - manual_memo_id, - decl: memo_decl.clone(), - pruned: false, - loc: fn_expr.loc.clone(), - }, - loc: fn_expr.loc.clone(), - effects: None, - }; - (start, finish) -} - -fn extract_manual_memoization_args( - instr: &Instruction, - kind: ManualMemoKind, - sidemap: &IdentifierSidemap, - env: &mut Environment, -) -> Option<ExtractedMemoArgs> { - let args: &[PlaceOrSpread] = match &instr.value { - InstructionValue::CallExpression { args, .. } => args, - InstructionValue::MethodCall { args, .. } => args, - _ => return None, - }; - - let kind_name = match kind { - ManualMemoKind::UseMemo => "useMemo", - ManualMemoKind::UseCallback => "useCallback", - }; - - // Get the first arg (fn) - let fn_place = match args.first() { - Some(PlaceOrSpread::Place(p)) => p.clone(), - _ => { - let loc = instr.value.loc().cloned(); - env.record_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::UseMemo, - format!("Expected a callback function to be passed to {kind_name}"), - Some(if kind == ManualMemoKind::UseCallback { - "The first argument to useCallback() must be a function to cache".to_string() - } else { - "The first argument to useMemo() must be a function that calculates a result to cache".to_string() - }), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc, - message: Some(if kind == ManualMemoKind::UseCallback { - "Expected a callback function".to_string() - } else { - "Expected a memoization function".to_string() - }), - identifier_name: None, - }), - ); - return None; - } - }; - - // Get the second arg (deps list), if present - let deps_list_place = args.get(1); - if deps_list_place.is_none() { - return Some(ExtractedMemoArgs { - fn_place, - deps_list: None, - deps_loc: None, - }); - } - - let deps_list_id = match deps_list_place { - Some(PlaceOrSpread::Place(p)) => Some(p.identifier), - _ => None, - }; - - let maybe_deps_list = deps_list_id.and_then(|id| sidemap.maybe_deps_lists.get(&id)); - - if maybe_deps_list.is_none() { - let loc = match deps_list_place { - Some(PlaceOrSpread::Place(p)) => p.loc.clone(), - _ => instr.loc.clone(), - }; - env.record_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::UseMemo, - format!("Expected the dependency list for {kind_name} to be an array literal"), - Some(format!( - "Expected the dependency list for {kind_name} to be an array literal" - )), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc, - message: Some(format!( - "Expected the dependency list for {kind_name} to be an array literal" - )), - identifier_name: None, - }), - ); - return None; - } - - let deps_info = maybe_deps_list.unwrap(); - let mut deps_list: Vec<ManualMemoDependency> = Vec::new(); - for dep in &deps_info.deps { - let maybe_dep = sidemap.maybe_deps.get(&dep.identifier); - if let Some(d) = maybe_dep { - deps_list.push(d.clone()); - } else { - env.record_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::UseMemo, - "Expected the dependency list to be an array of simple expressions (e.g. `x`, `x.y.z`, `x?.y?.z`)", - Some("Expected the dependency list to be an array of simple expressions (e.g. `x`, `x.y.z`, `x?.y?.z`)".to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: dep.loc.clone(), - message: Some("Expected the dependency list to be an array of simple expressions (e.g. `x`, `x.y.z`, `x?.y?.z`)".to_string()), - identifier_name: None, - }), - ); - } - } - - Some(ExtractedMemoArgs { - fn_place, - deps_list: Some(deps_list), - deps_loc: deps_info.loc.clone(), - }) -} - -// ============================================================================= -// findOptionalPlaces -// ============================================================================= - -fn find_optional_places(func: &HirFunction) -> Result<HashSet<IdentifierId>, CompilerDiagnostic> { - use react_compiler_hir::Terminal; - - let mut optionals = HashSet::new(); - for block in func.body.blocks.values() { - if let Terminal::Optional { - optional: true, - test, - fallthrough, - .. - } = &block.terminal - { - let optional_fallthrough = *fallthrough; - let mut test_block_id = *test; - loop { - let test_block = &func.body.blocks[&test_block_id]; - match &test_block.terminal { - Terminal::Branch { - consequent, - fallthrough, - .. - } => { - if *fallthrough == optional_fallthrough { - // Found it - let consequent_block = &func.body.blocks[consequent]; - if let Some(&last_instr_id) = consequent_block.instructions.last() { - let last_instr = &func.instructions[last_instr_id.0 as usize]; - if let InstructionValue::StoreLocal { value, .. } = - &last_instr.value - { - optionals.insert(value.identifier); - } - } - break; - } else { - test_block_id = *fallthrough; - } - } - Terminal::Optional { fallthrough, .. } - | Terminal::Logical { fallthrough, .. } - | Terminal::Sequence { fallthrough, .. } - | Terminal::Ternary { fallthrough, .. } => { - test_block_id = *fallthrough; - } - Terminal::MaybeThrow { continuation, .. } => { - test_block_id = *continuation; - } - other => { - // Invariant: unexpected terminal in optional - // In TS this throws CompilerError.invariant - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!( - "Unexpected terminal kind in optional: {:?}", - std::mem::discriminant(other) - ), - None, - )); - } - } - } - } - } - Ok(optionals) -} - -fn is_known_react_module(module: &str) -> bool { - let lower = module.to_lowercase(); - lower == "react" || lower == "react-dom" -} - -/// Returns the name to use for useMemo/useCallback detection, matching the TS -/// behavior of `getGlobalDeclaration` + `getHookKindForType`. -/// -/// - `Global`: use the binding name (matches globals.get(name) in TS) -/// - `ImportSpecifier` from known React module: use the `imported` name -/// - `ImportSpecifier` from unknown module: return None (TS returns a generic -/// custom hook type with hookKind 'Custom', not 'useMemo'/'useCallback') -/// - `ModuleLocal`: return None (same reason as above) -/// - `ImportDefault`/`ImportNamespace` from known React module: use the local name -/// - `ImportDefault`/`ImportNamespace` from unknown module: return None -fn get_hook_detection_name(binding: &NonLocalBinding) -> Option<&str> { - match binding { - NonLocalBinding::Global { name } => Some(name.as_str()), - NonLocalBinding::ImportSpecifier { - imported, module, .. - } => { - if is_known_react_module(module) { - Some(imported.as_str()) - } else { - None - } - } - NonLocalBinding::ImportDefault { name, module } - | NonLocalBinding::ImportNamespace { name, module } => { - if is_known_react_module(module) { - Some(name.as_str()) - } else { - None - } - } - NonLocalBinding::ModuleLocal { .. } => None, - } -} diff --git a/compiler/crates/react_compiler_optimization/src/inline_iifes.rs b/compiler/crates/react_compiler_optimization/src/inline_iifes.rs deleted file mode 100644 index 32d15eb83204..000000000000 --- a/compiler/crates/react_compiler_optimization/src/inline_iifes.rs +++ /dev/null @@ -1,418 +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. - -//! Inlines immediately invoked function expressions (IIFEs) to allow more -//! fine-grained memoization of the values they produce. -//! -//! Example: -//! ```text -//! const x = (() => { -//! const x = []; -//! x.push(foo()); -//! return x; -//! })(); -//! -//! => -//! -//! bb0: -//! // placeholder for the result, all return statements will assign here -//! let t0; -//! // Label allows using a goto (break) to exit out of the body -//! Label block=bb1 fallthrough=bb2 -//! bb1: -//! // code within the function expression -//! const x0 = []; -//! x0.push(foo()); -//! // return is replaced by assignment to the result variable... -//! t0 = x0; -//! // ...and a goto to the code after the function expression invocation -//! Goto bb2 -//! bb2: -//! // code after the IIFE call -//! const x = t0; -//! ``` -//! -//! If the inlined function has only one return, we avoid the labeled block -//! and fully inline the code. The original return is replaced with an assignment -//! to the IIFE's call expression lvalue. -//! -//! Analogous to TS `Inference/InlineImmediatelyInvokedFunctionExpressions.ts`. - -use std::collections::{HashMap, HashSet}; - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors; -use react_compiler_hir::{ - BasicBlock, BlockId, BlockKind, EvaluationOrder, FunctionId, GENERATED_SOURCE, GotoVariant, - HirFunction, IdentifierId, IdentifierName, Instruction, InstructionId, InstructionKind, - InstructionValue, LValue, Place, Terminal, -}; -use react_compiler_lowering::{ - create_temporary_place, get_reverse_postordered_blocks, mark_instruction_ids, mark_predecessors, -}; - -use crate::merge_consecutive_blocks::merge_consecutive_blocks; - -/// Inline immediately invoked function expressions into the enclosing function's -/// control flow graph. -pub fn inline_immediately_invoked_function_expressions( - func: &mut HirFunction, - env: &mut Environment, -) { - // Track all function expressions that are assigned to a temporary - let mut functions: HashMap<IdentifierId, FunctionId> = HashMap::new(); - // Functions that are inlined (by identifier id of the callee) - let mut inlined_functions: HashSet<IdentifierId> = HashSet::new(); - - // Iterate the *existing* blocks from the outer component to find IIFEs - // and inline them. During iteration we will modify `func` (by inlining the CFG - // of IIFEs) so we explicitly copy references to just the original - // function's block IDs first. As blocks are split to make room for IIFE calls, - // the split portions of the blocks will be added to this queue. - let mut queue: Vec<BlockId> = func.body.blocks.keys().copied().collect(); - let mut queue_idx = 0; - - 'queue: while queue_idx < queue.len() { - let block_id = queue[queue_idx]; - queue_idx += 1; - - let block = match func.body.blocks.get(&block_id) { - Some(b) => b, - None => continue, - }; - - // We can't handle labels inside expressions yet, so we don't inline IIFEs - // if they are in an expression block. - if !is_statement_block_kind(block.kind) { - continue; - } - - let num_instructions = block.instructions.len(); - for ii in 0..num_instructions { - let instr_id = func.body.blocks[&block_id].instructions[ii]; - let instr = &func.instructions[instr_id.0 as usize]; - - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } => { - let identifier_id = instr.lvalue.identifier; - if env.identifiers[identifier_id.0 as usize].name.is_none() { - functions.insert(identifier_id, lowered_func.func); - } - continue; - } - InstructionValue::CallExpression { callee, args, .. } => { - if !args.is_empty() { - // We don't support inlining when there are arguments - continue; - } - - let callee_id = callee.identifier; - let inner_func_id = match functions.get(&callee_id) { - Some(id) => *id, - None => continue, // Not invoking a local function expression - }; - - let inner_func = &env.functions[inner_func_id.0 as usize]; - if !inner_func.params.is_empty() || inner_func.is_async || inner_func.generator - { - // Can't inline functions with params, or async/generator functions - continue; - } - - // We know this function is used for an IIFE and can prune it later - inlined_functions.insert(callee_id); - - // Capture the lvalue from the call instruction - let call_lvalue = func.instructions[instr_id.0 as usize].lvalue.clone(); - let block_terminal_id = func.body.blocks[&block_id].terminal.evaluation_order(); - let block_terminal_loc = func.body.blocks[&block_id].terminal.loc().cloned(); - let block_kind = func.body.blocks[&block_id].kind; - - // Create a new block which will contain code following the IIFE call - let continuation_block_id = env.next_block_id(); - let continuation_instructions: Vec<InstructionId> = - func.body.blocks[&block_id].instructions[ii + 1..].to_vec(); - let continuation_terminal = func.body.blocks[&block_id].terminal.clone(); - let continuation_block = BasicBlock { - id: continuation_block_id, - instructions: continuation_instructions, - kind: block_kind, - phis: Vec::new(), - preds: indexmap::IndexSet::new(), - terminal: continuation_terminal, - }; - func.body - .blocks - .insert(continuation_block_id, continuation_block); - - // Trim the original block to contain instructions up to (but not including) - // the IIFE - func.body - .blocks - .get_mut(&block_id) - .unwrap() - .instructions - .truncate(ii); - - let has_single_return = - has_single_exit_return_terminal(&env.functions[inner_func_id.0 as usize]); - let inner_entry = env.functions[inner_func_id.0 as usize].body.entry; - - if has_single_return { - // Single-return path: simple goto replacement - func.body.blocks.get_mut(&block_id).unwrap().terminal = Terminal::Goto { - block: inner_entry, - id: block_terminal_id, - loc: block_terminal_loc, - variant: GotoVariant::Break, - }; - - // Take blocks and instructions from inner function - let inner_func = &mut env.functions[inner_func_id.0 as usize]; - let inner_blocks: Vec<(BlockId, BasicBlock)> = - inner_func.body.blocks.drain(..).collect(); - let inner_instructions: Vec<Instruction> = - inner_func.instructions.drain(..).collect(); - - // Append inner instructions first, then remap block instruction IDs - let instr_offset = func.instructions.len() as u32; - func.instructions.extend(inner_instructions); - - for (_, mut inner_block) in inner_blocks { - // Remap instruction IDs in the block - for iid in &mut inner_block.instructions { - *iid = InstructionId(iid.0 + instr_offset); - } - inner_block.preds.clear(); - - if let Terminal::Return { - value, - id: ret_id, - loc: ret_loc, - .. - } = &inner_block.terminal - { - // Replace return with LoadLocal + goto - let load_instr = Instruction { - id: EvaluationOrder(0), - loc: ret_loc.clone(), - lvalue: call_lvalue.clone(), - value: InstructionValue::LoadLocal { - place: value.clone(), - loc: ret_loc.clone(), - }, - effects: None, - }; - let load_instr_id = InstructionId(func.instructions.len() as u32); - func.instructions.push(load_instr); - inner_block.instructions.push(load_instr_id); - - let ret_id = *ret_id; - let ret_loc = ret_loc.clone(); - inner_block.terminal = Terminal::Goto { - block: continuation_block_id, - id: ret_id, - loc: ret_loc, - variant: GotoVariant::Break, - }; - } - - func.body.blocks.insert(inner_block.id, inner_block); - } - } else { - // Multi-return path: uses LabelTerminal - let result = call_lvalue.clone(); - - // Set block terminal to Label - func.body.blocks.get_mut(&block_id).unwrap().terminal = Terminal::Label { - block: inner_entry, - id: EvaluationOrder(0), - fallthrough: continuation_block_id, - loc: block_terminal_loc, - }; - - // Declare the IIFE temporary - declare_temporary(env, func, block_id, &result); - - // Promote the temporary with a name as we require this to persist - let identifier_id = result.identifier; - if env.identifiers[identifier_id.0 as usize].name.is_none() { - promote_temporary(env, identifier_id); - } - - // Take blocks and instructions from inner function - let inner_func = &mut env.functions[inner_func_id.0 as usize]; - let inner_blocks: Vec<(BlockId, BasicBlock)> = - inner_func.body.blocks.drain(..).collect(); - let inner_instructions: Vec<Instruction> = - inner_func.instructions.drain(..).collect(); - - // Append inner instructions first, then remap block instruction IDs - let instr_offset = func.instructions.len() as u32; - func.instructions.extend(inner_instructions); - - for (_, mut inner_block) in inner_blocks { - for iid in &mut inner_block.instructions { - *iid = InstructionId(iid.0 + instr_offset); - } - inner_block.preds.clear(); - - // Rewrite return terminals to StoreLocal + goto - if matches!(inner_block.terminal, Terminal::Return { .. }) { - rewrite_block( - env, - &mut func.instructions, - &mut inner_block, - continuation_block_id, - &result, - ); - } - - func.body.blocks.insert(inner_block.id, inner_block); - } - } - - // Ensure we visit the continuation block, since there may have been - // sequential IIFEs that need to be visited. - queue.push(continuation_block_id); - continue 'queue; - } - _ => { - // Any other use of a function expression means it isn't an IIFE - for id in visitors::each_instruction_value_operand_ids(&instr.value, env) { - functions.remove(&id); - } - } - } - } - } - - if !inlined_functions.is_empty() { - // Remove instructions that define lambdas which we inlined - for block in func.body.blocks.values_mut() { - block.instructions.retain(|instr_id| { - let instr = &func.instructions[instr_id.0 as usize]; - !inlined_functions.contains(&instr.lvalue.identifier) - }); - } - - // If terminals have changed then blocks may have become newly unreachable. - // Re-run minification of the graph (incl reordering instruction ids). - func.body.blocks = get_reverse_postordered_blocks(&func.body, &func.instructions); - mark_instruction_ids(&mut func.body, &mut func.instructions); - mark_predecessors(&mut func.body); - merge_consecutive_blocks(func, &mut env.functions); - } -} - -/// Returns true for "block" and "catch" block kinds which correspond to statements -/// in the source. -fn is_statement_block_kind(kind: BlockKind) -> bool { - matches!(kind, BlockKind::Block | BlockKind::Catch) -} - -/// Returns true if the function has a single exit terminal (throw/return) which is a return. -fn has_single_exit_return_terminal(func: &HirFunction) -> bool { - let mut has_return = false; - let mut exit_count = 0; - for block in func.body.blocks.values() { - match &block.terminal { - Terminal::Return { .. } => { - has_return = true; - exit_count += 1; - } - Terminal::Throw { .. } => { - exit_count += 1; - } - _ => {} - } - } - exit_count == 1 && has_return -} - -/// Rewrites the block so that all `return` terminals are replaced: -/// * Add a StoreLocal <return_value> = <terminal.value> -/// * Replace the terminal with a Goto to <return_target> -fn rewrite_block( - env: &mut Environment, - instructions: &mut Vec<Instruction>, - block: &mut BasicBlock, - return_target: BlockId, - return_value: &Place, -) { - if let Terminal::Return { - value, - loc: ret_loc, - .. - } = &block.terminal - { - let store_lvalue = create_temporary_place(env, ret_loc.clone()); - let store_instr = Instruction { - id: EvaluationOrder(0), - loc: ret_loc.clone(), - lvalue: store_lvalue, - value: InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Reassign, - place: return_value.clone(), - }, - value: value.clone(), - type_annotation: None, - loc: ret_loc.clone(), - }, - effects: None, - }; - let store_instr_id = InstructionId(instructions.len() as u32); - instructions.push(store_instr); - block.instructions.push(store_instr_id); - - let ret_loc = ret_loc.clone(); - block.terminal = Terminal::Goto { - block: return_target, - id: EvaluationOrder(0), - variant: GotoVariant::Break, - loc: ret_loc, - }; - } -} - -/// Emits a DeclareLocal instruction for the result temporary. -fn declare_temporary( - env: &mut Environment, - func: &mut HirFunction, - block_id: BlockId, - result: &Place, -) { - let declare_lvalue = create_temporary_place(env, result.loc.clone()); - let declare_instr = Instruction { - id: EvaluationOrder(0), - loc: GENERATED_SOURCE, - lvalue: declare_lvalue, - value: InstructionValue::DeclareLocal { - lvalue: LValue { - place: result.clone(), - kind: InstructionKind::Let, - }, - type_annotation: None, - loc: result.loc.clone(), - }, - effects: None, - }; - let instr_id = InstructionId(func.instructions.len() as u32); - func.instructions.push(declare_instr); - func.body - .blocks - .get_mut(&block_id) - .unwrap() - .instructions - .push(instr_id); -} - -/// Promote a temporary identifier to a named identifier. -fn promote_temporary(env: &mut Environment, identifier_id: IdentifierId) { - let decl_id = env.identifiers[identifier_id.0 as usize].declaration_id; - env.identifiers[identifier_id.0 as usize].name = - Some(IdentifierName::Promoted(format!("#t{}", decl_id.0))); -} diff --git a/compiler/crates/react_compiler_optimization/src/lib.rs b/compiler/crates/react_compiler_optimization/src/lib.rs deleted file mode 100644 index bdb7c276a28f..000000000000 --- a/compiler/crates/react_compiler_optimization/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -pub mod constant_propagation; -pub mod dead_code_elimination; -pub mod drop_manual_memoization; -pub mod inline_iifes; -pub mod merge_consecutive_blocks; -pub mod name_anonymous_functions; -pub mod optimize_for_ssr; -pub mod optimize_props_method_calls; -pub mod outline_functions; -pub mod outline_jsx; -pub mod prune_maybe_throws; -pub mod prune_unused_labels_hir; - -pub use constant_propagation::constant_propagation; -pub use dead_code_elimination::dead_code_elimination; -pub use drop_manual_memoization::drop_manual_memoization; -pub use inline_iifes::inline_immediately_invoked_function_expressions; -pub use name_anonymous_functions::name_anonymous_functions; -pub use optimize_for_ssr::optimize_for_ssr; -pub use optimize_props_method_calls::optimize_props_method_calls; -pub use outline_functions::outline_functions; -pub use outline_jsx::outline_jsx; -pub use prune_maybe_throws::prune_maybe_throws; -pub use prune_unused_labels_hir::prune_unused_labels_hir; diff --git a/compiler/crates/react_compiler_optimization/src/merge_consecutive_blocks.rs b/compiler/crates/react_compiler_optimization/src/merge_consecutive_blocks.rs deleted file mode 100644 index 3ef96f64dbaf..000000000000 --- a/compiler/crates/react_compiler_optimization/src/merge_consecutive_blocks.rs +++ /dev/null @@ -1,214 +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. - -//! Merges sequences of blocks that will always execute consecutively — -//! i.e., where the predecessor always transfers control to the successor -//! (ends in a goto) and where the predecessor is the only predecessor -//! for that successor (no other way to reach the successor). -//! -//! Value/loop blocks are left alone because they cannot be merged without -//! breaking the structure of the high-level terminals that reference them. -//! -//! Analogous to TS `HIR/MergeConsecutiveBlocks.ts`. - -use std::collections::{HashMap, HashSet}; - -use react_compiler_hir::visitors; -use react_compiler_hir::{ - AliasingEffect, BlockId, BlockKind, Effect, GENERATED_SOURCE, HirFunction, Instruction, - InstructionId, InstructionValue, Place, Terminal, -}; -use react_compiler_lowering::mark_predecessors; -use react_compiler_ssa::enter_ssa::placeholder_function; - -/// Merge consecutive blocks in the function's CFG, including inner functions. -pub fn merge_consecutive_blocks(func: &mut HirFunction, functions: &mut [HirFunction]) { - // Collect inner function IDs for recursive processing - let inner_func_ids: Vec<usize> = func - .body - .blocks - .values() - .flat_map(|block| block.instructions.iter()) - .filter_map(|instr_id| { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - Some(lowered_func.func.0 as usize) - } - _ => None, - } - }) - .collect(); - - // Recursively merge consecutive blocks in inner functions - for func_id in inner_func_ids { - // Use std::mem::replace to temporarily take the inner function out, - // process it, then put it back (standard borrow checker workaround) - let mut inner_func = std::mem::replace(&mut functions[func_id], placeholder_function()); - merge_consecutive_blocks(&mut inner_func, functions); - functions[func_id] = inner_func; - } - - // Build fallthrough set - let mut fallthrough_blocks: HashSet<BlockId> = HashSet::new(); - for block in func.body.blocks.values() { - if let Some(ft) = visitors::terminal_fallthrough(&block.terminal) { - fallthrough_blocks.insert(ft); - } - } - - let mut merged = MergedBlocks::new(); - - // Collect block IDs for iteration (since we modify during iteration) - let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect(); - - for block_id in &block_ids { - let block = match func.body.blocks.get(block_id) { - Some(b) => b, - None => continue, // already removed - }; - - if block.preds.len() != 1 - || block.kind != BlockKind::Block - || fallthrough_blocks.contains(block_id) - { - continue; - } - - let original_pred_id = *block.preds.iter().next().unwrap(); - let pred_id = merged.get(original_pred_id); - - // Check predecessor exists and ends in goto with block kind - let pred_is_mergeable = func - .body - .blocks - .get(&pred_id) - .map(|p| matches!(p.terminal, Terminal::Goto { .. }) && p.kind == BlockKind::Block) - .unwrap_or(false); - - if !pred_is_mergeable { - continue; - } - - // Get evaluation order from predecessor's terminal (for phi instructions) - let eval_order = func.body.blocks[&pred_id].terminal.evaluation_order(); - - // Collect phi data from the block being merged - let phis: Vec<_> = block - .phis - .iter() - .map(|phi| { - assert_eq!( - phi.operands.len(), - 1, - "Found a block with a single predecessor but where a phi has multiple ({}) operands", - phi.operands.len() - ); - let operand = phi.operands.values().next().unwrap().clone(); - (phi.place.identifier, operand) - }) - .collect(); - let block_instr_ids = block.instructions.clone(); - let block_terminal = block.terminal.clone(); - - // Create phi instructions and add to instruction table - let mut new_instr_ids = Vec::new(); - for (identifier, operand) in phis { - let lvalue = Place { - identifier, - effect: Effect::ConditionallyMutate, - reactive: false, - loc: GENERATED_SOURCE, - }; - let instr = Instruction { - id: eval_order, - lvalue: lvalue.clone(), - value: InstructionValue::LoadLocal { - place: operand.clone(), - loc: GENERATED_SOURCE, - }, - loc: GENERATED_SOURCE, - effects: Some(vec![AliasingEffect::Alias { - from: operand, - into: lvalue, - }]), - }; - let instr_id = InstructionId(func.instructions.len() as u32); - func.instructions.push(instr); - new_instr_ids.push(instr_id); - } - - // Apply merge to predecessor - let pred = func.body.blocks.get_mut(&pred_id).unwrap(); - pred.instructions.extend(new_instr_ids); - pred.instructions.extend(block_instr_ids); - pred.terminal = block_terminal; - - // Record merge and remove block - merged.merge(*block_id, pred_id); - func.body.blocks.shift_remove(block_id); - } - - // Update phi operands for merged blocks - for block in func.body.blocks.values_mut() { - for phi in &mut block.phis { - let updates: Vec<_> = phi - .operands - .iter() - .filter_map(|(pred_id, operand)| { - let mapped = merged.get(*pred_id); - if mapped != *pred_id { - Some((*pred_id, mapped, operand.clone())) - } else { - None - } - }) - .collect(); - for (old_id, new_id, operand) in updates { - phi.operands.shift_remove(&old_id); - phi.operands.insert(new_id, operand); - } - } - } - - mark_predecessors(&mut func.body); - - // Update terminal successors (including fallthroughs) for merged blocks - for block in func.body.blocks.values_mut() { - visitors::map_terminal_successors(&mut block.terminal, &mut |block_id| { - merged.get(block_id) - }); - } -} - -/// Tracks which blocks have been merged and into which target. -struct MergedBlocks { - map: HashMap<BlockId, BlockId>, -} - -impl MergedBlocks { - fn new() -> Self { - Self { - map: HashMap::new(), - } - } - - /// Record that `block` was merged into `into`. - fn merge(&mut self, block: BlockId, into: BlockId) { - let target = self.get(into); - self.map.insert(block, target); - } - - /// Get the id of the block that `block` has been merged into. - /// Transitive: if A merged into B which merged into C, get(A) returns C. - fn get(&self, block: BlockId) -> BlockId { - let mut current = block; - while let Some(&target) = self.map.get(¤t) { - current = target; - } - current - } -} diff --git a/compiler/crates/react_compiler_optimization/src/name_anonymous_functions.rs b/compiler/crates/react_compiler_optimization/src/name_anonymous_functions.rs deleted file mode 100644 index 8f9c2f080d39..000000000000 --- a/compiler/crates/react_compiler_optimization/src/name_anonymous_functions.rs +++ /dev/null @@ -1,313 +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. - -//! Port of NameAnonymousFunctions from TypeScript. -//! -//! Generates descriptive names for anonymous function expressions based on -//! how they are used (assigned to variables, passed as arguments to hooks/functions, -//! used as JSX props, etc.). These names appear in React DevTools and error stacks. -//! -//! Conditional on `env.config.enable_name_anonymous_functions`. - -use std::collections::HashMap; - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::object_shape::HookKind; -use react_compiler_hir::{ - FunctionId, HirFunction, IdentifierId, IdentifierName, Instruction, InstructionValue, - JsxAttribute, JsxTag, PlaceOrSpread, -}; - -/// Assign generated names to anonymous function expressions. -/// -/// Ported from TS `nameAnonymousFunctions` in `Transform/NameAnonymousFunctions.ts`. -pub fn name_anonymous_functions(func: &mut HirFunction, env: &mut Environment) { - let fn_id = match &func.id { - Some(id) => id.clone(), - None => return, - }; - - let nodes = name_anonymous_functions_impl(func, env); - - fn visit(node: &Node, prefix: &str, updates: &mut Vec<(FunctionId, String)>) { - if node.generated_name.is_some() && node.existing_name_hint.is_none() { - // Only add the prefix to anonymous functions regardless of nesting depth - let name = format!("{}{}]", prefix, node.generated_name.as_ref().unwrap()); - updates.push((node.function_id, name)); - } - // Whether or not we generated a name for the function at this node, - // traverse into its nested functions to assign them names - let fallback; - let label = if let Some(ref gen_name) = node.generated_name { - gen_name.as_str() - } else if let Some(ref existing) = node.fn_name { - existing.as_str() - } else { - fallback = "<anonymous>"; - fallback - }; - let next_prefix = format!("{}{} > ", prefix, label); - for inner in &node.inner { - visit(inner, &next_prefix, updates); - } - } - - let mut updates: Vec<(FunctionId, String)> = Vec::new(); - let prefix = format!("{}[", fn_id); - for node in &nodes { - visit(node, &prefix, &mut updates); - } - - if updates.is_empty() { - return; - } - let update_map: HashMap<FunctionId, &String> = - updates.iter().map(|(fid, name)| (*fid, name)).collect(); - - // Apply name updates to the inner HirFunction in the arena - for (function_id, name) in &updates { - env.functions[function_id.0 as usize].name_hint = Some(name.clone()); - } - - // Update name_hint on FunctionExpression instruction values in the outer function - apply_name_hints_to_instructions(&mut func.instructions, &update_map); - - // Update name_hint on FunctionExpression instruction values in all arena functions - for i in 0..env.functions.len() { - // We need to temporarily take the instructions to avoid borrow issues - let mut instructions = std::mem::take(&mut env.functions[i].instructions); - apply_name_hints_to_instructions(&mut instructions, &update_map); - env.functions[i].instructions = instructions; - } -} - -/// Apply name hints to FunctionExpression instruction values. -fn apply_name_hints_to_instructions( - instructions: &mut [Instruction], - update_map: &HashMap<FunctionId, &String>, -) { - for instr in instructions.iter_mut() { - if let InstructionValue::FunctionExpression { - lowered_func, - name_hint, - .. - } = &mut instr.value - { - if let Some(new_name) = update_map.get(&lowered_func.func) { - *name_hint = Some((*new_name).clone()); - } - } - } -} - -struct Node { - /// The FunctionId for the inner function (via lowered_func.func) - function_id: FunctionId, - /// The generated name for this anonymous function (set based on usage context) - generated_name: Option<String>, - /// The existing `name` on the FunctionExpression (non-anonymous functions have this) - fn_name: Option<String>, - /// Whether the inner HirFunction already has a name_hint - existing_name_hint: Option<String>, - /// Nested function nodes - inner: Vec<Node>, -} - -fn name_anonymous_functions_impl(func: &HirFunction, env: &Environment) -> Vec<Node> { - // Functions that we track to generate names for - let mut functions: HashMap<IdentifierId, usize> = HashMap::new(); - // Tracks temporaries that read from variables/globals/properties - let mut names: HashMap<IdentifierId, String> = HashMap::new(); - // Tracks all function nodes - let mut nodes: Vec<Node> = Vec::new(); - - for block in func.body.blocks.values() { - for instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - match &instr.value { - InstructionValue::LoadGlobal { binding, .. } => { - names.insert(lvalue_id, binding.name().to_string()); - } - InstructionValue::LoadContext { place, .. } - | InstructionValue::LoadLocal { place, .. } => { - let ident = &env.identifiers[place.identifier.0 as usize]; - if let Some(IdentifierName::Named(ref name)) = ident.name { - names.insert(lvalue_id, name.clone()); - } - // If the loaded place was tracked as a function, propagate - if let Some(&node_idx) = functions.get(&place.identifier) { - functions.insert(lvalue_id, node_idx); - } - } - InstructionValue::PropertyLoad { - object, property, .. - } => { - if let Some(object_name) = names.get(&object.identifier) { - names.insert(lvalue_id, format!("{}.{}", object_name, property)); - } - } - InstructionValue::FunctionExpression { - name, lowered_func, .. - } => { - let inner_func = &env.functions[lowered_func.func.0 as usize]; - let inner = name_anonymous_functions_impl(inner_func, env); - let node = Node { - function_id: lowered_func.func, - generated_name: None, - fn_name: name.clone(), - existing_name_hint: inner_func.name_hint.clone(), - inner, - }; - let idx = nodes.len(); - nodes.push(node); - if name.is_none() { - // Only generate names for anonymous functions - functions.insert(lvalue_id, idx); - } - } - InstructionValue::StoreContext { - lvalue: store_lvalue, - value, - .. - } - | InstructionValue::StoreLocal { - lvalue: store_lvalue, - value, - .. - } => { - if let Some(&node_idx) = functions.get(&value.identifier) { - let node = &mut nodes[node_idx]; - let var_ident = &env.identifiers[store_lvalue.place.identifier.0 as usize]; - if node.generated_name.is_none() { - if let Some(IdentifierName::Named(ref var_name)) = var_ident.name { - node.generated_name = Some(var_name.clone()); - functions.remove(&value.identifier); - } - } - } - } - InstructionValue::CallExpression { callee, args, .. } => { - handle_call( - env, - func, - callee.identifier, - args, - &mut functions, - &names, - &mut nodes, - ); - } - InstructionValue::MethodCall { property, args, .. } => { - handle_call( - env, - func, - property.identifier, - args, - &mut functions, - &names, - &mut nodes, - ); - } - InstructionValue::JsxExpression { tag, props, .. } => { - for attr in props { - match attr { - JsxAttribute::SpreadAttribute { .. } => continue, - JsxAttribute::Attribute { - name: attr_name, - place, - } => { - if let Some(&node_idx) = functions.get(&place.identifier) { - let node = &mut nodes[node_idx]; - if node.generated_name.is_none() { - let element_name = match tag { - JsxTag::Builtin(builtin) => Some(builtin.name.clone()), - JsxTag::Place(tag_place) => { - names.get(&tag_place.identifier).cloned() - } - }; - let prop_name = match element_name { - None => attr_name.clone(), - Some(ref el_name) => { - format!("<{}>.{}", el_name, attr_name) - } - }; - node.generated_name = Some(prop_name); - functions.remove(&place.identifier); - } - } - } - } - } - } - _ => {} - } - } - } - - nodes -} - -/// Handle CallExpression / MethodCall to generate names for function arguments. -fn handle_call( - env: &Environment, - _func: &HirFunction, - callee_id: IdentifierId, - args: &[PlaceOrSpread], - functions: &mut HashMap<IdentifierId, usize>, - names: &HashMap<IdentifierId, String>, - nodes: &mut Vec<Node>, -) { - let callee_ident = &env.identifiers[callee_id.0 as usize]; - let callee_ty = &env.types[callee_ident.type_.0 as usize]; - let hook_kind = env.get_hook_kind_for_type(callee_ty).ok().flatten(); - - let callee_name: String = if let Some(hk) = hook_kind { - if *hk != HookKind::Custom { - hk.to_string() - } else { - names - .get(&callee_id) - .cloned() - .unwrap_or_else(|| "(anonymous)".to_string()) - } - } else { - names - .get(&callee_id) - .cloned() - .unwrap_or_else(|| "(anonymous)".to_string()) - }; - - // Count how many args are tracked functions - let fn_arg_count = args - .iter() - .filter(|arg| { - if let PlaceOrSpread::Place(p) = arg { - functions.contains_key(&p.identifier) - } else { - false - } - }) - .count(); - - for (i, arg) in args.iter().enumerate() { - let place = match arg { - PlaceOrSpread::Spread(_) => continue, - PlaceOrSpread::Place(p) => p, - }; - if let Some(&node_idx) = functions.get(&place.identifier) { - let node = &mut nodes[node_idx]; - if node.generated_name.is_none() { - let generated_name = if fn_arg_count > 1 { - format!("{}(arg{})", callee_name, i) - } else { - format!("{}()", callee_name) - }; - node.generated_name = Some(generated_name); - functions.remove(&place.identifier); - } - } - } -} diff --git a/compiler/crates/react_compiler_optimization/src/optimize_for_ssr.rs b/compiler/crates/react_compiler_optimization/src/optimize_for_ssr.rs deleted file mode 100644 index b4a6f251d4dd..000000000000 --- a/compiler/crates/react_compiler_optimization/src/optimize_for_ssr.rs +++ /dev/null @@ -1,351 +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. - -//! Optimizes the code for running in an SSR environment. -//! -//! Assumes that setState will not be called during render during initial mount, -//! which allows inlining useState/useReducer. -//! -//! Optimizations: -//! - Inline useState/useReducer -//! - Remove effects (useEffect, useLayoutEffect, useInsertionEffect) -//! - Remove event handlers (functions that call setState or startTransition) -//! - Remove known event handler props and ref props from builtin JSX tags -//! - Inline useEffectEvent to its argument -//! -//! Ported from TypeScript `src/Optimization/OptimizeForSSR.ts`. - -use std::collections::HashMap; - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::object_shape::HookKind; -use react_compiler_hir::visitors::{each_instruction_value_operand, each_terminal_operand}; -use react_compiler_hir::{ - ArrayPatternElement, HirFunction, IdentifierId, InstructionValue, PlaceOrSpread, - PrimitiveValue, is_set_state_type, is_start_transition_type, -}; - -/// Optimizes a function for SSR by inlining state hooks, removing effects, -/// removing event handlers, and stripping known event handler / ref JSX props. -/// -/// Corresponds to TS `optimizeForSSR(fn: HIRFunction): void`. -pub fn optimize_for_ssr(func: &mut HirFunction, env: &Environment) { - // Phase 1: Identify useState/useReducer calls that can be safely inlined. - // - // For useState(initialValue) where initialValue is primitive/object/array, - // store a LoadLocal of the initial value. - // - // For useReducer(reducer, initialArg) store a LoadLocal of initialArg. - // For useReducer(reducer, initialArg, init) store a CallExpression of init(initialArg). - // - // Any use of the hook return other than the expected destructuring pattern - // prevents inlining (we delete from inlined_state if we see the identifier used - // as an operand elsewhere). - let mut inlined_state: HashMap<IdentifierId, InlinedStateReplacement> = HashMap::new(); - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::Destructure { value, lvalue, .. } => { - if inlined_state.contains_key(&env.identifiers[value.identifier.0 as usize].id) - { - if let react_compiler_hir::Pattern::Array(arr) = &lvalue.pattern { - if !arr.items.is_empty() { - if let ArrayPatternElement::Place(_) = &arr.items[0] { - // Allow destructuring of inlined states - continue; - } - } - } - } - } - InstructionValue::MethodCall { property, args, .. } - | InstructionValue::CallExpression { - callee: property, - args, - .. - } => { - // Determine callee based on instruction kind - let callee_id = property.identifier; - let hook_kind = get_hook_kind(env, callee_id); - match hook_kind { - Some(HookKind::UseReducer) => { - if args.len() == 2 { - if let (PlaceOrSpread::Place(_), PlaceOrSpread::Place(arg)) = - (&args[0], &args[1]) - { - let lvalue_id = - env.identifiers[instr.lvalue.identifier.0 as usize].id; - inlined_state.insert( - lvalue_id, - InlinedStateReplacement::LoadLocal { - place: arg.clone(), - loc: arg.loc, - }, - ); - } - } else if args.len() == 3 { - if let ( - PlaceOrSpread::Place(_), - PlaceOrSpread::Place(arg), - PlaceOrSpread::Place(initializer), - ) = (&args[0], &args[1], &args[2]) - { - let lvalue_id = - env.identifiers[instr.lvalue.identifier.0 as usize].id; - let call_loc = instr.value.loc().copied(); - inlined_state.insert( - lvalue_id, - InlinedStateReplacement::CallExpression { - callee: initializer.clone(), - arg: arg.clone(), - loc: call_loc, - }, - ); - } - } - } - Some(HookKind::UseState) => { - if args.len() == 1 { - if let PlaceOrSpread::Place(arg) = &args[0] { - let arg_type = &env.types[env.identifiers - [arg.identifier.0 as usize] - .type_ - .0 - as usize]; - if react_compiler_hir::is_primitive_type(arg_type) - || react_compiler_hir::is_plain_object_type(arg_type) - || react_compiler_hir::is_array_type(arg_type) - { - let lvalue_id = - env.identifiers[instr.lvalue.identifier.0 as usize].id; - inlined_state.insert( - lvalue_id, - InlinedStateReplacement::LoadLocal { - place: arg.clone(), - loc: arg.loc, - }, - ); - } - } - } - } - _ => {} - } - } - _ => {} - } - - // Any use of useState/useReducer return besides destructuring prevents inlining - if !inlined_state.is_empty() { - let operands = each_instruction_value_operand(&instr.value, env); - for operand in &operands { - let id = env.identifiers[operand.identifier.0 as usize].id; - inlined_state.remove(&id); - } - } - } - if !inlined_state.is_empty() { - let operands = each_terminal_operand(&block.terminal); - for operand in &operands { - let id = env.identifiers[operand.identifier.0 as usize].id; - inlined_state.remove(&id); - } - } - } - - // Phase 2: Apply transformations - // - // - Replace FunctionExpression with Primitive(undefined) if it calls setState/startTransition - // - Remove known event handler props and ref props from builtin JSX tags - // - Replace Destructure of inlined state with StoreLocal - // - Replace useEffectEvent(fn) with LoadLocal(fn) - // - Replace useEffect/useLayoutEffect/useInsertionEffect with Primitive(undefined) - // - Replace useState/useReducer with their inlined replacement - for (_block_id, block) in &mut func.body.blocks { - for &instr_id in &block.instructions { - let instr = &mut func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { - lowered_func, loc, .. - } => { - let inner_func = &env.functions[lowered_func.func.0 as usize]; - if has_known_non_render_call(inner_func, env) { - let loc = *loc; - instr.value = InstructionValue::Primitive { - value: PrimitiveValue::Undefined, - loc, - }; - } - } - InstructionValue::JsxExpression { tag, .. } => { - if let react_compiler_hir::JsxTag::Builtin(builtin) = tag { - // Only optimize non-custom-element builtin tags - if !builtin.name.contains('-') { - let tag_name = builtin.name.clone(); - // Retain only props that are not known event handlers and not "ref" - if let InstructionValue::JsxExpression { props, .. } = &mut instr.value - { - props.retain(|prop| match prop { - react_compiler_hir::JsxAttribute::SpreadAttribute { - .. - } => true, - react_compiler_hir::JsxAttribute::Attribute { - name, .. - } => !is_known_event_handler(&tag_name, name) && name != "ref", - }); - } - } - } - } - InstructionValue::Destructure { value, lvalue, loc } => { - let value_id = env.identifiers[value.identifier.0 as usize].id; - if inlined_state.contains_key(&value_id) { - // Invariant: destructuring pattern must be ArrayPattern with at least one Identifier item - if let react_compiler_hir::Pattern::Array(arr) = &lvalue.pattern { - if !arr.items.is_empty() { - if let ArrayPatternElement::Place(first_place) = &arr.items[0] { - let loc = *loc; - let kind = lvalue.kind; - let store = InstructionValue::StoreLocal { - lvalue: react_compiler_hir::LValue { - place: first_place.clone(), - kind, - }, - value: value.clone(), - type_annotation: None, - loc, - }; - instr.value = store; - } - } - } - } - } - InstructionValue::MethodCall { - property, - args, - loc, - .. - } - | InstructionValue::CallExpression { - callee: property, - args, - loc, - .. - } => { - let callee_id = property.identifier; - let hook_kind = get_hook_kind(env, callee_id); - match hook_kind { - Some(HookKind::UseEffectEvent) => { - if args.len() == 1 { - if let PlaceOrSpread::Place(arg) = &args[0] { - let loc = *loc; - instr.value = InstructionValue::LoadLocal { - place: arg.clone(), - loc, - }; - } - } - } - Some( - HookKind::UseEffect - | HookKind::UseLayoutEffect - | HookKind::UseInsertionEffect, - ) => { - let loc = *loc; - instr.value = InstructionValue::Primitive { - value: PrimitiveValue::Undefined, - loc, - }; - } - Some(HookKind::UseReducer | HookKind::UseState) => { - let lvalue_id = env.identifiers[instr.lvalue.identifier.0 as usize].id; - if let Some(replacement) = inlined_state.get(&lvalue_id) { - instr.value = match replacement { - InlinedStateReplacement::LoadLocal { place, loc } => { - InstructionValue::LoadLocal { - place: place.clone(), - loc: *loc, - } - } - InlinedStateReplacement::CallExpression { - callee, - arg, - loc, - } => InstructionValue::CallExpression { - callee: callee.clone(), - args: vec![PlaceOrSpread::Place(arg.clone())], - loc: *loc, - }, - }; - } - } - _ => {} - } - } - _ => {} - } - } - } -} - -/// Replacement values for inlined useState/useReducer calls. -#[derive(Debug, Clone)] -enum InlinedStateReplacement { - /// Replace with `LoadLocal { place }` — used for useState and useReducer(reducer, initialArg) - LoadLocal { - place: react_compiler_hir::Place, - loc: Option<react_compiler_hir::SourceLocation>, - }, - /// Replace with `CallExpression { callee, args: [arg] }` — used for useReducer(reducer, initialArg, init) - CallExpression { - callee: react_compiler_hir::Place, - arg: react_compiler_hir::Place, - loc: Option<react_compiler_hir::SourceLocation>, - }, -} - -/// Returns true if the function body contains a call to setState or startTransition. -/// This identifies functions that are event handlers and can be replaced with undefined -/// during SSR. -/// -/// Corresponds to TS `hasKnownNonRenderCall(fn: HIRFunction): boolean`. -fn has_known_non_render_call(func: &HirFunction, env: &Environment) -> bool { - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - if let InstructionValue::CallExpression { callee, .. } = &instr.value { - let callee_type = - &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize]; - if is_set_state_type(callee_type) || is_start_transition_type(callee_type) { - return true; - } - } - } - } - false -} - -/// Returns true if the prop name matches the known event handler pattern `on[A-Z]`. -fn is_known_event_handler(_tag: &str, prop: &str) -> bool { - if prop.len() < 3 { - return false; - } - if !prop.starts_with("on") { - return false; - } - let third_char = prop.as_bytes()[2]; - third_char.is_ascii_uppercase() -} - -/// Get the hook kind for an identifier, if its type represents a hook. -fn get_hook_kind(env: &Environment, identifier_id: IdentifierId) -> Option<HookKind> { - env.get_hook_kind_for_id(identifier_id) - .ok() - .flatten() - .cloned() -} diff --git a/compiler/crates/react_compiler_optimization/src/optimize_props_method_calls.rs b/compiler/crates/react_compiler_optimization/src/optimize_props_method_calls.rs deleted file mode 100644 index 1effca3bd6db..000000000000 --- a/compiler/crates/react_compiler_optimization/src/optimize_props_method_calls.rs +++ /dev/null @@ -1,56 +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. - -//! Converts `MethodCall` instructions on props objects into `CallExpression` -//! instructions. -//! -//! When the receiver of a method call is typed as the component's props object, -//! we can safely convert the method call `props.foo(args)` into a direct call -//! `foo(args)` using the property as the callee. This simplifies downstream -//! analysis by removing the receiver dependency. -//! -//! Analogous to TS `Optimization/OptimizePropsMethodCalls.ts`. - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{HirFunction, InstructionValue, is_props_type}; - -pub fn optimize_props_method_calls(func: &mut HirFunction, env: &Environment) { - for (_block_id, block) in &func.body.blocks { - let instruction_ids: Vec<_> = block.instructions.clone(); - for instr_id in instruction_ids { - let instr = &mut func.instructions[instr_id.0 as usize]; - let should_replace = matches!( - &instr.value, - InstructionValue::MethodCall { receiver, .. } - if { - let identifier = &env.identifiers[receiver.identifier.0 as usize]; - let ty = &env.types[identifier.type_.0 as usize]; - is_props_type(ty) - } - ); - if should_replace { - // Take the old value out, replacing with a temporary. - // The if-let is guaranteed to match since we checked above. - let old = - std::mem::replace(&mut instr.value, InstructionValue::Debugger { loc: None }); - match old { - InstructionValue::MethodCall { - property, - args, - loc, - .. - } => { - instr.value = InstructionValue::CallExpression { - callee: property, - args, - loc, - }; - } - _ => unreachable!(), - } - } - } - } -} diff --git a/compiler/crates/react_compiler_optimization/src/outline_functions.rs b/compiler/crates/react_compiler_optimization/src/outline_functions.rs deleted file mode 100644 index 8c51ecc2ed7d..000000000000 --- a/compiler/crates/react_compiler_optimization/src/outline_functions.rs +++ /dev/null @@ -1,128 +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. - -//! Port of OutlineFunctions from TypeScript (`Optimization/OutlineFunctions.ts`). -//! -//! Extracts anonymous function expressions that do not close over any local -//! variables into top-level outlined functions. The original instruction is -//! replaced with a `LoadGlobal` referencing the outlined function's generated name. -//! -//! Conditional on `env.config.enable_function_outlining`. - -use std::collections::HashSet; - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{ - FunctionId, HirFunction, IdentifierId, InstructionValue, NonLocalBinding, -}; -use react_compiler_ssa::enter_ssa::placeholder_function; - -/// Outline anonymous function expressions that have no captured context variables. -/// -/// Ported from TS `outlineFunctions` in `Optimization/OutlineFunctions.ts`. -pub fn outline_functions( - func: &mut HirFunction, - env: &mut Environment, - fbt_operands: &HashSet<IdentifierId>, -) { - // Collect per-instruction actions to maintain depth-first name allocation order. - // Each entry: (instr index, function_id to recurse into, should_outline) - enum Action { - /// Recurse into an inner function (FunctionExpression or ObjectMethod) - Recurse(FunctionId), - /// Recurse then outline a FunctionExpression - RecurseAndOutline { - instr_idx: usize, - function_id: FunctionId, - }, - } - - let mut actions: Vec<Action> = Vec::new(); - - for block in func.body.blocks.values() { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } => { - let inner_func = &env.functions[lowered_func.func.0 as usize]; - - // Check outlining conditions (TS only checks func.id === null, not name): - // 1. No captured context variables - // 2. Anonymous (no explicit id on the inner function) - // 3. Not an fbt operand - if inner_func.context.is_empty() - && inner_func.id.is_none() - && !fbt_operands.contains(&lvalue_id) - { - actions.push(Action::RecurseAndOutline { - instr_idx: instr_id.0 as usize, - function_id: lowered_func.func, - }); - } else { - actions.push(Action::Recurse(lowered_func.func)); - } - } - InstructionValue::ObjectMethod { lowered_func, .. } => { - // Recurse into object methods (but don't outline them) - actions.push(Action::Recurse(lowered_func.func)); - } - _ => {} - } - } - } - - // Process actions sequentially: for each instruction, recurse first (depth-first), - // then generate name and outline. This matches TS ordering where inner functions - // get names allocated before outer ones. - for action in actions { - match action { - Action::Recurse(function_id) => { - let mut inner_func = std::mem::replace( - &mut env.functions[function_id.0 as usize], - placeholder_function(), - ); - outline_functions(&mut inner_func, env, fbt_operands); - env.functions[function_id.0 as usize] = inner_func; - } - Action::RecurseAndOutline { - instr_idx, - function_id, - } => { - // First recurse into the inner function (depth-first) - let mut inner_func = std::mem::replace( - &mut env.functions[function_id.0 as usize], - placeholder_function(), - ); - outline_functions(&mut inner_func, env, fbt_operands); - env.functions[function_id.0 as usize] = inner_func; - - // Then generate the name and outline (after recursion, matching TS order) - let hint: Option<String> = env.functions[function_id.0 as usize] - .id - .clone() - .or_else(|| env.functions[function_id.0 as usize].name_hint.clone()); - let generated_name = env.generate_globally_unique_identifier_name(hint.as_deref()); - - // Set the id on the inner function - env.functions[function_id.0 as usize].id = Some(generated_name.clone()); - - // Outline the function - let outlined_func = env.functions[function_id.0 as usize].clone(); - env.outline_function(outlined_func, None); - - // Replace the instruction value with LoadGlobal - let loc = func.instructions[instr_idx].value.loc().cloned(); - func.instructions[instr_idx].value = InstructionValue::LoadGlobal { - binding: NonLocalBinding::Global { - name: generated_name, - }, - loc, - }; - } - } - } -} diff --git a/compiler/crates/react_compiler_optimization/src/outline_jsx.rs b/compiler/crates/react_compiler_optimization/src/outline_jsx.rs deleted file mode 100644 index 75a7fd97fe7c..000000000000 --- a/compiler/crates/react_compiler_optimization/src/outline_jsx.rs +++ /dev/null @@ -1,670 +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. - -//! Port of OutlineJsx from TypeScript. -//! -//! Outlines JSX expressions in callbacks into separate component functions. -//! This pass is conditional on `env.config.enable_jsx_outlining` (defaults to false). - -use std::collections::{HashMap, HashSet}; - -use indexmap::IndexMap; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{ - BasicBlock, BlockId, BlockKind, EvaluationOrder, FunctionId, HIR, HirFunction, IdentifierId, - IdentifierName, Instruction, InstructionId, InstructionKind, InstructionValue, JsxAttribute, - JsxTag, LValuePattern, NonLocalBinding, ObjectPattern, ObjectProperty, ObjectPropertyKey, - ObjectPropertyOrSpread, ObjectPropertyType, ParamPattern, Pattern, Place, ReactFunctionType, - ReturnVariant, Terminal, -}; - -/// Outline JSX expressions in inner functions into separate outlined components. -/// -/// Ported from TS `outlineJSX` in `Optimization/OutlineJsx.ts`. -pub fn outline_jsx(func: &mut HirFunction, env: &mut Environment) { - let mut outlined_fns: Vec<HirFunction> = Vec::new(); - outline_jsx_impl(func, env, &mut outlined_fns); - - for outlined_fn in outlined_fns { - env.outline_function(outlined_fn, Some(ReactFunctionType::Component)); - } -} - -/// Data about a JSX instruction for outlining -struct JsxInstrInfo { - instr_idx: usize, // index into func.instructions - #[allow(dead_code)] - instr_id: InstructionId, // the InstructionId - lvalue_id: IdentifierId, - eval_order: EvaluationOrder, -} - -struct OutlinedJsxAttribute { - original_name: String, - new_name: String, - place: Place, -} - -struct OutlinedResult { - instrs: Vec<Instruction>, - func: HirFunction, -} - -fn outline_jsx_impl( - func: &mut HirFunction, - env: &mut Environment, - outlined_fns: &mut Vec<HirFunction>, -) { - // Collect LoadGlobal instructions (tag -> instr) - let mut globals: HashMap<IdentifierId, usize> = HashMap::new(); // id -> instr_idx - - // Process each block - let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect(); - for block_id in &block_ids { - let block = &func.body.blocks[block_id]; - let instr_ids = block.instructions.clone(); - - let mut rewrite_instr: HashMap<EvaluationOrder, Vec<Instruction>> = HashMap::new(); - let mut jsx_group: Vec<JsxInstrInfo> = Vec::new(); - let mut children_ids: HashSet<IdentifierId> = HashSet::new(); - - // First pass: collect all instruction info without borrowing func mutably - enum InstrAction { - LoadGlobal { - lvalue_id: IdentifierId, - instr_idx: usize, - }, - FunctionExpr { - func_id: FunctionId, - }, - JsxExpr { - lvalue_id: IdentifierId, - instr_idx: usize, - eval_order: EvaluationOrder, - child_ids: Vec<IdentifierId>, - }, - Other, - } - - let mut actions: Vec<InstrAction> = Vec::new(); - for i in (0..instr_ids.len()).rev() { - let iid = instr_ids[i]; - let instr = &func.instructions[iid.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - - match &instr.value { - InstructionValue::LoadGlobal { .. } => { - actions.push(InstrAction::LoadGlobal { - lvalue_id, - instr_idx: iid.0 as usize, - }); - } - InstructionValue::FunctionExpression { lowered_func, .. } => { - actions.push(InstrAction::FunctionExpr { - func_id: lowered_func.func, - }); - } - InstructionValue::JsxExpression { children, .. } => { - let child_ids = children - .as_ref() - .map(|kids| kids.iter().map(|c| c.identifier).collect()) - .unwrap_or_default(); - actions.push(InstrAction::JsxExpr { - lvalue_id, - instr_idx: iid.0 as usize, - eval_order: instr.id, - child_ids, - }); - } - _ => { - actions.push(InstrAction::Other); - } - } - } - - // Second pass: process actions - for action in actions { - match action { - InstrAction::LoadGlobal { - lvalue_id, - instr_idx, - } => { - globals.insert(lvalue_id, instr_idx); - } - InstrAction::FunctionExpr { func_id } => { - let mut inner_func = std::mem::replace( - &mut env.functions[func_id.0 as usize], - react_compiler_ssa::enter_ssa::placeholder_function(), - ); - outline_jsx_impl(&mut inner_func, env, outlined_fns); - env.functions[func_id.0 as usize] = inner_func; - } - InstrAction::JsxExpr { - lvalue_id, - instr_idx, - eval_order, - child_ids, - } => { - if !children_ids.contains(&lvalue_id) { - process_and_outline_jsx( - func, - env, - &mut jsx_group, - &globals, - &mut rewrite_instr, - outlined_fns, - ); - jsx_group.clear(); - children_ids.clear(); - } - jsx_group.push(JsxInstrInfo { - instr_idx, - instr_id: InstructionId(instr_idx as u32), - lvalue_id, - eval_order, - }); - for child_id in child_ids { - children_ids.insert(child_id); - } - } - InstrAction::Other => {} - } - } - // Process remaining JSX group after the loop - process_and_outline_jsx( - func, - env, - &mut jsx_group, - &globals, - &mut rewrite_instr, - outlined_fns, - ); - if !rewrite_instr.is_empty() { - let block = func.body.blocks.get_mut(block_id).unwrap(); - let old_instr_ids = block.instructions.clone(); - let mut new_instr_ids = Vec::new(); - for &iid in &old_instr_ids { - let eval_order = func.instructions[iid.0 as usize].id; - if let Some(replacement_instrs) = rewrite_instr.get(&eval_order) { - // Add replacement instructions to the instruction table and reference them - for new_instr in replacement_instrs { - let new_idx = func.instructions.len(); - func.instructions.push(new_instr.clone()); - new_instr_ids.push(InstructionId(new_idx as u32)); - } - } else { - new_instr_ids.push(iid); - } - } - let block = func.body.blocks.get_mut(block_id).unwrap(); - block.instructions = new_instr_ids; - - // Run dead code elimination after rewriting - super::dead_code_elimination(func, env); - } - } -} - -fn process_and_outline_jsx( - func: &mut HirFunction, - env: &mut Environment, - jsx_group: &mut Vec<JsxInstrInfo>, - globals: &HashMap<IdentifierId, usize>, - rewrite_instr: &mut HashMap<EvaluationOrder, Vec<Instruction>>, - outlined_fns: &mut Vec<HirFunction>, -) { - if jsx_group.len() <= 1 { - return; - } - // Sort by eval order ascending (TS: sort by a.id - b.id) - jsx_group.sort_by_key(|j| j.eval_order); - - let result = process_jsx_group(func, env, jsx_group, globals); - if let Some(result) = result { - outlined_fns.push(result.func); - // Map from the LAST JSX instruction's eval order to the replacement instructions - // In the TS code, `state.jsx.at(0)` is the first element pushed during reverse iteration, - // which is the last JSX in forward block order (highest eval order). - // After sorting by eval_order ascending, that's jsx_group.last(). - let last_eval_order = jsx_group.last().unwrap().eval_order; - rewrite_instr.insert(last_eval_order, result.instrs); - } -} - -fn process_jsx_group( - func: &HirFunction, - env: &mut Environment, - jsx_group: &[JsxInstrInfo], - globals: &HashMap<IdentifierId, usize>, -) -> Option<OutlinedResult> { - // Only outline in callbacks, not top-level components - if func.fn_type == ReactFunctionType::Component { - return None; - } - - let props = collect_props(func, env, jsx_group)?; - - let outlined_tag = env.generate_globally_unique_identifier_name(None); - let new_instrs = emit_outlined_jsx(func, env, jsx_group, &props, &outlined_tag)?; - let outlined_fn = emit_outlined_fn(func, env, jsx_group, &props, globals)?; - - // Set the outlined function's id - let mut outlined_fn = outlined_fn; - outlined_fn.id = Some(outlined_tag); - - Some(OutlinedResult { - instrs: new_instrs, - func: outlined_fn, - }) -} - -fn collect_props( - func: &HirFunction, - env: &mut Environment, - jsx_group: &[JsxInstrInfo], -) -> Option<Vec<OutlinedJsxAttribute>> { - let mut id_counter = 1u32; - let mut seen: HashSet<String> = HashSet::new(); - let mut attributes = Vec::new(); - let jsx_ids: HashSet<IdentifierId> = jsx_group.iter().map(|j| j.lvalue_id).collect(); - - let mut generate_name = |old_name: &str, _env: &mut Environment| -> String { - let mut new_name = old_name.to_string(); - while seen.contains(&new_name) { - new_name = format!("{}{}", old_name, id_counter); - id_counter += 1; - } - seen.insert(new_name.clone()); - // TS: env.programContext.addNewReference(newName) - // We don't have programContext in Rust, but this is needed for unique name tracking - new_name - }; - - for info in jsx_group { - let instr = &func.instructions[info.instr_idx]; - if let InstructionValue::JsxExpression { - props, children, .. - } = &instr.value - { - for attr in props { - match attr { - JsxAttribute::SpreadAttribute { .. } => return None, - JsxAttribute::Attribute { name, place } => { - let new_name = generate_name(name, env); - attributes.push(OutlinedJsxAttribute { - original_name: name.clone(), - new_name, - place: place.clone(), - }); - } - } - } - - if let Some(kids) = children { - for child in kids { - if jsx_ids.contains(&child.identifier) { - continue; - } - // Promote the child's identifier to a named temporary - let child_id = child.identifier; - let decl_id = env.identifiers[child_id.0 as usize].declaration_id; - if env.identifiers[child_id.0 as usize].name.is_none() { - env.identifiers[child_id.0 as usize].name = - Some(IdentifierName::Promoted(format!("#t{}", decl_id.0))); - } - - let child_name = match &env.identifiers[child_id.0 as usize].name { - Some(IdentifierName::Named(n)) => n.clone(), - Some(IdentifierName::Promoted(n)) => n.clone(), - None => format!("#t{}", decl_id.0), - }; - let new_name = generate_name("t", env); - attributes.push(OutlinedJsxAttribute { - original_name: child_name, - new_name, - place: child.clone(), - }); - } - } - } - } - - Some(attributes) -} - -fn emit_outlined_jsx( - func: &HirFunction, - env: &mut Environment, - jsx_group: &[JsxInstrInfo], - outlined_props: &[OutlinedJsxAttribute], - outlined_tag: &str, -) -> Option<Vec<Instruction>> { - let props: Vec<JsxAttribute> = outlined_props - .iter() - .map(|p| JsxAttribute::Attribute { - name: p.new_name.clone(), - place: p.place.clone(), - }) - .collect(); - - // Create LoadGlobal for the outlined component - let load_id = env.next_identifier_id(); - // Promote it as a JSX tag temporary - let decl_id = env.identifiers[load_id.0 as usize].declaration_id; - env.identifiers[load_id.0 as usize].name = - Some(IdentifierName::Promoted(format!("#T{}", decl_id.0))); - - let load_place = Place { - identifier: load_id, - effect: react_compiler_hir::Effect::Unknown, - reactive: false, - loc: None, - }; - - let load_jsx = Instruction { - id: EvaluationOrder(0), - lvalue: load_place.clone(), - value: InstructionValue::LoadGlobal { - binding: NonLocalBinding::ModuleLocal { - name: outlined_tag.to_string(), - }, - loc: None, - }, - loc: None, - effects: None, - }; - - // Create the replacement JsxExpression using the last JSX instruction's lvalue - let last_info = jsx_group.last().unwrap(); - let last_instr = &func.instructions[last_info.instr_idx]; - let jsx_expr = Instruction { - id: EvaluationOrder(0), - lvalue: last_instr.lvalue.clone(), - value: InstructionValue::JsxExpression { - tag: JsxTag::Place(load_place), - props, - children: None, - loc: None, - opening_loc: None, - closing_loc: None, - }, - loc: None, - effects: None, - }; - - Some(vec![load_jsx, jsx_expr]) -} - -fn emit_outlined_fn( - func: &HirFunction, - env: &mut Environment, - jsx_group: &[JsxInstrInfo], - old_props: &[OutlinedJsxAttribute], - globals: &HashMap<IdentifierId, usize>, -) -> Option<HirFunction> { - let old_to_new_props = create_old_to_new_props_mapping(env, old_props); - - // Create props parameter - let props_obj_id = env.next_identifier_id(); - let decl_id = env.identifiers[props_obj_id.0 as usize].declaration_id; - env.identifiers[props_obj_id.0 as usize].name = - Some(IdentifierName::Promoted(format!("#t{}", decl_id.0))); - let props_obj = Place { - identifier: props_obj_id, - effect: react_compiler_hir::Effect::Unknown, - reactive: false, - loc: None, - }; - - // Create destructure instruction - let destructure_instr = emit_destructure_props(env, &props_obj, &old_to_new_props); - - // Emit load globals for JSX tags - let load_global_instrs = emit_load_globals(func, jsx_group, globals)?; - - // Emit updated JSX instructions - let updated_jsx_instrs = emit_updated_jsx(func, jsx_group, &old_to_new_props); - - // Build instructions list - let mut instructions = Vec::new(); - instructions.push(destructure_instr); - instructions.extend(load_global_instrs); - instructions.extend(updated_jsx_instrs); - - // Build instruction table and instruction IDs - let mut instr_table = Vec::new(); - let mut instr_ids = Vec::new(); - for instr in instructions { - let idx = instr_table.len(); - instr_table.push(instr); - instr_ids.push(InstructionId(idx as u32)); - } - - // Return terminal uses the last instruction's lvalue - let last_lvalue = instr_table.last().unwrap().lvalue.clone(); - - // Create return place - let returns_id = env.next_identifier_id(); - let returns_place = Place { - identifier: returns_id, - effect: react_compiler_hir::Effect::Unknown, - reactive: false, - loc: None, - }; - - let block = BasicBlock { - kind: BlockKind::Block, - id: BlockId(0), - instructions: instr_ids, - preds: indexmap::IndexSet::new(), - terminal: Terminal::Return { - value: last_lvalue, - return_variant: ReturnVariant::Explicit, - id: EvaluationOrder(0), - loc: None, - effects: None, - }, - phis: Vec::new(), - }; - - let mut blocks = IndexMap::new(); - blocks.insert(BlockId(0), block); - - let outlined_fn = HirFunction { - id: None, - name_hint: None, - fn_type: ReactFunctionType::Other, - params: vec![ParamPattern::Place(props_obj)], - return_type_annotation: None, - returns: returns_place, - context: Vec::new(), - body: HIR { - entry: BlockId(0), - blocks, - }, - instructions: instr_table, - generator: false, - is_async: false, - directives: Vec::new(), - aliasing_effects: Some(vec![]), - loc: None, - }; - - Some(outlined_fn) -} - -fn emit_load_globals( - func: &HirFunction, - jsx_group: &[JsxInstrInfo], - globals: &HashMap<IdentifierId, usize>, -) -> Option<Vec<Instruction>> { - let mut instructions = Vec::new(); - for info in jsx_group { - let instr = &func.instructions[info.instr_idx]; - if let InstructionValue::JsxExpression { tag, .. } = &instr.value { - if let JsxTag::Place(tag_place) = tag { - let global_instr_idx = globals.get(&tag_place.identifier)?; - instructions.push(func.instructions[*global_instr_idx].clone()); - } - } - } - Some(instructions) -} - -fn emit_updated_jsx( - func: &HirFunction, - jsx_group: &[JsxInstrInfo], - old_to_new_props: &IndexMap<IdentifierId, OutlinedJsxAttribute>, -) -> Vec<Instruction> { - let jsx_ids: HashSet<IdentifierId> = jsx_group.iter().map(|j| j.lvalue_id).collect(); - let mut new_instrs = Vec::new(); - - for info in jsx_group { - let instr = &func.instructions[info.instr_idx]; - if let InstructionValue::JsxExpression { - tag, - props, - children, - loc, - opening_loc, - closing_loc, - } = &instr.value - { - let mut new_props = Vec::new(); - for prop in props { - // TS: invariant(prop.kind === 'JsxAttribute', ...) - // Spread attributes would have caused collectProps to return null earlier - let (name, place) = match prop { - JsxAttribute::Attribute { name, place } => (name, place), - JsxAttribute::SpreadAttribute { .. } => { - unreachable!("Expected only JsxAttribute, not spread") - } - }; - if name == "key" { - continue; - } - // TS: invariant(newProp !== undefined, ...) - let new_prop = old_to_new_props - .get(&place.identifier) - .expect("Expected a new property for identifier"); - new_props.push(JsxAttribute::Attribute { - name: new_prop.original_name.clone(), - place: new_prop.place.clone(), - }); - } - - let new_children = children.as_ref().map(|kids| { - kids.iter() - .map(|child| { - if jsx_ids.contains(&child.identifier) { - child.clone() - } else { - // TS: invariant(newChild !== undefined, ...) - let new_prop = old_to_new_props - .get(&child.identifier) - .expect("Expected a new prop for child identifier"); - new_prop.place.clone() - } - }) - .collect() - }); - - new_instrs.push(Instruction { - id: instr.id, - lvalue: instr.lvalue.clone(), - value: InstructionValue::JsxExpression { - tag: tag.clone(), - props: new_props, - children: new_children, - loc: *loc, - opening_loc: *opening_loc, - closing_loc: *closing_loc, - }, - loc: instr.loc, - effects: instr.effects.clone(), - }); - } - } - - new_instrs -} - -fn create_old_to_new_props_mapping( - env: &mut Environment, - old_props: &[OutlinedJsxAttribute], -) -> IndexMap<IdentifierId, OutlinedJsxAttribute> { - let mut old_to_new = IndexMap::new(); - - for old_prop in old_props { - if old_prop.original_name == "key" { - continue; - } - - let new_id = env.next_identifier_id(); - env.identifiers[new_id.0 as usize].name = - Some(IdentifierName::Named(old_prop.new_name.clone())); - - let new_place = Place { - identifier: new_id, - effect: react_compiler_hir::Effect::Unknown, - reactive: false, - loc: None, - }; - - old_to_new.insert( - old_prop.place.identifier, - OutlinedJsxAttribute { - original_name: old_prop.original_name.clone(), - new_name: old_prop.new_name.clone(), - place: new_place, - }, - ); - } - - old_to_new -} - -fn emit_destructure_props( - env: &mut Environment, - props_obj: &Place, - old_to_new_props: &IndexMap<IdentifierId, OutlinedJsxAttribute>, -) -> Instruction { - let mut properties = Vec::new(); - for prop in old_to_new_props.values() { - properties.push(ObjectPropertyOrSpread::Property(ObjectProperty { - key: ObjectPropertyKey::String { - name: prop.new_name.clone(), - }, - property_type: ObjectPropertyType::Property, - place: prop.place.clone(), - })); - } - - let lvalue_id = env.next_identifier_id(); - let lvalue = Place { - identifier: lvalue_id, - effect: react_compiler_hir::Effect::Unknown, - reactive: false, - loc: None, - }; - - Instruction { - id: EvaluationOrder(0), - lvalue, - value: InstructionValue::Destructure { - lvalue: LValuePattern { - pattern: Pattern::Object(ObjectPattern { - properties, - loc: None, - }), - kind: InstructionKind::Let, - }, - value: props_obj.clone(), - loc: None, - }, - loc: None, - effects: None, - } -} diff --git a/compiler/crates/react_compiler_optimization/src/prune_maybe_throws.rs b/compiler/crates/react_compiler_optimization/src/prune_maybe_throws.rs deleted file mode 100644 index c6d959f293ab..000000000000 --- a/compiler/crates/react_compiler_optimization/src/prune_maybe_throws.rs +++ /dev/null @@ -1,131 +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. - -//! Prunes `MaybeThrow` terminals for blocks that can provably never throw. -//! -//! Currently very conservative: only affects blocks with primitives or -//! array/object literals. Even a variable reference could throw due to TDZ. -//! -//! Analogous to TS `Optimization/PruneMaybeThrows.ts`. - -use std::collections::HashMap; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, GENERATED_SOURCE, -}; -use react_compiler_hir::{BlockId, HirFunction, Instruction, InstructionValue, Terminal}; -use react_compiler_lowering::{ - get_reverse_postordered_blocks, mark_instruction_ids, remove_dead_do_while_statements, - remove_unnecessary_try_catch, remove_unreachable_for_updates, -}; - -use crate::merge_consecutive_blocks::merge_consecutive_blocks; - -/// Prune `MaybeThrow` terminals for blocks that cannot throw, then clean up the CFG. -pub fn prune_maybe_throws( - func: &mut HirFunction, - functions: &mut [HirFunction], -) -> Result<(), CompilerDiagnostic> { - let terminal_mapping = prune_maybe_throws_impl(func); - if let Some(terminal_mapping) = terminal_mapping { - // If terminals have changed then blocks may have become newly unreachable. - // Re-run minification of the graph (incl reordering instruction ids). - func.body.blocks = get_reverse_postordered_blocks(&func.body, &func.instructions); - remove_unreachable_for_updates(&mut func.body); - remove_dead_do_while_statements(&mut func.body); - remove_unnecessary_try_catch(&mut func.body); - mark_instruction_ids(&mut func.body, &mut func.instructions); - merge_consecutive_blocks(func, functions); - - // Rewrite phi operands to reference the updated predecessor blocks - for block in func.body.blocks.values_mut() { - let preds = &block.preds; - let mut phi_updates: Vec<(usize, Vec<(BlockId, BlockId)>)> = Vec::new(); - - for (phi_idx, phi) in block.phis.iter().enumerate() { - let mut updates = Vec::new(); - for (predecessor, _) in &phi.operands { - if !preds.contains(predecessor) { - let mapped_terminal = - terminal_mapping.get(predecessor).copied().ok_or_else(|| { - CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected non-existing phi operand's predecessor to have been mapped to a new terminal", - Some(format!( - "Could not find mapping for predecessor bb{} in block bb{}", - predecessor.0, block.id.0, - )), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: GENERATED_SOURCE, - message: None, - identifier_name: None, - }) - })?; - updates.push((*predecessor, mapped_terminal)); - } - } - if !updates.is_empty() { - phi_updates.push((phi_idx, updates)); - } - } - - for (phi_idx, updates) in phi_updates { - for (old_pred, new_pred) in updates { - let operand = block.phis[phi_idx] - .operands - .shift_remove(&old_pred) - .unwrap(); - block.phis[phi_idx].operands.insert(new_pred, operand); - } - } - } - } - Ok(()) -} - -fn prune_maybe_throws_impl(func: &mut HirFunction) -> Option<HashMap<BlockId, BlockId>> { - let mut terminal_mapping: HashMap<BlockId, BlockId> = HashMap::new(); - let instructions = &func.instructions; - - for block in func.body.blocks.values_mut() { - let continuation = match &block.terminal { - Terminal::MaybeThrow { continuation, .. } => *continuation, - _ => continue, - }; - - let can_throw = block - .instructions - .iter() - .any(|instr_id| instruction_may_throw(&instructions[instr_id.0 as usize])); - - if !can_throw { - let source = terminal_mapping.get(&block.id).copied().unwrap_or(block.id); - terminal_mapping.insert(continuation, source); - // Null out the handler rather than replacing with Goto. - // Preserving the MaybeThrow makes the continuations clear for - // BuildReactiveFunction, while nulling out the handler tells us - // that control cannot flow to the handler. - if let Terminal::MaybeThrow { handler, .. } = &mut block.terminal { - *handler = None; - } - } - } - - if terminal_mapping.is_empty() { - None - } else { - Some(terminal_mapping) - } -} - -fn instruction_may_throw(instr: &Instruction) -> bool { - match &instr.value { - InstructionValue::Primitive { .. } - | InstructionValue::ArrayExpression { .. } - | InstructionValue::ObjectExpression { .. } => false, - _ => true, - } -} diff --git a/compiler/crates/react_compiler_optimization/src/prune_unused_labels_hir.rs b/compiler/crates/react_compiler_optimization/src/prune_unused_labels_hir.rs deleted file mode 100644 index 7858314038f4..000000000000 --- a/compiler/crates/react_compiler_optimization/src/prune_unused_labels_hir.rs +++ /dev/null @@ -1,104 +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. - -//! Removes unused labels from the HIR. -//! -//! A label terminal whose body block immediately breaks to the label's -//! fallthrough (with no other predecessors) is effectively a no-op label. -//! This pass merges such label/body/fallthrough triples into a single block. -//! -//! Analogous to TS `PruneUnusedLabelsHIR.ts`. - -use react_compiler_hir::{BlockId, BlockKind, GotoVariant, HirFunction, Terminal}; -use std::collections::HashMap; - -pub fn prune_unused_labels_hir(func: &mut HirFunction) { - // Phase 1: Identify label terminals whose body block immediately breaks - // to the fallthrough, and both body and fallthrough are normal blocks. - let mut merged: Vec<(BlockId, BlockId, BlockId)> = Vec::new(); // (label, next, fallthrough) - - for (&block_id, block) in &func.body.blocks { - if let Terminal::Label { - block: next_id, - fallthrough: fallthrough_id, - .. - } = &block.terminal - { - let next = &func.body.blocks[next_id]; - let fallthrough = &func.body.blocks[fallthrough_id]; - if let Terminal::Goto { - block: goto_target, - variant: GotoVariant::Break, - .. - } = &next.terminal - { - if goto_target == fallthrough_id - && next.kind == BlockKind::Block - && fallthrough.kind == BlockKind::Block - { - merged.push((block_id, *next_id, *fallthrough_id)); - } - } - } - } - - // Phase 2: Apply merges - let mut rewrites: HashMap<BlockId, BlockId> = HashMap::new(); - - for (original_label_id, next_id, fallthrough_id) in &merged { - let label_id = rewrites - .get(original_label_id) - .copied() - .unwrap_or(*original_label_id); - - // Validate: no phis in next or fallthrough - let next_phis_empty = func.body.blocks[next_id].phis.is_empty(); - let fallthrough_phis_empty = func.body.blocks[fallthrough_id].phis.is_empty(); - assert!( - next_phis_empty && fallthrough_phis_empty, - "Unexpected phis when merging label blocks" - ); - - // Validate: single predecessors - let next_preds_ok = func.body.blocks[next_id].preds.len() == 1 - && func.body.blocks[next_id].preds.contains(original_label_id); - let fallthrough_preds_ok = func.body.blocks[fallthrough_id].preds.len() == 1 - && func.body.blocks[fallthrough_id].preds.contains(next_id); - assert!( - next_preds_ok && fallthrough_preds_ok, - "Unexpected block predecessors when merging label blocks" - ); - - // Collect instructions from next and fallthrough - let next_instructions = func.body.blocks[next_id].instructions.clone(); - let fallthrough_instructions = func.body.blocks[fallthrough_id].instructions.clone(); - let fallthrough_terminal = func.body.blocks[fallthrough_id].terminal.clone(); - - // Merge into the label block - let label_block = func.body.blocks.get_mut(&label_id).unwrap(); - label_block.instructions.extend(next_instructions); - label_block.instructions.extend(fallthrough_instructions); - label_block.terminal = fallthrough_terminal; - - // Remove merged blocks - func.body.blocks.shift_remove(next_id); - func.body.blocks.shift_remove(fallthrough_id); - - rewrites.insert(*fallthrough_id, label_id); - } - - // Phase 3: Rewrite predecessor sets - for block in func.body.blocks.values_mut() { - let preds_to_rewrite: Vec<(BlockId, BlockId)> = block - .preds - .iter() - .filter_map(|pred| rewrites.get(pred).map(|rewritten| (*pred, *rewritten))) - .collect(); - for (old, new) in preds_to_rewrite { - block.preds.shift_remove(&old); - block.preds.insert(new); - } - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/Cargo.toml b/compiler/crates/react_compiler_reactive_scopes/Cargo.toml deleted file mode 100644 index 5ec27f2a5f33..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "react_compiler_reactive_scopes" -version = "0.1.0" -edition = "2024" - -[dependencies] -react_compiler_ast = { path = "../react_compiler_ast" } -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" } -react_compiler_hir = { path = "../react_compiler_hir" } -indexmap = "2" -serde_json = "1" -sha2 = "0.10" -hmac = "0.12" diff --git a/compiler/crates/react_compiler_reactive_scopes/src/assert_scope_instructions_within_scopes.rs b/compiler/crates/react_compiler_reactive_scopes/src/assert_scope_instructions_within_scopes.rs deleted file mode 100644 index 2a14823eafda..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/assert_scope_instructions_within_scopes.rs +++ /dev/null @@ -1,118 +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. - -//! Assert that all instructions involved in creating values for a given scope -//! are within the corresponding ReactiveScopeBlock. -//! -//! Corresponds to `src/ReactiveScopes/AssertScopeInstructionsWithinScope.ts`. - -use std::collections::HashSet; - -use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{EvaluationOrder, Place, ReactiveFunction, ReactiveScopeBlock, ScopeId}; - -use crate::visitors::{ReactiveFunctionVisitor, visit_reactive_function}; - -/// Assert that scope instructions are within their scopes. -/// Two-pass visitor: -/// 1. Collect all scope IDs -/// 2. Check that places referencing those scopes are within active scope blocks -pub fn assert_scope_instructions_within_scopes( - func: &ReactiveFunction, - env: &Environment, -) -> Result<(), CompilerDiagnostic> { - // Pass 1: Collect all scope IDs - let mut existing_scopes: HashSet<ScopeId> = HashSet::new(); - let find_visitor = FindAllScopesVisitor { env }; - visit_reactive_function(func, &find_visitor, &mut existing_scopes); - - // Pass 2: Check instructions against scopes - let check_visitor = CheckInstructionsAgainstScopesVisitor { env }; - let mut check_state = CheckState { - existing_scopes, - active_scopes: HashSet::new(), - error: None, - }; - visit_reactive_function(func, &check_visitor, &mut check_state); - if let Some(err) = check_state.error { - return Err(err); - } - Ok(()) -} - -// ============================================================================= -// Pass 1: Find all scopes -// ============================================================================= - -struct FindAllScopesVisitor<'a> { - env: &'a Environment, -} - -impl<'a> ReactiveFunctionVisitor for FindAllScopesVisitor<'a> { - type State = HashSet<ScopeId>; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut HashSet<ScopeId>) { - self.traverse_scope(scope, state); - state.insert(scope.scope); - } -} - -// ============================================================================= -// Pass 2: Check instructions against scopes -// ============================================================================= - -struct CheckState { - existing_scopes: HashSet<ScopeId>, - active_scopes: HashSet<ScopeId>, - error: Option<CompilerDiagnostic>, -} - -struct CheckInstructionsAgainstScopesVisitor<'a> { - env: &'a Environment, -} - -impl<'a> ReactiveFunctionVisitor for CheckInstructionsAgainstScopesVisitor<'a> { - type State = CheckState; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_place(&self, id: EvaluationOrder, place: &Place, state: &mut CheckState) { - // getPlaceScope: check if the place's identifier has a scope that is active at this id - let identifier = &self.env.identifiers[place.identifier.0 as usize]; - if let Some(scope_id) = identifier.scope { - let scope = &self.env.scopes[scope_id.0 as usize]; - // isScopeActive: id >= scope.range.start && id < scope.range.end - let is_active_at_id = id >= scope.range.start && id < scope.range.end; - if is_active_at_id - && state.existing_scopes.contains(&scope_id) - && !state.active_scopes.contains(&scope_id) - { - state.error = Some(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Encountered an instruction that should be part of a scope, \ - but where that scope has already completed", - Some(format!( - "Instruction [{:?}] is part of scope @{:?}, \ - but that scope has already completed", - id, scope_id - )), - )); - } - } - } - - fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut CheckState) { - state.active_scopes.insert(scope.scope); - self.traverse_scope(scope, state); - state.active_scopes.remove(&scope.scope); - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/assert_well_formed_break_targets.rs b/compiler/crates/react_compiler_reactive_scopes/src/assert_well_formed_break_targets.rs deleted file mode 100644 index 17b80c4587cc..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/assert_well_formed_break_targets.rs +++ /dev/null @@ -1,57 +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. - -//! Assert that all break/continue targets reference existent labels. -//! -//! Corresponds to `src/ReactiveScopes/AssertWellFormedBreakTargets.ts`. - -use std::collections::HashSet; - -use react_compiler_hir::{ - BlockId, ReactiveFunction, ReactiveTerminal, ReactiveTerminalStatement, - environment::Environment, -}; - -use crate::visitors::{ReactiveFunctionVisitor, visit_reactive_function}; - -/// Assert that all break/continue targets reference existent labels. -pub fn assert_well_formed_break_targets(func: &ReactiveFunction, env: &Environment) { - let visitor = Visitor { env }; - let mut state: HashSet<BlockId> = HashSet::new(); - visit_reactive_function(func, &visitor, &mut state); -} - -struct Visitor<'a> { - env: &'a Environment, -} - -impl<'a> ReactiveFunctionVisitor for Visitor<'a> { - type State = HashSet<BlockId>; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_terminal(&self, stmt: &ReactiveTerminalStatement, seen_labels: &mut HashSet<BlockId>) { - if let Some(label) = &stmt.label { - seen_labels.insert(label.id); - } - let terminal = &stmt.terminal; - match terminal { - ReactiveTerminal::Break { target, .. } | ReactiveTerminal::Continue { target, .. } => { - assert!( - seen_labels.contains(target), - "Unexpected break/continue to invalid label: {:?}", - target - ); - } - _ => {} - } - // Note: intentionally NOT calling self.traverse_terminal() here, - // matching TS behavior where visitTerminal override does not call traverseTerminal. - // Recursion into child blocks happens via traverseBlock→visitTerminal for nested blocks. - // The TS visitor only checks break/continue at the block level, not terminal child blocks. - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/build_reactive_function.rs b/compiler/crates/react_compiler_reactive_scopes/src/build_reactive_function.rs deleted file mode 100644 index 181e0977634d..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/build_reactive_function.rs +++ /dev/null @@ -1,1674 +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. - -//! Converts the HIR CFG into a tree-structured ReactiveFunction. -//! -//! Corresponds to `src/ReactiveScopes/BuildReactiveFunction.ts`. - -use std::collections::HashSet; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, SourceLocation, -}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{ - BasicBlock, BlockId, EvaluationOrder, GotoVariant, HirFunction, InstructionValue, Place, - PrunedReactiveScopeBlock, ReactiveBlock, ReactiveFunction, ReactiveInstruction, ReactiveLabel, - ReactiveScopeBlock, ReactiveStatement, ReactiveSwitchCase, ReactiveTerminal, - ReactiveTerminalStatement, ReactiveTerminalTargetKind, ReactiveValue, Terminal, -}; - -/// Convert the HIR CFG into a tree-structured ReactiveFunction. -pub fn build_reactive_function( - hir: &HirFunction, - env: &Environment, -) -> Result<ReactiveFunction, CompilerDiagnostic> { - let mut ctx = Context::new(hir); - let mut driver = Driver { - cx: &mut ctx, - hir, - env, - }; - - let entry_block_id = hir.body.entry; - let mut body = Vec::new(); - driver.visit_block(entry_block_id, &mut body)?; - - Ok(ReactiveFunction { - loc: hir.loc, - id: hir.id.clone(), - name_hint: hir.name_hint.clone(), - params: hir.params.clone(), - generator: hir.generator, - is_async: hir.is_async, - body, - directives: hir.directives.clone(), - }) -} - -// ============================================================================= -// ControlFlowTarget -// ============================================================================= - -#[derive(Debug)] -enum ControlFlowTarget { - If { - block: BlockId, - id: u32, - }, - Switch { - block: BlockId, - id: u32, - }, - Case { - block: BlockId, - id: u32, - }, - Loop { - block: BlockId, - #[allow(dead_code)] - owns_block: bool, - continue_block: BlockId, - loop_block: Option<BlockId>, - owns_loop: bool, - id: u32, - }, -} - -impl ControlFlowTarget { - fn block(&self) -> BlockId { - match self { - ControlFlowTarget::If { block, .. } - | ControlFlowTarget::Switch { block, .. } - | ControlFlowTarget::Case { block, .. } - | ControlFlowTarget::Loop { block, .. } => *block, - } - } - - fn id(&self) -> u32 { - match self { - ControlFlowTarget::If { id, .. } - | ControlFlowTarget::Switch { id, .. } - | ControlFlowTarget::Case { id, .. } - | ControlFlowTarget::Loop { id, .. } => *id, - } - } - - fn is_loop(&self) -> bool { - matches!(self, ControlFlowTarget::Loop { .. }) - } -} - -// ============================================================================= -// Context -// ============================================================================= - -struct Context<'a> { - ir: &'a HirFunction, - next_schedule_id: u32, - emitted: HashSet<BlockId>, - scope_fallthroughs: HashSet<BlockId>, - scheduled: HashSet<BlockId>, - catch_handlers: HashSet<BlockId>, - control_flow_stack: Vec<ControlFlowTarget>, -} - -impl<'a> Context<'a> { - fn new(ir: &'a HirFunction) -> Self { - Self { - ir, - next_schedule_id: 0, - emitted: HashSet::new(), - scope_fallthroughs: HashSet::new(), - scheduled: HashSet::new(), - catch_handlers: HashSet::new(), - control_flow_stack: Vec::new(), - } - } - - fn block(&self, id: BlockId) -> &BasicBlock { - &self.ir.body.blocks[&id] - } - - fn schedule_catch_handler(&mut self, block: BlockId) { - self.catch_handlers.insert(block); - } - - fn reachable(&self, id: BlockId) -> bool { - let block = self.block(id); - !matches!(block.terminal, Terminal::Unreachable { .. }) - } - - fn schedule(&mut self, block: BlockId, target_type: &str) -> Result<u32, CompilerDiagnostic> { - let id = self.next_schedule_id; - self.next_schedule_id += 1; - if self.scheduled.contains(&block) { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!("Break block is already scheduled: bb{}", block.0), - None, - )); - } - self.scheduled.insert(block); - let target = match target_type { - "if" => ControlFlowTarget::If { block, id }, - "switch" => ControlFlowTarget::Switch { block, id }, - "case" => ControlFlowTarget::Case { block, id }, - _ => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!("Unknown target type: {}", target_type), - None, - )); - } - }; - self.control_flow_stack.push(target); - Ok(id) - } - - fn schedule_loop( - &mut self, - fallthrough_block: BlockId, - continue_block: BlockId, - loop_block: Option<BlockId>, - ) -> Result<u32, CompilerDiagnostic> { - let id = self.next_schedule_id; - self.next_schedule_id += 1; - let owns_block = !self.scheduled.contains(&fallthrough_block); - self.scheduled.insert(fallthrough_block); - if self.scheduled.contains(&continue_block) { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!( - "Continue block is already scheduled: bb{}", - continue_block.0 - ), - None, - )); - } - self.scheduled.insert(continue_block); - let mut owns_loop = false; - if let Some(lb) = loop_block { - owns_loop = !self.scheduled.contains(&lb); - self.scheduled.insert(lb); - } - - self.control_flow_stack.push(ControlFlowTarget::Loop { - block: fallthrough_block, - owns_block, - continue_block, - loop_block, - owns_loop, - id, - }); - Ok(id) - } - - fn unschedule(&mut self, schedule_id: u32) -> Result<(), CompilerDiagnostic> { - let last = self - .control_flow_stack - .pop() - .expect("Can only unschedule the last target"); - if last.id() != schedule_id { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Can only unschedule the last target".to_string(), - None, - )); - } - match &last { - ControlFlowTarget::Loop { - block, - continue_block, - loop_block, - owns_loop, - .. - } => { - // TS: always removes block from scheduled for loops - // (ownsBlock is boolean, so `!== null` is always true) - self.scheduled.remove(block); - self.scheduled.remove(continue_block); - if *owns_loop { - if let Some(lb) = loop_block { - self.scheduled.remove(lb); - } - } - } - _ => { - self.scheduled.remove(&last.block()); - } - } - Ok(()) - } - - fn unschedule_all(&mut self, schedule_ids: &[u32]) -> Result<(), CompilerDiagnostic> { - for &id in schedule_ids.iter().rev() { - self.unschedule(id)?; - } - Ok(()) - } - - fn is_scheduled(&self, block: BlockId) -> bool { - self.scheduled.contains(&block) || self.catch_handlers.contains(&block) - } - - fn get_break_target( - &self, - block: BlockId, - ) -> Result<(BlockId, ReactiveTerminalTargetKind), CompilerDiagnostic> { - let mut has_preceding_loop = false; - for i in (0..self.control_flow_stack.len()).rev() { - let target = &self.control_flow_stack[i]; - if target.block() == block { - let kind = if target.is_loop() { - if has_preceding_loop { - ReactiveTerminalTargetKind::Labeled - } else { - ReactiveTerminalTargetKind::Unlabeled - } - } else if i == self.control_flow_stack.len() - 1 { - ReactiveTerminalTargetKind::Implicit - } else { - ReactiveTerminalTargetKind::Labeled - }; - return Ok((target.block(), kind)); - } - has_preceding_loop = has_preceding_loop || target.is_loop(); - } - Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!("Expected a break target for bb{}", block.0), - None, - )) - } - - fn get_continue_target(&self, block: BlockId) -> Option<(BlockId, ReactiveTerminalTargetKind)> { - let mut has_preceding_loop = false; - for i in (0..self.control_flow_stack.len()).rev() { - let target = &self.control_flow_stack[i]; - if let ControlFlowTarget::Loop { - block: fallthrough_block, - continue_block, - .. - } = target - { - if *continue_block == block { - let kind = if has_preceding_loop { - ReactiveTerminalTargetKind::Labeled - } else if i == self.control_flow_stack.len() - 1 { - ReactiveTerminalTargetKind::Implicit - } else { - ReactiveTerminalTargetKind::Unlabeled - }; - return Some((*fallthrough_block, kind)); - } - } - has_preceding_loop = has_preceding_loop || target.is_loop(); - } - None - } -} - -// ============================================================================= -// Driver -// ============================================================================= - -struct Driver<'a, 'b> { - cx: &'b mut Context<'a>, - hir: &'a HirFunction, - #[allow(dead_code)] - env: &'a Environment, -} - -impl<'a, 'b> Driver<'a, 'b> { - fn traverse_block(&mut self, block_id: BlockId) -> Result<ReactiveBlock, CompilerDiagnostic> { - let mut block_value = Vec::new(); - self.visit_block(block_id, &mut block_value)?; - Ok(block_value) - } - - fn visit_block( - &mut self, - mut block_id: BlockId, - block_value: &mut ReactiveBlock, - ) -> Result<(), CompilerDiagnostic> { - // Use a loop to avoid deep recursion for fallthrough chains. - // Each terminal that would tail-call visit_block(fallthrough, block_value) - // instead sets next_block and continues the loop. - loop { - // Extract data from block before any mutable operations - let block = &self.hir.body.blocks[&block_id]; - let block_id_val = block.id; - let instructions: Vec<_> = block.instructions.clone(); - let terminal = block.terminal.clone(); - - if !self.cx.emitted.insert(block_id_val) { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!("Block bb{} was already emitted", block_id_val.0), - None, - )); - } - - // Emit instructions - for instr_id in &instructions { - let instr = &self.hir.instructions[instr_id.0 as usize]; - block_value.push(ReactiveStatement::Instruction(ReactiveInstruction { - id: instr.id, - lvalue: Some(instr.lvalue.clone()), - value: ReactiveValue::Instruction(instr.value.clone()), - effects: instr.effects.clone(), - loc: instr.loc, - })); - } - - // Process terminal - let mut schedule_ids: Vec<u32> = Vec::new(); - let mut next_block: Option<BlockId> = None; - - match &terminal { - Terminal::If { - test, - consequent, - alternate, - fallthrough, - id, - loc, - } => { - // TS: reachable(fallthrough) && !isScheduled(fallthrough) - let fallthrough_id = - if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) { - Some(*fallthrough) - } else { - None - }; - // TS: alternate !== fallthrough ? alternate : null - let alternate_id = if *alternate != *fallthrough { - Some(*alternate) - } else { - None - }; - - if let Some(ft) = fallthrough_id { - schedule_ids.push(self.cx.schedule(ft, "if")?); - } - - let consequent_block = if self.cx.is_scheduled(*consequent) { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!( - "Unexpected 'if' where consequent is already scheduled (bb{})", - consequent.0 - ), - None, - )); - } else { - self.traverse_block(*consequent)? - }; - - let alternate_block = if let Some(alt) = alternate_id { - if self.cx.is_scheduled(alt) { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!( - "Unexpected 'if' where the alternate is already scheduled (bb{})", - alt.0 - ), - None, - )); - } else { - Some(self.traverse_block(alt)?) - } - } else { - None - }; - - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::If { - test: test.clone(), - consequent: consequent_block, - alternate: alternate_block, - id: *id, - loc: *loc, - }, - label: fallthrough_id.map(|ft| ReactiveLabel { - id: ft, - implicit: false, - }), - })); - - next_block = fallthrough_id; - } - - Terminal::Switch { - test, - cases, - fallthrough, - id, - loc, - } => { - // TS: reachable(fallthrough) && !isScheduled(fallthrough) - let fallthrough_id = - if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) { - Some(*fallthrough) - } else { - None - }; - if let Some(ft) = fallthrough_id { - schedule_ids.push(self.cx.schedule(ft, "switch")?); - } - - // TS processes cases in reverse order, then reverses the result. - // This ensures that later cases are scheduled when earlier cases - // are traversed, matching fallthrough semantics. - let mut reactive_cases = Vec::new(); - for case in cases.iter().rev() { - let case_block_id = case.block; - - if self.cx.is_scheduled(case_block_id) { - // TS: asserts case.block === fallthrough, then skips (return) - if case_block_id != *fallthrough { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected 'switch' where a case is already scheduled and block is not the fallthrough".to_string(), - None, - )); - } - continue; - } - - let consequent = self.traverse_block(case_block_id)?; - let case_schedule_id = self.cx.schedule(case_block_id, "case")?; - schedule_ids.push(case_schedule_id); - - reactive_cases.push(ReactiveSwitchCase { - test: case.test.clone(), - block: Some(consequent), - }); - } - reactive_cases.reverse(); - - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::Switch { - test: test.clone(), - cases: reactive_cases, - id: *id, - loc: *loc, - }, - label: fallthrough_id.map(|ft| ReactiveLabel { - id: ft, - implicit: false, - }), - })); - - next_block = fallthrough_id; - } - - Terminal::DoWhile { - loop_block, - test, - fallthrough, - id, - loc, - } => { - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) { - Some(*fallthrough) - } else { - None - }; - let loop_id = - if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough { - Some(*loop_block) - } else { - None - }; - - schedule_ids.push(self.cx.schedule_loop( - *fallthrough, - *test, - Some(*loop_block), - )?); - - let loop_body = if let Some(lid) = loop_id { - self.traverse_block(lid)? - } else { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected 'do-while' where the loop is already scheduled", - None, - )); - }; - let test_result = self.visit_value_block(*test, *loc, None)?; - - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::DoWhile { - loop_block: loop_body, - test: test_result.value, - id: *id, - loc: *loc, - }, - label: fallthrough_id.map(|ft| ReactiveLabel { - id: ft, - implicit: false, - }), - })); - - next_block = fallthrough_id; - } - - Terminal::While { - test, - loop_block, - fallthrough, - id, - loc, - } => { - // TS: reachable(fallthrough) && !isScheduled(fallthrough) - let fallthrough_id = - if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) { - Some(*fallthrough) - } else { - None - }; - let loop_id = - if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough { - Some(*loop_block) - } else { - None - }; - - schedule_ids.push(self.cx.schedule_loop( - *fallthrough, - *test, - Some(*loop_block), - )?); - - let test_result = self.visit_value_block(*test, *loc, None)?; - - let loop_body = if let Some(lid) = loop_id { - self.traverse_block(lid)? - } else { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected 'while' where the loop is already scheduled", - None, - )); - }; - - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::While { - test: test_result.value, - loop_block: loop_body, - id: *id, - loc: *loc, - }, - label: fallthrough_id.map(|ft| ReactiveLabel { - id: ft, - implicit: false, - }), - })); - - next_block = fallthrough_id; - } - - Terminal::For { - init, - test, - update, - loop_block, - fallthrough, - id, - loc, - } => { - let loop_id = - if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough { - Some(*loop_block) - } else { - None - }; - - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) { - Some(*fallthrough) - } else { - None - }; - - // Continue block is update (if present) or test - let continue_block = update.unwrap_or(*test); - schedule_ids.push(self.cx.schedule_loop( - *fallthrough, - continue_block, - Some(*loop_block), - )?); - - let init_result = self.visit_value_block(*init, *loc, None)?; - let init_value = self.value_block_result_to_sequence(init_result, *loc); - - let test_result = self.visit_value_block(*test, *loc, None)?; - - let update_result = match update { - Some(u) => Some(self.visit_value_block(*u, *loc, None)?), - None => None, - }; - - let loop_body = if let Some(lid) = loop_id { - self.traverse_block(lid)? - } else { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected 'for' where the loop is already scheduled", - None, - )); - }; - - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::For { - init: init_value, - test: test_result.value, - update: update_result.map(|r| r.value), - loop_block: loop_body, - id: *id, - loc: *loc, - }, - label: fallthrough_id.map(|ft| ReactiveLabel { - id: ft, - implicit: false, - }), - })); - - next_block = fallthrough_id; - } - - Terminal::ForOf { - init, - test, - loop_block, - fallthrough, - id, - loc, - } => { - let loop_id = - if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough { - Some(*loop_block) - } else { - None - }; - - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) { - Some(*fallthrough) - } else { - None - }; - - // TS: scheduleLoop(fallthrough, init, loop) - schedule_ids.push(self.cx.schedule_loop( - *fallthrough, - *init, - Some(*loop_block), - )?); - - let init_result = self.visit_value_block(*init, *loc, None)?; - let init_value = self.value_block_result_to_sequence(init_result, *loc); - - let test_result = self.visit_value_block(*test, *loc, None)?; - let test_value = self.value_block_result_to_sequence(test_result, *loc); - - let loop_body = if let Some(lid) = loop_id { - self.traverse_block(lid)? - } else { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected 'for-of' where the loop is already scheduled", - None, - )); - }; - - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::ForOf { - init: init_value, - test: test_value, - loop_block: loop_body, - id: *id, - loc: *loc, - }, - label: fallthrough_id.map(|ft| ReactiveLabel { - id: ft, - implicit: false, - }), - })); - - next_block = fallthrough_id; - } - - Terminal::ForIn { - init, - loop_block, - fallthrough, - id, - loc, - } => { - let loop_id = - if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough { - Some(*loop_block) - } else { - None - }; - - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) { - Some(*fallthrough) - } else { - None - }; - - schedule_ids.push(self.cx.schedule_loop( - *fallthrough, - *init, - Some(*loop_block), - )?); - - let init_result = self.visit_value_block(*init, *loc, None)?; - let init_value = self.value_block_result_to_sequence(init_result, *loc); - - let loop_body = if let Some(lid) = loop_id { - self.traverse_block(lid)? - } else { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected 'for-in' where the loop is already scheduled", - None, - )); - }; - - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::ForIn { - init: init_value, - loop_block: loop_body, - id: *id, - loc: *loc, - }, - label: fallthrough_id.map(|ft| ReactiveLabel { - id: ft, - implicit: false, - }), - })); - - next_block = fallthrough_id; - } - - Terminal::Label { - block: label_block, - fallthrough, - id, - loc, - } => { - // TS: reachable(fallthrough) && !isScheduled(fallthrough) - let fallthrough_id = - if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) { - Some(*fallthrough) - } else { - None - }; - if let Some(ft) = fallthrough_id { - schedule_ids.push(self.cx.schedule(ft, "if")?); - } - - if self.cx.is_scheduled(*label_block) { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected 'label' where the block is already scheduled".to_string(), - None, - )); - } - let label_body = self.traverse_block(*label_block)?; - - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::Label { - block: label_body, - id: *id, - loc: *loc, - }, - label: fallthrough_id.map(|ft| ReactiveLabel { - id: ft, - implicit: false, - }), - })); - - next_block = fallthrough_id; - } - - Terminal::Sequence { .. } - | Terminal::Optional { .. } - | Terminal::Ternary { .. } - | Terminal::Logical { .. } => { - let fallthrough = match &terminal { - Terminal::Sequence { fallthrough, .. } - | Terminal::Optional { fallthrough, .. } - | Terminal::Ternary { fallthrough, .. } - | Terminal::Logical { fallthrough, .. } => *fallthrough, - _ => unreachable!(), - }; - let fallthrough_id = if !self.cx.is_scheduled(fallthrough) { - Some(fallthrough) - } else { - None - }; - if let Some(ft) = fallthrough_id { - schedule_ids.push(self.cx.schedule(ft, "if")?); - } - - let result = self.visit_value_block_terminal(&terminal)?; - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::Instruction(ReactiveInstruction { - id: result.id, - lvalue: Some(result.place), - value: result.value, - effects: None, - loc: *terminal_loc(&terminal), - })); - - next_block = fallthrough_id; - } - - Terminal::Goto { - block: goto_block, - variant, - id, - loc, - } => { - match variant { - GotoVariant::Break => { - if let Some(stmt) = self.visit_break(*goto_block, *id, *loc)? { - block_value.push(stmt); - } - } - GotoVariant::Continue => { - let stmt = self.visit_continue(*goto_block, *id, *loc)?; - block_value.push(stmt); - } - GotoVariant::Try => { - // noop - } - } - } - - Terminal::MaybeThrow { continuation, .. } => { - if !self.cx.is_scheduled(*continuation) { - next_block = Some(*continuation); - } - } - - Terminal::Try { - block: try_block, - handler_binding, - handler, - fallthrough, - id, - loc, - } => { - let fallthrough_id = - if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) { - Some(*fallthrough) - } else { - None - }; - if let Some(ft) = fallthrough_id { - schedule_ids.push(self.cx.schedule(ft, "if")?); - } - self.cx.schedule_catch_handler(*handler); - - let try_body = self.traverse_block(*try_block)?; - let handler_body = self.traverse_block(*handler)?; - - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::Try { - block: try_body, - handler_binding: handler_binding.clone(), - handler: handler_body, - id: *id, - loc: *loc, - }, - label: fallthrough_id.map(|ft| ReactiveLabel { - id: ft, - implicit: false, - }), - })); - - next_block = fallthrough_id; - } - - Terminal::Scope { - fallthrough, - block: scope_block, - scope, - .. - } => { - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) { - Some(*fallthrough) - } else { - None - }; - if let Some(ft) = fallthrough_id { - schedule_ids.push(self.cx.schedule(ft, "if")?); - self.cx.scope_fallthroughs.insert(ft); - } - - if self.cx.is_scheduled(*scope_block) { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected 'scope' where the block is already scheduled".to_string(), - None, - )); - } - let scope_body = self.traverse_block(*scope_block)?; - - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::Scope(ReactiveScopeBlock { - scope: *scope, - instructions: scope_body, - })); - - next_block = fallthrough_id; - } - - Terminal::PrunedScope { - fallthrough, - block: scope_block, - scope, - .. - } => { - let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) { - Some(*fallthrough) - } else { - None - }; - if let Some(ft) = fallthrough_id { - schedule_ids.push(self.cx.schedule(ft, "if")?); - self.cx.scope_fallthroughs.insert(ft); - } - - if self.cx.is_scheduled(*scope_block) { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected 'scope' where the block is already scheduled".to_string(), - None, - )); - } - let scope_body = self.traverse_block(*scope_block)?; - - self.cx.unschedule_all(&schedule_ids)?; - block_value.push(ReactiveStatement::PrunedScope(PrunedReactiveScopeBlock { - scope: *scope, - instructions: scope_body, - })); - - next_block = fallthrough_id; - } - - Terminal::Return { value, id, loc, .. } => { - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::Return { - value: value.clone(), - id: *id, - loc: *loc, - }, - label: None, - })); - } - - Terminal::Throw { value, id, loc } => { - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::Throw { - value: value.clone(), - id: *id, - loc: *loc, - }, - label: None, - })); - } - - Terminal::Unreachable { .. } => { - // noop - } - - Terminal::Unsupported { .. } => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected unsupported terminal", - None, - )); - } - - Terminal::Branch { - test, - consequent, - alternate, - id, - loc, - .. - } => { - let consequent_block = if self.cx.is_scheduled(*consequent) { - if let Some(stmt) = self.visit_break(*consequent, *id, *loc)? { - vec![stmt] - } else { - Vec::new() - } - } else { - self.traverse_block(*consequent)? - }; - - if self.cx.is_scheduled(*alternate) { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected 'branch' where the alternate is already scheduled" - .to_string(), - None, - )); - } - let alternate_block = self.traverse_block(*alternate)?; - - block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::If { - test: test.clone(), - consequent: consequent_block, - alternate: Some(alternate_block), - id: *id, - loc: *loc, - }, - label: None, - })); - } - } - match next_block { - Some(nb) => block_id = nb, - None => return Ok(()), - } - } // end loop - } - - // ========================================================================= - // Value block processing - // ========================================================================= - - fn visit_value_block( - &mut self, - block_id: BlockId, - loc: Option<SourceLocation>, - fallthrough: Option<BlockId>, - ) -> Result<ValueBlockResult, CompilerDiagnostic> { - let block = &self.hir.body.blocks[&block_id]; - let block_id_val = block.id; - let terminal = block.terminal.clone(); - let instructions: Vec<_> = block.instructions.clone(); - - // If we've reached the fallthrough, stop - if let Some(ft) = fallthrough { - if block_id == ft { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!( - "Did not expect to reach the fallthrough of a value block (bb{})", - block_id.0 - ), - None, - )); - } - } - - match &terminal { - Terminal::Branch { - test, id: term_id, .. - } => { - if instructions.is_empty() { - Ok(ValueBlockResult { - block: block_id_val, - place: test.clone(), - value: ReactiveValue::Instruction(InstructionValue::LoadLocal { - place: test.clone(), - loc: test.loc, - }), - id: *term_id, - }) - } else { - Ok(self.extract_value_block_result(&instructions, block_id_val, loc)) - } - } - Terminal::Goto { .. } => { - if instructions.is_empty() { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected empty block with `goto` terminal", - Some(format!("Block bb{} is empty", block_id.0)), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc, - message: Some("Unexpected empty block with `goto` terminal".to_string()), - identifier_name: None, - })); - } - Ok(self.extract_value_block_result(&instructions, block_id_val, loc)) - } - Terminal::MaybeThrow { continuation, .. } => { - let continuation_id = *continuation; - let continuation_block = self.cx.block(continuation_id); - let cont_instructions_empty = continuation_block.instructions.is_empty(); - let cont_is_goto = matches!(continuation_block.terminal, Terminal::Goto { .. }); - let cont_block_id = continuation_block.id; - - if cont_instructions_empty && cont_is_goto { - Ok(self.extract_value_block_result(&instructions, cont_block_id, loc)) - } else { - let continuation = self.visit_value_block(continuation_id, loc, fallthrough)?; - Ok(self.wrap_with_sequence(&instructions, continuation, loc)) - } - } - _ => { - // Value block ended in a value terminal, recurse to get the value - // of that terminal and stitch them together in a sequence. - // TS: visitValueBlock(init.fallthrough, loc) — does NOT propagate fallthrough - let init = self.visit_value_block_terminal(&terminal)?; - let init_fallthrough = init.fallthrough; - let init_instr = ReactiveInstruction { - id: init.id, - lvalue: Some(init.place), - value: init.value, - effects: None, - loc, - }; - let final_result = self.visit_value_block(init_fallthrough, loc, None)?; - - // Combine block instructions + init instruction, then wrap - let mut all_instrs: Vec<ReactiveInstruction> = instructions - .iter() - .map(|iid| { - let instr = &self.hir.instructions[iid.0 as usize]; - ReactiveInstruction { - id: instr.id, - lvalue: Some(instr.lvalue.clone()), - value: ReactiveValue::Instruction(instr.value.clone()), - effects: instr.effects.clone(), - loc: instr.loc, - } - }) - .collect(); - all_instrs.push(init_instr); - - if all_instrs.is_empty() { - Ok(final_result) - } else { - Ok(ValueBlockResult { - block: final_result.block, - place: final_result.place.clone(), - value: ReactiveValue::SequenceExpression { - instructions: all_instrs, - id: final_result.id, - value: Box::new(final_result.value), - loc, - }, - id: final_result.id, - }) - } - } - } - } - - fn visit_test_block( - &mut self, - test_block_id: BlockId, - loc: Option<SourceLocation>, - terminal_kind: &str, - ) -> Result<TestBlockResult, CompilerDiagnostic> { - let test = self.visit_value_block(test_block_id, loc, None)?; - let test_block = &self.hir.body.blocks[&test.block]; - match &test_block.terminal { - Terminal::Branch { - consequent, - alternate, - loc: branch_loc, - .. - } => Ok(TestBlockResult { - test, - consequent: *consequent, - alternate: *alternate, - branch_loc: *branch_loc, - }), - other => Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!( - "Expected a branch terminal for {} test block, got {:?}", - terminal_kind, - std::mem::discriminant(other) - ), - None, - )), - } - } - - fn visit_value_block_terminal( - &mut self, - terminal: &Terminal, - ) -> Result<ValueTerminalResult, CompilerDiagnostic> { - match terminal { - Terminal::Sequence { - block, - fallthrough, - id, - loc, - } => { - let block_result = self.visit_value_block(*block, *loc, Some(*fallthrough))?; - Ok(ValueTerminalResult { - value: block_result.value, - place: block_result.place, - fallthrough: *fallthrough, - id: *id, - }) - } - Terminal::Optional { - optional, - test, - fallthrough, - id, - loc, - } => { - let test_result = self.visit_test_block(*test, *loc, "optional")?; - let consequent = - self.visit_value_block(test_result.consequent, *loc, Some(*fallthrough))?; - let call = ReactiveValue::SequenceExpression { - instructions: vec![ReactiveInstruction { - id: test_result.test.id, - lvalue: Some(test_result.test.place.clone()), - value: test_result.test.value, - effects: None, - loc: test_result.branch_loc, - }], - id: consequent.id, - value: Box::new(consequent.value), - loc: *loc, - }; - Ok(ValueTerminalResult { - place: consequent.place, - value: ReactiveValue::OptionalExpression { - optional: *optional, - value: Box::new(call), - id: *id, - loc: *loc, - }, - fallthrough: *fallthrough, - id: *id, - }) - } - Terminal::Logical { - operator, - test, - fallthrough, - id, - loc, - } => { - let test_result = self.visit_test_block(*test, *loc, "logical")?; - let left_final = - self.visit_value_block(test_result.consequent, *loc, Some(*fallthrough))?; - let left = ReactiveValue::SequenceExpression { - instructions: vec![ReactiveInstruction { - id: test_result.test.id, - lvalue: Some(test_result.test.place.clone()), - value: test_result.test.value, - effects: None, - loc: *loc, - }], - id: left_final.id, - value: Box::new(left_final.value), - loc: *loc, - }; - let right = - self.visit_value_block(test_result.alternate, *loc, Some(*fallthrough))?; - Ok(ValueTerminalResult { - place: left_final.place, - value: ReactiveValue::LogicalExpression { - operator: *operator, - left: Box::new(left), - right: Box::new(right.value), - loc: *loc, - }, - fallthrough: *fallthrough, - id: *id, - }) - } - Terminal::Ternary { - test, - fallthrough, - id, - loc, - } => { - let test_result = self.visit_test_block(*test, *loc, "ternary")?; - let consequent = - self.visit_value_block(test_result.consequent, *loc, Some(*fallthrough))?; - let alternate = - self.visit_value_block(test_result.alternate, *loc, Some(*fallthrough))?; - Ok(ValueTerminalResult { - place: consequent.place, - value: ReactiveValue::ConditionalExpression { - test: Box::new(test_result.test.value), - consequent: Box::new(consequent.value), - alternate: Box::new(alternate.value), - loc: *loc, - }, - fallthrough: *fallthrough, - id: *id, - }) - } - Terminal::MaybeThrow { .. } => Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected maybe-throw in visit_value_block_terminal", - None, - )), - Terminal::Label { .. } => Err(CompilerDiagnostic::new( - ErrorCategory::Todo, - "Support labeled statements combined with value blocks is not yet implemented", - None, - )), - _ => Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unsupported terminal kind in value block", - None, - )), - } - } - - fn extract_value_block_result( - &self, - instructions: &[react_compiler_hir::InstructionId], - block_id: BlockId, - loc: Option<SourceLocation>, - ) -> ValueBlockResult { - let last_id = instructions - .last() - .expect("Expected non-empty instructions"); - let last_instr = &self.hir.instructions[last_id.0 as usize]; - - let remaining: Vec<ReactiveInstruction> = instructions[..instructions.len() - 1] - .iter() - .map(|iid| { - let instr = &self.hir.instructions[iid.0 as usize]; - ReactiveInstruction { - id: instr.id, - lvalue: Some(instr.lvalue.clone()), - value: ReactiveValue::Instruction(instr.value.clone()), - effects: instr.effects.clone(), - loc: instr.loc, - } - }) - .collect(); - - // If the last instruction is a StoreLocal to a temporary (unnamed identifier), - // convert it to a LoadLocal of the value being stored, matching the TS behavior. - let (value, place) = match &last_instr.value { - InstructionValue::StoreLocal { - lvalue, - value: store_value, - .. - } => { - let ident = &self.env.identifiers[lvalue.place.identifier.0 as usize]; - if ident.name.is_none() { - ( - ReactiveValue::Instruction(InstructionValue::LoadLocal { - place: store_value.clone(), - loc: store_value.loc, - }), - lvalue.place.clone(), - ) - } else { - ( - ReactiveValue::Instruction(last_instr.value.clone()), - last_instr.lvalue.clone(), - ) - } - } - _ => ( - ReactiveValue::Instruction(last_instr.value.clone()), - last_instr.lvalue.clone(), - ), - }; - let id = last_instr.id; - - if remaining.is_empty() { - ValueBlockResult { - block: block_id, - place, - value, - id, - } - } else { - ValueBlockResult { - block: block_id, - place: place.clone(), - value: ReactiveValue::SequenceExpression { - instructions: remaining, - id, - value: Box::new(value), - loc, - }, - id, - } - } - } - - fn wrap_with_sequence( - &self, - instructions: &[react_compiler_hir::InstructionId], - continuation: ValueBlockResult, - loc: Option<SourceLocation>, - ) -> ValueBlockResult { - if instructions.is_empty() { - return continuation; - } - - let reactive_instrs: Vec<ReactiveInstruction> = instructions - .iter() - .map(|iid| { - let instr = &self.hir.instructions[iid.0 as usize]; - ReactiveInstruction { - id: instr.id, - lvalue: Some(instr.lvalue.clone()), - value: ReactiveValue::Instruction(instr.value.clone()), - effects: instr.effects.clone(), - loc: instr.loc, - } - }) - .collect(); - - ValueBlockResult { - block: continuation.block, - place: continuation.place.clone(), - value: ReactiveValue::SequenceExpression { - instructions: reactive_instrs, - id: continuation.id, - value: Box::new(continuation.value), - loc, - }, - id: continuation.id, - } - } - - /// Converts the result of visit_value_block into a SequenceExpression that includes - /// the instruction with its lvalue. This is needed for for/for-of/for-in init/test - /// blocks where the instruction's lvalue assignment must be preserved. - /// - /// This also flattens nested SequenceExpressions that can occur from MaybeThrow - /// handling in try-catch blocks. - /// - /// TS: valueBlockResultToSequence() - fn value_block_result_to_sequence( - &self, - result: ValueBlockResult, - loc: Option<SourceLocation>, - ) -> ReactiveValue { - // Collect all instructions from potentially nested SequenceExpressions - let mut instructions: Vec<ReactiveInstruction> = Vec::new(); - let mut inner_value = result.value; - - // Flatten nested SequenceExpressions - loop { - match inner_value { - ReactiveValue::SequenceExpression { - instructions: seq_instrs, - value, - .. - } => { - instructions.extend(seq_instrs); - inner_value = *value; - } - _ => break, - } - } - - // Only add the final instruction if the innermost value is not just a LoadLocal - // of the same place we're storing to (which would be a no-op). - let is_load_of_same_place = match &inner_value { - ReactiveValue::Instruction(InstructionValue::LoadLocal { place, .. }) => { - place.identifier == result.place.identifier - } - _ => false, - }; - - if !is_load_of_same_place { - instructions.push(ReactiveInstruction { - id: result.id, - lvalue: Some(result.place), - value: inner_value, - effects: None, - loc, - }); - } - - ReactiveValue::SequenceExpression { - instructions, - id: result.id, - value: Box::new(ReactiveValue::Instruction(InstructionValue::Primitive { - value: react_compiler_hir::PrimitiveValue::Undefined, - loc, - })), - loc, - } - } - - fn visit_break( - &self, - block: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - ) -> Result<Option<ReactiveStatement>, CompilerDiagnostic> { - let (target_block, target_kind) = self.cx.get_break_target(block)?; - if self.cx.scope_fallthroughs.contains(&target_block) { - if target_kind != ReactiveTerminalTargetKind::Implicit { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected reactive scope to implicitly break to fallthrough".to_string(), - None, - )); - } - return Ok(None); - } - Ok(Some(ReactiveStatement::Terminal( - ReactiveTerminalStatement { - terminal: ReactiveTerminal::Break { - target: target_block, - id, - target_kind, - loc, - }, - label: None, - }, - ))) - } - - fn visit_continue( - &self, - block: BlockId, - id: EvaluationOrder, - loc: Option<SourceLocation>, - ) -> Result<ReactiveStatement, CompilerDiagnostic> { - let (target_block, target_kind) = match self.cx.get_continue_target(block) { - Some(result) => result, - None => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!("Expected continue target to be scheduled for bb{}", block.0), - None, - )); - } - }; - - Ok(ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::Continue { - target: target_block, - id, - target_kind, - loc, - }, - label: None, - })) - } -} - -// ============================================================================= -// Helper types -// ============================================================================= - -struct ValueBlockResult { - block: BlockId, - place: Place, - value: ReactiveValue, - id: EvaluationOrder, -} - -struct TestBlockResult { - test: ValueBlockResult, - consequent: BlockId, - alternate: BlockId, - branch_loc: Option<SourceLocation>, -} - -struct ValueTerminalResult { - value: ReactiveValue, - place: Place, - fallthrough: BlockId, - id: EvaluationOrder, -} - -/// Helper to get loc from a terminal -fn terminal_loc(terminal: &Terminal) -> &Option<SourceLocation> { - match terminal { - Terminal::If { loc, .. } - | Terminal::Branch { loc, .. } - | Terminal::Logical { loc, .. } - | Terminal::Ternary { loc, .. } - | Terminal::Optional { loc, .. } - | Terminal::Throw { loc, .. } - | Terminal::Return { loc, .. } - | Terminal::Goto { loc, .. } - | Terminal::Switch { loc, .. } - | Terminal::DoWhile { loc, .. } - | Terminal::While { loc, .. } - | Terminal::For { loc, .. } - | Terminal::ForOf { loc, .. } - | Terminal::ForIn { loc, .. } - | Terminal::Label { loc, .. } - | Terminal::Sequence { loc, .. } - | Terminal::Unreachable { loc, .. } - | Terminal::Unsupported { loc, .. } - | Terminal::MaybeThrow { loc, .. } - | Terminal::Scope { loc, .. } - | Terminal::PrunedScope { loc, .. } - | Terminal::Try { loc, .. } => loc, - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/codegen_reactive_function.rs b/compiler/crates/react_compiler_reactive_scopes/src/codegen_reactive_function.rs deleted file mode 100644 index 078db1f7a3b2..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/codegen_reactive_function.rs +++ /dev/null @@ -1,4311 +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. - -//! Code generation pass: converts a `ReactiveFunction` tree back into a Babel-compatible -//! AST with memoization (useMemoCache) wired in. -//! -//! This is the final pass in the compilation pipeline. -//! -//! Corresponds to `src/ReactiveScopes/CodegenReactiveFunction.ts` in the TS compiler. - -use std::collections::HashMap; -use std::collections::HashSet; - -use react_compiler_ast::common::BaseNode; -use react_compiler_ast::common::Position as AstPosition; -use react_compiler_ast::common::RawNode; -use react_compiler_ast::common::SourceLocation as AstSourceLocation; -use react_compiler_ast::expressions::ArrowFunctionBody; -use react_compiler_ast::expressions::Expression; -use react_compiler_ast::expressions::Identifier as AstIdentifier; -use react_compiler_ast::expressions::{self as ast_expr}; -use react_compiler_ast::jsx::JSXAttribute as AstJSXAttribute; -use react_compiler_ast::jsx::JSXAttributeItem; -use react_compiler_ast::jsx::JSXAttributeName; -use react_compiler_ast::jsx::JSXAttributeValue; -use react_compiler_ast::jsx::JSXChild; -use react_compiler_ast::jsx::JSXClosingElement; -use react_compiler_ast::jsx::JSXClosingFragment; -use react_compiler_ast::jsx::JSXElement; -use react_compiler_ast::jsx::JSXElementName; -use react_compiler_ast::jsx::JSXExpressionContainer; -use react_compiler_ast::jsx::JSXExpressionContainerExpr; -use react_compiler_ast::jsx::JSXFragment; -use react_compiler_ast::jsx::JSXIdentifier; -use react_compiler_ast::jsx::JSXMemberExprObject; -use react_compiler_ast::jsx::JSXMemberExpression; -use react_compiler_ast::jsx::JSXNamespacedName; -use react_compiler_ast::jsx::JSXOpeningElement; -use react_compiler_ast::jsx::JSXOpeningFragment; -use react_compiler_ast::jsx::JSXSpreadAttribute; -use react_compiler_ast::jsx::JSXText; -use react_compiler_ast::literals::BooleanLiteral; -use react_compiler_ast::literals::NullLiteral; -use react_compiler_ast::literals::NumericLiteral; -use react_compiler_ast::literals::RegExpLiteral as AstRegExpLiteral; -use react_compiler_ast::literals::StringLiteral; -use react_compiler_ast::literals::TemplateElement; -use react_compiler_ast::literals::TemplateElementValue; -use react_compiler_ast::operators::AssignmentOperator; -use react_compiler_ast::operators::BinaryOperator as AstBinaryOperator; -use react_compiler_ast::operators::LogicalOperator as AstLogicalOperator; -use react_compiler_ast::operators::UnaryOperator as AstUnaryOperator; -use react_compiler_ast::operators::UpdateOperator as AstUpdateOperator; -use react_compiler_ast::patterns::ArrayPattern as AstArrayPattern; -use react_compiler_ast::patterns::ObjectPatternProp; -use react_compiler_ast::patterns::ObjectPatternProperty; -use react_compiler_ast::patterns::PatternLike; -use react_compiler_ast::patterns::RestElement; -use react_compiler_ast::statements::BlockStatement; -use react_compiler_ast::statements::BreakStatement; -use react_compiler_ast::statements::CatchClause; -use react_compiler_ast::statements::ContinueStatement; -use react_compiler_ast::statements::DebuggerStatement; -use react_compiler_ast::statements::Directive; -use react_compiler_ast::statements::DirectiveLiteral; -use react_compiler_ast::statements::DoWhileStatement; -use react_compiler_ast::statements::EmptyStatement; -use react_compiler_ast::statements::ExpressionStatement; -use react_compiler_ast::statements::ForInStatement; -use react_compiler_ast::statements::ForInit; -use react_compiler_ast::statements::ForOfStatement; -use react_compiler_ast::statements::ForStatement; -use react_compiler_ast::statements::FunctionDeclaration; -use react_compiler_ast::statements::IfStatement; -use react_compiler_ast::statements::LabeledStatement; -use react_compiler_ast::statements::ReturnStatement; -use react_compiler_ast::statements::Statement; -use react_compiler_ast::statements::SwitchCase; -use react_compiler_ast::statements::SwitchStatement; -use react_compiler_ast::statements::ThrowStatement; -use react_compiler_ast::statements::TryStatement; -use react_compiler_ast::statements::UnknownStatement; -use react_compiler_ast::statements::VariableDeclaration; -use react_compiler_ast::statements::VariableDeclarationKind; -use react_compiler_ast::statements::VariableDeclarator; -use react_compiler_ast::statements::WhileStatement; -use react_compiler_ast::statements::is_known_statement_type; -use react_compiler_diagnostics::CompilerDiagnostic; -use react_compiler_diagnostics::CompilerDiagnosticDetail; -use react_compiler_diagnostics::CompilerError; -use react_compiler_diagnostics::CompilerErrorDetail; -use react_compiler_diagnostics::ErrorCategory; -use react_compiler_diagnostics::SourceLocation as DiagSourceLocation; -use react_compiler_hir::ArrayElement; -use react_compiler_hir::ArrayPattern; -use react_compiler_hir::BlockId; -use react_compiler_hir::DeclarationId; -use react_compiler_hir::FunctionExpressionType; -use react_compiler_hir::IdentifierId; -use react_compiler_hir::InstructionKind; -use react_compiler_hir::InstructionValue; -use react_compiler_hir::JsxAttribute; -use react_compiler_hir::JsxTag; -use react_compiler_hir::LogicalOperator; -use react_compiler_hir::ObjectPattern; -use react_compiler_hir::ObjectPropertyKey; -use react_compiler_hir::ObjectPropertyOrSpread; -use react_compiler_hir::ObjectPropertyType; -use react_compiler_hir::ParamPattern; -use react_compiler_hir::Pattern; -use react_compiler_hir::Place; -use react_compiler_hir::PlaceOrSpread; -use react_compiler_hir::PrimitiveValue; -use react_compiler_hir::PropertyLiteral; -use react_compiler_hir::ScopeId; -use react_compiler_hir::SpreadPattern; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::reactive::PrunedReactiveScopeBlock; -use react_compiler_hir::reactive::ReactiveBlock; -use react_compiler_hir::reactive::ReactiveFunction; -use react_compiler_hir::reactive::ReactiveInstruction; -use react_compiler_hir::reactive::ReactiveScopeBlock; -use react_compiler_hir::reactive::ReactiveStatement; -use react_compiler_hir::reactive::ReactiveTerminal; -use react_compiler_hir::reactive::ReactiveTerminalTargetKind; -use react_compiler_hir::reactive::ReactiveValue; - -use crate::build_reactive_function::build_reactive_function; -use crate::prune_hoisted_contexts::prune_hoisted_contexts; -use crate::prune_unused_labels::prune_unused_labels; -use crate::prune_unused_lvalues::prune_unused_lvalues; -use crate::rename_variables::rename_variables; -use crate::visitors::ReactiveFunctionVisitor; -use crate::visitors::visit_reactive_function; - -// ============================================================================= -// Public API -// ============================================================================= - -pub const MEMO_CACHE_SENTINEL: &str = "react.memo_cache_sentinel"; -pub const EARLY_RETURN_SENTINEL: &str = "react.early_return_sentinel"; - -/// FBT tags whose children get special codegen treatment. -const SINGLE_CHILD_FBT_TAGS: &[&str] = &["fbt:param", "fbs:param"]; - -/// Result of code generation for a single function. -pub struct CodegenFunction { - pub loc: Option<DiagSourceLocation>, - pub id: Option<AstIdentifier>, - pub name_hint: Option<String>, - pub params: Vec<PatternLike>, - pub body: BlockStatement, - pub generator: bool, - pub is_async: bool, - pub memo_slots_used: u32, - pub memo_blocks: u32, - pub memo_values: u32, - pub pruned_memo_blocks: u32, - pub pruned_memo_values: u32, - pub outlined: Vec<OutlinedFunction>, -} - -impl std::fmt::Debug for CodegenFunction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CodegenFunction") - .field("memo_slots_used", &self.memo_slots_used) - .field("memo_blocks", &self.memo_blocks) - .field("memo_values", &self.memo_values) - .field("pruned_memo_blocks", &self.pruned_memo_blocks) - .field("pruned_memo_values", &self.pruned_memo_values) - .finish() - } -} - -/// An outlined function extracted during compilation. -pub struct OutlinedFunction { - pub func: CodegenFunction, - pub fn_type: Option<react_compiler_hir::ReactFunctionType>, -} - -/// Top-level entry point: generates code for a reactive function. -pub fn codegen_function( - func: &ReactiveFunction, - env: &mut Environment, - unique_identifiers: HashSet<String>, - fbt_operands: HashSet<IdentifierId>, -) -> Result<CodegenFunction, CompilerError> { - let fn_name = func.id.as_deref().unwrap_or("[[ anonymous ]]"); - let mut cx = Context::new(env, fn_name.to_string(), unique_identifiers, fbt_operands); - - // Fast Refresh: compute source hash and reserve a cache slot if enabled - let fast_refresh_state: Option<(u32, String)> = - if cx.env.config.enable_reset_cache_on_source_file_changes == Some(true) { - if let Some(ref code) = cx.env.code { - use hmac::Hmac; - use hmac::Mac; - use sha2::Sha256; - type HmacSha256 = Hmac<Sha256>; - // Match TS: createHmac('sha256', code).digest('hex') - // Node's createHmac uses the code as the HMAC key and hashes empty data. - let mac = HmacSha256::new_from_slice(code.as_bytes()) - .expect("HMAC can take key of any size"); - let hash = format!("{:x}", mac.finalize().into_bytes()); - let cache_index = cx.alloc_cache_index(); // Reserve slot 0 for the hash check - Some((cache_index, hash)) - } else { - None - } - } else { - None - }; - - let mut compiled = codegen_reactive_function(&mut cx, func)?; - - // enableEmitHookGuards: wrap entire function body in try/finally with - // $dispatcherGuard(PushHookGuard=0) / $dispatcherGuard(PopHookGuard=1). - // Per-hook-call wrapping is done inline during codegen (CallExpression/MethodCall). - if cx.env.hook_guard_name.is_some() - && cx.env.output_mode == react_compiler_hir::environment::OutputMode::Client - { - let guard_name = cx.env.hook_guard_name.as_ref().unwrap().clone(); - let body_stmts = std::mem::replace(&mut compiled.body.body, Vec::new()); - compiled.body.body = vec![create_function_body_hook_guard( - &guard_name, - body_stmts, - 0, - 1, - )]; - } - - let cache_count = compiled.memo_slots_used; - if cache_count != 0 { - let mut preface: Vec<Statement> = Vec::new(); - let cache_name = cx.synthesize_name("$"); - - // const $ = useMemoCache(N) - preface.push(Statement::VariableDeclaration(VariableDeclaration { - base: BaseNode::typed("VariableDeclaration"), - declarations: vec![VariableDeclarator { - base: BaseNode::typed("VariableDeclarator"), - id: PatternLike::Identifier(make_identifier(&cache_name)), - init: Some(Box::new(Expression::CallExpression( - ast_expr::CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(Expression::Identifier(make_identifier("useMemoCache"))), - arguments: vec![Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: cache_count as f64, - extra: None, - })], - type_parameters: None, - type_arguments: None, - optional: None, - }, - ))), - definite: None, - }], - kind: VariableDeclarationKind::Const, - declare: None, - })); - - // Fast Refresh: emit cache invalidation check after useMemoCache - if let Some((cache_index, ref hash)) = fast_refresh_state { - let index_var = cx.synthesize_name("$i"); - // if ($[cacheIndex] !== "hash") { for (let $i = 0; $i < N; $i += 1) { $[$i] = Symbol.for("react.memo_cache_sentinel"); } $[cacheIndex] = "hash"; } - preface.push(Statement::IfStatement(IfStatement { - base: BaseNode::typed("IfStatement"), - test: Box::new(Expression::BinaryExpression(ast_expr::BinaryExpression { - base: BaseNode::typed("BinaryExpression"), - operator: AstBinaryOperator::StrictNeq, - left: Box::new(Expression::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(Expression::Identifier(make_identifier(&cache_name))), - property: Box::new(Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: cache_index as f64, - extra: None, - })), - computed: true, - })), - right: Box::new(Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: hash.clone(), - })), - })), - consequent: Box::new(Statement::BlockStatement(BlockStatement { - base: BaseNode::typed("BlockStatement"), - body: vec![ - // for (let $i = 0; $i < N; $i += 1) { $[$i] = Symbol.for("react.memo_cache_sentinel"); } - Statement::ForStatement(ForStatement { - base: BaseNode::typed("ForStatement"), - init: Some(Box::new(ForInit::VariableDeclaration( - VariableDeclaration { - base: BaseNode::typed("VariableDeclaration"), - declarations: vec![VariableDeclarator { - base: BaseNode::typed("VariableDeclarator"), - id: PatternLike::Identifier(make_identifier(&index_var)), - init: Some(Box::new(Expression::NumericLiteral( - NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: 0.0, - extra: None, - }, - ))), - definite: None, - }], - kind: VariableDeclarationKind::Let, - declare: None, - }, - ))), - test: Some(Box::new(Expression::BinaryExpression( - ast_expr::BinaryExpression { - base: BaseNode::typed("BinaryExpression"), - operator: AstBinaryOperator::Lt, - left: Box::new(Expression::Identifier(make_identifier( - &index_var, - ))), - right: Box::new(Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: cache_count as f64, - extra: None, - })), - }, - ))), - update: Some(Box::new(Expression::AssignmentExpression( - ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::AddAssign, - left: Box::new(PatternLike::Identifier(make_identifier( - &index_var, - ))), - right: Box::new(Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: 1.0, - extra: None, - })), - }, - ))), - body: Box::new(Statement::BlockStatement(BlockStatement { - base: BaseNode::typed("BlockStatement"), - body: vec![Statement::ExpressionStatement(ExpressionStatement { - base: BaseNode::typed("ExpressionStatement"), - expression: Box::new(Expression::AssignmentExpression( - ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::Assign, - left: Box::new(PatternLike::MemberExpression( - ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(Expression::Identifier( - make_identifier(&cache_name), - )), - property: Box::new(Expression::Identifier( - make_identifier(&index_var), - )), - computed: true, - }, - )), - right: Box::new(Expression::CallExpression( - ast_expr::CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(Expression::MemberExpression( - ast_expr::MemberExpression { - base: BaseNode::typed( - "MemberExpression", - ), - object: Box::new( - Expression::Identifier( - make_identifier("Symbol"), - ), - ), - property: Box::new( - Expression::Identifier( - make_identifier("for"), - ), - ), - computed: false, - }, - )), - arguments: vec![Expression::StringLiteral( - StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: MEMO_CACHE_SENTINEL.to_string(), - }, - )], - type_parameters: None, - type_arguments: None, - optional: None, - }, - )), - }, - )), - })], - directives: Vec::new(), - })), - }), - // $[cacheIndex] = "hash" - Statement::ExpressionStatement(ExpressionStatement { - base: BaseNode::typed("ExpressionStatement"), - expression: Box::new(Expression::AssignmentExpression( - ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::Assign, - left: Box::new(PatternLike::MemberExpression( - ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(Expression::Identifier( - make_identifier(&cache_name), - )), - property: Box::new(Expression::NumericLiteral( - NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: cache_index as f64, - extra: None, - }, - )), - computed: true, - }, - )), - right: Box::new(Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: hash.clone(), - })), - }, - )), - }), - ], - directives: Vec::new(), - })), - alternate: None, - })); - } - - // Insert preface at the beginning of the body - let mut new_body = preface; - new_body.append(&mut compiled.body.body); - compiled.body.body = new_body; - } - - // Instrument forget: emit instrumentation call at the top of the function body - let emit_instrument_forget = cx.env.config.enable_emit_instrument_forget.clone(); - if let Some(ref instrument_config) = emit_instrument_forget { - if func.id.is_some() - && cx.env.output_mode == react_compiler_hir::environment::OutputMode::Client - { - // Use pre-resolved import names from environment (set by program-level code) - let instrument_fn_local = cx - .env - .instrument_fn_name - .clone() - .unwrap_or_else(|| instrument_config.fn_.import_specifier_name.clone()); - let instrument_gating_local = cx.env.instrument_gating_name.clone(); - - // Build the gating condition - let gating_expr: Option<Expression> = - instrument_gating_local.map(|name| Expression::Identifier(make_identifier(&name))); - let global_gating_expr: Option<Expression> = instrument_config - .global_gating - .as_ref() - .map(|g| Expression::Identifier(make_identifier(g))); - - let if_test = match (gating_expr, global_gating_expr) { - (Some(gating), Some(global)) => { - Expression::LogicalExpression(ast_expr::LogicalExpression { - base: BaseNode::typed("LogicalExpression"), - operator: AstLogicalOperator::And, - left: Box::new(global), - right: Box::new(gating), - }) - } - (Some(gating), None) => gating, - (None, Some(global)) => global, - (None, None) => unreachable!( - "InstrumentationConfig requires at least one of gating or globalGating" - ), - }; - - let fn_name_str = func.id.as_deref().unwrap_or(""); - let filename_str = cx.env.filename.as_deref().unwrap_or(""); - - let instrument_call = Statement::IfStatement(IfStatement { - base: BaseNode::typed("IfStatement"), - test: Box::new(if_test), - consequent: Box::new(Statement::ExpressionStatement(ExpressionStatement { - base: BaseNode::typed("ExpressionStatement"), - expression: Box::new(Expression::CallExpression(ast_expr::CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(Expression::Identifier(make_identifier( - &instrument_fn_local, - ))), - arguments: vec![ - Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: fn_name_str.to_string(), - }), - Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: filename_str.to_string(), - }), - ], - type_parameters: None, - type_arguments: None, - optional: None, - })), - })), - alternate: None, - }); - compiled.body.body.insert(0, instrument_call); - } - } - - // Process outlined functions. - // Use clone (not take) to match TS behavior: getOutlinedFunctions() returns - // a reference, so outlined functions persist on the environment and are also - // available to the parent function's codegen. The inner function codegen - // processes them here, and the parent/top-level codegen processes them again. - let outlined_entries = cx.env.get_outlined_functions().to_vec(); - let mut outlined: Vec<OutlinedFunction> = Vec::new(); - for entry in outlined_entries { - let reactive_fn = build_reactive_function(&entry.func, cx.env)?; - let mut reactive_fn_mut = reactive_fn; - prune_unused_labels(&mut reactive_fn_mut, cx.env)?; - prune_unused_lvalues(&mut reactive_fn_mut, cx.env); - prune_hoisted_contexts(&mut reactive_fn_mut, cx.env)?; - - let identifiers = rename_variables(&mut reactive_fn_mut, cx.env); - let mut outlined_cx = Context::new( - cx.env, - reactive_fn_mut - .id - .as_deref() - .unwrap_or("[[ anonymous ]]") - .to_string(), - identifiers, - cx.fbt_operands.clone(), - ); - let codegen = codegen_reactive_function(&mut outlined_cx, &reactive_fn_mut)?; - outlined.push(OutlinedFunction { - func: codegen, - fn_type: entry.fn_type, - }); - } - compiled.outlined = outlined; - - Ok(compiled) -} - -// ============================================================================= -// Context -// ============================================================================= - -type Temporaries = HashMap<DeclarationId, Option<ExpressionOrJsxText>>; - -#[derive(Clone)] -enum ExpressionOrJsxText { - Expression(Expression), - JsxText(JSXText), -} - -struct Context<'env> { - env: &'env mut Environment, - #[allow(dead_code)] - fn_name: String, - next_cache_index: u32, - declarations: HashSet<DeclarationId>, - temp: Temporaries, - object_methods: HashMap< - IdentifierId, - ( - InstructionValue, - Option<react_compiler_diagnostics::SourceLocation>, - ), - >, - unique_identifiers: HashSet<String>, - fbt_operands: HashSet<IdentifierId>, - synthesized_names: HashMap<String, String>, -} - -impl<'env> Context<'env> { - fn new( - env: &'env mut Environment, - fn_name: String, - unique_identifiers: HashSet<String>, - fbt_operands: HashSet<IdentifierId>, - ) -> Self { - Context { - env, - fn_name, - next_cache_index: 0, - declarations: HashSet::new(), - temp: HashMap::new(), - object_methods: HashMap::new(), - unique_identifiers, - fbt_operands, - synthesized_names: HashMap::new(), - } - } - - fn alloc_cache_index(&mut self) -> u32 { - let idx = self.next_cache_index; - self.next_cache_index += 1; - idx - } - - fn declare(&mut self, identifier_id: IdentifierId) { - let ident = &self.env.identifiers[identifier_id.0 as usize]; - self.declarations.insert(ident.declaration_id); - } - - fn has_declared(&self, identifier_id: IdentifierId) -> bool { - let ident = &self.env.identifiers[identifier_id.0 as usize]; - self.declarations.contains(&ident.declaration_id) - } - - fn synthesize_name(&mut self, name: &str) -> String { - if let Some(prev) = self.synthesized_names.get(name) { - return prev.clone(); - } - let mut validated = name.to_string(); - let mut index = 0u32; - while self.unique_identifiers.contains(&validated) { - validated = format!("{name}{index}"); - index += 1; - } - self.unique_identifiers.insert(validated.clone()); - self.synthesized_names - .insert(name.to_string(), validated.clone()); - validated - } - - fn record_error(&mut self, detail: CompilerErrorDetail) -> Result<(), CompilerError> { - self.env.record_error(detail) - } -} - -// ============================================================================= -// Core codegen functions -// ============================================================================= - -fn codegen_reactive_function( - cx: &mut Context, - func: &ReactiveFunction, -) -> Result<CodegenFunction, CompilerError> { - // Register parameters - for param in &func.params { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(sp) => &sp.place, - }; - let ident = &cx.env.identifiers[place.identifier.0 as usize]; - cx.temp.insert(ident.declaration_id, None); - cx.declare(place.identifier); - } - - let params: Vec<PatternLike> = func - .params - .iter() - .map(|p| convert_parameter(p, cx.env)) - .collect::<Result<_, _>>()?; - let mut body = codegen_block(cx, &func.body)?; - - // Add directives - body.directives = func - .directives - .iter() - .map(|d| Directive { - base: BaseNode::typed("Directive"), - value: DirectiveLiteral { - base: BaseNode::typed("DirectiveLiteral"), - value: d.clone(), - }, - }) - .collect(); - - // Remove trailing `return undefined` - if let Some(last) = body.body.last() { - if matches!(last, Statement::ReturnStatement(ret) if ret.argument.is_none()) { - body.body.pop(); - } - } - - // Count memo blocks - let (memo_blocks, memo_values, pruned_memo_blocks, pruned_memo_values) = - count_memo_blocks(func, cx.env); - - Ok(CodegenFunction { - loc: func.loc, - id: func.id.as_ref().map(|name| make_identifier(name)), - name_hint: func.name_hint.clone(), - params, - body, - generator: func.generator, - is_async: func.is_async, - memo_slots_used: cx.next_cache_index, - memo_blocks, - memo_values, - pruned_memo_blocks, - pruned_memo_values, - outlined: Vec::new(), - }) -} - -fn convert_parameter( - param: &ParamPattern, - env: &Environment, -) -> Result<PatternLike, CompilerError> { - match param { - ParamPattern::Place(place) => Ok(PatternLike::Identifier(convert_identifier( - place.identifier, - env, - )?)), - ParamPattern::Spread(spread) => Ok(PatternLike::RestElement(RestElement { - base: BaseNode::typed("RestElement"), - argument: Box::new(PatternLike::Identifier(convert_identifier( - spread.place.identifier, - env, - )?)), - type_annotation: None, - decorators: None, - })), - } -} - -// ============================================================================= -// Block codegen -// ============================================================================= - -fn codegen_block(cx: &mut Context, block: &ReactiveBlock) -> Result<BlockStatement, CompilerError> { - let temp_snapshot: Temporaries = cx.temp.clone(); - let result = codegen_block_no_reset(cx, block)?; - cx.temp = temp_snapshot; - Ok(result) -} - -fn codegen_block_no_reset( - cx: &mut Context, - block: &ReactiveBlock, -) -> Result<BlockStatement, CompilerError> { - let mut statements: Vec<Statement> = Vec::new(); - for item in block { - match item { - ReactiveStatement::Instruction(instr) => { - if let Some(stmt) = codegen_instruction_nullable(cx, instr)? { - statements.push(stmt); - } - } - ReactiveStatement::PrunedScope(PrunedReactiveScopeBlock { instructions, .. }) => { - let scope_block = codegen_block_no_reset(cx, instructions)?; - statements.extend(scope_block.body); - } - ReactiveStatement::Scope(ReactiveScopeBlock { - scope, - instructions, - }) => { - let temp_snapshot = cx.temp.clone(); - codegen_reactive_scope(cx, &mut statements, *scope, instructions)?; - cx.temp = temp_snapshot; - } - ReactiveStatement::Terminal(term_stmt) => { - let stmt = codegen_terminal(cx, &term_stmt.terminal)?; - let Some(stmt) = stmt else { - continue; - }; - if let Some(ref label) = term_stmt.label { - if !label.implicit { - let inner = if let Statement::BlockStatement(bs) = &stmt { - if bs.body.len() == 1 { - bs.body[0].clone() - } else { - stmt - } - } else { - stmt - }; - statements.push(Statement::LabeledStatement(LabeledStatement { - base: BaseNode::typed("LabeledStatement"), - label: make_identifier(&codegen_label(label.id)), - body: Box::new(inner), - })); - } else if let Statement::BlockStatement(bs) = stmt { - statements.extend(bs.body); - } else { - statements.push(stmt); - } - } else if let Statement::BlockStatement(bs) = stmt { - statements.extend(bs.body); - } else { - statements.push(stmt); - } - } - } - } - Ok(BlockStatement { - base: BaseNode::typed("BlockStatement"), - body: statements, - directives: Vec::new(), - }) -} - -// ============================================================================= -// Reactive scope codegen (memoization) -// ============================================================================= - -fn codegen_reactive_scope( - cx: &mut Context, - statements: &mut Vec<Statement>, - scope_id: ScopeId, - block: &ReactiveBlock, -) -> Result<(), CompilerError> { - // Clone scope data upfront to avoid holding a borrow on cx.env - let scope_deps = cx.env.scopes[scope_id.0 as usize].dependencies.clone(); - let scope_decls = cx.env.scopes[scope_id.0 as usize].declarations.clone(); - let scope_reassignments = cx.env.scopes[scope_id.0 as usize].reassignments.clone(); - - let mut cache_store_stmts: Vec<Statement> = Vec::new(); - let mut cache_load_stmts: Vec<Statement> = Vec::new(); - let mut cache_loads: Vec<(AstIdentifier, u32, Expression)> = Vec::new(); - let mut change_exprs: Vec<Expression> = Vec::new(); - - // Sort dependencies - let mut deps = scope_deps; - deps.sort_by(|a, b| compare_scope_dependency(a, b, cx.env)); - - for dep in &deps { - let index = cx.alloc_cache_index(); - let cache_name = cx.synthesize_name("$"); - let comparison = Expression::BinaryExpression(ast_expr::BinaryExpression { - base: BaseNode::typed("BinaryExpression"), - operator: AstBinaryOperator::StrictNeq, - left: Box::new(Expression::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(Expression::Identifier(make_identifier(&cache_name))), - property: Box::new(Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: index as f64, - extra: None, - })), - computed: true, - })), - right: Box::new(codegen_dependency(cx, dep)?), - }); - change_exprs.push(comparison); - - // Store dependency value into cache - let dep_value = codegen_dependency(cx, dep)?; - cache_store_stmts.push(Statement::ExpressionStatement(ExpressionStatement { - base: BaseNode::typed("ExpressionStatement"), - expression: Box::new(Expression::AssignmentExpression( - ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::Assign, - left: Box::new(PatternLike::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(Expression::Identifier(make_identifier(&cache_name))), - property: Box::new(Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: index as f64, - extra: None, - })), - computed: true, - })), - right: Box::new(dep_value), - }, - )), - })); - } - - let mut first_output_index: Option<u32> = None; - - // Sort declarations - let mut decls = scope_decls; - decls.sort_by(|(_id_a, a), (_id_b, b)| compare_scope_declaration(a, b, cx.env)); - - for (_ident_id, decl) in &decls { - let index = cx.alloc_cache_index(); - if first_output_index.is_none() { - first_output_index = Some(index); - } - - let ident = &cx.env.identifiers[decl.identifier.0 as usize]; - invariant( - ident.name.is_some(), - &format!( - "Expected scope declaration identifier to be named, id={}", - decl.identifier.0 - ), - None, - )?; - - let name = convert_identifier(decl.identifier, cx.env)?; - if !cx.has_declared(decl.identifier) { - statements.push(Statement::VariableDeclaration(VariableDeclaration { - base: BaseNode::typed("VariableDeclaration"), - declarations: vec![make_var_declarator( - PatternLike::Identifier(name.clone()), - None, - )], - kind: VariableDeclarationKind::Let, - declare: None, - })); - } - cache_loads.push((name.clone(), index, Expression::Identifier(name.clone()))); - cx.declare(decl.identifier); - } - - for reassignment_id in scope_reassignments { - let index = cx.alloc_cache_index(); - if first_output_index.is_none() { - first_output_index = Some(index); - } - let name = convert_identifier(reassignment_id, cx.env)?; - cache_loads.push((name.clone(), index, Expression::Identifier(name))); - } - - // Build test condition - let test_condition = if change_exprs.is_empty() { - let first_idx = first_output_index.ok_or_else(|| { - invariant_err("Expected scope to have at least one declaration", None) - })?; - let cache_name = cx.synthesize_name("$"); - Expression::BinaryExpression(ast_expr::BinaryExpression { - base: BaseNode::typed("BinaryExpression"), - operator: AstBinaryOperator::StrictEq, - left: Box::new(Expression::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(Expression::Identifier(make_identifier(&cache_name))), - property: Box::new(Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: first_idx as f64, - extra: None, - })), - computed: true, - })), - right: Box::new(symbol_for(MEMO_CACHE_SENTINEL)), - }) - } else { - change_exprs - .into_iter() - .reduce(|acc, expr| { - Expression::LogicalExpression(ast_expr::LogicalExpression { - base: BaseNode::typed("LogicalExpression"), - operator: AstLogicalOperator::Or, - left: Box::new(acc), - right: Box::new(expr), - }) - }) - .unwrap() - }; - - let mut computation_block = codegen_block(cx, block)?; - - // Build cache store and load statements for declarations - for (name, index, value) in &cache_loads { - let cache_name = cx.synthesize_name("$"); - cache_store_stmts.push(Statement::ExpressionStatement(ExpressionStatement { - base: BaseNode::typed("ExpressionStatement"), - expression: Box::new(Expression::AssignmentExpression( - ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::Assign, - left: Box::new(PatternLike::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(Expression::Identifier(make_identifier(&cache_name))), - property: Box::new(Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: *index as f64, - extra: None, - })), - computed: true, - })), - right: Box::new(value.clone()), - }, - )), - })); - cache_load_stmts.push(Statement::ExpressionStatement(ExpressionStatement { - base: BaseNode::typed("ExpressionStatement"), - expression: Box::new(Expression::AssignmentExpression( - ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::Assign, - left: Box::new(PatternLike::Identifier(name.clone())), - right: Box::new(Expression::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(Expression::Identifier(make_identifier(&cache_name))), - property: Box::new(Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: *index as f64, - extra: None, - })), - computed: true, - })), - }, - )), - })); - } - - computation_block.body.extend(cache_store_stmts); - - let memo_stmt = Statement::IfStatement(IfStatement { - base: BaseNode::typed("IfStatement"), - test: Box::new(test_condition), - consequent: Box::new(Statement::BlockStatement(computation_block)), - alternate: Some(Box::new(Statement::BlockStatement(BlockStatement { - base: BaseNode::typed("BlockStatement"), - body: cache_load_stmts, - directives: Vec::new(), - }))), - }); - statements.push(memo_stmt); - - // Handle early return - let early_return_value = cx.env.scopes[scope_id.0 as usize] - .early_return_value - .clone(); - if let Some(ref early_return) = early_return_value { - let early_ident = &cx.env.identifiers[early_return.value.0 as usize]; - let name = match &early_ident.name { - Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(), - Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(), - None => { - return Err(invariant_err( - "Expected early return value to be promoted to a named variable", - early_return.loc, - )); - } - }; - statements.push(Statement::IfStatement(IfStatement { - base: BaseNode::typed("IfStatement"), - test: Box::new(Expression::BinaryExpression(ast_expr::BinaryExpression { - base: BaseNode::typed("BinaryExpression"), - operator: AstBinaryOperator::StrictNeq, - left: Box::new(Expression::Identifier(make_identifier(&name))), - right: Box::new(symbol_for(EARLY_RETURN_SENTINEL)), - })), - consequent: Box::new(Statement::BlockStatement(BlockStatement { - base: BaseNode::typed("BlockStatement"), - body: vec![Statement::ReturnStatement(ReturnStatement { - base: BaseNode::typed("ReturnStatement"), - argument: Some(Box::new(Expression::Identifier(make_identifier(&name)))), - })], - directives: Vec::new(), - })), - alternate: None, - })); - } - - Ok(()) -} - -// ============================================================================= -// Terminal codegen -// ============================================================================= - -fn codegen_terminal( - cx: &mut Context, - terminal: &ReactiveTerminal, -) -> Result<Option<Statement>, CompilerError> { - match terminal { - ReactiveTerminal::Break { - target, - target_kind, - loc, - .. - } => { - if *target_kind == ReactiveTerminalTargetKind::Implicit { - return Ok(None); - } - Ok(Some(Statement::BreakStatement(BreakStatement { - base: base_node_with_loc("BreakStatement", *loc), - label: if *target_kind == ReactiveTerminalTargetKind::Labeled { - Some(make_identifier(&codegen_label(*target))) - } else { - None - }, - }))) - } - ReactiveTerminal::Continue { - target, - target_kind, - loc, - .. - } => { - if *target_kind == ReactiveTerminalTargetKind::Implicit { - return Ok(None); - } - Ok(Some(Statement::ContinueStatement(ContinueStatement { - base: base_node_with_loc("ContinueStatement", *loc), - label: if *target_kind == ReactiveTerminalTargetKind::Labeled { - Some(make_identifier(&codegen_label(*target))) - } else { - None - }, - }))) - } - ReactiveTerminal::Return { value, loc, .. } => { - let expr = codegen_place_to_expression(cx, value)?; - if let Expression::Identifier(ref ident) = expr { - if ident.name == "undefined" { - return Ok(Some(Statement::ReturnStatement(ReturnStatement { - base: base_node_with_loc("ReturnStatement", *loc), - argument: None, - }))); - } - } - Ok(Some(Statement::ReturnStatement(ReturnStatement { - base: base_node_with_loc("ReturnStatement", *loc), - argument: Some(Box::new(expr)), - }))) - } - ReactiveTerminal::Throw { value, loc, .. } => { - let expr = codegen_place_to_expression(cx, value)?; - Ok(Some(Statement::ThrowStatement(ThrowStatement { - base: base_node_with_loc("ThrowStatement", *loc), - argument: Box::new(expr), - }))) - } - ReactiveTerminal::If { - test, - consequent, - alternate, - loc, - .. - } => { - let test_expr = codegen_place_to_expression(cx, test)?; - let consequent_block = codegen_block(cx, consequent)?; - let alternate_stmt = if let Some(alt) = alternate { - let block = codegen_block(cx, alt)?; - if block.body.is_empty() { - None - } else { - Some(Box::new(Statement::BlockStatement(block))) - } - } else { - None - }; - Ok(Some(Statement::IfStatement(IfStatement { - base: base_node_with_loc("IfStatement", *loc), - test: Box::new(test_expr), - consequent: Box::new(Statement::BlockStatement(consequent_block)), - alternate: alternate_stmt, - }))) - } - ReactiveTerminal::Switch { - test, cases, loc, .. - } => { - let test_expr = codegen_place_to_expression(cx, test)?; - let switch_cases: Vec<SwitchCase> = cases - .iter() - .map(|case| { - let test = case - .test - .as_ref() - .map(|t| codegen_place_to_expression(cx, t)) - .transpose()?; - let block = case - .block - .as_ref() - .map(|b| codegen_block(cx, b)) - .transpose()?; - let consequent = match block { - Some(b) if b.body.is_empty() => Vec::new(), - Some(b) => vec![Statement::BlockStatement(b)], - None => Vec::new(), - }; - Ok(SwitchCase { - base: BaseNode::typed("SwitchCase"), - test: test.map(Box::new), - consequent, - }) - }) - .collect::<Result<_, CompilerError>>()?; - Ok(Some(Statement::SwitchStatement(SwitchStatement { - base: base_node_with_loc("SwitchStatement", *loc), - discriminant: Box::new(test_expr), - cases: switch_cases, - }))) - } - ReactiveTerminal::DoWhile { - loop_block, - test, - loc, - .. - } => { - let test_expr = codegen_instruction_value_to_expression(cx, test)?; - let body = codegen_block(cx, loop_block)?; - Ok(Some(Statement::DoWhileStatement(DoWhileStatement { - base: base_node_with_loc("DoWhileStatement", *loc), - test: Box::new(test_expr), - body: Box::new(Statement::BlockStatement(body)), - }))) - } - ReactiveTerminal::While { - test, - loop_block, - loc, - .. - } => { - let test_expr = codegen_instruction_value_to_expression(cx, test)?; - let body = codegen_block(cx, loop_block)?; - Ok(Some(Statement::WhileStatement(WhileStatement { - base: base_node_with_loc("WhileStatement", *loc), - test: Box::new(test_expr), - body: Box::new(Statement::BlockStatement(body)), - }))) - } - ReactiveTerminal::For { - init, - test, - update, - loop_block, - loc, - .. - } => { - let init_val = codegen_for_init(cx, init)?; - let test_expr = codegen_instruction_value_to_expression(cx, test)?; - let update_expr = update - .as_ref() - .map(|u| codegen_instruction_value_to_expression(cx, u)) - .transpose()?; - let body = codegen_block(cx, loop_block)?; - Ok(Some(Statement::ForStatement(ForStatement { - base: base_node_with_loc("ForStatement", *loc), - init: init_val.map(|v| Box::new(v)), - test: Some(Box::new(test_expr)), - update: update_expr.map(Box::new), - body: Box::new(Statement::BlockStatement(body)), - }))) - } - ReactiveTerminal::ForIn { - init, - loop_block, - loc, - .. - } => codegen_for_in(cx, init, loop_block, *loc), - ReactiveTerminal::ForOf { - init, - test, - loop_block, - loc, - .. - } => codegen_for_of(cx, init, test, loop_block, *loc), - ReactiveTerminal::Label { block, .. } => { - let body = codegen_block(cx, block)?; - Ok(Some(Statement::BlockStatement(body))) - } - ReactiveTerminal::Try { - block, - handler_binding, - handler, - loc, - .. - } => { - let catch_param = match handler_binding.as_ref() { - Some(binding) => { - let ident = &cx.env.identifiers[binding.identifier.0 as usize]; - cx.temp.insert(ident.declaration_id, None); - Some(PatternLike::Identifier(convert_identifier( - binding.identifier, - cx.env, - )?)) - } - None => None, - }; - let try_block = codegen_block(cx, block)?; - let handler_block = codegen_block(cx, handler)?; - Ok(Some(Statement::TryStatement(TryStatement { - base: base_node_with_loc("TryStatement", *loc), - block: try_block, - handler: Some(CatchClause { - base: BaseNode::typed("CatchClause"), - param: catch_param, - body: handler_block, - }), - finalizer: None, - }))) - } - } -} - -fn codegen_for_in( - cx: &mut Context, - init: &ReactiveValue, - loop_block: &ReactiveBlock, - loc: Option<DiagSourceLocation>, -) -> Result<Option<Statement>, CompilerError> { - let ReactiveValue::SequenceExpression { instructions, .. } = init else { - return Err(invariant_err( - "Expected a sequence expression init for for..in", - None, - )); - }; - if instructions.len() != 2 { - cx.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "Support non-trivial for..in inits".to_string(), - description: None, - loc, - suggestions: None, - })?; - return Ok(Some(Statement::EmptyStatement(EmptyStatement { - base: BaseNode::typed("EmptyStatement"), - }))); - } - let iterable_collection = &instructions[0]; - let iterable_item = &instructions[1]; - let instr_value = get_instruction_value(&iterable_item.value)?; - let (lval, var_decl_kind) = extract_for_in_of_lval(cx, instr_value, "for..in", loc)?; - let right = codegen_instruction_value_to_expression(cx, &iterable_collection.value)?; - let body = codegen_block(cx, loop_block)?; - Ok(Some(Statement::ForInStatement(ForInStatement { - base: base_node_with_loc("ForInStatement", loc), - left: Box::new( - react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(VariableDeclaration { - base: BaseNode::typed("VariableDeclaration"), - declarations: vec![VariableDeclarator { - base: BaseNode::typed("VariableDeclarator"), - id: lval, - init: None, - definite: None, - }], - kind: var_decl_kind, - declare: None, - }), - ), - right: Box::new(right), - body: Box::new(Statement::BlockStatement(body)), - }))) -} - -fn codegen_for_of( - cx: &mut Context, - init: &ReactiveValue, - test: &ReactiveValue, - loop_block: &ReactiveBlock, - loc: Option<DiagSourceLocation>, -) -> Result<Option<Statement>, CompilerError> { - // Validate init is SequenceExpression with single GetIterator instruction - let ReactiveValue::SequenceExpression { - instructions: init_instrs, - .. - } = init - else { - return Err(invariant_err( - "Expected a sequence expression init for for..of", - None, - )); - }; - if init_instrs.len() != 1 { - return Err(invariant_err( - "Expected a single-expression sequence expression init for for..of", - None, - )); - } - let get_iter_value = get_instruction_value(&init_instrs[0].value)?; - let InstructionValue::GetIterator { collection, .. } = get_iter_value else { - return Err(invariant_err("Expected GetIterator in for..of init", None)); - }; - - let ReactiveValue::SequenceExpression { - instructions: test_instrs, - .. - } = test - else { - return Err(invariant_err( - "Expected a sequence expression test for for..of", - None, - )); - }; - if test_instrs.len() != 2 { - cx.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: "Support non-trivial for..of inits".to_string(), - description: None, - loc, - suggestions: None, - })?; - return Ok(Some(Statement::EmptyStatement(EmptyStatement { - base: BaseNode::typed("EmptyStatement"), - }))); - } - let iterable_item = &test_instrs[1]; - let instr_value = get_instruction_value(&iterable_item.value)?; - let (lval, var_decl_kind) = extract_for_in_of_lval(cx, instr_value, "for..of", loc)?; - - let right = codegen_place_to_expression(cx, collection)?; - let body = codegen_block(cx, loop_block)?; - Ok(Some(Statement::ForOfStatement(ForOfStatement { - base: base_node_with_loc("ForOfStatement", loc), - left: Box::new( - react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(VariableDeclaration { - base: BaseNode::typed("VariableDeclaration"), - declarations: vec![VariableDeclarator { - base: BaseNode::typed("VariableDeclarator"), - id: lval, - init: None, - definite: None, - }], - kind: var_decl_kind, - declare: None, - }), - ), - right: Box::new(right), - body: Box::new(Statement::BlockStatement(body)), - is_await: false, - }))) -} - -/// Extract lval and declaration kind from a for-in/for-of iterable item instruction. -fn extract_for_in_of_lval( - cx: &mut Context, - instr_value: &InstructionValue, - context_name: &str, - loc: Option<DiagSourceLocation>, -) -> Result<(PatternLike, VariableDeclarationKind), CompilerError> { - let (lval, kind) = match instr_value { - InstructionValue::StoreLocal { lvalue, .. } => ( - codegen_lvalue(cx, &LvalueRef::Place(&lvalue.place))?, - lvalue.kind, - ), - InstructionValue::Destructure { lvalue, .. } => ( - codegen_lvalue(cx, &LvalueRef::Pattern(&lvalue.pattern))?, - lvalue.kind, - ), - InstructionValue::StoreContext { .. } => { - cx.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: format!("Support non-trivial {} inits", context_name), - description: None, - loc, - suggestions: None, - })?; - return Ok(( - PatternLike::Identifier(make_identifier("_")), - VariableDeclarationKind::Let, - )); - } - _ => { - return Err(invariant_err( - &format!( - "Expected a StoreLocal or Destructure in {} collection, found {:?}", - context_name, - std::mem::discriminant(instr_value) - ), - None, - )); - } - }; - let var_decl_kind = match kind { - InstructionKind::Const => VariableDeclarationKind::Const, - InstructionKind::Let => VariableDeclarationKind::Let, - _ => { - return Err(invariant_err( - &format!( - "Unexpected {:?} variable in {} collection", - kind, context_name - ), - None, - )); - } - }; - Ok((lval, var_decl_kind)) -} - -fn codegen_for_init( - cx: &mut Context, - init: &ReactiveValue, -) -> Result<Option<ForInit>, CompilerError> { - if let ReactiveValue::SequenceExpression { instructions, .. } = init { - let block_items: Vec<ReactiveStatement> = instructions - .iter() - .map(|i| ReactiveStatement::Instruction(i.clone())) - .collect(); - let body = codegen_block(cx, &block_items)?.body; - let mut declarators: Vec<VariableDeclarator> = Vec::new(); - let mut kind = VariableDeclarationKind::Const; - for instr in body { - // Check if this is an assignment that can be folded into the last declarator - if let Statement::ExpressionStatement(ref expr_stmt) = instr { - if let Expression::AssignmentExpression(ref assign) = *expr_stmt.expression { - if matches!(assign.operator, AssignmentOperator::Assign) { - if let PatternLike::Identifier(ref left_ident) = *assign.left { - if let Some(top) = declarators.last_mut() { - if let PatternLike::Identifier(ref top_ident) = top.id { - if top_ident.name == left_ident.name && top.init.is_none() { - top.init = Some(assign.right.clone()); - continue; - } - } - } - } - } - } - } - - if let Statement::VariableDeclaration(var_decl) = instr { - match var_decl.kind { - VariableDeclarationKind::Let | VariableDeclarationKind::Const => {} - _ => { - return Err(invariant_err( - "Expected a let or const variable declaration", - None, - )); - } - } - if matches!(var_decl.kind, VariableDeclarationKind::Let) { - kind = VariableDeclarationKind::Let; - } - declarators.extend(var_decl.declarations); - } else { - let stmt_type = get_statement_type_name(&instr); - let stmt_loc = get_statement_loc(&instr); - let reason = "Expected a variable declaration".to_string(); - let mut err = CompilerError::new(); - err.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::Invariant, - reason.clone(), - Some(format!("Got {}", stmt_type)), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: stmt_loc, - message: Some(reason), - identifier_name: None, - }), - ); - return Err(err); - } - } - if declarators.is_empty() { - return Err(invariant_err( - "Expected a variable declaration in for-init", - None, - )); - } - Ok(Some(ForInit::VariableDeclaration(VariableDeclaration { - base: BaseNode::typed("VariableDeclaration"), - declarations: declarators, - kind, - declare: None, - }))) - } else { - let expr = codegen_instruction_value_to_expression(cx, init)?; - Ok(Some(ForInit::Expression(Box::new(expr)))) - } -} - -// ============================================================================= -// Instruction codegen -// ============================================================================= - -/// How statement-position codegen disposes of an `UnsupportedNode`'s -/// `original_node`. See [`codegen_unsupported_original_node`]. -enum UnsupportedOriginalNode { - /// Emit this statement directly (early return). - Statement(Statement), - /// Flow through the general expression codegen path so the instruction's - /// lvalue temporary is bound/registered. - ExpressionCodegen, -} - -/// Discriminate an `UnsupportedNode`'s `original_node` by its `type` tag. -/// -/// Lowering serializes typed `Expression`/`Statement`/`PatternLike` bailout -/// nodes, plus the raw nodes of `Statement::Unknown` (whose tags are -/// unmodeled by construction). Dispatch accordingly: -/// -/// - Modeled statement tag: parse the typed statement and emit it directly. -/// A parse failure here is a serialize/deserialize asymmetry, surfaced as -/// an invariant rather than degraded. -/// - Tag parseable as `Expression` or `PatternLike` (both enums are strict, -/// no catch-all): expression codegen. Patterns (e.g. `ObjectPattern` -/// destructuring targets) keep their existing placeholder fallback there. -/// - Anything else is an unmodeled tag, producible only by the -/// unknown-statement lowering bailout — i.e. it came from a statement -/// position — so preserve it verbatim as `Statement::Unknown`, matching -/// the TS codegen's `return node` for non-expressions. -fn codegen_unsupported_original_node( - node: &serde_json::Value, -) -> Result<UnsupportedOriginalNode, CompilerError> { - let tag = node.get("type").and_then(serde_json::Value::as_str); - if tag.is_some_and(is_known_statement_type) { - let stmt: Statement = serde_json::from_value(node.clone()).map_err(|e| { - invariant_err( - &format!("Failed to deserialize original AST node: {}", e), - None, - ) - })?; - return Ok(UnsupportedOriginalNode::Statement(stmt)); - } - if serde_json::from_value::<Expression>(node.clone()).is_ok() - || serde_json::from_value::<PatternLike>(node.clone()).is_ok() - { - return Ok(UnsupportedOriginalNode::ExpressionCodegen); - } - let unknown = UnknownStatement::from_raw(RawNode::from_value(node)).map_err(|e| { - invariant_err( - &format!("Failed to read unsupported original AST node: {}", e), - None, - ) - })?; - Ok(UnsupportedOriginalNode::Statement(Statement::Unknown( - unknown, - ))) -} - -fn codegen_instruction_nullable( - cx: &mut Context, - instr: &ReactiveInstruction, -) -> Result<Option<Statement>, CompilerError> { - // Only check specific InstructionValue kinds for the base Instruction variant - if let ReactiveValue::Instruction(ref value) = instr.value { - match value { - InstructionValue::StoreLocal { .. } - | InstructionValue::StoreContext { .. } - | InstructionValue::Destructure { .. } - | InstructionValue::DeclareLocal { .. } - | InstructionValue::DeclareContext { .. } => { - return codegen_store_or_declare(cx, instr, value); - } - InstructionValue::StartMemoize { .. } | InstructionValue::FinishMemoize { .. } => { - return Ok(None); - } - InstructionValue::Debugger { .. } => { - return Ok(Some(Statement::DebuggerStatement(DebuggerStatement { - base: base_node_with_loc("DebuggerStatement", instr.loc), - }))); - } - InstructionValue::UnsupportedNode { - original_node: Some(node), - .. - } => { - // Statement-vs-expression discrimination must be explicit by - // `type` tag: `Statement`'s deserializer has a tolerant - // `Statement::Unknown` catch-all, so "does it deserialize as - // a Statement?" succeeds for ANY tagged object and would - // emit expression nodes as raw statements, orphaning their - // lvalue temporaries (the regression the explicit dispatch - // below prevents; TS codegen's equivalent check is - // `if (!t.isExpression(node)) return node; value = node`). - match codegen_unsupported_original_node(node)? { - UnsupportedOriginalNode::Statement(stmt) => return Ok(Some(stmt)), - UnsupportedOriginalNode::ExpressionCodegen => { - // Expression (or pattern) node — fall through to the - // general codegen path which handles lvalue binding - // and temporary registration. - } - } - } - InstructionValue::ObjectMethod { loc, .. } => { - invariant( - instr.lvalue.is_some(), - "Expected object methods to have a temp lvalue", - None, - )?; - let lvalue = instr.lvalue.as_ref().unwrap(); - cx.object_methods - .insert(lvalue.identifier, (value.clone(), *loc)); - return Ok(None); - } - _ => {} // fall through to general codegen - } - } - // General case: codegen the full ReactiveValue - let expr_value = codegen_instruction_value(cx, &instr.value)?; - let stmt = codegen_instruction(cx, instr, expr_value)?; - if matches!(stmt, Statement::EmptyStatement(_)) { - Ok(None) - } else { - Ok(Some(stmt)) - } -} - -fn codegen_store_or_declare( - cx: &mut Context, - instr: &ReactiveInstruction, - value: &InstructionValue, -) -> Result<Option<Statement>, CompilerError> { - match value { - InstructionValue::StoreLocal { - lvalue, value: val, .. - } => { - let mut kind = lvalue.kind; - if cx.has_declared(lvalue.place.identifier) { - kind = InstructionKind::Reassign; - } - let rhs = codegen_place_to_expression(cx, val)?; - emit_store(cx, instr, kind, &LvalueRef::Place(&lvalue.place), Some(rhs)) - } - InstructionValue::StoreContext { - lvalue, value: val, .. - } => { - let rhs = codegen_place_to_expression(cx, val)?; - emit_store( - cx, - instr, - lvalue.kind, - &LvalueRef::Place(&lvalue.place), - Some(rhs), - ) - } - InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::DeclareContext { lvalue, .. } => { - if cx.has_declared(lvalue.place.identifier) { - return Ok(None); - } - emit_store( - cx, - instr, - lvalue.kind, - &LvalueRef::Place(&lvalue.place), - None, - ) - } - InstructionValue::Destructure { - lvalue, value: val, .. - } => { - let kind = lvalue.kind; - // Register temporaries for unnamed pattern operands - for place in react_compiler_hir::visitors::each_pattern_operand(&lvalue.pattern) { - let ident = &cx.env.identifiers[place.identifier.0 as usize]; - if kind != InstructionKind::Reassign && ident.name.is_none() { - cx.temp.insert(ident.declaration_id, None); - } - } - let rhs = codegen_place_to_expression(cx, val)?; - emit_store( - cx, - instr, - kind, - &LvalueRef::Pattern(&lvalue.pattern), - Some(rhs), - ) - } - _ => unreachable!(), - } -} - -fn emit_store( - cx: &mut Context, - instr: &ReactiveInstruction, - kind: InstructionKind, - lvalue: &LvalueRef, - value: Option<Expression>, -) -> Result<Option<Statement>, CompilerError> { - match kind { - InstructionKind::Const => { - // Invariant: Const declarations cannot also have an outer lvalue - // (i.e., cannot be referenced as an expression) - if instr.lvalue.is_some() { - return Err(invariant_err_with_detail_message( - "Const declaration cannot be referenced as an expression", - "this is Const", - instr.loc, - )); - } - let lval = codegen_lvalue(cx, lvalue)?; - Ok(Some(Statement::VariableDeclaration(VariableDeclaration { - base: base_node_with_loc("VariableDeclaration", instr.loc), - declarations: vec![make_var_declarator(lval, value)], - kind: VariableDeclarationKind::Const, - declare: None, - }))) - } - InstructionKind::Function => { - let lval = codegen_lvalue(cx, lvalue)?; - let PatternLike::Identifier(fn_id) = lval else { - return Err(invariant_err( - "Expected an identifier as function declaration lvalue", - None, - )); - }; - let Some(rhs) = value else { - return Err(invariant_err( - "Expected a function value for function declaration", - None, - )); - }; - match rhs { - Expression::FunctionExpression(func_expr) => { - Ok(Some(Statement::FunctionDeclaration(FunctionDeclaration { - base: base_node_with_loc("FunctionDeclaration", instr.loc), - id: Some(fn_id), - params: func_expr.params, - body: func_expr.body, - generator: func_expr.generator, - is_async: func_expr.is_async, - declare: None, - return_type: None, - type_parameters: None, - predicate: None, - component_declaration: false, - hook_declaration: false, - }))) - } - _ => Err(invariant_err( - "Expected a function expression for function declaration", - None, - )), - } - } - InstructionKind::Let => { - // Invariant: Let declarations cannot also have an outer lvalue - if instr.lvalue.is_some() { - return Err(invariant_err_with_detail_message( - "Const declaration cannot be referenced as an expression", - "this is Let", - instr.loc, - )); - } - let lval = codegen_lvalue(cx, lvalue)?; - Ok(Some(Statement::VariableDeclaration(VariableDeclaration { - base: base_node_with_loc("VariableDeclaration", instr.loc), - declarations: vec![make_var_declarator(lval, value)], - kind: VariableDeclarationKind::Let, - declare: None, - }))) - } - InstructionKind::Reassign => { - let Some(rhs) = value else { - return Err(invariant_err("Expected a value for reassignment", None)); - }; - let lval = codegen_lvalue(cx, lvalue)?; - let expr = Expression::AssignmentExpression(ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::Assign, - left: Box::new(lval), - right: Box::new(rhs), - }); - if let Some(ref lvalue_place) = instr.lvalue { - let is_store_context = matches!( - &instr.value, - ReactiveValue::Instruction(InstructionValue::StoreContext { .. }) - ); - if !is_store_context { - let ident = &cx.env.identifiers[lvalue_place.identifier.0 as usize]; - cx.temp.insert( - ident.declaration_id, - Some(ExpressionOrJsxText::Expression(expr)), - ); - return Ok(None); - } else { - let stmt = - codegen_instruction(cx, instr, ExpressionOrJsxText::Expression(expr))?; - if matches!(stmt, Statement::EmptyStatement(_)) { - return Ok(None); - } - return Ok(Some(stmt)); - } - } - Ok(Some(Statement::ExpressionStatement(ExpressionStatement { - base: base_node_with_loc("ExpressionStatement", instr.loc), - expression: Box::new(expr), - }))) - } - InstructionKind::Catch => Ok(Some(Statement::EmptyStatement(EmptyStatement { - base: BaseNode::typed("EmptyStatement"), - }))), - InstructionKind::HoistedLet - | InstructionKind::HoistedConst - | InstructionKind::HoistedFunction => Err(invariant_err( - &format!( - "Expected {:?} to have been pruned in PruneHoistedContexts", - kind - ), - None, - )), - } -} - -fn codegen_instruction( - cx: &mut Context, - instr: &ReactiveInstruction, - value: ExpressionOrJsxText, -) -> Result<Statement, CompilerError> { - let Some(ref lvalue) = instr.lvalue else { - let expr = convert_value_to_expression(value); - return Ok(Statement::ExpressionStatement(ExpressionStatement { - base: base_node_with_loc("ExpressionStatement", instr.loc), - expression: Box::new(expr), - })); - }; - let ident = &cx.env.identifiers[lvalue.identifier.0 as usize]; - if ident.name.is_none() { - // temporary - cx.temp.insert(ident.declaration_id, Some(value)); - return Ok(Statement::EmptyStatement(EmptyStatement { - base: BaseNode::typed("EmptyStatement"), - })); - } - let expr_value = convert_value_to_expression(value); - if cx.has_declared(lvalue.identifier) { - Ok(Statement::ExpressionStatement(ExpressionStatement { - base: base_node_with_loc("ExpressionStatement", instr.loc), - expression: Box::new(Expression::AssignmentExpression( - ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::Assign, - left: Box::new(PatternLike::Identifier(convert_identifier( - lvalue.identifier, - cx.env, - )?)), - right: Box::new(expr_value), - }, - )), - })) - } else { - Ok(Statement::VariableDeclaration(VariableDeclaration { - base: base_node_with_loc("VariableDeclaration", instr.loc), - declarations: vec![make_var_declarator( - PatternLike::Identifier(convert_identifier(lvalue.identifier, cx.env)?), - Some(expr_value), - )], - kind: VariableDeclarationKind::Const, - declare: None, - })) - } -} - -// ============================================================================= -// Instruction value codegen -// ============================================================================= - -fn codegen_instruction_value_to_expression( - cx: &mut Context, - instr_value: &ReactiveValue, -) -> Result<Expression, CompilerError> { - let value = codegen_instruction_value(cx, instr_value)?; - Ok(convert_value_to_expression(value)) -} - -fn codegen_instruction_value( - cx: &mut Context, - instr_value: &ReactiveValue, -) -> Result<ExpressionOrJsxText, CompilerError> { - match instr_value { - ReactiveValue::Instruction(iv) => { - let mut result = codegen_base_instruction_value(cx, iv)?; - // Propagate instrValue.loc to the generated expression, matching TS: - // if (instrValue.loc != null && instrValue.loc != GeneratedSource) { - // value.loc = instrValue.loc; - // } - if let Some(loc) = iv.loc() { - apply_loc_to_value(&mut result, *loc); - } - Ok(result) - } - ReactiveValue::LogicalExpression { - operator, - left, - right, - .. - } => { - let left_expr = codegen_instruction_value_to_expression(cx, left)?; - let right_expr = codegen_instruction_value_to_expression(cx, right)?; - Ok(ExpressionOrJsxText::Expression( - Expression::LogicalExpression(ast_expr::LogicalExpression { - base: BaseNode::typed("LogicalExpression"), - operator: convert_logical_operator(operator), - left: Box::new(left_expr), - right: Box::new(right_expr), - }), - )) - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - .. - } => { - let test_expr = codegen_instruction_value_to_expression(cx, test)?; - let cons_expr = codegen_instruction_value_to_expression(cx, consequent)?; - let alt_expr = codegen_instruction_value_to_expression(cx, alternate)?; - Ok(ExpressionOrJsxText::Expression( - Expression::ConditionalExpression(ast_expr::ConditionalExpression { - base: BaseNode::typed("ConditionalExpression"), - test: Box::new(test_expr), - consequent: Box::new(cons_expr), - alternate: Box::new(alt_expr), - }), - )) - } - ReactiveValue::SequenceExpression { - instructions, - value, - .. - } => { - let block_items: Vec<ReactiveStatement> = instructions - .iter() - .map(|i| ReactiveStatement::Instruction(i.clone())) - .collect(); - let body = codegen_block_no_reset(cx, &block_items)?.body; - let mut expressions: Vec<Expression> = Vec::new(); - for stmt in body { - match stmt { - Statement::ExpressionStatement(es) => { - expressions.push(*es.expression); - } - Statement::VariableDeclaration(ref var_decl) => { - let _declarator = &var_decl.declarations[0]; - cx.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: format!( - "(CodegenReactiveFunction::codegenInstructionValue) Cannot declare variables in a value block" - ), - description: None, - loc: None, - suggestions: None, - })?; - expressions.push(Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: format!("TODO handle declaration"), - })); - } - _ => { - cx.record_error(CompilerErrorDetail { - category: ErrorCategory::Todo, - reason: format!( - "(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of statement to expression" - ), - description: None, - loc: None, - suggestions: None, - })?; - expressions.push(Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: format!("TODO handle statement"), - })); - } - } - } - let final_expr = codegen_instruction_value_to_expression(cx, value)?; - if expressions.is_empty() { - Ok(ExpressionOrJsxText::Expression(final_expr)) - } else { - expressions.push(final_expr); - Ok(ExpressionOrJsxText::Expression( - Expression::SequenceExpression(ast_expr::SequenceExpression { - base: BaseNode::typed("SequenceExpression"), - expressions, - }), - )) - } - } - ReactiveValue::OptionalExpression { - value, optional, .. - } => { - let opt_value = codegen_instruction_value_to_expression(cx, value)?; - match opt_value { - Expression::OptionalCallExpression(oce) => Ok(ExpressionOrJsxText::Expression( - Expression::OptionalCallExpression(ast_expr::OptionalCallExpression { - base: BaseNode::typed("OptionalCallExpression"), - callee: oce.callee, - arguments: oce.arguments, - optional: *optional, - type_parameters: oce.type_parameters, - type_arguments: oce.type_arguments, - }), - )), - Expression::CallExpression(ce) => Ok(ExpressionOrJsxText::Expression( - Expression::OptionalCallExpression(ast_expr::OptionalCallExpression { - base: BaseNode::typed("OptionalCallExpression"), - callee: ce.callee, - arguments: ce.arguments, - optional: *optional, - type_parameters: None, - type_arguments: None, - }), - )), - Expression::OptionalMemberExpression(ome) => Ok(ExpressionOrJsxText::Expression( - Expression::OptionalMemberExpression(ast_expr::OptionalMemberExpression { - base: BaseNode::typed("OptionalMemberExpression"), - object: ome.object, - property: ome.property, - computed: ome.computed, - optional: *optional, - }), - )), - Expression::MemberExpression(me) => Ok(ExpressionOrJsxText::Expression( - Expression::OptionalMemberExpression(ast_expr::OptionalMemberExpression { - base: BaseNode::typed("OptionalMemberExpression"), - object: me.object, - property: me.property, - computed: me.computed, - optional: *optional, - }), - )), - other => Err(invariant_err( - &format!( - "Expected optional value to resolve to call or member expression, got {:?}", - std::mem::discriminant(&other) - ), - None, - )), - } - } - } -} - -fn codegen_base_instruction_value( - cx: &mut Context, - iv: &InstructionValue, -) -> Result<ExpressionOrJsxText, CompilerError> { - match iv { - InstructionValue::Primitive { value, loc } => Ok(ExpressionOrJsxText::Expression( - codegen_primitive_value(value, *loc), - )), - InstructionValue::BinaryExpression { - operator, - left, - right, - .. - } => { - let left_expr = codegen_place_to_expression(cx, left)?; - let right_expr = codegen_place_to_expression(cx, right)?; - Ok(ExpressionOrJsxText::Expression( - Expression::BinaryExpression(ast_expr::BinaryExpression { - base: BaseNode::typed("BinaryExpression"), - operator: convert_binary_operator(operator), - left: Box::new(left_expr), - right: Box::new(right_expr), - }), - )) - } - InstructionValue::UnaryExpression { - operator, value, .. - } => { - let arg = codegen_place_to_expression(cx, value)?; - Ok(ExpressionOrJsxText::Expression( - Expression::UnaryExpression(ast_expr::UnaryExpression { - base: BaseNode::typed("UnaryExpression"), - operator: convert_unary_operator(operator), - prefix: true, - argument: Box::new(arg), - }), - )) - } - InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => { - let expr = codegen_place_to_expression(cx, place)?; - Ok(ExpressionOrJsxText::Expression(expr)) - } - InstructionValue::LoadGlobal { binding, .. } => Ok(ExpressionOrJsxText::Expression( - Expression::Identifier(make_identifier(binding.name())), - )), - InstructionValue::CallExpression { - callee, - args, - loc: _, - } => { - let callee_expr = codegen_place_to_expression(cx, callee)?; - let arguments = args - .iter() - .map(|arg| codegen_argument(cx, arg)) - .collect::<Result<_, _>>()?; - let call_expr = Expression::CallExpression(ast_expr::CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(callee_expr), - arguments, - type_parameters: None, - type_arguments: None, - optional: None, - }); - // enableEmitHookGuards: wrap hook calls in try/finally IIFE - let result = maybe_wrap_hook_call(cx, call_expr, callee.identifier); - Ok(ExpressionOrJsxText::Expression(result)) - } - InstructionValue::MethodCall { - receiver: _, - property, - args, - loc: _, - } => { - let member_expr = codegen_place_to_expression(cx, property)?; - // Invariant: MethodCall::property must resolve to a MemberExpression - if !matches!( - member_expr, - Expression::MemberExpression(_) | Expression::OptionalMemberExpression(_) - ) { - let expr_type = match &member_expr { - Expression::Identifier(_) => "Identifier", - _ => "unknown", - }; - { - let msg = format!("Got: '{}'", expr_type); - let mut err = CompilerError::new(); - err.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::Invariant, - "[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression", - None, - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: property.loc, - message: Some(msg), - identifier_name: None, - }), - ); - return Err(err); - } - } - let arguments = args - .iter() - .map(|arg| codegen_argument(cx, arg)) - .collect::<Result<_, _>>()?; - let call_expr = Expression::CallExpression(ast_expr::CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(member_expr), - arguments, - type_parameters: None, - type_arguments: None, - optional: None, - }); - // enableEmitHookGuards: wrap hook method calls in try/finally IIFE - let result = maybe_wrap_hook_call(cx, call_expr, property.identifier); - Ok(ExpressionOrJsxText::Expression(result)) - } - InstructionValue::NewExpression { callee, args, .. } => { - let callee_expr = codegen_place_to_expression(cx, callee)?; - let arguments = args - .iter() - .map(|arg| codegen_argument(cx, arg)) - .collect::<Result<_, _>>()?; - Ok(ExpressionOrJsxText::Expression(Expression::NewExpression( - ast_expr::NewExpression { - base: BaseNode::typed("NewExpression"), - callee: Box::new(callee_expr), - arguments, - type_parameters: None, - type_arguments: None, - }, - ))) - } - InstructionValue::ArrayExpression { elements, .. } => { - let elems: Vec<Option<Expression>> = elements - .iter() - .map(|el| match el { - ArrayElement::Place(place) => Ok(Some(codegen_place_to_expression(cx, place)?)), - ArrayElement::Spread(spread) => { - let arg = codegen_place_to_expression(cx, &spread.place)?; - Ok(Some(Expression::SpreadElement(ast_expr::SpreadElement { - base: BaseNode::typed("SpreadElement"), - argument: Box::new(arg), - }))) - } - ArrayElement::Hole => Ok(None), - }) - .collect::<Result<_, CompilerError>>()?; - Ok(ExpressionOrJsxText::Expression( - Expression::ArrayExpression(ast_expr::ArrayExpression { - base: BaseNode::typed("ArrayExpression"), - elements: elems, - }), - )) - } - InstructionValue::ObjectExpression { properties, .. } => { - codegen_object_expression(cx, properties) - } - InstructionValue::PropertyLoad { - object, property, .. - } => { - let obj = codegen_place_to_expression(cx, object)?; - let (prop, computed) = property_literal_to_expression(property); - Ok(ExpressionOrJsxText::Expression( - Expression::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(obj), - property: Box::new(prop), - computed, - }), - )) - } - InstructionValue::PropertyStore { - object, - property, - value, - .. - } => { - let obj = codegen_place_to_expression(cx, object)?; - let (prop, computed) = property_literal_to_expression(property); - let val = codegen_place_to_expression(cx, value)?; - Ok(ExpressionOrJsxText::Expression( - Expression::AssignmentExpression(ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::Assign, - left: Box::new(PatternLike::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(obj), - property: Box::new(prop), - computed, - })), - right: Box::new(val), - }), - )) - } - InstructionValue::PropertyDelete { - object, property, .. - } => { - let obj = codegen_place_to_expression(cx, object)?; - let (prop, computed) = property_literal_to_expression(property); - Ok(ExpressionOrJsxText::Expression( - Expression::UnaryExpression(ast_expr::UnaryExpression { - base: BaseNode::typed("UnaryExpression"), - operator: AstUnaryOperator::Delete, - prefix: true, - argument: Box::new(Expression::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(obj), - property: Box::new(prop), - computed, - })), - }), - )) - } - InstructionValue::ComputedLoad { - object, property, .. - } => { - let obj = codegen_place_to_expression(cx, object)?; - let prop = codegen_place_to_expression(cx, property)?; - Ok(ExpressionOrJsxText::Expression( - Expression::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(obj), - property: Box::new(prop), - computed: true, - }), - )) - } - InstructionValue::ComputedStore { - object, - property, - value, - .. - } => { - let obj = codegen_place_to_expression(cx, object)?; - let prop = codegen_place_to_expression(cx, property)?; - let val = codegen_place_to_expression(cx, value)?; - Ok(ExpressionOrJsxText::Expression( - Expression::AssignmentExpression(ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::Assign, - left: Box::new(PatternLike::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(obj), - property: Box::new(prop), - computed: true, - })), - right: Box::new(val), - }), - )) - } - InstructionValue::ComputedDelete { - object, property, .. - } => { - let obj = codegen_place_to_expression(cx, object)?; - let prop = codegen_place_to_expression(cx, property)?; - Ok(ExpressionOrJsxText::Expression( - Expression::UnaryExpression(ast_expr::UnaryExpression { - base: BaseNode::typed("UnaryExpression"), - operator: AstUnaryOperator::Delete, - prefix: true, - argument: Box::new(Expression::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(obj), - property: Box::new(prop), - computed: true, - })), - }), - )) - } - InstructionValue::RegExpLiteral { pattern, flags, .. } => Ok( - ExpressionOrJsxText::Expression(Expression::RegExpLiteral(AstRegExpLiteral { - base: BaseNode::typed("RegExpLiteral"), - pattern: pattern.clone(), - flags: flags.clone(), - })), - ), - InstructionValue::MetaProperty { meta, property, .. } => Ok( - ExpressionOrJsxText::Expression(Expression::MetaProperty(ast_expr::MetaProperty { - base: BaseNode::typed("MetaProperty"), - meta: make_identifier(meta), - property: make_identifier(property), - })), - ), - InstructionValue::Await { value, .. } => { - let arg = codegen_place_to_expression(cx, value)?; - Ok(ExpressionOrJsxText::Expression( - Expression::AwaitExpression(ast_expr::AwaitExpression { - base: BaseNode::typed("AwaitExpression"), - argument: Box::new(arg), - }), - )) - } - InstructionValue::GetIterator { collection, .. } => { - let expr = codegen_place_to_expression(cx, collection)?; - Ok(ExpressionOrJsxText::Expression(expr)) - } - InstructionValue::IteratorNext { iterator, .. } => { - let expr = codegen_place_to_expression(cx, iterator)?; - Ok(ExpressionOrJsxText::Expression(expr)) - } - InstructionValue::NextPropertyOf { value, .. } => { - let expr = codegen_place_to_expression(cx, value)?; - Ok(ExpressionOrJsxText::Expression(expr)) - } - InstructionValue::PostfixUpdate { - operation, lvalue, .. - } => { - let arg = codegen_place_to_expression(cx, lvalue)?; - Ok(ExpressionOrJsxText::Expression( - Expression::UpdateExpression(ast_expr::UpdateExpression { - base: BaseNode::typed("UpdateExpression"), - operator: convert_update_operator(operation), - argument: Box::new(arg), - prefix: false, - }), - )) - } - InstructionValue::PrefixUpdate { - operation, lvalue, .. - } => { - let arg = codegen_place_to_expression(cx, lvalue)?; - Ok(ExpressionOrJsxText::Expression( - Expression::UpdateExpression(ast_expr::UpdateExpression { - base: BaseNode::typed("UpdateExpression"), - operator: convert_update_operator(operation), - argument: Box::new(arg), - prefix: true, - }), - )) - } - InstructionValue::StoreLocal { lvalue, value, .. } => { - invariant( - lvalue.kind == InstructionKind::Reassign, - "Unexpected StoreLocal in codegenInstructionValue", - None, - )?; - let lval = codegen_lvalue(cx, &LvalueRef::Place(&lvalue.place))?; - let rhs = codegen_place_to_expression(cx, value)?; - Ok(ExpressionOrJsxText::Expression( - Expression::AssignmentExpression(ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::Assign, - left: Box::new(lval), - right: Box::new(rhs), - }), - )) - } - InstructionValue::StoreGlobal { name, value, .. } => { - let rhs = codegen_place_to_expression(cx, value)?; - Ok(ExpressionOrJsxText::Expression( - Expression::AssignmentExpression(ast_expr::AssignmentExpression { - base: BaseNode::typed("AssignmentExpression"), - operator: AssignmentOperator::Assign, - left: Box::new(PatternLike::Identifier(make_identifier(name))), - right: Box::new(rhs), - }), - )) - } - InstructionValue::FunctionExpression { - name, - name_hint, - lowered_func, - expr_type, - .. - } => codegen_function_expression(cx, name, name_hint, lowered_func, expr_type), - InstructionValue::TaggedTemplateExpression { tag, value, .. } => { - let tag_expr = codegen_place_to_expression(cx, tag)?; - Ok(ExpressionOrJsxText::Expression( - Expression::TaggedTemplateExpression(ast_expr::TaggedTemplateExpression { - base: BaseNode::typed("TaggedTemplateExpression"), - tag: Box::new(tag_expr), - quasi: ast_expr::TemplateLiteral { - base: BaseNode::typed("TemplateLiteral"), - quasis: vec![TemplateElement { - base: BaseNode::typed("TemplateElement"), - value: TemplateElementValue { - raw: value.raw.clone(), - cooked: value.cooked.clone(), - }, - tail: true, - }], - expressions: Vec::new(), - }, - type_parameters: None, - }), - )) - } - InstructionValue::TemplateLiteral { - subexprs, quasis, .. - } => { - let exprs: Vec<Expression> = subexprs - .iter() - .map(|p| codegen_place_to_expression(cx, p)) - .collect::<Result<_, _>>()?; - let template_elems: Vec<TemplateElement> = quasis - .iter() - .enumerate() - .map(|(i, q)| TemplateElement { - base: BaseNode::typed("TemplateElement"), - value: TemplateElementValue { - raw: q.raw.clone(), - cooked: q.cooked.clone(), - }, - tail: i == quasis.len() - 1, - }) - .collect(); - Ok(ExpressionOrJsxText::Expression( - Expression::TemplateLiteral(ast_expr::TemplateLiteral { - base: BaseNode::typed("TemplateLiteral"), - quasis: template_elems, - expressions: exprs, - }), - )) - } - InstructionValue::TypeCastExpression { - value, - type_annotation_kind, - type_annotation, - .. - } => { - let expr = codegen_place_to_expression(cx, value)?; - let wrapped = match (type_annotation_kind.as_deref(), type_annotation) { - (Some("satisfies"), Some(ta)) => { - let mut ta = ta.clone(); - apply_renames_to_json(&mut ta, &cx.env.renames, &cx.env.reference_node_ids); - Expression::TSSatisfiesExpression(ast_expr::TSSatisfiesExpression { - base: BaseNode::typed("TSSatisfiesExpression"), - expression: Box::new(expr), - type_annotation: RawNode::from_value(&ta), - }) - } - (Some("as"), Some(ta)) => { - let mut ta = ta.clone(); - apply_renames_to_json(&mut ta, &cx.env.renames, &cx.env.reference_node_ids); - Expression::TSAsExpression(ast_expr::TSAsExpression { - base: BaseNode::typed("TSAsExpression"), - expression: Box::new(expr), - type_annotation: RawNode::from_value(&ta), - }) - } - (Some("cast"), Some(ta)) => { - let mut ta = ta.clone(); - apply_renames_to_json(&mut ta, &cx.env.renames, &cx.env.reference_node_ids); - Expression::TypeCastExpression(ast_expr::TypeCastExpression { - base: BaseNode::typed("TypeCastExpression"), - expression: Box::new(expr), - type_annotation: RawNode::from_value(&ta), - }) - } - _ => expr, - }; - Ok(ExpressionOrJsxText::Expression(wrapped)) - } - InstructionValue::JSXText { value, loc } => Ok(ExpressionOrJsxText::JsxText(JSXText { - base: base_node_with_loc("JSXText", *loc), - value: value.clone(), - })), - InstructionValue::JsxExpression { - tag, - props, - children, - loc, - opening_loc, - closing_loc, - } => codegen_jsx_expression(cx, tag, props, children, *loc, *opening_loc, *closing_loc), - InstructionValue::JsxFragment { children, .. } => { - let child_elems: Vec<JSXChild> = children - .iter() - .map(|child| codegen_jsx_element(cx, child)) - .collect::<Result<_, _>>()?; - Ok(ExpressionOrJsxText::Expression(Expression::JSXFragment( - JSXFragment { - base: BaseNode::typed("JSXFragment"), - opening_fragment: JSXOpeningFragment { - base: BaseNode::typed("JSXOpeningFragment"), - }, - closing_fragment: JSXClosingFragment { - base: BaseNode::typed("JSXClosingFragment"), - }, - children: child_elems, - }, - ))) - } - InstructionValue::UnsupportedNode { - original_node, - node_type, - .. - } => { - // Try to deserialize the original AST node from JSON (mirrors statement-level handler) - match original_node { - Some(node) => { - match serde_json::from_value::<Expression>(node.clone()) { - Ok(expr) => Ok(ExpressionOrJsxText::Expression(expr)), - Err(_) => { - // Not a valid expression — fall back to placeholder - Ok(ExpressionOrJsxText::Expression(Expression::Identifier( - make_identifier(&format!( - "__unsupported_{}", - node_type.as_deref().unwrap_or("unknown") - )), - ))) - } - } - } - None => { - // No original node available — fall back to placeholder - Ok(ExpressionOrJsxText::Expression(Expression::Identifier( - make_identifier(&format!( - "__unsupported_{}", - node_type.as_deref().unwrap_or("unknown") - )), - ))) - } - } - } - InstructionValue::StartMemoize { .. } - | InstructionValue::FinishMemoize { .. } - | InstructionValue::Debugger { .. } - | InstructionValue::DeclareLocal { .. } - | InstructionValue::DeclareContext { .. } - | InstructionValue::Destructure { .. } - | InstructionValue::ObjectMethod { .. } - | InstructionValue::StoreContext { .. } => Err(invariant_err( - &format!( - "Unexpected {:?} in codegenInstructionValue", - std::mem::discriminant(iv) - ), - None, - )), - } -} - -// ============================================================================= -// Function expression codegen -// ============================================================================= - -fn codegen_function_expression( - cx: &mut Context, - name: &Option<String>, - name_hint: &Option<String>, - lowered_func: &react_compiler_hir::LoweredFunction, - expr_type: &FunctionExpressionType, -) -> Result<ExpressionOrJsxText, CompilerError> { - let func = &cx.env.functions[lowered_func.func.0 as usize]; - let reactive_fn = build_reactive_function(func, cx.env)?; - let mut reactive_fn_mut = reactive_fn; - prune_unused_labels(&mut reactive_fn_mut, cx.env)?; - prune_unused_lvalues(&mut reactive_fn_mut, cx.env); - prune_hoisted_contexts(&mut reactive_fn_mut, cx.env)?; - - let mut inner_cx = Context::new( - cx.env, - reactive_fn_mut - .id - .as_deref() - .unwrap_or("[[ anonymous ]]") - .to_string(), - cx.unique_identifiers.clone(), - cx.fbt_operands.clone(), - ); - inner_cx.temp = cx.temp.clone(); - - let fn_result = codegen_reactive_function(&mut inner_cx, &reactive_fn_mut)?; - - let value = match expr_type { - FunctionExpressionType::ArrowFunctionExpression => { - let mut body: ArrowFunctionBody = - ArrowFunctionBody::BlockStatement(fn_result.body.clone()); - // Optimize single-return arrow functions - if fn_result.body.body.len() == 1 && reactive_fn_mut.directives.is_empty() { - if let Statement::ReturnStatement(ret) = &fn_result.body.body[0] { - if let Some(ref arg) = ret.argument { - body = ArrowFunctionBody::Expression(arg.clone()); - } - } - } - let is_expression = matches!(body, ArrowFunctionBody::Expression(_)); - Expression::ArrowFunctionExpression(ast_expr::ArrowFunctionExpression { - base: BaseNode::typed("ArrowFunctionExpression"), - params: fn_result.params, - body: Box::new(body), - id: None, - generator: false, - is_async: fn_result.is_async, - expression: Some(is_expression), - return_type: None, - type_parameters: None, - predicate: None, - }) - } - _ => Expression::FunctionExpression(ast_expr::FunctionExpression { - base: BaseNode::typed("FunctionExpression"), - params: fn_result.params, - body: fn_result.body, - id: name.as_ref().map(|n| make_identifier(n)), - generator: fn_result.generator, - is_async: fn_result.is_async, - return_type: None, - type_parameters: None, - predicate: None, - }), - }; - - // Handle enableNameAnonymousFunctions - if cx.env.config.enable_name_anonymous_functions && name.is_none() && name_hint.is_some() { - let hint = name_hint.as_ref().unwrap(); - let wrapped = Expression::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(Expression::ObjectExpression(ast_expr::ObjectExpression { - base: BaseNode::typed("ObjectExpression"), - properties: vec![ast_expr::ObjectExpressionProperty::ObjectProperty( - ast_expr::ObjectProperty { - base: BaseNode::typed("ObjectProperty"), - key: Box::new(Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: hint.clone(), - })), - value: Box::new(value), - computed: false, - shorthand: false, - decorators: None, - method: None, - }, - )], - })), - property: Box::new(Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: hint.clone(), - })), - computed: true, - }); - return Ok(ExpressionOrJsxText::Expression(wrapped)); - } - - Ok(ExpressionOrJsxText::Expression(value)) -} - -// ============================================================================= -// Object expression codegen -// ============================================================================= - -fn codegen_object_expression( - cx: &mut Context, - properties: &[ObjectPropertyOrSpread], -) -> Result<ExpressionOrJsxText, CompilerError> { - let mut ast_properties: Vec<ast_expr::ObjectExpressionProperty> = Vec::new(); - for prop in properties { - match prop { - ObjectPropertyOrSpread::Property(obj_prop) => { - let key = codegen_object_property_key(cx, &obj_prop.key)?; - match obj_prop.property_type { - ObjectPropertyType::Property => { - let value = codegen_place_to_expression(cx, &obj_prop.place)?; - let is_shorthand = matches!(&key, Expression::Identifier(k_id) - if matches!(&value, Expression::Identifier(v_id) if v_id.name == k_id.name)); - ast_properties.push(ast_expr::ObjectExpressionProperty::ObjectProperty( - ast_expr::ObjectProperty { - base: BaseNode::typed("ObjectProperty"), - key: Box::new(key), - value: Box::new(value), - computed: matches!( - obj_prop.key, - ObjectPropertyKey::Computed { .. } - ), - shorthand: is_shorthand, - decorators: None, - method: None, - }, - )); - } - ObjectPropertyType::Method => { - let method_data = cx.object_methods.get(&obj_prop.place.identifier); - let method_data = method_data.cloned(); - let Some((InstructionValue::ObjectMethod { lowered_func, .. }, _)) = - method_data - else { - return Err(invariant_err("Expected ObjectMethod instruction", None)); - }; - - let func = &cx.env.functions[lowered_func.func.0 as usize]; - let reactive_fn = build_reactive_function(func, cx.env)?; - let mut reactive_fn_mut = reactive_fn; - prune_unused_labels(&mut reactive_fn_mut, cx.env)?; - prune_unused_lvalues(&mut reactive_fn_mut, cx.env); - - let mut inner_cx = Context::new( - cx.env, - reactive_fn_mut - .id - .as_deref() - .unwrap_or("[[ anonymous ]]") - .to_string(), - cx.unique_identifiers.clone(), - cx.fbt_operands.clone(), - ); - inner_cx.temp = cx.temp.clone(); - - let fn_result = codegen_reactive_function(&mut inner_cx, &reactive_fn_mut)?; - - ast_properties.push(ast_expr::ObjectExpressionProperty::ObjectMethod( - ast_expr::ObjectMethod { - base: BaseNode::typed("ObjectMethod"), - method: true, - kind: ast_expr::ObjectMethodKind::Method, - key: Box::new(key), - params: fn_result.params, - body: fn_result.body, - computed: matches!( - obj_prop.key, - ObjectPropertyKey::Computed { .. } - ), - id: None, - generator: fn_result.generator, - is_async: fn_result.is_async, - decorators: None, - return_type: None, - type_parameters: None, - predicate: None, - }, - )); - } - } - } - ObjectPropertyOrSpread::Spread(spread) => { - let arg = codegen_place_to_expression(cx, &spread.place)?; - ast_properties.push(ast_expr::ObjectExpressionProperty::SpreadElement( - ast_expr::SpreadElement { - base: BaseNode::typed("SpreadElement"), - argument: Box::new(arg), - }, - )); - } - } - } - Ok(ExpressionOrJsxText::Expression( - Expression::ObjectExpression(ast_expr::ObjectExpression { - base: BaseNode::typed("ObjectExpression"), - properties: ast_properties, - }), - )) -} - -fn codegen_object_property_key( - cx: &mut Context, - key: &ObjectPropertyKey, -) -> Result<Expression, CompilerError> { - match key { - ObjectPropertyKey::String { name } => Ok(Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: name.clone(), - })), - ObjectPropertyKey::Identifier { name } => Ok(Expression::Identifier(make_identifier(name))), - ObjectPropertyKey::Computed { name } => { - let expr = codegen_place(cx, name)?; - match expr { - ExpressionOrJsxText::Expression(e) => Ok(e), - ExpressionOrJsxText::JsxText(_) => Err(invariant_err( - "Expected object property key to be an expression", - None, - )), - } - } - ObjectPropertyKey::Number { name } => Ok(Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: name.value(), - extra: None, - })), - } -} - -// ============================================================================= -// JSX codegen -// ============================================================================= - -fn codegen_jsx_expression( - cx: &mut Context, - tag: &JsxTag, - props: &[JsxAttribute], - children: &Option<Vec<Place>>, - loc: Option<DiagSourceLocation>, - opening_loc: Option<DiagSourceLocation>, - closing_loc: Option<DiagSourceLocation>, -) -> Result<ExpressionOrJsxText, CompilerError> { - let mut attributes: Vec<JSXAttributeItem> = Vec::new(); - for attr in props { - attributes.push(codegen_jsx_attribute(cx, attr)?); - } - - let (tag_value, _tag_loc) = match tag { - JsxTag::Place(place) => (codegen_place_to_expression(cx, place)?, place.loc), - JsxTag::Builtin(builtin) => ( - Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: builtin.name.clone(), - }), - None, - ), - }; - - let jsx_tag = expression_to_jsx_tag(&tag_value, jsx_tag_loc(tag))?; - - let is_fbt_tag = if let Expression::StringLiteral(ref s) = tag_value { - SINGLE_CHILD_FBT_TAGS.contains(&s.value.as_str()) - } else { - false - }; - - let child_nodes = if is_fbt_tag { - children - .as_ref() - .map(|c| { - c.iter() - .map(|child| codegen_jsx_fbt_child_element(cx, child)) - .collect::<Result<Vec<_>, _>>() - }) - .transpose()? - .unwrap_or_default() - } else { - children - .as_ref() - .map(|c| { - c.iter() - .map(|child| codegen_jsx_element(cx, child)) - .collect::<Result<Vec<_>, _>>() - }) - .transpose()? - .unwrap_or_default() - }; - - let is_self_closing = children.is_none(); - - let element = JSXElement { - base: base_node_with_loc("JSXElement", loc), - opening_element: JSXOpeningElement { - base: base_node_with_loc("JSXOpeningElement", opening_loc), - name: jsx_tag.clone(), - attributes, - self_closing: is_self_closing, - type_parameters: None, - }, - closing_element: if !is_self_closing { - Some(JSXClosingElement { - base: base_node_with_loc("JSXClosingElement", closing_loc), - name: jsx_tag, - }) - } else { - None - }, - children: child_nodes, - self_closing: if is_self_closing { Some(true) } else { None }, - }; - - Ok(ExpressionOrJsxText::Expression(Expression::JSXElement( - Box::new(element), - ))) -} - -const JSX_TEXT_CHILD_REQUIRES_EXPR_CONTAINER_PATTERN: &[char] = &['<', '>', '&', '{', '}']; -const STRING_REQUIRES_EXPR_CONTAINER_CHARS: &str = "\"\\"; - -fn string_requires_expr_container(s: &str) -> bool { - for c in s.chars() { - if STRING_REQUIRES_EXPR_CONTAINER_CHARS.contains(c) { - return true; - } - // Check for control chars and non-basic-latin - let code = c as u32; - if code <= 0x1F || code == 0x7F || (code >= 0x80 && code <= 0x9F) || (code >= 0xA0) { - return true; - } - } - false -} - -fn codegen_jsx_attribute( - cx: &mut Context, - attr: &JsxAttribute, -) -> Result<JSXAttributeItem, CompilerError> { - match attr { - JsxAttribute::Attribute { name, place } => { - let prop_name = if name.contains(':') { - let parts: Vec<&str> = name.splitn(2, ':').collect(); - JSXAttributeName::JSXNamespacedName(JSXNamespacedName { - base: BaseNode::typed("JSXNamespacedName"), - namespace: JSXIdentifier { - base: BaseNode::typed("JSXIdentifier"), - name: parts[0].to_string(), - }, - name: JSXIdentifier { - base: BaseNode::typed("JSXIdentifier"), - name: parts[1].to_string(), - }, - }) - } else { - JSXAttributeName::JSXIdentifier(JSXIdentifier { - base: BaseNode::typed("JSXIdentifier"), - name: name.clone(), - }) - }; - - let inner_value = codegen_place_to_expression(cx, place)?; - let attr_value = match &inner_value { - Expression::StringLiteral(s) => { - if string_requires_expr_container(&s.value) - && !cx.fbt_operands.contains(&place.identifier) - { - Some(JSXAttributeValue::JSXExpressionContainer( - JSXExpressionContainer { - base: base_node_with_loc("JSXExpressionContainer", place.loc), - expression: JSXExpressionContainerExpr::Expression(Box::new( - inner_value, - )), - }, - )) - } else { - // Preserve loc from the inner StringLiteral (or fall back to - // the place's loc) so downstream plugins (e.g., babel-plugin-fbt) - // can read loc on attribute values. - let base = if s.base.loc.is_some() { - s.base.clone() - } else { - base_node_with_loc("StringLiteral", place.loc) - }; - Some(JSXAttributeValue::StringLiteral(StringLiteral { - base, - value: s.value.clone(), - })) - } - } - _ => Some(JSXAttributeValue::JSXExpressionContainer( - JSXExpressionContainer { - base: base_node_with_loc("JSXExpressionContainer", place.loc), - expression: JSXExpressionContainerExpr::Expression(Box::new(inner_value)), - }, - )), - }; - Ok(JSXAttributeItem::JSXAttribute(AstJSXAttribute { - base: base_node_with_loc("JSXAttribute", place.loc), - name: prop_name, - value: attr_value, - })) - } - JsxAttribute::SpreadAttribute { argument } => { - let expr = codegen_place_to_expression(cx, argument)?; - Ok(JSXAttributeItem::JSXSpreadAttribute(JSXSpreadAttribute { - base: BaseNode::typed("JSXSpreadAttribute"), - argument: Box::new(expr), - })) - } - } -} - -fn codegen_jsx_element(cx: &mut Context, place: &Place) -> Result<JSXChild, CompilerError> { - let loc = place.loc; - let value = codegen_place(cx, place)?; - match value { - ExpressionOrJsxText::JsxText(text) => { - if text - .value - .contains(JSX_TEXT_CHILD_REQUIRES_EXPR_CONTAINER_PATTERN) - { - Ok(JSXChild::JSXExpressionContainer(JSXExpressionContainer { - base: base_node_with_loc("JSXExpressionContainer", loc), - expression: JSXExpressionContainerExpr::Expression(Box::new( - Expression::StringLiteral(StringLiteral { - base: base_node_with_loc("StringLiteral", loc), - value: text.value.clone(), - }), - )), - })) - } else { - Ok(JSXChild::JSXText(text)) - } - } - ExpressionOrJsxText::Expression(Expression::JSXElement(elem)) => { - Ok(JSXChild::JSXElement(elem)) - } - ExpressionOrJsxText::Expression(Expression::JSXFragment(frag)) => { - Ok(JSXChild::JSXFragment(frag)) - } - ExpressionOrJsxText::Expression(expr) => { - Ok(JSXChild::JSXExpressionContainer(JSXExpressionContainer { - base: base_node_with_loc("JSXExpressionContainer", loc), - expression: JSXExpressionContainerExpr::Expression(Box::new(expr)), - })) - } - } -} - -fn codegen_jsx_fbt_child_element( - cx: &mut Context, - place: &Place, -) -> Result<JSXChild, CompilerError> { - let loc = place.loc; - let value = codegen_place(cx, place)?; - match value { - ExpressionOrJsxText::JsxText(text) => Ok(JSXChild::JSXText(text)), - ExpressionOrJsxText::Expression(Expression::JSXElement(elem)) => { - Ok(JSXChild::JSXElement(elem)) - } - ExpressionOrJsxText::Expression(expr) => { - Ok(JSXChild::JSXExpressionContainer(JSXExpressionContainer { - base: base_node_with_loc("JSXExpressionContainer", loc), - expression: JSXExpressionContainerExpr::Expression(Box::new(expr)), - })) - } - } -} - -fn expression_to_jsx_tag( - expr: &Expression, - loc: Option<DiagSourceLocation>, -) -> Result<JSXElementName, CompilerError> { - match expr { - Expression::Identifier(ident) => Ok(JSXElementName::JSXIdentifier(JSXIdentifier { - base: base_node_with_loc("JSXIdentifier", loc), - name: ident.name.clone(), - })), - Expression::MemberExpression(me) => Ok(JSXElementName::JSXMemberExpression( - convert_member_expression_to_jsx(me)?, - )), - Expression::StringLiteral(s) => { - if s.value.contains(':') { - let parts: Vec<&str> = s.value.splitn(2, ':').collect(); - Ok(JSXElementName::JSXNamespacedName(JSXNamespacedName { - base: base_node_with_loc("JSXNamespacedName", loc), - namespace: JSXIdentifier { - base: base_node_with_loc("JSXIdentifier", loc), - name: parts[0].to_string(), - }, - name: JSXIdentifier { - base: base_node_with_loc("JSXIdentifier", loc), - name: parts[1].to_string(), - }, - })) - } else { - Ok(JSXElementName::JSXIdentifier(JSXIdentifier { - base: base_node_with_loc("JSXIdentifier", loc), - name: s.value.clone(), - })) - } - } - _ => Err(invariant_err( - &format!("Expected JSX tag to be an identifier or string"), - None, - )), - } -} - -fn convert_member_expression_to_jsx( - me: &ast_expr::MemberExpression, -) -> Result<JSXMemberExpression, CompilerError> { - let Expression::Identifier(ref prop_ident) = *me.property else { - return Err(invariant_err( - "Expected JSX member expression property to be a string", - None, - )); - }; - let property = JSXIdentifier { - base: BaseNode::typed("JSXIdentifier"), - name: prop_ident.name.clone(), - }; - match &*me.object { - Expression::Identifier(ident) => Ok(JSXMemberExpression { - base: BaseNode::typed("JSXMemberExpression"), - object: Box::new(JSXMemberExprObject::JSXIdentifier(JSXIdentifier { - base: BaseNode::typed("JSXIdentifier"), - name: ident.name.clone(), - })), - property, - }), - Expression::MemberExpression(inner_me) => { - let inner = convert_member_expression_to_jsx(inner_me)?; - Ok(JSXMemberExpression { - base: BaseNode::typed("JSXMemberExpression"), - object: Box::new(JSXMemberExprObject::JSXMemberExpression(Box::new(inner))), - property, - }) - } - _ => Err(invariant_err( - "Expected JSX member expression to be an identifier or nested member expression", - None, - )), - } -} - -// ============================================================================= -// Pattern codegen (lvalues) -// ============================================================================= - -enum LvalueRef<'a> { - Place(&'a Place), - Pattern(&'a Pattern), - Spread(&'a SpreadPattern), -} - -fn codegen_lvalue(cx: &mut Context, pattern: &LvalueRef) -> Result<PatternLike, CompilerError> { - match pattern { - LvalueRef::Place(place) => Ok(PatternLike::Identifier(convert_identifier( - place.identifier, - cx.env, - )?)), - LvalueRef::Pattern(pat) => match pat { - Pattern::Array(arr) => codegen_array_pattern(cx, arr), - Pattern::Object(obj) => codegen_object_pattern(cx, obj), - }, - LvalueRef::Spread(spread) => { - let inner = codegen_lvalue(cx, &LvalueRef::Place(&spread.place))?; - Ok(PatternLike::RestElement(RestElement { - base: BaseNode::typed("RestElement"), - argument: Box::new(inner), - type_annotation: None, - decorators: None, - })) - } - } -} - -fn codegen_array_pattern( - cx: &mut Context, - pattern: &ArrayPattern, -) -> Result<PatternLike, CompilerError> { - let elements: Vec<Option<PatternLike>> = pattern - .items - .iter() - .map(|item| match item { - react_compiler_hir::ArrayPatternElement::Place(place) => { - Ok(Some(codegen_lvalue(cx, &LvalueRef::Place(place))?)) - } - react_compiler_hir::ArrayPatternElement::Spread(spread) => { - Ok(Some(codegen_lvalue(cx, &LvalueRef::Spread(spread))?)) - } - react_compiler_hir::ArrayPatternElement::Hole => Ok(None), - }) - .collect::<Result<_, CompilerError>>()?; - Ok(PatternLike::ArrayPattern(AstArrayPattern { - base: base_node_with_loc("ArrayPattern", pattern.loc), - elements, - type_annotation: None, - decorators: None, - })) -} - -fn codegen_object_pattern( - cx: &mut Context, - pattern: &ObjectPattern, -) -> Result<PatternLike, CompilerError> { - let properties: Vec<ObjectPatternProperty> = pattern - .properties - .iter() - .map(|prop| match prop { - ObjectPropertyOrSpread::Property(obj_prop) => { - let key = codegen_object_property_key(cx, &obj_prop.key)?; - let value = codegen_lvalue(cx, &LvalueRef::Place(&obj_prop.place))?; - let is_shorthand = matches!(&key, Expression::Identifier(k_id) - if matches!(&value, PatternLike::Identifier(v_id) if v_id.name == k_id.name)); - Ok(ObjectPatternProperty::ObjectProperty(ObjectPatternProp { - base: BaseNode::typed("ObjectProperty"), - key: Box::new(key), - value: Box::new(value), - computed: matches!(obj_prop.key, ObjectPropertyKey::Computed { .. }), - shorthand: is_shorthand, - decorators: None, - method: None, - })) - } - ObjectPropertyOrSpread::Spread(spread) => { - let inner = codegen_lvalue(cx, &LvalueRef::Place(&spread.place))?; - Ok(ObjectPatternProperty::RestElement(RestElement { - base: BaseNode::typed("RestElement"), - argument: Box::new(inner), - type_annotation: None, - decorators: None, - })) - } - }) - .collect::<Result<_, CompilerError>>()?; - Ok(PatternLike::ObjectPattern( - react_compiler_ast::patterns::ObjectPattern { - base: base_node_with_loc("ObjectPattern", pattern.loc), - properties, - type_annotation: None, - decorators: None, - }, - )) -} - -// ============================================================================= -// Place / identifier codegen -// ============================================================================= - -fn codegen_place_to_expression( - cx: &mut Context, - place: &Place, -) -> Result<Expression, CompilerError> { - let value = codegen_place(cx, place)?; - Ok(convert_value_to_expression(value)) -} - -fn codegen_place(cx: &mut Context, place: &Place) -> Result<ExpressionOrJsxText, CompilerError> { - let ident = &cx.env.identifiers[place.identifier.0 as usize]; - if let Some(tmp) = cx.temp.get(&ident.declaration_id) { - if let Some(val) = tmp { - return Ok(val.clone()); - } - // tmp is None — means declared but no temp value, fall through - } - // Check if it's an unnamed identifier without a temp - if ident.name.is_none() && !cx.temp.contains_key(&ident.declaration_id) { - return Err(invariant_err( - &format!( - "[Codegen] No value found for temporary, identifier id={}", - place.identifier.0 - ), - place.loc, - )); - } - let mut ast_ident = convert_identifier(place.identifier, cx.env)?; - // Override identifier loc with place.loc, matching TS: identifier.loc = place.loc - if let Some(loc) = place.loc { - ast_ident.base.loc = Some(AstSourceLocation { - start: AstPosition { - line: loc.start.line, - column: loc.start.column, - index: None, - }, - end: AstPosition { - line: loc.end.line, - column: loc.end.column, - index: None, - }, - filename: None, - identifier_name: None, - }); - } - Ok(ExpressionOrJsxText::Expression(Expression::Identifier( - ast_ident, - ))) -} - -fn convert_identifier( - identifier_id: IdentifierId, - env: &Environment, -) -> Result<AstIdentifier, CompilerError> { - let ident = &env.identifiers[identifier_id.0 as usize]; - let name = match &ident.name { - Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(), - Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(), - None => { - // Use CompilerDiagnostic (with details array) to match TS CompilerError.invariant() - // which creates a CompilerDiagnostic with details: [{kind: "error", loc, message}]. - let reason = - "Expected temporaries to be promoted to named identifiers in an earlier pass" - .to_string(); - let description = format!("identifier {} is unnamed", identifier_id.0); - let mut err = CompilerError::new(); - err.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::Invariant, - reason.clone(), - Some(description), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: None, - message: Some(reason), - identifier_name: None, - }), - ); - return Err(err); - } - }; - Ok(make_identifier_with_loc(&name, ident.loc)) -} - -fn codegen_argument(cx: &mut Context, arg: &PlaceOrSpread) -> Result<Expression, CompilerError> { - match arg { - PlaceOrSpread::Place(place) => codegen_place_to_expression(cx, place), - PlaceOrSpread::Spread(spread) => { - let expr = codegen_place_to_expression(cx, &spread.place)?; - Ok(Expression::SpreadElement(ast_expr::SpreadElement { - base: BaseNode::typed("SpreadElement"), - argument: Box::new(expr), - })) - } - } -} - -// ============================================================================= -// Dependency codegen -// ============================================================================= - -fn codegen_dependency( - cx: &mut Context, - dep: &react_compiler_hir::ReactiveScopeDependency, -) -> Result<Expression, CompilerError> { - let mut object: Expression = - Expression::Identifier(convert_identifier(dep.identifier, cx.env)?); - if !dep.path.is_empty() { - let has_optional = dep.path.iter().any(|p| p.optional); - for path_entry in &dep.path { - let (property, is_computed) = property_literal_to_expression(&path_entry.property); - if has_optional { - object = Expression::OptionalMemberExpression(ast_expr::OptionalMemberExpression { - base: BaseNode::typed("OptionalMemberExpression"), - object: Box::new(object), - property: Box::new(property), - computed: is_computed, - optional: path_entry.optional, - }); - } else { - object = Expression::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(object), - property: Box::new(property), - computed: is_computed, - }); - } - } - } - Ok(object) -} - -// ============================================================================= -// CountMemoBlockVisitor — uses ReactiveFunctionVisitor trait -// ============================================================================= - -/// Counts memo blocks and pruned memo blocks in a reactive function. -/// TS: `class CountMemoBlockVisitor extends ReactiveFunctionVisitor<void>` -struct CountMemoBlockVisitor<'a> { - env: &'a Environment, -} - -struct CountMemoBlockState { - memo_blocks: u32, - memo_values: u32, - pruned_memo_blocks: u32, - pruned_memo_values: u32, -} - -impl<'a> ReactiveFunctionVisitor for CountMemoBlockVisitor<'a> { - type State = CountMemoBlockState; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_scope(&self, scope_block: &ReactiveScopeBlock, state: &mut CountMemoBlockState) { - state.memo_blocks += 1; - let scope = &self.env.scopes[scope_block.scope.0 as usize]; - state.memo_values += scope.declarations.len() as u32; - self.traverse_scope(scope_block, state); - } - - fn visit_pruned_scope( - &self, - scope_block: &PrunedReactiveScopeBlock, - state: &mut CountMemoBlockState, - ) { - state.pruned_memo_blocks += 1; - let scope = &self.env.scopes[scope_block.scope.0 as usize]; - state.pruned_memo_values += scope.declarations.len() as u32; - self.traverse_pruned_scope(scope_block, state); - } -} - -fn count_memo_blocks(func: &ReactiveFunction, env: &Environment) -> (u32, u32, u32, u32) { - let visitor = CountMemoBlockVisitor { env }; - let mut state = CountMemoBlockState { - memo_blocks: 0, - memo_values: 0, - pruned_memo_blocks: 0, - pruned_memo_values: 0, - }; - visit_reactive_function(func, &visitor, &mut state); - ( - state.memo_blocks, - state.memo_values, - state.pruned_memo_blocks, - state.pruned_memo_values, - ) -} - -// ============================================================================= -// Operator conversions -// ============================================================================= - -fn convert_binary_operator(op: &react_compiler_hir::BinaryOperator) -> AstBinaryOperator { - match op { - react_compiler_hir::BinaryOperator::Equal => AstBinaryOperator::Eq, - react_compiler_hir::BinaryOperator::NotEqual => AstBinaryOperator::Neq, - react_compiler_hir::BinaryOperator::StrictEqual => AstBinaryOperator::StrictEq, - react_compiler_hir::BinaryOperator::StrictNotEqual => AstBinaryOperator::StrictNeq, - react_compiler_hir::BinaryOperator::LessThan => AstBinaryOperator::Lt, - react_compiler_hir::BinaryOperator::LessEqual => AstBinaryOperator::Lte, - react_compiler_hir::BinaryOperator::GreaterThan => AstBinaryOperator::Gt, - react_compiler_hir::BinaryOperator::GreaterEqual => AstBinaryOperator::Gte, - react_compiler_hir::BinaryOperator::ShiftLeft => AstBinaryOperator::Shl, - react_compiler_hir::BinaryOperator::ShiftRight => AstBinaryOperator::Shr, - react_compiler_hir::BinaryOperator::UnsignedShiftRight => AstBinaryOperator::UShr, - react_compiler_hir::BinaryOperator::Add => AstBinaryOperator::Add, - react_compiler_hir::BinaryOperator::Subtract => AstBinaryOperator::Sub, - react_compiler_hir::BinaryOperator::Multiply => AstBinaryOperator::Mul, - react_compiler_hir::BinaryOperator::Divide => AstBinaryOperator::Div, - react_compiler_hir::BinaryOperator::Modulo => AstBinaryOperator::Rem, - react_compiler_hir::BinaryOperator::Exponent => AstBinaryOperator::Exp, - react_compiler_hir::BinaryOperator::BitwiseOr => AstBinaryOperator::BitOr, - react_compiler_hir::BinaryOperator::BitwiseXor => AstBinaryOperator::BitXor, - react_compiler_hir::BinaryOperator::BitwiseAnd => AstBinaryOperator::BitAnd, - react_compiler_hir::BinaryOperator::In => AstBinaryOperator::In, - react_compiler_hir::BinaryOperator::InstanceOf => AstBinaryOperator::Instanceof, - } -} - -fn convert_unary_operator(op: &react_compiler_hir::UnaryOperator) -> AstUnaryOperator { - match op { - react_compiler_hir::UnaryOperator::Minus => AstUnaryOperator::Neg, - react_compiler_hir::UnaryOperator::Plus => AstUnaryOperator::Plus, - react_compiler_hir::UnaryOperator::Not => AstUnaryOperator::Not, - react_compiler_hir::UnaryOperator::BitwiseNot => AstUnaryOperator::BitNot, - react_compiler_hir::UnaryOperator::TypeOf => AstUnaryOperator::TypeOf, - react_compiler_hir::UnaryOperator::Void => AstUnaryOperator::Void, - } -} - -fn convert_logical_operator(op: &LogicalOperator) -> AstLogicalOperator { - match op { - LogicalOperator::And => AstLogicalOperator::And, - LogicalOperator::Or => AstLogicalOperator::Or, - LogicalOperator::NullishCoalescing => AstLogicalOperator::NullishCoalescing, - } -} - -fn convert_update_operator(op: &react_compiler_hir::UpdateOperator) -> AstUpdateOperator { - match op { - react_compiler_hir::UpdateOperator::Increment => AstUpdateOperator::Increment, - react_compiler_hir::UpdateOperator::Decrement => AstUpdateOperator::Decrement, - } -} - -// ============================================================================= -// Helpers -// ============================================================================= - -/// Create a BaseNode with the given type name and optional source location. -/// Converts from the diagnostics SourceLocation (line, column) to the AST -/// SourceLocation format. This is critical for Babel's `retainLines: true` -/// option to insert blank lines at correct positions. -fn base_node_with_loc(type_name: &str, loc: Option<DiagSourceLocation>) -> BaseNode { - match loc { - Some(loc) => BaseNode { - node_type: Some(type_name.to_string()), - loc: Some(AstSourceLocation { - start: AstPosition { - line: loc.start.line, - column: loc.start.column, - index: loc.start.index, - }, - end: AstPosition { - line: loc.end.line, - column: loc.end.column, - index: loc.end.index, - }, - filename: None, - identifier_name: None, - }), - ..Default::default() - }, - None => BaseNode::typed(type_name), - } -} - -fn make_identifier(name: &str) -> AstIdentifier { - AstIdentifier { - base: BaseNode::typed("Identifier"), - name: name.to_string(), - type_annotation: None, - optional: None, - decorators: None, - } -} - -fn make_identifier_with_loc(name: &str, loc: Option<DiagSourceLocation>) -> AstIdentifier { - AstIdentifier { - base: base_node_with_loc("Identifier", loc), - name: name.to_string(), - type_annotation: None, - optional: None, - decorators: None, - } -} - -fn make_var_declarator(id: PatternLike, init: Option<Expression>) -> VariableDeclarator { - // Reconstruct VariableDeclarator.loc from id.loc.start and init.loc.end, - // matching TS createVariableDeclarator behavior for retainLines support. - let loc = get_pattern_loc(&id).and_then(|id_loc| { - let end = match &init { - Some(expr) => get_expression_loc(expr) - .map(|l| l.end.clone()) - .unwrap_or_else(|| id_loc.end.clone()), - None => id_loc.end.clone(), - }; - Some(AstSourceLocation { - start: id_loc.start.clone(), - end, - filename: id_loc.filename.clone(), - identifier_name: None, - }) - }); - VariableDeclarator { - base: if let Some(loc) = loc { - BaseNode { - node_type: Some("VariableDeclarator".to_string()), - loc: Some(loc), - ..Default::default() - } - } else { - BaseNode::typed("VariableDeclarator") - }, - id, - init: init.map(Box::new), - definite: None, - } -} - -/// Extract the loc from a PatternLike's base node. -fn get_pattern_loc(pattern: &PatternLike) -> Option<&AstSourceLocation> { - match pattern { - PatternLike::Identifier(id) => id.base.loc.as_ref(), - PatternLike::ObjectPattern(p) => p.base.loc.as_ref(), - PatternLike::ArrayPattern(p) => p.base.loc.as_ref(), - PatternLike::AssignmentPattern(p) => p.base.loc.as_ref(), - PatternLike::RestElement(p) => p.base.loc.as_ref(), - _ => None, - } -} - -/// Extract the loc from an Expression's base node. -fn get_expression_loc(expr: &Expression) -> Option<&AstSourceLocation> { - match expr { - Expression::Identifier(e) => e.base.loc.as_ref(), - Expression::StringLiteral(e) => e.base.loc.as_ref(), - Expression::NumericLiteral(e) => e.base.loc.as_ref(), - Expression::BooleanLiteral(e) => e.base.loc.as_ref(), - Expression::NullLiteral(e) => e.base.loc.as_ref(), - Expression::CallExpression(e) => e.base.loc.as_ref(), - Expression::MemberExpression(e) => e.base.loc.as_ref(), - Expression::OptionalMemberExpression(e) => e.base.loc.as_ref(), - Expression::ArrayExpression(e) => e.base.loc.as_ref(), - Expression::ObjectExpression(e) => e.base.loc.as_ref(), - Expression::ArrowFunctionExpression(e) => e.base.loc.as_ref(), - Expression::FunctionExpression(e) => e.base.loc.as_ref(), - Expression::BinaryExpression(e) => e.base.loc.as_ref(), - Expression::UnaryExpression(e) => e.base.loc.as_ref(), - Expression::UpdateExpression(e) => e.base.loc.as_ref(), - Expression::LogicalExpression(e) => e.base.loc.as_ref(), - Expression::ConditionalExpression(e) => e.base.loc.as_ref(), - Expression::SequenceExpression(e) => e.base.loc.as_ref(), - Expression::AssignmentExpression(e) => e.base.loc.as_ref(), - Expression::TemplateLiteral(e) => e.base.loc.as_ref(), - Expression::TaggedTemplateExpression(e) => e.base.loc.as_ref(), - Expression::SpreadElement(e) => e.base.loc.as_ref(), - Expression::RegExpLiteral(e) => e.base.loc.as_ref(), - Expression::JSXElement(e) => e.base.loc.as_ref(), - Expression::JSXFragment(e) => e.base.loc.as_ref(), - Expression::NewExpression(e) => e.base.loc.as_ref(), - Expression::OptionalCallExpression(e) => e.base.loc.as_ref(), - _ => None, - } -} - -/// Apply a source location to an ExpressionOrJsxText value, matching the TS behavior -/// where `value.loc = instrValue.loc` is set at the end of codegenInstructionValue. -fn apply_loc_to_value(value: &mut ExpressionOrJsxText, loc: DiagSourceLocation) { - let ast_loc = AstSourceLocation { - start: AstPosition { - line: loc.start.line, - column: loc.start.column, - index: None, - }, - end: AstPosition { - line: loc.end.line, - column: loc.end.column, - index: None, - }, - filename: None, - identifier_name: None, - }; - match value { - ExpressionOrJsxText::Expression(expr) => { - apply_loc_to_expression(expr, ast_loc); - } - ExpressionOrJsxText::JsxText(text) => { - text.base.loc = Some(ast_loc); - } - } -} - -/// Apply a source location to an Expression's base node. -fn apply_loc_to_expression(expr: &mut Expression, loc: AstSourceLocation) { - let base = match expr { - Expression::Identifier(e) => &mut e.base, - Expression::StringLiteral(e) => &mut e.base, - Expression::NumericLiteral(e) => &mut e.base, - Expression::BooleanLiteral(e) => &mut e.base, - Expression::NullLiteral(e) => &mut e.base, - Expression::CallExpression(e) => &mut e.base, - Expression::MemberExpression(e) => &mut e.base, - Expression::OptionalMemberExpression(e) => &mut e.base, - Expression::ArrayExpression(e) => &mut e.base, - Expression::ObjectExpression(e) => &mut e.base, - Expression::ArrowFunctionExpression(e) => &mut e.base, - Expression::FunctionExpression(e) => &mut e.base, - Expression::BinaryExpression(e) => &mut e.base, - Expression::UnaryExpression(e) => &mut e.base, - Expression::UpdateExpression(e) => &mut e.base, - Expression::LogicalExpression(e) => &mut e.base, - Expression::ConditionalExpression(e) => &mut e.base, - Expression::SequenceExpression(e) => &mut e.base, - Expression::AssignmentExpression(e) => &mut e.base, - Expression::TemplateLiteral(e) => &mut e.base, - Expression::TaggedTemplateExpression(e) => &mut e.base, - Expression::SpreadElement(e) => &mut e.base, - Expression::RegExpLiteral(e) => &mut e.base, - Expression::JSXElement(e) => &mut e.base, - Expression::JSXFragment(e) => &mut e.base, - Expression::NewExpression(e) => &mut e.base, - Expression::OptionalCallExpression(e) => &mut e.base, - _ => return, - }; - base.loc = Some(loc); -} - -fn codegen_label(id: BlockId) -> String { - format!("bb{}", id.0) -} - -fn symbol_for(name: &str) -> Expression { - Expression::CallExpression(ast_expr::CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(Expression::MemberExpression(ast_expr::MemberExpression { - base: BaseNode::typed("MemberExpression"), - object: Box::new(Expression::Identifier(make_identifier("Symbol"))), - property: Box::new(Expression::Identifier(make_identifier("for"))), - computed: false, - })), - arguments: vec![Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: name.to_string(), - })], - type_parameters: None, - type_arguments: None, - optional: None, - }) -} - -fn codegen_primitive_value(value: &PrimitiveValue, loc: Option<DiagSourceLocation>) -> Expression { - match value { - PrimitiveValue::Number(n) => { - let f = n.value(); - if f.is_nan() { - Expression::Identifier(make_identifier("NaN")) - } else if f.is_infinite() { - if f > 0.0 { - Expression::Identifier(make_identifier("Infinity")) - } else { - Expression::UnaryExpression(ast_expr::UnaryExpression { - base: base_node_with_loc("UnaryExpression", loc), - operator: AstUnaryOperator::Neg, - prefix: true, - argument: Box::new(Expression::Identifier(make_identifier("Infinity"))), - }) - } - } else if f < 0.0 { - Expression::UnaryExpression(ast_expr::UnaryExpression { - base: base_node_with_loc("UnaryExpression", loc), - operator: AstUnaryOperator::Neg, - prefix: true, - argument: Box::new(Expression::NumericLiteral(NumericLiteral { - base: base_node_with_loc("NumericLiteral", loc), - value: -f, - extra: None, - })), - }) - } else { - Expression::NumericLiteral(NumericLiteral { - base: base_node_with_loc("NumericLiteral", loc), - value: f, - extra: None, - }) - } - } - PrimitiveValue::Boolean(b) => Expression::BooleanLiteral(BooleanLiteral { - base: base_node_with_loc("BooleanLiteral", loc), - value: *b, - }), - PrimitiveValue::String(s) => Expression::StringLiteral(StringLiteral { - base: base_node_with_loc("StringLiteral", loc), - value: s.clone(), - }), - PrimitiveValue::Null => Expression::NullLiteral(NullLiteral { - base: base_node_with_loc("NullLiteral", loc), - }), - PrimitiveValue::Undefined => Expression::Identifier(make_identifier("undefined")), - } -} - -fn property_literal_to_expression(prop: &PropertyLiteral) -> (Expression, bool) { - match prop { - PropertyLiteral::String(s) => (Expression::Identifier(make_identifier(s)), false), - PropertyLiteral::Number(n) => ( - Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: n.value(), - extra: None, - }), - true, - ), - } -} - -fn convert_value_to_expression(value: ExpressionOrJsxText) -> Expression { - match value { - ExpressionOrJsxText::Expression(e) => e, - ExpressionOrJsxText::JsxText(text) => Expression::StringLiteral(StringLiteral { - base: BaseNode::typed("StringLiteral"), - value: text.value, - }), - } -} - -fn get_instruction_value( - reactive_value: &ReactiveValue, -) -> Result<&InstructionValue, CompilerError> { - match reactive_value { - ReactiveValue::Instruction(iv) => Ok(iv), - _ => Err(invariant_err("Expected base instruction value", None)), - } -} - -fn invariant( - condition: bool, - reason: &str, - loc: Option<DiagSourceLocation>, -) -> Result<(), CompilerError> { - if !condition { - Err(invariant_err(reason, loc)) - } else { - Ok(()) - } -} - -fn invariant_err(reason: &str, loc: Option<DiagSourceLocation>) -> CompilerError { - // Use CompilerDiagnostic (with details array) to match TS CompilerError.invariant() - let mut err = CompilerError::new(); - err.push_diagnostic( - CompilerDiagnostic::new(ErrorCategory::Invariant, reason, None::<String>).with_detail( - CompilerDiagnosticDetail::Error { - loc, - message: Some(reason.to_string()), - identifier_name: None, - }, - ), - ); - err -} - -fn invariant_err_with_detail_message( - reason: &str, - message: &str, - loc: Option<DiagSourceLocation>, -) -> CompilerError { - let mut err = CompilerError::new(); - let diagnostic = react_compiler_diagnostics::CompilerDiagnostic::new( - ErrorCategory::Invariant, - reason, - None::<String>, - ) - .with_detail( - react_compiler_diagnostics::CompilerDiagnosticDetail::Error { - loc, - message: Some(message.to_string()), - identifier_name: None, - }, - ); - err.push_diagnostic(diagnostic); - err -} - -fn get_statement_type_name(stmt: &Statement) -> &'static str { - match stmt { - Statement::ExpressionStatement(_) => "ExpressionStatement", - Statement::BlockStatement(_) => "BlockStatement", - Statement::VariableDeclaration(_) => "VariableDeclaration", - Statement::ReturnStatement(_) => "ReturnStatement", - Statement::IfStatement(_) => "IfStatement", - Statement::SwitchStatement(_) => "SwitchStatement", - Statement::ForStatement(_) => "ForStatement", - Statement::ForInStatement(_) => "ForInStatement", - Statement::ForOfStatement(_) => "ForOfStatement", - Statement::WhileStatement(_) => "WhileStatement", - Statement::DoWhileStatement(_) => "DoWhileStatement", - Statement::LabeledStatement(_) => "LabeledStatement", - Statement::ThrowStatement(_) => "ThrowStatement", - Statement::TryStatement(_) => "TryStatement", - Statement::BreakStatement(_) => "BreakStatement", - Statement::ContinueStatement(_) => "ContinueStatement", - Statement::FunctionDeclaration(_) => "FunctionDeclaration", - Statement::DebuggerStatement(_) => "DebuggerStatement", - Statement::EmptyStatement(_) => "EmptyStatement", - _ => "Statement", - } -} - -fn get_statement_loc(stmt: &Statement) -> Option<DiagSourceLocation> { - let base = match stmt { - Statement::ExpressionStatement(s) => &s.base, - Statement::BlockStatement(s) => &s.base, - Statement::VariableDeclaration(s) => &s.base, - Statement::ReturnStatement(s) => &s.base, - Statement::IfStatement(s) => &s.base, - Statement::ForStatement(s) => &s.base, - Statement::ForInStatement(s) => &s.base, - Statement::ForOfStatement(s) => &s.base, - Statement::WhileStatement(s) => &s.base, - Statement::DoWhileStatement(s) => &s.base, - Statement::LabeledStatement(s) => &s.base, - Statement::ThrowStatement(s) => &s.base, - Statement::TryStatement(s) => &s.base, - Statement::SwitchStatement(s) => &s.base, - Statement::BreakStatement(s) => &s.base, - Statement::ContinueStatement(s) => &s.base, - Statement::FunctionDeclaration(s) => &s.base, - Statement::DebuggerStatement(s) => &s.base, - Statement::EmptyStatement(s) => &s.base, - _ => return None, - }; - base.loc.as_ref().map(|loc| DiagSourceLocation { - start: react_compiler_diagnostics::Position { - line: loc.start.line, - column: loc.start.column, - index: loc.start.index, - }, - end: react_compiler_diagnostics::Position { - line: loc.end.line, - column: loc.end.column, - index: loc.end.index, - }, - }) -} - -fn compare_scope_dependency( - a: &react_compiler_hir::ReactiveScopeDependency, - b: &react_compiler_hir::ReactiveScopeDependency, - env: &Environment, -) -> std::cmp::Ordering { - let a_name = dep_to_sort_key(a, env); - let b_name = dep_to_sort_key(b, env); - a_name.cmp(&b_name) -} - -fn dep_to_sort_key(dep: &react_compiler_hir::ReactiveScopeDependency, env: &Environment) -> String { - let ident = &env.identifiers[dep.identifier.0 as usize]; - let base = match &ident.name { - Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(), - Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(), - None => format!("_t{}", dep.identifier.0), - }; - let mut parts = vec![base]; - for entry in &dep.path { - let prefix = if entry.optional { "?" } else { "" }; - let prop = match &entry.property { - PropertyLiteral::String(s) => s.clone(), - PropertyLiteral::Number(n) => format!("{}", n), - }; - parts.push(format!("{prefix}{prop}")); - } - parts.join(".") -} - -fn compare_scope_declaration( - a: &react_compiler_hir::ReactiveScopeDeclaration, - b: &react_compiler_hir::ReactiveScopeDeclaration, - env: &Environment, -) -> std::cmp::Ordering { - let a_name = ident_sort_key(a.identifier, env); - let b_name = ident_sort_key(b.identifier, env); - a_name.cmp(&b_name) -} - -fn ident_sort_key(id: IdentifierId, env: &Environment) -> String { - let ident = &env.identifiers[id.0 as usize]; - match &ident.name { - Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(), - Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(), - None => format!("_t{}", id.0), - } -} - -fn jsx_tag_loc(tag: &JsxTag) -> Option<DiagSourceLocation> { - match tag { - JsxTag::Place(p) => p.loc, - JsxTag::Builtin(_) => None, - } -} - -/// Conditionally wrap a call expression in a hook guard IIFE if enableEmitHookGuards -/// is enabled and the callee is a hook. -fn maybe_wrap_hook_call( - cx: &Context<'_>, - call_expr: Expression, - callee_id: IdentifierId, -) -> Expression { - if let Some(ref guard_name) = cx.env.hook_guard_name { - if cx.env.output_mode == react_compiler_hir::environment::OutputMode::Client - && is_hook_identifier(cx, callee_id) - { - return wrap_hook_call_with_guard(guard_name, call_expr, 2, 3); - } - } - call_expr -} - -/// Check if a callee identifier refers to a hook function. -fn is_hook_identifier(cx: &Context<'_>, identifier_id: IdentifierId) -> bool { - let identifier = &cx.env.identifiers[identifier_id.0 as usize]; - let type_ = &cx.env.types[identifier.type_.0 as usize]; - cx.env - .get_hook_kind_for_type(type_) - .ok() - .flatten() - .is_some() -} - -/// Create the hook guard IIFE wrapper for a hook call expression. -/// Wraps the call in: `(function() { try { $guard(before); return callExpr; } finally { $guard(after); } })()` -fn wrap_hook_call_with_guard( - guard_name: &str, - call_expr: Expression, - before: u32, - after: u32, -) -> Expression { - let guard_call = |kind: u32| -> Statement { - Statement::ExpressionStatement(ExpressionStatement { - base: BaseNode::typed("ExpressionStatement"), - expression: Box::new(Expression::CallExpression(ast_expr::CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(Expression::Identifier(make_identifier(guard_name))), - arguments: vec![Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: kind as f64, - extra: None, - })], - type_parameters: None, - type_arguments: None, - optional: None, - })), - }) - }; - - let try_stmt = Statement::TryStatement(TryStatement { - base: BaseNode::typed("TryStatement"), - block: BlockStatement { - base: BaseNode::typed("BlockStatement"), - body: vec![ - guard_call(before), - Statement::ReturnStatement(ReturnStatement { - base: BaseNode::typed("ReturnStatement"), - argument: Some(Box::new(call_expr)), - }), - ], - directives: Vec::new(), - }, - handler: None, - finalizer: Some(BlockStatement { - base: BaseNode::typed("BlockStatement"), - body: vec![guard_call(after)], - directives: Vec::new(), - }), - }); - - let iife = Expression::FunctionExpression(ast_expr::FunctionExpression { - base: BaseNode::typed("FunctionExpression"), - id: None, - params: Vec::new(), - body: BlockStatement { - base: BaseNode::typed("BlockStatement"), - body: vec![try_stmt], - directives: Vec::new(), - }, - generator: false, - is_async: false, - return_type: None, - type_parameters: None, - predicate: None, - }); - - Expression::CallExpression(ast_expr::CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(iife), - arguments: vec![], - type_parameters: None, - type_arguments: None, - optional: None, - }) -} - -/// Create a try/finally wrapping for the entire function body. -/// `try { $guard(before); ...body...; } finally { $guard(after); }` -fn create_function_body_hook_guard( - guard_name: &str, - body_stmts: Vec<Statement>, - before: u32, - after: u32, -) -> Statement { - let guard_call = |kind: u32| -> Statement { - Statement::ExpressionStatement(ExpressionStatement { - base: BaseNode::typed("ExpressionStatement"), - expression: Box::new(Expression::CallExpression(ast_expr::CallExpression { - base: BaseNode::typed("CallExpression"), - callee: Box::new(Expression::Identifier(make_identifier(guard_name))), - arguments: vec![Expression::NumericLiteral(NumericLiteral { - base: BaseNode::typed("NumericLiteral"), - value: kind as f64, - extra: None, - })], - type_parameters: None, - type_arguments: None, - optional: None, - })), - }) - }; - - let mut try_body = vec![guard_call(before)]; - try_body.extend(body_stmts); - - Statement::TryStatement(TryStatement { - base: BaseNode::typed("TryStatement"), - block: BlockStatement { - base: BaseNode::typed("BlockStatement"), - body: try_body, - directives: Vec::new(), - }, - handler: None, - finalizer: Some(BlockStatement { - base: BaseNode::typed("BlockStatement"), - body: vec![guard_call(after)], - directives: Vec::new(), - }), - }) -} - -fn apply_renames_to_json( - value: &mut serde_json::Value, - renames: &[react_compiler_hir::environment::BindingRename], - reference_node_ids: &std::collections::HashSet<u32>, -) { - apply_renames_to_json_inner(value, renames, reference_node_ids, false); -} - -fn apply_renames_to_json_inner( - value: &mut serde_json::Value, - renames: &[react_compiler_hir::environment::BindingRename], - reference_node_ids: &std::collections::HashSet<u32>, - is_property_key: bool, -) { - if renames.is_empty() { - return; - } - match value { - serde_json::Value::Object(map) => { - let node_type = map - .get("type") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - // Rename Identifier nodes that are NOT object property keys. - // Property keys in object type annotations (e.g., `id: string`) - // use the original property name, not a variable binding name. - if (node_type == "Identifier" || node_type == "GenericTypeAnnotation") - && !is_property_key - { - let ident_node_id = map.get("_nodeId").and_then(|v| v.as_u64()).unwrap_or(0) as u32; - let ident_start = map.get("start").and_then(|v| v.as_u64()).unwrap_or(0) as u32; - // Only rename identifiers that are actual references to bindings - // (identified by node_id). Type-level labels (e.g., ObjectTypeIndexer - // params) are NOT in the reference set and keep their original names. - let is_reference = ident_node_id > 0 && reference_node_ids.contains(&ident_node_id); - let maybe_rename = if is_reference { - map.get("name").and_then(|v| v.as_str()).and_then(|name| { - renames - .iter() - .filter(|r| r.original == name && r.declaration_start <= ident_start) - .max_by_key(|r| r.declaration_start) - .map(|r| r.renamed.clone()) - }) - } else if ident_node_id == 0 { - map.get("name").and_then(|v| v.as_str()).and_then(|name| { - renames - .iter() - .find(|r| r.original == name) - .map(|r| r.renamed.clone()) - }) - } else { - None - }; - if let Some(renamed) = maybe_rename { - map.insert("name".to_string(), serde_json::Value::String(renamed)); - } - if let Some(id) = map.get_mut("id") { - apply_renames_to_json_inner(id, renames, reference_node_ids, false); - } - } - let is_obj_type_prop = - node_type == "ObjectTypeProperty" || node_type == "ObjectTypeIndexer"; - for (key, val) in map.iter_mut() { - let child_is_key = is_obj_type_prop && key == "key"; - apply_renames_to_json_inner(val, renames, reference_node_ids, child_is_key); - } - } - serde_json::Value::Array(arr) => { - for item in arr { - apply_renames_to_json_inner(item, renames, reference_node_ids, false); - } - } - _ => {} - } -} - -#[cfg(test)] -mod tests { - use react_compiler_ast::statements::Statement; - use serde_json::json; - - use super::{UnsupportedOriginalNode, codegen_unsupported_original_node}; - - /// A modeled statement tag parses typed and is emitted directly. - #[test] - fn unsupported_original_node_modeled_statement_tag_emits_statement() { - let node = json!({ "type": "DebuggerStatement", "start": 0, "end": 9 }); - match codegen_unsupported_original_node(&node).unwrap() { - UnsupportedOriginalNode::Statement(Statement::DebuggerStatement(_)) => {} - UnsupportedOriginalNode::Statement(other) => { - panic!("expected typed DebuggerStatement, got {other:?}") - } - UnsupportedOriginalNode::ExpressionCodegen => { - panic!("statement tag must not flow to expression codegen") - } - } - } - - /// A modeled statement tag with a malformed body is a serialize/ - /// deserialize asymmetry: error loudly, never degrade to `Unknown`. - #[test] - fn unsupported_original_node_malformed_statement_tag_errors() { - let node = json!({ "type": "IfStatement", "consequent": { "type": "EmptyStatement" } }); - assert!(codegen_unsupported_original_node(&node).is_err()); - } - - /// An expression tag flows to expression codegen, which binds the - /// instruction's lvalue temporary. With the tolerant `Statement` - /// deserializer, a plain try-parse-as-`Statement` would wrongly claim - /// this node as `Statement::Unknown`. - #[test] - fn unsupported_original_node_expression_tag_flows_to_expression_codegen() { - let node = json!({ - "type": "CallExpression", - "callee": { "type": "Identifier", "name": "foo" }, - "arguments": [] - }); - assert!(matches!( - codegen_unsupported_original_node(&node).unwrap(), - UnsupportedOriginalNode::ExpressionCodegen - )); - } - - /// A pattern tag (destructuring bailout target) also flows to expression - /// codegen, preserving its placeholder fallback there. - #[test] - fn unsupported_original_node_pattern_tag_flows_to_expression_codegen() { - let node = json!({ "type": "ObjectPattern", "properties": [] }); - assert!(matches!( - codegen_unsupported_original_node(&node).unwrap(), - UnsupportedOriginalNode::ExpressionCodegen - )); - } - - /// A genuinely unmodeled tag is producible only by the unknown-statement - /// lowering bailout, so it is preserved verbatim at statement position. - #[test] - fn unsupported_original_node_unknown_tag_becomes_unknown_statement() { - let node = json!({ - "type": "TSImportEqualsDeclaration", - "start": 0, - "end": 39, - "id": { "type": "Identifier", "name": "lib" } - }); - match codegen_unsupported_original_node(&node).unwrap() { - UnsupportedOriginalNode::Statement(Statement::Unknown(unknown)) => { - assert_eq!(unknown.node_type(), "TSImportEqualsDeclaration"); - assert_eq!(unknown.raw().parse_value(), node); - } - UnsupportedOriginalNode::Statement(other) => { - panic!("expected Statement::Unknown, got {other:?}") - } - UnsupportedOriginalNode::ExpressionCodegen => { - panic!("unmodeled tag must not flow to expression codegen") - } - } - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/extract_scope_declarations_from_destructuring.rs b/compiler/crates/react_compiler_reactive_scopes/src/extract_scope_declarations_from_destructuring.rs deleted file mode 100644 index dea0736828b7..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/extract_scope_declarations_from_destructuring.rs +++ /dev/null @@ -1,222 +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. - -//! ExtractScopeDeclarationsFromDestructuring — handles destructuring patterns -//! where some bindings are scope declarations and others aren't. -//! -//! Corresponds to `src/ReactiveScopes/ExtractScopeDeclarationsFromDestructuring.ts`. - -use std::collections::HashSet; - -use react_compiler_hir::{ - DeclarationId, IdentifierId, IdentifierName, InstructionKind, InstructionValue, LValue, - ParamPattern, Place, ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock, - ReactiveStatement, ReactiveValue, environment::Environment, visitors, -}; - -use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function}; - -// ============================================================================= -// Public entry point -// ============================================================================= - -/// Extracts scope declarations from destructuring patterns where some bindings -/// are scope declarations and others aren't. -/// TS: `extractScopeDeclarationsFromDestructuring` -pub fn extract_scope_declarations_from_destructuring( - func: &mut ReactiveFunction, - env: &mut Environment, -) -> Result<(), react_compiler_diagnostics::CompilerError> { - let mut declared: HashSet<DeclarationId> = HashSet::new(); - for param in &func.params { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - let identifier = &env.identifiers[place.identifier.0 as usize]; - declared.insert(identifier.declaration_id); - } - let mut transform = Transform { env }; - let mut state = ExtractState { declared }; - transform_reactive_function(func, &mut transform, &mut state) -} - -struct ExtractState { - declared: HashSet<DeclarationId>, -} - -struct Transform<'a> { - env: &'a mut Environment, -} - -impl<'a> ReactiveFunctionTransform for Transform<'a> { - type State = ExtractState; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_scope( - &mut self, - scope: &mut ReactiveScopeBlock, - state: &mut ExtractState, - ) -> Result<(), react_compiler_diagnostics::CompilerError> { - let scope_data = &self.env.scopes[scope.scope.0 as usize]; - let decl_ids: Vec<DeclarationId> = scope_data - .declarations - .iter() - .map(|(_, d)| { - let identifier = &self.env.identifiers[d.identifier.0 as usize]; - identifier.declaration_id - }) - .collect(); - for decl_id in decl_ids { - state.declared.insert(decl_id); - } - self.traverse_scope(scope, state) - } - - fn transform_instruction( - &mut self, - instruction: &mut ReactiveInstruction, - state: &mut ExtractState, - ) -> Result<Transformed<ReactiveStatement>, react_compiler_diagnostics::CompilerError> { - self.visit_instruction(instruction, state)?; - - let mut extra_instructions: Option<Vec<ReactiveInstruction>> = None; - - if let ReactiveValue::Instruction(InstructionValue::Destructure { - lvalue, - value: _destr_value, - loc, - }) = &mut instruction.value - { - // Check if this is a mixed destructuring (some declared, some not) - let mut reassigned: HashSet<IdentifierId> = HashSet::new(); - let mut has_declaration = false; - - for place in visitors::each_pattern_operand(&lvalue.pattern) { - let identifier = &self.env.identifiers[place.identifier.0 as usize]; - if state.declared.contains(&identifier.declaration_id) { - reassigned.insert(place.identifier); - } else { - has_declaration = true; - } - } - - if !has_declaration { - // All reassignments - lvalue.kind = InstructionKind::Reassign; - } else if !reassigned.is_empty() { - // Mixed: replace reassigned items with temporaries and emit separate assignments - let mut renamed: Vec<(Place, Place)> = Vec::new(); - let instr_loc = instruction.loc.clone(); - let destr_loc = loc.clone(); - - let env = &mut *self.env; // reborrow - visitors::map_pattern_operands(&mut lvalue.pattern, &mut |place: Place| { - if !reassigned.contains(&place.identifier) { - return place; - } - // Create a temporary place (matches TS clonePlaceToTemporary) - let temp_id = env.next_identifier_id(); - let decl_id = env.identifiers[temp_id.0 as usize].declaration_id; - // Copy type from original identifier to temporary - let original_type = env.identifiers[place.identifier.0 as usize].type_; - env.identifiers[temp_id.0 as usize].type_ = original_type; - // Set identifier loc to the place's source location - // (matches TS makeTemporaryIdentifier which receives place.loc) - env.identifiers[temp_id.0 as usize].loc = place.loc.clone(); - // Promote the temporary - env.identifiers[temp_id.0 as usize].name = - Some(IdentifierName::Promoted(format!("#t{}", decl_id.0))); - let temporary = Place { - identifier: temp_id, - effect: place.effect, - reactive: place.reactive, - loc: None, // GeneratedSource — matches TS createTemporaryPlace - }; - let original = place; - renamed.push((original.clone(), temporary.clone())); - temporary - }); - - // Build extra StoreLocal instructions for each renamed place - let mut extra = Vec::new(); - for (original, temporary) in renamed { - extra.push(ReactiveInstruction { - id: instruction.id, - lvalue: None, - value: ReactiveValue::Instruction(InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Reassign, - place: original, - }, - value: temporary, - type_annotation: None, - loc: destr_loc.clone(), - }), - effects: None, - loc: instr_loc.clone(), - }); - } - extra_instructions = Some(extra); - } - } - - // Update state.declared with declarations from the instruction(s) - if let Some(ref extras) = extra_instructions { - // Process the original instruction - update_declared_from_instruction(instruction, &self.env, state); - // Process extra instructions - for extra_instr in extras { - update_declared_from_instruction(extra_instr, &self.env, state); - } - } else { - update_declared_from_instruction(instruction, &self.env, state); - } - - if let Some(extras) = extra_instructions { - // Clone the original instruction and build the replacement list - let mut all_instructions = Vec::new(); - all_instructions.push(ReactiveStatement::Instruction(instruction.clone())); - for extra in extras { - all_instructions.push(ReactiveStatement::Instruction(extra)); - } - Ok(Transformed::ReplaceMany(all_instructions)) - } else { - Ok(Transformed::Keep) - } - } -} - -fn update_declared_from_instruction( - instr: &ReactiveInstruction, - env: &Environment, - state: &mut ExtractState, -) { - if let ReactiveValue::Instruction(iv) = &instr.value { - match iv { - InstructionValue::DeclareContext { lvalue, .. } - | InstructionValue::StoreContext { lvalue, .. } - | InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::StoreLocal { lvalue, .. } => { - if lvalue.kind != InstructionKind::Reassign { - let identifier = &env.identifiers[lvalue.place.identifier.0 as usize]; - state.declared.insert(identifier.declaration_id); - } - } - InstructionValue::Destructure { lvalue, .. } => { - if lvalue.kind != InstructionKind::Reassign { - for place in visitors::each_pattern_operand(&lvalue.pattern) { - let identifier = &env.identifiers[place.identifier.0 as usize]; - state.declared.insert(identifier.declaration_id); - } - } - } - _ => {} - } - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/lib.rs b/compiler/crates/react_compiler_reactive_scopes/src/lib.rs deleted file mode 100644 index 2c6451d15333..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/lib.rs +++ /dev/null @@ -1,50 +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. - -//! Reactive scope passes for the React Compiler. -//! -//! Converts the HIR CFG into a tree-structured `ReactiveFunction` and runs -//! scope-related transformation passes (pruning, merging, renaming, etc.). -//! -//! Corresponds to `src/ReactiveScopes/` in the TypeScript compiler. - -mod assert_scope_instructions_within_scopes; -mod assert_well_formed_break_targets; -mod build_reactive_function; -pub mod codegen_reactive_function; -mod extract_scope_declarations_from_destructuring; -mod merge_reactive_scopes_that_invalidate_together; -pub mod print_reactive_function; -mod promote_used_temporaries; -mod propagate_early_returns; -mod prune_always_invalidating_scopes; -mod prune_hoisted_contexts; -mod prune_non_escaping_scopes; -mod prune_non_reactive_dependencies; -mod prune_unused_labels; -mod prune_unused_lvalues; -mod prune_unused_scopes; -mod rename_variables; -mod stabilize_block_ids; -pub mod visitors; - -pub use assert_scope_instructions_within_scopes::assert_scope_instructions_within_scopes; -pub use assert_well_formed_break_targets::assert_well_formed_break_targets; -pub use build_reactive_function::build_reactive_function; -pub use codegen_reactive_function::codegen_function; -pub use extract_scope_declarations_from_destructuring::extract_scope_declarations_from_destructuring; -pub use merge_reactive_scopes_that_invalidate_together::merge_reactive_scopes_that_invalidate_together; -pub use print_reactive_function::debug_reactive_function; -pub use promote_used_temporaries::promote_used_temporaries; -pub use propagate_early_returns::propagate_early_returns; -pub use prune_always_invalidating_scopes::prune_always_invalidating_scopes; -pub use prune_hoisted_contexts::prune_hoisted_contexts; -pub use prune_non_escaping_scopes::prune_non_escaping_scopes; -pub use prune_non_reactive_dependencies::prune_non_reactive_dependencies; -pub use prune_unused_labels::prune_unused_labels; -pub use prune_unused_lvalues::prune_unused_lvalues; -pub use prune_unused_scopes::prune_unused_scopes; -pub use rename_variables::rename_variables; -pub use stabilize_block_ids::stabilize_block_ids; diff --git a/compiler/crates/react_compiler_reactive_scopes/src/merge_reactive_scopes_that_invalidate_together.rs b/compiler/crates/react_compiler_reactive_scopes/src/merge_reactive_scopes_that_invalidate_together.rs deleted file mode 100644 index 349a5480f513..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/merge_reactive_scopes_that_invalidate_together.rs +++ /dev/null @@ -1,559 +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. - -//! MergeReactiveScopesThatInvalidateTogether — merges adjacent or nested scopes -//! that share dependencies (and thus invalidate together) to reduce memoization overhead. -//! -//! Corresponds to `src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts`. - -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::CompilerError; -use react_compiler_hir::{ - DeclarationId, DependencyPathEntry, EvaluationOrder, InstructionKind, InstructionValue, Place, - ReactiveBlock, ReactiveFunction, ReactiveScopeBlock, ReactiveScopeDependency, - ReactiveStatement, ReactiveValue, ScopeId, Type, - environment::Environment, - object_shape::{BUILT_IN_ARRAY_ID, BUILT_IN_FUNCTION_ID, BUILT_IN_JSX_ID, BUILT_IN_OBJECT_ID}, -}; - -use crate::visitors::{ - ReactiveFunctionTransform, ReactiveFunctionVisitor, Transformed, transform_reactive_function, - visit_reactive_function, -}; - -// ============================================================================= -// Public entry point -// ============================================================================= - -/// Merges adjacent reactive scopes that share dependencies (invalidate together). -/// TS: `mergeReactiveScopesThatInvalidateTogether` -pub fn merge_reactive_scopes_that_invalidate_together( - func: &mut ReactiveFunction, - env: &mut Environment, -) -> Result<(), CompilerError> { - // Pass 1: find last usage of each declaration - let visitor = FindLastUsageVisitor { env: &*env }; - let mut last_usage: HashMap<DeclarationId, EvaluationOrder> = HashMap::new(); - visit_reactive_function(func, &visitor, &mut last_usage); - - // Pass 2+3: merge scopes - let mut transform = MergeTransform { - env, - last_usage, - temporaries: HashMap::new(), - }; - let mut state: Option<Vec<ReactiveScopeDependency>> = None; - transform_reactive_function(func, &mut transform, &mut state) -} - -// ============================================================================= -// Pass 1: FindLastUsageVisitor -// ============================================================================= - -/// TS: `class FindLastUsageVisitor extends ReactiveFunctionVisitor<void>` -struct FindLastUsageVisitor<'a> { - env: &'a Environment, -} - -impl<'a> ReactiveFunctionVisitor for FindLastUsageVisitor<'a> { - type State = HashMap<DeclarationId, EvaluationOrder>; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_place(&self, id: EvaluationOrder, place: &Place, state: &mut Self::State) { - let decl_id = self.env.identifiers[place.identifier.0 as usize].declaration_id; - let entry = state.entry(decl_id).or_insert(id); - if id > *entry { - *entry = id; - } - } -} - -// ============================================================================= -// Pass 2+3: MergeTransform -// ============================================================================= - -/// TS: `class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | null>` -struct MergeTransform<'a> { - env: &'a mut Environment, - last_usage: HashMap<DeclarationId, EvaluationOrder>, - temporaries: HashMap<DeclarationId, DeclarationId>, -} - -impl<'a> ReactiveFunctionTransform for MergeTransform<'a> { - type State = Option<Vec<ReactiveScopeDependency>>; - - fn env(&self) -> &Environment { - self.env - } - - /// TS: `override transformScope(scopeBlock, state)` - fn transform_scope( - &mut self, - scope: &mut ReactiveScopeBlock, - state: &mut Self::State, - ) -> Result<Transformed<ReactiveStatement>, CompilerError> { - let scope_deps = self.env.scopes[scope.scope.0 as usize].dependencies.clone(); - // Save parent state and recurse with this scope's deps as state - let parent_state = state.take(); - *state = Some(scope_deps.clone()); - self.visit_scope(scope, state)?; - // Restore parent state - *state = parent_state; - - // If parent has deps and they match, flatten the inner scope - if let Some(parent_deps) = state.as_ref() { - if are_equal_dependencies(parent_deps, &scope_deps, self.env) { - let instructions = std::mem::take(&mut scope.instructions); - return Ok(Transformed::ReplaceMany(instructions)); - } - } - Ok(Transformed::Keep) - } - - /// TS: `override visitBlock(block, state)` - fn visit_block( - &mut self, - block: &mut ReactiveBlock, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - // Pass 1: traverse nested (scope flattening handled by transform_scope) - self.traverse_block(block, state)?; - // Pass 2+3: merge consecutive scopes in this block - self.merge_scopes_in_block(block)?; - Ok(()) - } -} - -impl<'a> MergeTransform<'a> { - /// Identify and merge consecutive scopes that invalidate together. - fn merge_scopes_in_block(&mut self, block: &mut ReactiveBlock) -> Result<(), CompilerError> { - // Pass 2: identify scopes for merging - struct MergedScope { - scope_id: ScopeId, - from: usize, - to: usize, - lvalues: HashSet<DeclarationId>, - } - - let mut current: Option<MergedScope> = None; - let mut merged: Vec<MergedScope> = Vec::new(); - - let block_len = block.len(); - for i in 0..block_len { - match &block[i] { - ReactiveStatement::Terminal(_) => { - // Don't merge across terminals - if let Some(c) = current.take() { - if c.to > c.from + 1 { - merged.push(c); - } - } - } - ReactiveStatement::PrunedScope(_) => { - // Don't merge across pruned scopes - if let Some(c) = current.take() { - if c.to > c.from + 1 { - merged.push(c); - } - } - } - ReactiveStatement::Instruction(instr) => { - match &instr.value { - ReactiveValue::Instruction(iv) => { - match iv { - InstructionValue::BinaryExpression { .. } - | InstructionValue::ComputedLoad { .. } - | InstructionValue::JSXText { .. } - | InstructionValue::LoadGlobal { .. } - | InstructionValue::LoadLocal { .. } - | InstructionValue::Primitive { .. } - | InstructionValue::PropertyLoad { .. } - | InstructionValue::TemplateLiteral { .. } - | InstructionValue::UnaryExpression { .. } => { - if let Some(ref mut c) = current { - if let Some(lvalue) = &instr.lvalue { - let decl_id = self.env.identifiers - [lvalue.identifier.0 as usize] - .declaration_id; - c.lvalues.insert(decl_id); - if let InstructionValue::LoadLocal { place, .. } = iv { - let src_decl = self.env.identifiers - [place.identifier.0 as usize] - .declaration_id; - self.temporaries.insert(decl_id, src_decl); - } - } - } - } - InstructionValue::StoreLocal { lvalue, value, .. } => { - if let Some(ref mut c) = current { - if lvalue.kind == InstructionKind::Const { - // Add the instruction lvalue (if any) - if let Some(instr_lvalue) = &instr.lvalue { - let decl_id = self.env.identifiers - [instr_lvalue.identifier.0 as usize] - .declaration_id; - c.lvalues.insert(decl_id); - } - // Add the StoreLocal's lvalue place - let store_decl = self.env.identifiers - [lvalue.place.identifier.0 as usize] - .declaration_id; - c.lvalues.insert(store_decl); - // Track temporary mapping - let value_decl = self.env.identifiers - [value.identifier.0 as usize] - .declaration_id; - let mapped = self - .temporaries - .get(&value_decl) - .copied() - .unwrap_or(value_decl); - self.temporaries.insert(store_decl, mapped); - } else { - // Non-const StoreLocal — reset - let c = current.take().unwrap(); - if c.to > c.from + 1 { - merged.push(c); - } - } - } - } - _ => { - // Other instructions prevent merging - if let Some(c) = current.take() { - if c.to > c.from + 1 { - merged.push(c); - } - } - } - } - } - _ => { - // Non-Instruction reactive values prevent merging - if let Some(c) = current.take() { - if c.to > c.from + 1 { - merged.push(c); - } - } - } - } - } - ReactiveStatement::Scope(scope_block) => { - let next_scope_id = scope_block.scope; - if let Some(ref mut c) = current { - let current_scope_id = c.scope_id; - if can_merge_scopes( - current_scope_id, - next_scope_id, - self.env, - &self.temporaries, - ) && are_lvalues_last_used_by_scope( - next_scope_id, - &c.lvalues, - &self.last_usage, - self.env, - ) { - // Merge: extend the current scope's range - let next_range_end = - self.env.scopes[next_scope_id.0 as usize].range.end; - let current_range_end = - self.env.scopes[current_scope_id.0 as usize].range.end; - self.env.scopes[current_scope_id.0 as usize].range.end = - EvaluationOrder(current_range_end.0.max(next_range_end.0)); - - // Merge declarations from next into current - let next_decls = self.env.scopes[next_scope_id.0 as usize] - .declarations - .clone(); - for (key, value) in next_decls { - let current_decls = - &mut self.env.scopes[current_scope_id.0 as usize].declarations; - if let Some(existing) = - current_decls.iter_mut().find(|(k, _)| *k == key) - { - existing.1 = value; - } else { - current_decls.push((key, value)); - } - } - - // Prune declarations that are no longer used after the merged scope - update_scope_declarations(current_scope_id, &self.last_usage, self.env); - - c.to = i + 1; - c.lvalues.clear(); - - if !scope_is_eligible_for_merging(next_scope_id, self.env) { - let c = current.take().unwrap(); - if c.to > c.from + 1 { - merged.push(c); - } - } - } else { - // Cannot merge — reset - let c = current.take().unwrap(); - if c.to > c.from + 1 { - merged.push(c); - } - // Start new candidate if eligible - if scope_is_eligible_for_merging(next_scope_id, self.env) { - current = Some(MergedScope { - scope_id: next_scope_id, - from: i, - to: i + 1, - lvalues: HashSet::new(), - }); - } - } - } else { - // No current — start new candidate if eligible - if scope_is_eligible_for_merging(next_scope_id, self.env) { - current = Some(MergedScope { - scope_id: next_scope_id, - from: i, - to: i + 1, - lvalues: HashSet::new(), - }); - } - } - } - } - } - // Flush remaining - if let Some(c) = current.take() { - if c.to > c.from + 1 { - merged.push(c); - } - } - - // Pass 3: apply merges - if merged.is_empty() { - return Ok(()); - } - - let mut next_instructions: Vec<ReactiveStatement> = Vec::new(); - let mut index = 0; - let all_stmts: Vec<ReactiveStatement> = std::mem::take(block); - - for entry in &merged { - // Push everything before the merge range - while index < entry.from { - next_instructions.push(all_stmts[index].clone()); - index += 1; - } - // The first item in the merge range must be a scope - let mut merged_scope = match &all_stmts[entry.from] { - ReactiveStatement::Scope(s) => s.clone(), - _ => { - return Err(react_compiler_diagnostics::CompilerDiagnostic::new( - react_compiler_diagnostics::ErrorCategory::Invariant, - "MergeConsecutiveScopes: Expected scope at starting index", - None, - ) - .into()); - } - }; - index += 1; - while index < entry.to { - let stmt = &all_stmts[index]; - index += 1; - match stmt { - ReactiveStatement::Scope(inner_scope) => { - merged_scope - .instructions - .extend(inner_scope.instructions.clone()); - self.env.scopes[merged_scope.scope.0 as usize] - .merged - .push(inner_scope.scope); - } - _ => { - merged_scope.instructions.push(stmt.clone()); - } - } - } - next_instructions.push(ReactiveStatement::Scope(merged_scope)); - } - // Push remaining - while index < all_stmts.len() { - next_instructions.push(all_stmts[index].clone()); - index += 1; - } - - *block = next_instructions; - Ok(()) - } -} - -// ============================================================================= -// Helper functions -// ============================================================================= - -/// Updates scope declarations to remove any that are not used after the scope. -fn update_scope_declarations( - scope_id: ScopeId, - last_usage: &HashMap<DeclarationId, EvaluationOrder>, - env: &mut Environment, -) { - let range_end = env.scopes[scope_id.0 as usize].range.end; - env.scopes[scope_id.0 as usize] - .declarations - .retain(|(_id, decl)| { - let decl_declaration_id = env.identifiers[decl.identifier.0 as usize].declaration_id; - match last_usage.get(&decl_declaration_id) { - Some(last_used_at) => *last_used_at >= range_end, - // If not tracked, keep the declaration (conservative) - None => true, - } - }); -} - -/// Returns whether all lvalues are last used at or before the given scope. -fn are_lvalues_last_used_by_scope( - scope_id: ScopeId, - lvalues: &HashSet<DeclarationId>, - last_usage: &HashMap<DeclarationId, EvaluationOrder>, - env: &Environment, -) -> bool { - let range_end = env.scopes[scope_id.0 as usize].range.end; - for lvalue in lvalues { - if let Some(&last_used_at) = last_usage.get(lvalue) { - if last_used_at >= range_end { - return false; - } - } - } - true -} - -/// Check if two scopes can be merged. -fn can_merge_scopes( - current_id: ScopeId, - next_id: ScopeId, - env: &Environment, - temporaries: &HashMap<DeclarationId, DeclarationId>, -) -> bool { - let current = &env.scopes[current_id.0 as usize]; - let next = &env.scopes[next_id.0 as usize]; - - // Don't merge scopes with reassignments - if !current.reassignments.is_empty() || !next.reassignments.is_empty() { - return false; - } - - // Merge scopes whose dependencies are identical - if are_equal_dependencies(¤t.dependencies, &next.dependencies, env) { - return true; - } - - // Merge scopes where outputs of current are inputs of next - // Build synthetic dependencies from current's declarations - let current_decl_deps: Vec<ReactiveScopeDependency> = current - .declarations - .iter() - .map(|(_key, decl)| ReactiveScopeDependency { - identifier: decl.identifier, - reactive: true, - path: Vec::new(), - loc: None, - }) - .collect(); - - if are_equal_dependencies(¤t_decl_deps, &next.dependencies, env) { - return true; - } - - // Check if all next deps have empty paths, always-invalidating types, - // and correspond to current declarations (possibly through temporaries) - if !next.dependencies.is_empty() - && next.dependencies.iter().all(|dep| { - if !dep.path.is_empty() { - return false; - } - let dep_type = &env.types[env.identifiers[dep.identifier.0 as usize].type_.0 as usize]; - if !is_always_invalidating_type(dep_type) { - return false; - } - let dep_decl = env.identifiers[dep.identifier.0 as usize].declaration_id; - current.declarations.iter().any(|(_key, decl)| { - let decl_decl_id = env.identifiers[decl.identifier.0 as usize].declaration_id; - decl_decl_id == dep_decl - || temporaries.get(&dep_decl).copied() == Some(decl_decl_id) - }) - }) - { - return true; - } - - false -} - -/// Check if a type is always invalidating (guaranteed to change when inputs change). -pub fn is_always_invalidating_type(ty: &Type) -> bool { - match ty { - Type::Object { shape_id } => { - if let Some(id) = shape_id { - matches!( - id.as_str(), - s if s == BUILT_IN_ARRAY_ID - || s == BUILT_IN_OBJECT_ID - || s == BUILT_IN_FUNCTION_ID - || s == BUILT_IN_JSX_ID - ) - } else { - false - } - } - Type::Function { .. } => true, - _ => false, - } -} - -/// Check if two dependency lists are equal. -fn are_equal_dependencies( - a: &[ReactiveScopeDependency], - b: &[ReactiveScopeDependency], - env: &Environment, -) -> bool { - if a.len() != b.len() { - return false; - } - for a_val in a { - let a_decl = env.identifiers[a_val.identifier.0 as usize].declaration_id; - let found = b.iter().any(|b_val| { - let b_decl = env.identifiers[b_val.identifier.0 as usize].declaration_id; - a_decl == b_decl && are_equal_paths(&a_val.path, &b_val.path) - }); - if !found { - return false; - } - } - true -} - -/// Check if two dependency paths are equal. -fn are_equal_paths(a: &[DependencyPathEntry], b: &[DependencyPathEntry]) -> bool { - a.len() == b.len() - && a.iter() - .zip(b.iter()) - .all(|(ai, bi)| ai.property == bi.property && ai.optional == bi.optional) -} - -/// Check if a scope is eligible for merging with subsequent scopes. -fn scope_is_eligible_for_merging(scope_id: ScopeId, env: &Environment) -> bool { - let scope = &env.scopes[scope_id.0 as usize]; - if scope.dependencies.is_empty() { - // No dependencies means output never changes — eligible - return true; - } - scope.declarations.iter().any(|(_key, decl)| { - let ty = &env.types[env.identifiers[decl.identifier.0 as usize].type_.0 as usize]; - is_always_invalidating_type(ty) - }) -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/print_reactive_function.rs b/compiler/crates/react_compiler_reactive_scopes/src/print_reactive_function.rs deleted file mode 100644 index b20d4b99f2d0..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/print_reactive_function.rs +++ /dev/null @@ -1,631 +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. - -//! Verbose debug printer for ReactiveFunction. -//! -//! Produces output identical to the TS `printDebugReactiveFunction`. -//! Delegates shared formatting (Places, Identifiers, Scopes, Types, -//! InstructionValues, Effects, Errors) to `react_compiler_hir::print::PrintFormatter`. - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::print::{self, PrintFormatter}; -use react_compiler_hir::{ - HirFunction, ParamPattern, ReactiveBlock, ReactiveFunction, ReactiveInstruction, - ReactiveStatement, ReactiveTerminal, ReactiveTerminalStatement, ReactiveValue, -}; - -// ============================================================================= -// DebugPrinter — thin wrapper around PrintFormatter for reactive-specific logic -// ============================================================================= - -pub struct DebugPrinter<'a> { - pub fmt: PrintFormatter<'a>, - /// Optional formatter for HIR functions (used for inner functions in FunctionExpression/ObjectMethod) - pub hir_formatter: Option<&'a HirFunctionFormatter>, -} - -impl<'a> DebugPrinter<'a> { - pub fn new(env: &'a Environment) -> Self { - Self { - fmt: PrintFormatter::new(env), - hir_formatter: None, - } - } - - // ========================================================================= - // ReactiveFunction - // ========================================================================= - - pub fn format_reactive_function(&mut self, func: &ReactiveFunction) { - self.fmt.indent(); - self.fmt.line(&format!( - "id: {}", - match &func.id { - Some(id) => format!("\"{}\"", id), - None => "null".to_string(), - } - )); - self.fmt.line(&format!( - "name_hint: {}", - match &func.name_hint { - Some(h) => format!("\"{}\"", h), - None => "null".to_string(), - } - )); - self.fmt.line(&format!("generator: {}", func.generator)); - self.fmt.line(&format!("is_async: {}", func.is_async)); - self.fmt - .line(&format!("loc: {}", print::format_loc(&func.loc))); - - // params - self.fmt.line("params:"); - self.fmt.indent(); - for (i, param) in func.params.iter().enumerate() { - match param { - ParamPattern::Place(place) => { - self.fmt.format_place_field(&format!("[{}]", i), place); - } - ParamPattern::Spread(spread) => { - self.fmt.line(&format!("[{}] Spread:", i)); - self.fmt.indent(); - self.fmt.format_place_field("place", &spread.place); - self.fmt.dedent(); - } - } - } - self.fmt.dedent(); - - // directives - self.fmt.line("directives:"); - self.fmt.indent(); - for (i, d) in func.directives.iter().enumerate() { - self.fmt.line(&format!("[{}] \"{}\"", i, d)); - } - self.fmt.dedent(); - - self.fmt.line(""); - self.fmt.line("Body:"); - self.fmt.indent(); - self.format_reactive_block(&func.body); - self.fmt.dedent(); - self.fmt.dedent(); - } - - // ========================================================================= - // ReactiveBlock - // ========================================================================= - - fn format_reactive_block(&mut self, block: &ReactiveBlock) { - for stmt in block.iter() { - self.format_reactive_statement(stmt); - } - } - - fn format_reactive_statement(&mut self, stmt: &ReactiveStatement) { - match stmt { - ReactiveStatement::Instruction(instr) => { - self.format_reactive_instruction_block(instr); - } - ReactiveStatement::Terminal(term) => { - self.fmt.line("ReactiveTerminalStatement {"); - self.fmt.indent(); - self.format_terminal_statement(term); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveStatement::Scope(scope) => { - self.fmt.line("ReactiveScopeBlock {"); - self.fmt.indent(); - self.fmt.format_scope_field("scope", scope.scope); - self.fmt.line("instructions:"); - self.fmt.indent(); - self.format_reactive_block(&scope.instructions); - self.fmt.dedent(); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveStatement::PrunedScope(scope) => { - self.fmt.line("PrunedReactiveScopeBlock {"); - self.fmt.indent(); - self.fmt.format_scope_field("scope", scope.scope); - self.fmt.line("instructions:"); - self.fmt.indent(); - self.format_reactive_block(&scope.instructions); - self.fmt.dedent(); - self.fmt.dedent(); - self.fmt.line("}"); - } - } - } - - // ========================================================================= - // ReactiveInstruction - // ========================================================================= - - fn format_reactive_instruction_block(&mut self, instr: &ReactiveInstruction) { - self.fmt.line("ReactiveInstruction {"); - self.fmt.indent(); - self.format_reactive_instruction(instr); - self.fmt.dedent(); - self.fmt.line("}"); - } - - fn format_reactive_instruction(&mut self, instr: &ReactiveInstruction) { - self.fmt.line(&format!("id: {}", instr.id.0)); - match &instr.lvalue { - Some(place) => self.fmt.format_place_field("lvalue", place), - None => self.fmt.line("lvalue: null"), - } - self.fmt.line("value:"); - self.fmt.indent(); - self.format_reactive_value(&instr.value); - self.fmt.dedent(); - match &instr.effects { - Some(effects) => { - self.fmt.line("effects:"); - self.fmt.indent(); - for (i, eff) in effects.iter().enumerate() { - self.fmt - .line(&format!("[{}] {}", i, self.fmt.format_effect(eff))); - } - self.fmt.dedent(); - } - None => self.fmt.line("effects: null"), - } - self.fmt - .line(&format!("loc: {}", print::format_loc(&instr.loc))); - } - - // ========================================================================= - // ReactiveValue - // ========================================================================= - - fn format_reactive_value(&mut self, value: &ReactiveValue) { - match value { - ReactiveValue::Instruction(iv) => { - // Build the inner function formatter callback if we have an hir_formatter - let hir_formatter = self.hir_formatter; - let inner_func_cb: Option<Box<dyn Fn(&mut PrintFormatter, &HirFunction) + '_>> = - hir_formatter.map(|hf| { - Box::new(move |fmt: &mut PrintFormatter, func: &HirFunction| { - hf(fmt, func); - }) - as Box<dyn Fn(&mut PrintFormatter, &HirFunction) + '_> - }); - self.fmt.format_instruction_value( - iv, - inner_func_cb - .as_ref() - .map(|cb| cb.as_ref() as &dyn Fn(&mut PrintFormatter, &HirFunction)), - ); - } - ReactiveValue::LogicalExpression { - operator, - left, - right, - loc, - } => { - self.fmt.line("LogicalExpression {"); - self.fmt.indent(); - self.fmt.line(&format!("operator: \"{}\"", operator)); - self.fmt.line("left:"); - self.fmt.indent(); - self.format_reactive_value(left); - self.fmt.dedent(); - self.fmt.line("right:"); - self.fmt.indent(); - self.format_reactive_value(right); - self.fmt.dedent(); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - loc, - } => { - self.fmt.line("ConditionalExpression {"); - self.fmt.indent(); - self.fmt.line("test:"); - self.fmt.indent(); - self.format_reactive_value(test); - self.fmt.dedent(); - self.fmt.line("consequent:"); - self.fmt.indent(); - self.format_reactive_value(consequent); - self.fmt.dedent(); - self.fmt.line("alternate:"); - self.fmt.indent(); - self.format_reactive_value(alternate); - self.fmt.dedent(); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveValue::SequenceExpression { - instructions, - id, - value, - loc, - } => { - self.fmt.line("SequenceExpression {"); - self.fmt.indent(); - self.fmt.line("instructions:"); - self.fmt.indent(); - for (i, instr) in instructions.iter().enumerate() { - self.fmt.line(&format!("[{}]:", i)); - self.fmt.indent(); - self.format_reactive_instruction_block(instr); - self.fmt.dedent(); - } - self.fmt.dedent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line("value:"); - self.fmt.indent(); - self.format_reactive_value(value); - self.fmt.dedent(); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveValue::OptionalExpression { - id, - value, - optional, - loc, - } => { - self.fmt.line("OptionalExpression {"); - self.fmt.indent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line("value:"); - self.fmt.indent(); - self.format_reactive_value(value); - self.fmt.dedent(); - self.fmt.line(&format!("optional: {}", optional)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - } - } - - // ========================================================================= - // ReactiveTerminal - // ========================================================================= - - fn format_terminal_statement(&mut self, stmt: &ReactiveTerminalStatement) { - match &stmt.label { - Some(label) => { - self.fmt.line(&format!( - "label: {{ id: bb{}, implicit: {} }}", - label.id.0, label.implicit - )); - } - None => self.fmt.line("label: null"), - } - self.fmt.line("terminal:"); - self.fmt.indent(); - self.format_reactive_terminal(&stmt.terminal); - self.fmt.dedent(); - } - - fn format_reactive_terminal(&mut self, terminal: &ReactiveTerminal) { - match terminal { - ReactiveTerminal::Break { - target, - id, - target_kind, - loc, - } => { - self.fmt.line("Break {"); - self.fmt.indent(); - self.fmt.line(&format!("target: bb{}", target.0)); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("targetKind: \"{}\"", target_kind)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::Continue { - target, - id, - target_kind, - loc, - } => { - self.fmt.line("Continue {"); - self.fmt.indent(); - self.fmt.line(&format!("target: bb{}", target.0)); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("targetKind: \"{}\"", target_kind)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::Return { value, id, loc } => { - self.fmt.line("Return {"); - self.fmt.indent(); - self.fmt.format_place_field("value", value); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::Throw { value, id, loc } => { - self.fmt.line("Throw {"); - self.fmt.indent(); - self.fmt.format_place_field("value", value); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::Switch { - test, - cases, - id, - loc, - } => { - self.fmt.line("Switch {"); - self.fmt.indent(); - self.fmt.format_place_field("test", test); - self.fmt.line("cases:"); - self.fmt.indent(); - for (i, case) in cases.iter().enumerate() { - self.fmt.line(&format!("[{}] {{", i)); - self.fmt.indent(); - match &case.test { - Some(p) => { - self.fmt.format_place_field("test", p); - } - None => { - self.fmt.line("test: null"); - } - } - match &case.block { - Some(block) => { - self.fmt.line("block:"); - self.fmt.indent(); - self.format_reactive_block(block); - self.fmt.dedent(); - } - None => self.fmt.line("block: undefined"), - } - self.fmt.dedent(); - self.fmt.line("}"); - } - self.fmt.dedent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::DoWhile { - loop_block, - test, - id, - loc, - } => { - self.fmt.line("DoWhile {"); - self.fmt.indent(); - self.fmt.line("loop:"); - self.fmt.indent(); - self.format_reactive_block(loop_block); - self.fmt.dedent(); - self.fmt.line("test:"); - self.fmt.indent(); - self.format_reactive_value(test); - self.fmt.dedent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::While { - test, - loop_block, - id, - loc, - } => { - self.fmt.line("While {"); - self.fmt.indent(); - self.fmt.line("test:"); - self.fmt.indent(); - self.format_reactive_value(test); - self.fmt.dedent(); - self.fmt.line("loop:"); - self.fmt.indent(); - self.format_reactive_block(loop_block); - self.fmt.dedent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::For { - init, - test, - update, - loop_block, - id, - loc, - } => { - self.fmt.line("For {"); - self.fmt.indent(); - self.fmt.line("init:"); - self.fmt.indent(); - self.format_reactive_value(init); - self.fmt.dedent(); - self.fmt.line("test:"); - self.fmt.indent(); - self.format_reactive_value(test); - self.fmt.dedent(); - match update { - Some(u) => { - self.fmt.line("update:"); - self.fmt.indent(); - self.format_reactive_value(u); - self.fmt.dedent(); - } - None => self.fmt.line("update: null"), - } - self.fmt.line("loop:"); - self.fmt.indent(); - self.format_reactive_block(loop_block); - self.fmt.dedent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::ForOf { - init, - test, - loop_block, - id, - loc, - } => { - self.fmt.line("ForOf {"); - self.fmt.indent(); - self.fmt.line("init:"); - self.fmt.indent(); - self.format_reactive_value(init); - self.fmt.dedent(); - self.fmt.line("test:"); - self.fmt.indent(); - self.format_reactive_value(test); - self.fmt.dedent(); - self.fmt.line("loop:"); - self.fmt.indent(); - self.format_reactive_block(loop_block); - self.fmt.dedent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::ForIn { - init, - loop_block, - id, - loc, - } => { - self.fmt.line("ForIn {"); - self.fmt.indent(); - self.fmt.line("init:"); - self.fmt.indent(); - self.format_reactive_value(init); - self.fmt.dedent(); - self.fmt.line("loop:"); - self.fmt.indent(); - self.format_reactive_block(loop_block); - self.fmt.dedent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::If { - test, - consequent, - alternate, - id, - loc, - } => { - self.fmt.line("If {"); - self.fmt.indent(); - self.fmt.format_place_field("test", test); - self.fmt.line("consequent:"); - self.fmt.indent(); - self.format_reactive_block(consequent); - self.fmt.dedent(); - match alternate { - Some(alt) => { - self.fmt.line("alternate:"); - self.fmt.indent(); - self.format_reactive_block(alt); - self.fmt.dedent(); - } - None => self.fmt.line("alternate: null"), - } - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::Label { block, id, loc } => { - self.fmt.line("Label {"); - self.fmt.indent(); - self.fmt.line("block:"); - self.fmt.indent(); - self.format_reactive_block(block); - self.fmt.dedent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - ReactiveTerminal::Try { - block, - handler_binding, - handler, - id, - loc, - } => { - self.fmt.line("Try {"); - self.fmt.indent(); - self.fmt.line("block:"); - self.fmt.indent(); - self.format_reactive_block(block); - self.fmt.dedent(); - match handler_binding { - Some(p) => self.fmt.format_place_field("handlerBinding", p), - None => self.fmt.line("handlerBinding: null"), - } - self.fmt.line("handler:"); - self.fmt.indent(); - self.format_reactive_block(handler); - self.fmt.dedent(); - self.fmt.line(&format!("id: {}", id.0)); - self.fmt.line(&format!("loc: {}", print::format_loc(loc))); - self.fmt.dedent(); - self.fmt.line("}"); - } - } - } -} - -// ============================================================================= -// Entry point -// ============================================================================= - -/// Type alias for a function formatter callback that can print HIR functions. -/// Used to format inner functions in FunctionExpression/ObjectMethod values. -pub type HirFunctionFormatter = dyn Fn(&mut PrintFormatter, &HirFunction); - -pub fn debug_reactive_function(func: &ReactiveFunction, env: &Environment) -> String { - debug_reactive_function_with_formatter(func, env, None) -} - -pub fn debug_reactive_function_with_formatter( - func: &ReactiveFunction, - env: &Environment, - hir_formatter: Option<&HirFunctionFormatter>, -) -> String { - let mut printer = DebugPrinter::new(env); - printer.hir_formatter = hir_formatter; - printer.format_reactive_function(func); - - // TODO: Print outlined functions when they've been converted to reactive form - - printer.fmt.line(""); - printer.fmt.line("Environment:"); - printer.fmt.indent(); - printer.fmt.format_errors(&env.errors); - printer.fmt.dedent(); - - printer.fmt.to_string_output() -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/promote_used_temporaries.rs b/compiler/crates/react_compiler_reactive_scopes/src/promote_used_temporaries.rs deleted file mode 100644 index bfd79bf96f52..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/promote_used_temporaries.rs +++ /dev/null @@ -1,1214 +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. - -//! PromoteUsedTemporaries — promotes temporary variables to named variables -//! if they're used by scopes. -//! -//! Corresponds to `src/ReactiveScopes/PromoteUsedTemporaries.ts`. - -use std::collections::HashMap; -use std::collections::HashSet; - -use react_compiler_hir::DeclarationId; -use react_compiler_hir::FunctionId; -use react_compiler_hir::IdentifierId; -use react_compiler_hir::IdentifierName; -use react_compiler_hir::InstructionKind; -use react_compiler_hir::InstructionValue; -use react_compiler_hir::JsxTag; -use react_compiler_hir::ParamPattern; -use react_compiler_hir::Place; -use react_compiler_hir::ReactiveBlock; -use react_compiler_hir::ReactiveFunction; -use react_compiler_hir::ReactiveInstruction; -use react_compiler_hir::ReactiveStatement; -use react_compiler_hir::ReactiveTerminal; -use react_compiler_hir::ReactiveTerminalStatement; -use react_compiler_hir::ReactiveValue; -use react_compiler_hir::ScopeId; -use react_compiler_hir::environment::Environment; - -// ============================================================================= -// State -// ============================================================================= - -struct State { - tags: HashSet<DeclarationId>, - promoted: HashSet<DeclarationId>, - pruned: HashMap<DeclarationId, PrunedInfo>, -} - -struct PrunedInfo { - active_scopes: Vec<ScopeId>, - used_outside_scope: bool, -} - -// ============================================================================= -// Public entry point -// ============================================================================= - -/// Promotes temporary (unnamed) identifiers used in scopes to named identifiers. -/// TS: `promoteUsedTemporaries` -pub fn promote_used_temporaries(func: &mut ReactiveFunction, env: &mut Environment) { - let mut state = State { - tags: HashSet::new(), - promoted: HashSet::new(), - pruned: HashMap::new(), - }; - - // Phase 1: collect promotable temporaries (jsx tags, pruned scope usage) - let mut active_scopes: Vec<ScopeId> = Vec::new(); - collect_promotable_block(&func.body, &mut state, &mut active_scopes, env); - - // Promote params - for param in &func.params { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - let identifier = &env.identifiers[place.identifier.0 as usize]; - if identifier.name.is_none() { - promote_identifier(place.identifier, &mut state, env); - } - } - - // Phase 2: promote identifiers used in scopes - promote_temporaries_block(&func.body, &mut state, env); - - // Phase 3: promote interposed temporaries - let mut consts: HashSet<IdentifierId> = HashSet::new(); - let mut globals: HashSet<IdentifierId> = HashSet::new(); - for param in &func.params { - match param { - ParamPattern::Place(p) => { - consts.insert(p.identifier); - } - ParamPattern::Spread(s) => { - consts.insert(s.place.identifier); - } - } - } - let mut inter_state: HashMap<IdentifierId, (IdentifierId, bool)> = HashMap::new(); - promote_interposed_block( - &func.body, - &mut state, - &mut inter_state, - &mut consts, - &mut globals, - env, - ); - - // Phase 4: promote all instances of promoted declaration IDs - promote_all_instances_params(func, &mut state, env); - promote_all_instances_block(&func.body, &mut state, env); -} - -// ============================================================================= -// Phase 1: CollectPromotableTemporaries -// ============================================================================= - -fn collect_promotable_block( - block: &ReactiveBlock, - state: &mut State, - active_scopes: &mut Vec<ScopeId>, - env: &Environment, -) { - for stmt in block { - match stmt { - ReactiveStatement::Instruction(instr) => { - collect_promotable_instruction(instr, state, active_scopes, env); - } - ReactiveStatement::Scope(scope) => { - let scope_id = scope.scope; - active_scopes.push(scope_id); - collect_promotable_block(&scope.instructions, state, active_scopes, env); - active_scopes.pop(); - } - ReactiveStatement::PrunedScope(scope) => { - let scope_data = &env.scopes[scope.scope.0 as usize]; - for (_id, decl) in &scope_data.declarations { - let identifier = &env.identifiers[decl.identifier.0 as usize]; - state.pruned.insert( - identifier.declaration_id, - PrunedInfo { - active_scopes: active_scopes.clone(), - used_outside_scope: false, - }, - ); - } - collect_promotable_block(&scope.instructions, state, active_scopes, env); - } - ReactiveStatement::Terminal(terminal) => { - collect_promotable_terminal(terminal, state, active_scopes, env); - } - } - } -} - -fn collect_promotable_place( - place: &Place, - state: &mut State, - active_scopes: &[ScopeId], - env: &Environment, -) { - if !active_scopes.is_empty() { - let identifier = &env.identifiers[place.identifier.0 as usize]; - if let Some(pruned) = state.pruned.get_mut(&identifier.declaration_id) { - if let Some(last) = active_scopes.last() { - if !pruned.active_scopes.contains(last) { - pruned.used_outside_scope = true; - } - } - } - } -} - -fn collect_promotable_instruction( - instr: &ReactiveInstruction, - state: &mut State, - active_scopes: &mut Vec<ScopeId>, - env: &Environment, -) { - collect_promotable_value(&instr.value, state, active_scopes, env); -} - -fn collect_promotable_value( - value: &ReactiveValue, - state: &mut State, - active_scopes: &mut Vec<ScopeId>, - env: &Environment, -) { - match value { - ReactiveValue::Instruction(instr_value) => { - // Visit operands - for place in - react_compiler_hir::visitors::each_instruction_value_operand(instr_value, env) - { - collect_promotable_place(&place, state, active_scopes, env); - } - // Check for JSX tag - if let InstructionValue::JsxExpression { - tag: JsxTag::Place(place), - .. - } = instr_value - { - let identifier = &env.identifiers[place.identifier.0 as usize]; - state.tags.insert(identifier.declaration_id); - } - } - ReactiveValue::SequenceExpression { - instructions, - value: inner, - .. - } => { - for instr in instructions { - collect_promotable_instruction(instr, state, active_scopes, env); - } - collect_promotable_value(inner, state, active_scopes, env); - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - .. - } => { - collect_promotable_value(test, state, active_scopes, env); - collect_promotable_value(consequent, state, active_scopes, env); - collect_promotable_value(alternate, state, active_scopes, env); - } - ReactiveValue::LogicalExpression { left, right, .. } => { - collect_promotable_value(left, state, active_scopes, env); - collect_promotable_value(right, state, active_scopes, env); - } - ReactiveValue::OptionalExpression { value: inner, .. } => { - collect_promotable_value(inner, state, active_scopes, env); - } - } -} - -fn collect_promotable_terminal( - stmt: &ReactiveTerminalStatement, - state: &mut State, - active_scopes: &mut Vec<ScopeId>, - env: &Environment, -) { - match &stmt.terminal { - ReactiveTerminal::Break { .. } | ReactiveTerminal::Continue { .. } => {} - ReactiveTerminal::Return { value, .. } | ReactiveTerminal::Throw { value, .. } => { - collect_promotable_place(value, state, active_scopes, env); - } - ReactiveTerminal::For { - init, - test, - update, - loop_block, - .. - } => { - collect_promotable_value(init, state, active_scopes, env); - collect_promotable_value(test, state, active_scopes, env); - collect_promotable_block(loop_block, state, active_scopes, env); - if let Some(update) = update { - collect_promotable_value(update, state, active_scopes, env); - } - } - ReactiveTerminal::ForOf { - init, - test, - loop_block, - .. - } => { - collect_promotable_value(init, state, active_scopes, env); - collect_promotable_value(test, state, active_scopes, env); - collect_promotable_block(loop_block, state, active_scopes, env); - } - ReactiveTerminal::ForIn { - init, loop_block, .. - } => { - collect_promotable_value(init, state, active_scopes, env); - collect_promotable_block(loop_block, state, active_scopes, env); - } - ReactiveTerminal::DoWhile { - loop_block, test, .. - } => { - collect_promotable_block(loop_block, state, active_scopes, env); - collect_promotable_value(test, state, active_scopes, env); - } - ReactiveTerminal::While { - test, loop_block, .. - } => { - collect_promotable_value(test, state, active_scopes, env); - collect_promotable_block(loop_block, state, active_scopes, env); - } - ReactiveTerminal::If { - test, - consequent, - alternate, - .. - } => { - collect_promotable_place(test, state, active_scopes, env); - collect_promotable_block(consequent, state, active_scopes, env); - if let Some(alt) = alternate { - collect_promotable_block(alt, state, active_scopes, env); - } - } - ReactiveTerminal::Switch { test, cases, .. } => { - collect_promotable_place(test, state, active_scopes, env); - for case in cases { - if let Some(t) = &case.test { - collect_promotable_place(t, state, active_scopes, env); - } - if let Some(block) = &case.block { - collect_promotable_block(block, state, active_scopes, env); - } - } - } - ReactiveTerminal::Label { block, .. } => { - collect_promotable_block(block, state, active_scopes, env); - } - ReactiveTerminal::Try { - block, - handler_binding, - handler, - .. - } => { - collect_promotable_block(block, state, active_scopes, env); - if let Some(binding) = handler_binding { - collect_promotable_place(binding, state, active_scopes, env); - } - collect_promotable_block(handler, state, active_scopes, env); - } - } -} - -// ============================================================================= -// Phase 2: PromoteTemporaries -// ============================================================================= - -fn promote_temporaries_block(block: &ReactiveBlock, state: &mut State, env: &mut Environment) { - for stmt in block { - match stmt { - ReactiveStatement::Instruction(instr) => { - promote_temporaries_value(&instr.value, state, env); - } - ReactiveStatement::Scope(scope) => { - let scope_id = scope.scope; - let scope_data = &env.scopes[scope_id.0 as usize]; - // Collect all IDs to promote first - let mut ids_to_check: Vec<IdentifierId> = Vec::new(); - ids_to_check.extend(scope_data.dependencies.iter().map(|d| d.identifier)); - ids_to_check.extend(scope_data.declarations.iter().map(|(_, d)| d.identifier)); - for id in ids_to_check { - let identifier = &env.identifiers[id.0 as usize]; - if identifier.name.is_none() { - promote_identifier(id, state, env); - } - } - promote_temporaries_block(&scope.instructions, state, env); - } - ReactiveStatement::PrunedScope(scope) => { - let scope_id = scope.scope; - let scope_data = &env.scopes[scope_id.0 as usize]; - let decls: Vec<(IdentifierId, DeclarationId)> = scope_data - .declarations - .iter() - .map(|(_, d)| { - let identifier = &env.identifiers[d.identifier.0 as usize]; - (d.identifier, identifier.declaration_id) - }) - .collect(); - for (id, decl_id) in decls { - let identifier = &env.identifiers[id.0 as usize]; - if identifier.name.is_none() { - if let Some(pruned) = state.pruned.get(&decl_id) { - if pruned.used_outside_scope { - promote_identifier(id, state, env); - } - } - } - } - promote_temporaries_block(&scope.instructions, state, env); - } - ReactiveStatement::Terminal(terminal) => { - promote_temporaries_terminal(terminal, state, env); - } - } - } -} - -fn promote_temporaries_value(value: &ReactiveValue, state: &mut State, env: &mut Environment) { - match value { - ReactiveValue::Instruction(instr_value) => { - // Visit inner functions: promote params and recurse into nested functions - // TS: visitHirFunction(value.loweredFunc.func, state) - match instr_value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - visit_hir_function_for_promotion(lowered_func.func, state, env); - } - _ => {} - } - } - ReactiveValue::SequenceExpression { - instructions, - value: inner, - .. - } => { - for instr in instructions { - promote_temporaries_value(&instr.value, state, env); - } - promote_temporaries_value(inner, state, env); - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - .. - } => { - promote_temporaries_value(test, state, env); - promote_temporaries_value(consequent, state, env); - promote_temporaries_value(alternate, state, env); - } - ReactiveValue::LogicalExpression { left, right, .. } => { - promote_temporaries_value(left, state, env); - promote_temporaries_value(right, state, env); - } - ReactiveValue::OptionalExpression { value: inner, .. } => { - promote_temporaries_value(inner, state, env); - } - } -} - -fn promote_temporaries_terminal( - stmt: &ReactiveTerminalStatement, - state: &mut State, - env: &mut Environment, -) { - match &stmt.terminal { - ReactiveTerminal::Break { .. } | ReactiveTerminal::Continue { .. } => {} - ReactiveTerminal::Return { .. } | ReactiveTerminal::Throw { .. } => {} - ReactiveTerminal::For { - init, - test, - update, - loop_block, - .. - } => { - promote_temporaries_value(init, state, env); - promote_temporaries_value(test, state, env); - promote_temporaries_block(loop_block, state, env); - if let Some(update) = update { - promote_temporaries_value(update, state, env); - } - } - ReactiveTerminal::ForOf { - init, - test, - loop_block, - .. - } => { - promote_temporaries_value(init, state, env); - promote_temporaries_value(test, state, env); - promote_temporaries_block(loop_block, state, env); - } - ReactiveTerminal::ForIn { - init, loop_block, .. - } => { - promote_temporaries_value(init, state, env); - promote_temporaries_block(loop_block, state, env); - } - ReactiveTerminal::DoWhile { - loop_block, test, .. - } => { - promote_temporaries_block(loop_block, state, env); - promote_temporaries_value(test, state, env); - } - ReactiveTerminal::While { - test, loop_block, .. - } => { - promote_temporaries_value(test, state, env); - promote_temporaries_block(loop_block, state, env); - } - ReactiveTerminal::If { - consequent, - alternate, - .. - } => { - promote_temporaries_block(consequent, state, env); - if let Some(alt) = alternate { - promote_temporaries_block(alt, state, env); - } - } - ReactiveTerminal::Switch { cases, .. } => { - for case in cases { - if let Some(block) = &case.block { - promote_temporaries_block(block, state, env); - } - } - } - ReactiveTerminal::Label { block, .. } => { - promote_temporaries_block(block, state, env); - } - ReactiveTerminal::Try { block, handler, .. } => { - promote_temporaries_block(block, state, env); - promote_temporaries_block(handler, state, env); - } - } -} - -// ============================================================================= -// Helper: visit inner HIR function for promotion (mirrors TS visitHirFunction) -// ============================================================================= - -/// Promotes params and recursively visits nested functions for param promotion. -/// Specialized version of the TS `visitHirFunction` pattern for the PromoteTemporaries -/// phase — only promotes unnamed params and recurses into nested functions. -/// Other `visitHirFunction` behaviors (visitPlace on terminal operands, visitInstruction -/// on all instructions) are no-ops for this phase and are intentionally omitted. -fn visit_hir_function_for_promotion(func_id: FunctionId, state: &mut State, env: &mut Environment) { - // Promote params of this function - let param_ids: Vec<IdentifierId> = { - let func = &env.functions[func_id.0 as usize]; - func.params - .iter() - .map(|param| match param { - ParamPattern::Place(p) => p.identifier, - ParamPattern::Spread(s) => s.place.identifier, - }) - .collect() - }; - for id in param_ids { - let identifier = &env.identifiers[id.0 as usize]; - if identifier.name.is_none() { - promote_identifier(id, state, env); - } - } - - // Find nested FunctionExpression/ObjectMethod in body instructions - let nested_func_ids: Vec<FunctionId> = { - let func = &env.functions[func_id.0 as usize]; - let mut nested = Vec::new(); - for (_, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - nested.push(lowered_func.func); - } - _ => {} - } - } - } - nested - }; - for nested_id in nested_func_ids { - visit_hir_function_for_promotion(nested_id, state, env); - } -} - -// ============================================================================= -// Phase 3: PromoteInterposedTemporaries -// ============================================================================= - -fn promote_interposed_block( - block: &ReactiveBlock, - state: &mut State, - inter_state: &mut HashMap<IdentifierId, (IdentifierId, bool)>, - consts: &mut HashSet<IdentifierId>, - globals: &mut HashSet<IdentifierId>, - env: &mut Environment, -) { - for stmt in block { - match stmt { - ReactiveStatement::Instruction(instr) => { - promote_interposed_instruction(instr, state, inter_state, consts, globals, env); - } - ReactiveStatement::Scope(scope) => { - promote_interposed_block( - &scope.instructions, - state, - inter_state, - consts, - globals, - env, - ); - } - ReactiveStatement::PrunedScope(scope) => { - promote_interposed_block( - &scope.instructions, - state, - inter_state, - consts, - globals, - env, - ); - } - ReactiveStatement::Terminal(terminal) => { - promote_interposed_terminal(terminal, state, inter_state, consts, globals, env); - } - } - } -} - -fn promote_interposed_place( - place: &Place, - state: &mut State, - inter_state: &mut HashMap<IdentifierId, (IdentifierId, bool)>, - consts: &HashSet<IdentifierId>, - env: &mut Environment, -) { - if let Some(&(id, needs_promotion)) = inter_state.get(&place.identifier) { - let identifier = &env.identifiers[id.0 as usize]; - if needs_promotion && identifier.name.is_none() && !consts.contains(&id) { - promote_identifier(id, state, env); - } - } -} - -fn promote_interposed_instruction( - instr: &ReactiveInstruction, - state: &mut State, - inter_state: &mut HashMap<IdentifierId, (IdentifierId, bool)>, - consts: &mut HashSet<IdentifierId>, - globals: &mut HashSet<IdentifierId>, - env: &mut Environment, -) { - // Check instruction value lvalues (assignment targets) - match &instr.value { - ReactiveValue::Instruction(iv) => { - // Check eachInstructionValueLValue: these should all be named - // (the TS pass asserts this but we just skip in Rust) - - match iv { - InstructionValue::CallExpression { .. } - | InstructionValue::MethodCall { .. } - | InstructionValue::Await { .. } - | InstructionValue::PropertyStore { .. } - | InstructionValue::PropertyDelete { .. } - | InstructionValue::ComputedStore { .. } - | InstructionValue::ComputedDelete { .. } - | InstructionValue::PostfixUpdate { .. } - | InstructionValue::PrefixUpdate { .. } - | InstructionValue::StoreLocal { .. } - | InstructionValue::StoreContext { .. } - | InstructionValue::StoreGlobal { .. } - | InstructionValue::Destructure { .. } => { - let mut const_store = false; - - match iv { - InstructionValue::StoreContext { lvalue, .. } - | InstructionValue::StoreLocal { lvalue, .. } => { - if lvalue.kind == InstructionKind::Const - || lvalue.kind == InstructionKind::HoistedConst - { - consts.insert(lvalue.place.identifier); - const_store = true; - } - } - _ => {} - } - if let InstructionValue::Destructure { lvalue, .. } = iv { - if lvalue.kind == InstructionKind::Const - || lvalue.kind == InstructionKind::HoistedConst - { - for operand in - react_compiler_hir::visitors::each_pattern_operand(&lvalue.pattern) - { - consts.insert(operand.identifier); - } - const_store = true; - } - } - if let InstructionValue::MethodCall { property, .. } = iv { - consts.insert(property.identifier); - } - - // Visit operands - for place in - react_compiler_hir::visitors::each_instruction_value_operand(iv, env) - { - promote_interposed_place(&place, state, inter_state, consts, env); - } - - if !const_store - && (instr.lvalue.is_none() - || env.identifiers - [instr.lvalue.as_ref().unwrap().identifier.0 as usize] - .name - .is_some()) - { - // Mark all tracked temporaries as needing promotion - let keys: Vec<IdentifierId> = inter_state.keys().cloned().collect(); - for key in keys { - if let Some(entry) = inter_state.get_mut(&key) { - entry.1 = true; - } - } - } - if let Some(lvalue) = &instr.lvalue { - let identifier = &env.identifiers[lvalue.identifier.0 as usize]; - if identifier.name.is_none() { - inter_state.insert(lvalue.identifier, (lvalue.identifier, false)); - } - } - } - InstructionValue::DeclareContext { lvalue, .. } - | InstructionValue::DeclareLocal { lvalue, .. } => { - if lvalue.kind == InstructionKind::Const - || lvalue.kind == InstructionKind::HoistedConst - { - consts.insert(lvalue.place.identifier); - } - // Visit operands - for place in - react_compiler_hir::visitors::each_instruction_value_operand(iv, env) - { - promote_interposed_place(&place, state, inter_state, consts, env); - } - } - InstructionValue::LoadContext { - place: load_place, .. - } - | InstructionValue::LoadLocal { - place: load_place, .. - } => { - if let Some(lvalue) = &instr.lvalue { - let identifier = &env.identifiers[lvalue.identifier.0 as usize]; - if identifier.name.is_none() { - if consts.contains(&load_place.identifier) { - consts.insert(lvalue.identifier); - } - inter_state.insert(lvalue.identifier, (lvalue.identifier, false)); - } - } - // Visit operands - for place in - react_compiler_hir::visitors::each_instruction_value_operand(iv, env) - { - promote_interposed_place(&place, state, inter_state, consts, env); - } - } - InstructionValue::PropertyLoad { object, .. } - | InstructionValue::ComputedLoad { object, .. } => { - if let Some(lvalue) = &instr.lvalue { - if globals.contains(&object.identifier) { - globals.insert(lvalue.identifier); - consts.insert(lvalue.identifier); - } - let identifier = &env.identifiers[lvalue.identifier.0 as usize]; - if identifier.name.is_none() { - inter_state.insert(lvalue.identifier, (lvalue.identifier, false)); - } - } - // Visit operands - for place in - react_compiler_hir::visitors::each_instruction_value_operand(iv, env) - { - promote_interposed_place(&place, state, inter_state, consts, env); - } - } - InstructionValue::LoadGlobal { .. } => { - if let Some(lvalue) = &instr.lvalue { - globals.insert(lvalue.identifier); - } - // Visit operands - for place in - react_compiler_hir::visitors::each_instruction_value_operand(iv, env) - { - promote_interposed_place(&place, state, inter_state, consts, env); - } - } - _ => { - // Default: visit operands - for place in - react_compiler_hir::visitors::each_instruction_value_operand(iv, env) - { - promote_interposed_place(&place, state, inter_state, consts, env); - } - } - } - } - ReactiveValue::SequenceExpression { - instructions, - value: inner, - .. - } => { - for sub_instr in instructions { - promote_interposed_instruction(sub_instr, state, inter_state, consts, globals, env); - } - promote_interposed_value(inner, state, inter_state, consts, globals, env); - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - .. - } => { - promote_interposed_value(test, state, inter_state, consts, globals, env); - promote_interposed_value(consequent, state, inter_state, consts, globals, env); - promote_interposed_value(alternate, state, inter_state, consts, globals, env); - } - ReactiveValue::LogicalExpression { left, right, .. } => { - promote_interposed_value(left, state, inter_state, consts, globals, env); - promote_interposed_value(right, state, inter_state, consts, globals, env); - } - ReactiveValue::OptionalExpression { value: inner, .. } => { - promote_interposed_value(inner, state, inter_state, consts, globals, env); - } - } -} - -fn promote_interposed_value( - value: &ReactiveValue, - state: &mut State, - inter_state: &mut HashMap<IdentifierId, (IdentifierId, bool)>, - consts: &mut HashSet<IdentifierId>, - globals: &mut HashSet<IdentifierId>, - env: &mut Environment, -) { - match value { - ReactiveValue::Instruction(iv) => { - for place in react_compiler_hir::visitors::each_instruction_value_operand(iv, env) { - promote_interposed_place(&place, state, inter_state, consts, env); - } - } - ReactiveValue::SequenceExpression { - instructions, - value: inner, - .. - } => { - for instr in instructions { - promote_interposed_instruction(instr, state, inter_state, consts, globals, env); - } - promote_interposed_value(inner, state, inter_state, consts, globals, env); - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - .. - } => { - promote_interposed_value(test, state, inter_state, consts, globals, env); - promote_interposed_value(consequent, state, inter_state, consts, globals, env); - promote_interposed_value(alternate, state, inter_state, consts, globals, env); - } - ReactiveValue::LogicalExpression { left, right, .. } => { - promote_interposed_value(left, state, inter_state, consts, globals, env); - promote_interposed_value(right, state, inter_state, consts, globals, env); - } - ReactiveValue::OptionalExpression { value: inner, .. } => { - promote_interposed_value(inner, state, inter_state, consts, globals, env); - } - } -} - -fn promote_interposed_terminal( - stmt: &ReactiveTerminalStatement, - state: &mut State, - inter_state: &mut HashMap<IdentifierId, (IdentifierId, bool)>, - consts: &mut HashSet<IdentifierId>, - globals: &mut HashSet<IdentifierId>, - env: &mut Environment, -) { - match &stmt.terminal { - ReactiveTerminal::Break { .. } | ReactiveTerminal::Continue { .. } => {} - ReactiveTerminal::Return { value, .. } | ReactiveTerminal::Throw { value, .. } => { - promote_interposed_place(value, state, inter_state, consts, env); - } - ReactiveTerminal::For { - init, - test, - update, - loop_block, - .. - } => { - promote_interposed_value(init, state, inter_state, consts, globals, env); - promote_interposed_value(test, state, inter_state, consts, globals, env); - promote_interposed_block(loop_block, state, inter_state, consts, globals, env); - if let Some(update) = update { - promote_interposed_value(update, state, inter_state, consts, globals, env); - } - } - ReactiveTerminal::ForOf { - init, - test, - loop_block, - .. - } => { - promote_interposed_value(init, state, inter_state, consts, globals, env); - promote_interposed_value(test, state, inter_state, consts, globals, env); - promote_interposed_block(loop_block, state, inter_state, consts, globals, env); - } - ReactiveTerminal::ForIn { - init, loop_block, .. - } => { - promote_interposed_value(init, state, inter_state, consts, globals, env); - promote_interposed_block(loop_block, state, inter_state, consts, globals, env); - } - ReactiveTerminal::DoWhile { - loop_block, test, .. - } => { - promote_interposed_block(loop_block, state, inter_state, consts, globals, env); - promote_interposed_value(test, state, inter_state, consts, globals, env); - } - ReactiveTerminal::While { - test, loop_block, .. - } => { - promote_interposed_value(test, state, inter_state, consts, globals, env); - promote_interposed_block(loop_block, state, inter_state, consts, globals, env); - } - ReactiveTerminal::If { - test, - consequent, - alternate, - .. - } => { - promote_interposed_place(test, state, inter_state, consts, env); - promote_interposed_block(consequent, state, inter_state, consts, globals, env); - if let Some(alt) = alternate { - promote_interposed_block(alt, state, inter_state, consts, globals, env); - } - } - ReactiveTerminal::Switch { test, cases, .. } => { - promote_interposed_place(test, state, inter_state, consts, env); - for case in cases { - if let Some(t) = &case.test { - promote_interposed_place(t, state, inter_state, consts, env); - } - if let Some(block) = &case.block { - promote_interposed_block(block, state, inter_state, consts, globals, env); - } - } - } - ReactiveTerminal::Label { block, .. } => { - promote_interposed_block(block, state, inter_state, consts, globals, env); - } - ReactiveTerminal::Try { - block, - handler_binding, - handler, - .. - } => { - promote_interposed_block(block, state, inter_state, consts, globals, env); - if let Some(binding) = handler_binding { - promote_interposed_place(binding, state, inter_state, consts, env); - } - promote_interposed_block(handler, state, inter_state, consts, globals, env); - } - } -} - -// ============================================================================= -// Phase 4: PromoteAllInstancesOfPromotedTemporaries -// ============================================================================= - -fn promote_all_instances_params(func: &ReactiveFunction, state: &mut State, env: &mut Environment) { - for param in &func.params { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - let identifier = &env.identifiers[place.identifier.0 as usize]; - if identifier.name.is_none() && state.promoted.contains(&identifier.declaration_id) { - promote_identifier(place.identifier, state, env); - } - } -} - -fn promote_all_instances_block(block: &ReactiveBlock, state: &mut State, env: &mut Environment) { - for stmt in block { - match stmt { - ReactiveStatement::Instruction(instr) => { - promote_all_instances_instruction(instr, state, env); - } - ReactiveStatement::Scope(scope) => { - promote_all_instances_block(&scope.instructions, state, env); - promote_all_instances_scope_identifiers(scope.scope, state, env); - } - ReactiveStatement::PrunedScope(scope) => { - promote_all_instances_block(&scope.instructions, state, env); - promote_all_instances_scope_identifiers(scope.scope, state, env); - } - ReactiveStatement::Terminal(terminal) => { - promote_all_instances_terminal(terminal, state, env); - } - } - } -} - -fn promote_all_instances_scope_identifiers( - scope_id: ScopeId, - state: &mut State, - env: &mut Environment, -) { - let scope_data = &env.scopes[scope_id.0 as usize]; - - // Collect identifiers to promote - let decl_ids: Vec<IdentifierId> = scope_data - .declarations - .iter() - .map(|(_, d)| d.identifier) - .collect(); - let dep_ids: Vec<IdentifierId> = scope_data - .dependencies - .iter() - .map(|d| d.identifier) - .collect(); - let reassign_ids: Vec<IdentifierId> = scope_data.reassignments.clone(); - - for id in decl_ids { - let identifier = &env.identifiers[id.0 as usize]; - if identifier.name.is_none() && state.promoted.contains(&identifier.declaration_id) { - promote_identifier(id, state, env); - } - } - for id in dep_ids { - let identifier = &env.identifiers[id.0 as usize]; - if identifier.name.is_none() && state.promoted.contains(&identifier.declaration_id) { - promote_identifier(id, state, env); - } - } - for id in reassign_ids { - let identifier = &env.identifiers[id.0 as usize]; - if identifier.name.is_none() && state.promoted.contains(&identifier.declaration_id) { - promote_identifier(id, state, env); - } - } -} - -fn promote_all_instances_place(place: &Place, state: &mut State, env: &mut Environment) { - let identifier = &env.identifiers[place.identifier.0 as usize]; - if identifier.name.is_none() && state.promoted.contains(&identifier.declaration_id) { - promote_identifier(place.identifier, state, env); - } -} - -fn promote_all_instances_instruction( - instr: &ReactiveInstruction, - state: &mut State, - env: &mut Environment, -) { - if let Some(lvalue) = &instr.lvalue { - promote_all_instances_place(lvalue, state, env); - } - promote_all_instances_value(&instr.value, state, env); -} - -fn promote_all_instances_value(value: &ReactiveValue, state: &mut State, env: &mut Environment) { - match value { - ReactiveValue::Instruction(iv) => { - for place in react_compiler_hir::visitors::each_instruction_value_operand(iv, env) { - promote_all_instances_place(&place, state, env); - } - // Visit inner functions - match iv { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - let func_id = lowered_func.func; - let inner_func = &env.functions[func_id.0 as usize]; - let param_ids: Vec<IdentifierId> = inner_func - .params - .iter() - .map(|p| match p { - ParamPattern::Place(p) => p.identifier, - ParamPattern::Spread(s) => s.place.identifier, - }) - .collect(); - for id in param_ids { - let identifier = &env.identifiers[id.0 as usize]; - if identifier.name.is_none() - && state.promoted.contains(&identifier.declaration_id) - { - promote_identifier(id, state, env); - } - } - } - _ => {} - } - } - ReactiveValue::SequenceExpression { - instructions, - value: inner, - .. - } => { - for instr in instructions { - promote_all_instances_instruction(instr, state, env); - } - promote_all_instances_value(inner, state, env); - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - .. - } => { - promote_all_instances_value(test, state, env); - promote_all_instances_value(consequent, state, env); - promote_all_instances_value(alternate, state, env); - } - ReactiveValue::LogicalExpression { left, right, .. } => { - promote_all_instances_value(left, state, env); - promote_all_instances_value(right, state, env); - } - ReactiveValue::OptionalExpression { value: inner, .. } => { - promote_all_instances_value(inner, state, env); - } - } -} - -fn promote_all_instances_terminal( - stmt: &ReactiveTerminalStatement, - state: &mut State, - env: &mut Environment, -) { - match &stmt.terminal { - ReactiveTerminal::Break { .. } | ReactiveTerminal::Continue { .. } => {} - ReactiveTerminal::Return { value, .. } | ReactiveTerminal::Throw { value, .. } => { - promote_all_instances_place(value, state, env); - } - ReactiveTerminal::For { - init, - test, - update, - loop_block, - .. - } => { - promote_all_instances_value(init, state, env); - promote_all_instances_value(test, state, env); - promote_all_instances_block(loop_block, state, env); - if let Some(update) = update { - promote_all_instances_value(update, state, env); - } - } - ReactiveTerminal::ForOf { - init, - test, - loop_block, - .. - } => { - promote_all_instances_value(init, state, env); - promote_all_instances_value(test, state, env); - promote_all_instances_block(loop_block, state, env); - } - ReactiveTerminal::ForIn { - init, loop_block, .. - } => { - promote_all_instances_value(init, state, env); - promote_all_instances_block(loop_block, state, env); - } - ReactiveTerminal::DoWhile { - loop_block, test, .. - } => { - promote_all_instances_block(loop_block, state, env); - promote_all_instances_value(test, state, env); - } - ReactiveTerminal::While { - test, loop_block, .. - } => { - promote_all_instances_value(test, state, env); - promote_all_instances_block(loop_block, state, env); - } - ReactiveTerminal::If { - test, - consequent, - alternate, - .. - } => { - promote_all_instances_place(test, state, env); - promote_all_instances_block(consequent, state, env); - if let Some(alt) = alternate { - promote_all_instances_block(alt, state, env); - } - } - ReactiveTerminal::Switch { test, cases, .. } => { - promote_all_instances_place(test, state, env); - for case in cases { - if let Some(t) = &case.test { - promote_all_instances_place(t, state, env); - } - if let Some(block) = &case.block { - promote_all_instances_block(block, state, env); - } - } - } - ReactiveTerminal::Label { block, .. } => { - promote_all_instances_block(block, state, env); - } - ReactiveTerminal::Try { - block, - handler_binding, - handler, - .. - } => { - promote_all_instances_block(block, state, env); - if let Some(binding) = handler_binding { - promote_all_instances_place(binding, state, env); - } - promote_all_instances_block(handler, state, env); - } - } -} - -// ============================================================================= -// Helpers -// ============================================================================= - -fn promote_identifier(identifier_id: IdentifierId, state: &mut State, env: &mut Environment) { - let identifier = &env.identifiers[identifier_id.0 as usize]; - assert!( - identifier.name.is_none(), - "promoteTemporary: Expected to be called only for temporary variables" - ); - let decl_id = identifier.declaration_id; - if state.tags.contains(&decl_id) { - // JSX tag temporary: use capitalized name - env.identifiers[identifier_id.0 as usize].name = - Some(IdentifierName::Promoted(format!("#T{}", decl_id.0))); - } else { - env.identifiers[identifier_id.0 as usize].name = - Some(IdentifierName::Promoted(format!("#t{}", decl_id.0))); - } - state.promoted.insert(decl_id); -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/propagate_early_returns.rs b/compiler/crates/react_compiler_reactive_scopes/src/propagate_early_returns.rs deleted file mode 100644 index 235fb8c1da79..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/propagate_early_returns.rs +++ /dev/null @@ -1,364 +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. - -//! PropagateEarlyReturns — ensures reactive blocks honor early return semantics. -//! -//! When a scope contains an early return, creates a sentinel-based check so that -//! cached scopes can properly replay the early return behavior. -//! -//! Corresponds to `src/ReactiveScopes/PropagateEarlyReturns.ts`. - -use react_compiler_hir::{ - BlockId, Effect, EvaluationOrder, IdentifierId, IdentifierName, InstructionKind, - InstructionValue, LValue, NonLocalBinding, Place, PlaceOrSpread, PrimitiveValue, - PropertyLiteral, ReactiveFunction, ReactiveInstruction, ReactiveLabel, ReactiveScopeBlock, - ReactiveScopeDeclaration, ReactiveScopeEarlyReturn, ReactiveStatement, ReactiveTerminal, - ReactiveTerminalStatement, ReactiveTerminalTargetKind, ReactiveValue, environment::Environment, -}; - -use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function}; - -/// The sentinel string used to detect early returns. -/// TS: `EARLY_RETURN_SENTINEL` from CodegenReactiveFunction. -const EARLY_RETURN_SENTINEL: &str = "react.early_return_sentinel"; - -// ============================================================================= -// Public entry point -// ============================================================================= - -/// Propagate early return semantics through reactive scopes. -/// TS: `propagateEarlyReturns` -pub fn propagate_early_returns(func: &mut ReactiveFunction, env: &mut Environment) { - let mut transform = Transform { env }; - let mut state = State { - within_reactive_scope: false, - early_return_value: None, - }; - // The TS version doesn't produce errors from this pass, so we ignore the Result. - let _ = transform_reactive_function(func, &mut transform, &mut state); -} - -// ============================================================================= -// State -// ============================================================================= - -#[derive(Debug, Clone)] -struct EarlyReturnInfo { - value: IdentifierId, - loc: Option<react_compiler_diagnostics::SourceLocation>, - label: BlockId, -} - -struct State { - within_reactive_scope: bool, - early_return_value: Option<EarlyReturnInfo>, -} - -// ============================================================================= -// Transform implementation (ReactiveFunctionTransform) -// ============================================================================= - -/// TS: `class Transform extends ReactiveFunctionTransform<State>` -struct Transform<'a> { - env: &'a mut Environment, -} - -impl<'a> ReactiveFunctionTransform for Transform<'a> { - type State = State; - - fn env(&self) -> &Environment { - self.env - } - - /// TS: `override visitScope` - fn visit_scope( - &mut self, - scope_block: &mut ReactiveScopeBlock, - parent_state: &mut State, - ) -> Result<(), react_compiler_diagnostics::CompilerError> { - let scope_id = scope_block.scope; - - // Exit early if an earlier pass has already created an early return - if self.env.scopes[scope_id.0 as usize] - .early_return_value - .is_some() - { - return Ok(()); - } - - let mut inner_state = State { - within_reactive_scope: true, - early_return_value: parent_state.early_return_value.clone(), - }; - self.traverse_scope(scope_block, &mut inner_state)?; - - if let Some(early_return_value) = inner_state.early_return_value { - if !parent_state.within_reactive_scope { - // This is the outermost scope wrapping an early return - apply_early_return_to_scope(scope_block, self.env, &early_return_value); - } else { - // Not outermost — bubble up - parent_state.early_return_value = Some(early_return_value); - } - } - - Ok(()) - } - - /// TS: `override transformTerminal` - fn transform_terminal( - &mut self, - stmt: &mut ReactiveTerminalStatement, - state: &mut State, - ) -> Result<Transformed<ReactiveStatement>, react_compiler_diagnostics::CompilerError> { - if state.within_reactive_scope { - if let ReactiveTerminal::Return { value, .. } = &stmt.terminal { - let loc = value.loc; - - let early_return_value = if let Some(ref existing) = state.early_return_value { - existing.clone() - } else { - // Create a new early return identifier - let identifier_id = create_temporary_place_id(self.env, loc); - promote_temporary(self.env, identifier_id); - let label = self.env.next_block_id(); - EarlyReturnInfo { - value: identifier_id, - loc, - label, - } - }; - - state.early_return_value = Some(early_return_value.clone()); - - let return_value = value.clone(); - - return Ok(Transformed::ReplaceMany(vec![ - // StoreLocal: reassign the early return value - ReactiveStatement::Instruction(ReactiveInstruction { - id: EvaluationOrder(0), - lvalue: None, - value: ReactiveValue::Instruction(InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Reassign, - place: Place { - identifier: early_return_value.value, - effect: Effect::Capture, - reactive: true, - loc, - }, - }, - value: return_value, - type_annotation: None, - loc, - }), - effects: None, - loc, - }), - // Break to the label - ReactiveStatement::Terminal(ReactiveTerminalStatement { - terminal: ReactiveTerminal::Break { - target: early_return_value.label, - id: EvaluationOrder(0), - target_kind: ReactiveTerminalTargetKind::Labeled, - loc, - }, - label: None, - }), - ])); - } - } - - // Default: traverse into the terminal's sub-blocks - self.visit_terminal(stmt, state)?; - Ok(Transformed::Keep) - } -} - -// ============================================================================= -// Apply early return transformation to the outermost scope -// ============================================================================= - -fn apply_early_return_to_scope( - scope_block: &mut ReactiveScopeBlock, - env: &mut Environment, - early_return: &EarlyReturnInfo, -) { - let scope_id = scope_block.scope; - let loc = early_return.loc; - - // Set early return value on the scope - env.scopes[scope_id.0 as usize].early_return_value = Some(ReactiveScopeEarlyReturn { - value: early_return.value, - loc: early_return.loc, - label: early_return.label, - }); - - // Add the early return identifier as a scope declaration - env.scopes[scope_id.0 as usize].declarations.push(( - early_return.value, - ReactiveScopeDeclaration { - identifier: early_return.value, - scope: scope_id, - }, - )); - - // Create temporary places for the sentinel initialization - let sentinel_temp = create_temporary_place_id(env, loc); - let symbol_temp = create_temporary_place_id(env, loc); - let for_temp = create_temporary_place_id(env, loc); - let arg_temp = create_temporary_place_id(env, loc); - - let original_instructions = std::mem::take(&mut scope_block.instructions); - - scope_block.instructions = vec![ - // LoadGlobal Symbol - ReactiveStatement::Instruction(ReactiveInstruction { - id: EvaluationOrder(0), - lvalue: Some(Place { - identifier: symbol_temp, - effect: Effect::Unknown, - reactive: false, - loc: None, // GeneratedSource - }), - value: ReactiveValue::Instruction(InstructionValue::LoadGlobal { - binding: NonLocalBinding::Global { - name: "Symbol".to_string(), - }, - loc, - }), - effects: None, - loc, - }), - // PropertyLoad Symbol.for - ReactiveStatement::Instruction(ReactiveInstruction { - id: EvaluationOrder(0), - lvalue: Some(Place { - identifier: for_temp, - effect: Effect::Unknown, - reactive: false, - loc: None, // GeneratedSource - }), - value: ReactiveValue::Instruction(InstructionValue::PropertyLoad { - object: Place { - identifier: symbol_temp, - effect: Effect::Unknown, - reactive: false, - loc: None, // GeneratedSource - }, - property: PropertyLiteral::String("for".to_string()), - loc, - }), - effects: None, - loc, - }), - // Primitive: the sentinel string - ReactiveStatement::Instruction(ReactiveInstruction { - id: EvaluationOrder(0), - lvalue: Some(Place { - identifier: arg_temp, - effect: Effect::Unknown, - reactive: false, - loc: None, // GeneratedSource - }), - value: ReactiveValue::Instruction(InstructionValue::Primitive { - value: PrimitiveValue::String(EARLY_RETURN_SENTINEL.to_string()), - loc, - }), - effects: None, - loc, - }), - // MethodCall: Symbol.for("react.early_return_sentinel") - ReactiveStatement::Instruction(ReactiveInstruction { - id: EvaluationOrder(0), - lvalue: Some(Place { - identifier: sentinel_temp, - effect: Effect::Unknown, - reactive: false, - loc: None, // GeneratedSource - }), - value: ReactiveValue::Instruction(InstructionValue::MethodCall { - receiver: Place { - identifier: symbol_temp, - effect: Effect::Unknown, - reactive: false, - loc: None, // GeneratedSource - }, - property: Place { - identifier: for_temp, - effect: Effect::Unknown, - reactive: false, - loc: None, // GeneratedSource - }, - args: vec![PlaceOrSpread::Place(Place { - identifier: arg_temp, - effect: Effect::Unknown, - reactive: false, - loc: None, // GeneratedSource - })], - loc, - }), - effects: None, - loc, - }), - // StoreLocal: let earlyReturnValue = sentinel - ReactiveStatement::Instruction(ReactiveInstruction { - id: EvaluationOrder(0), - lvalue: None, - value: ReactiveValue::Instruction(InstructionValue::StoreLocal { - lvalue: LValue { - kind: InstructionKind::Let, - place: Place { - identifier: early_return.value, - effect: Effect::ConditionallyMutate, - reactive: true, - loc, - }, - }, - value: Place { - identifier: sentinel_temp, - effect: Effect::Unknown, - reactive: false, - loc: None, // GeneratedSource - }, - type_annotation: None, - loc, - }), - effects: None, - loc, - }), - // Label terminal wrapping the original instructions - ReactiveStatement::Terminal(ReactiveTerminalStatement { - label: Some(ReactiveLabel { - id: early_return.label, - implicit: false, - }), - terminal: ReactiveTerminal::Label { - block: original_instructions, - id: EvaluationOrder(0), - loc: None, // GeneratedSource - }, - }), - ]; -} - -// ============================================================================= -// Helper: create a temporary place identifier -// ============================================================================= - -fn create_temporary_place_id( - env: &mut Environment, - loc: Option<react_compiler_diagnostics::SourceLocation>, -) -> IdentifierId { - let id = env.next_identifier_id(); - env.identifiers[id.0 as usize].loc = loc; - id -} - -fn promote_temporary(env: &mut Environment, identifier_id: IdentifierId) { - let decl_id = env.identifiers[identifier_id.0 as usize].declaration_id; - env.identifiers[identifier_id.0 as usize].name = - Some(IdentifierName::Promoted(format!("#t{}", decl_id.0))); -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/prune_always_invalidating_scopes.rs b/compiler/crates/react_compiler_reactive_scopes/src/prune_always_invalidating_scopes.rs deleted file mode 100644 index 5039a910aaca..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/prune_always_invalidating_scopes.rs +++ /dev/null @@ -1,150 +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. - -//! PruneAlwaysInvalidatingScopes -//! -//! Some instructions will *always* produce a new value, and unless memoized will *always* -//! invalidate downstream reactive scopes. This pass finds such values and prunes downstream -//! memoization. -//! -//! Corresponds to `src/ReactiveScopes/PruneAlwaysInvalidatingScopes.ts`. - -use std::collections::HashSet; - -use react_compiler_hir::{ - IdentifierId, InstructionValue, PrunedReactiveScopeBlock, ReactiveFunction, - ReactiveInstruction, ReactiveScopeBlock, ReactiveStatement, ReactiveValue, - environment::Environment, -}; - -use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function}; - -/// Prunes scopes that always invalidate because they depend on unmemoized -/// always-invalidating values. -/// TS: `pruneAlwaysInvalidatingScopes` -pub fn prune_always_invalidating_scopes( - func: &mut ReactiveFunction, - env: &Environment, -) -> Result<(), react_compiler_diagnostics::CompilerError> { - let mut transform = Transform { - env, - always_invalidating_values: HashSet::new(), - unmemoized_values: HashSet::new(), - }; - let mut state = false; // withinScope - transform_reactive_function(func, &mut transform, &mut state) -} - -struct Transform<'a> { - env: &'a Environment, - always_invalidating_values: HashSet<IdentifierId>, - unmemoized_values: HashSet<IdentifierId>, -} - -impl<'a> ReactiveFunctionTransform for Transform<'a> { - type State = bool; // withinScope - - fn env(&self) -> &Environment { - self.env - } - - fn transform_instruction( - &mut self, - instruction: &mut ReactiveInstruction, - within_scope: &mut bool, - ) -> Result<Transformed<ReactiveStatement>, react_compiler_diagnostics::CompilerError> { - self.visit_instruction(instruction, within_scope)?; - - let lvalue = &instruction.lvalue; - match &instruction.value { - ReactiveValue::Instruction( - InstructionValue::ArrayExpression { .. } - | InstructionValue::ObjectExpression { .. } - | InstructionValue::JsxExpression { .. } - | InstructionValue::JsxFragment { .. } - | InstructionValue::NewExpression { .. }, - ) => { - if let Some(lv) = lvalue { - self.always_invalidating_values.insert(lv.identifier); - if !*within_scope { - self.unmemoized_values.insert(lv.identifier); - } - } - } - ReactiveValue::Instruction(InstructionValue::StoreLocal { - value: store_value, - lvalue: store_lvalue, - .. - }) => { - if self - .always_invalidating_values - .contains(&store_value.identifier) - { - self.always_invalidating_values - .insert(store_lvalue.place.identifier); - } - if self.unmemoized_values.contains(&store_value.identifier) { - self.unmemoized_values.insert(store_lvalue.place.identifier); - } - } - ReactiveValue::Instruction(InstructionValue::LoadLocal { place, .. }) => { - if let Some(lv) = lvalue { - if self.always_invalidating_values.contains(&place.identifier) { - self.always_invalidating_values.insert(lv.identifier); - } - if self.unmemoized_values.contains(&place.identifier) { - self.unmemoized_values.insert(lv.identifier); - } - } - } - _ => {} - } - Ok(Transformed::Keep) - } - - fn transform_scope( - &mut self, - scope: &mut ReactiveScopeBlock, - _within_scope: &mut bool, - ) -> Result<Transformed<ReactiveStatement>, react_compiler_diagnostics::CompilerError> { - let mut within_scope = true; - self.visit_scope(scope, &mut within_scope)?; - - let scope_id = scope.scope; - let scope_data = &self.env.scopes[scope_id.0 as usize]; - - for dep in &scope_data.dependencies { - if self.unmemoized_values.contains(&dep.identifier) { - // This scope depends on an always-invalidating value, prune it - // Propagate always-invalidating and unmemoized to declarations/reassignments - let decl_ids: Vec<IdentifierId> = scope_data - .declarations - .iter() - .map(|(_, decl)| decl.identifier) - .collect(); - let reassign_ids: Vec<IdentifierId> = scope_data.reassignments.clone(); - - for id in &decl_ids { - if self.always_invalidating_values.contains(id) { - self.unmemoized_values.insert(*id); - } - } - for id in &reassign_ids { - if self.always_invalidating_values.contains(id) { - self.unmemoized_values.insert(*id); - } - } - - return Ok(Transformed::Replace(ReactiveStatement::PrunedScope( - PrunedReactiveScopeBlock { - scope: scope.scope, - instructions: std::mem::take(&mut scope.instructions), - }, - ))); - } - } - Ok(Transformed::Keep) - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/prune_hoisted_contexts.rs b/compiler/crates/react_compiler_reactive_scopes/src/prune_hoisted_contexts.rs deleted file mode 100644 index 83687410885e..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/prune_hoisted_contexts.rs +++ /dev/null @@ -1,210 +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. - -//! PruneHoistedContexts — removes hoisted context variable declarations -//! and transforms references to their original instruction kinds. -//! -//! Corresponds to `src/ReactiveScopes/PruneHoistedContexts.ts`. - -use std::collections::HashMap; - -use react_compiler_diagnostics::{CompilerError, CompilerErrorDetail, ErrorCategory}; -use react_compiler_hir::{ - EvaluationOrder, IdentifierId, InstructionKind, InstructionValue, Place, ReactiveFunction, - ReactiveInstruction, ReactiveScopeBlock, ReactiveStatement, ReactiveValue, - environment::Environment, -}; - -use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function}; - -// ============================================================================= -// Public entry point -// ============================================================================= - -/// Prunes DeclareContexts lowered for HoistedConsts and transforms any -/// references back to their original instruction kind. -/// TS: `pruneHoistedContexts` -pub fn prune_hoisted_contexts( - func: &mut ReactiveFunction, - env: &Environment, -) -> Result<(), CompilerError> { - let mut transform = Transform { env }; - let mut state = VisitorState { - active_scopes: Vec::new(), - uninitialized: HashMap::new(), - }; - transform_reactive_function(func, &mut transform, &mut state) -} - -// ============================================================================= -// State -// ============================================================================= - -#[derive(Debug, Clone)] -enum UninitializedKind { - UnknownKind, - Func { definition: Option<IdentifierId> }, -} - -struct VisitorState { - active_scopes: Vec<std::collections::HashSet<IdentifierId>>, - uninitialized: HashMap<IdentifierId, UninitializedKind>, -} - -impl VisitorState { - fn find_in_active_scopes(&self, id: IdentifierId) -> bool { - for scope in &self.active_scopes { - if scope.contains(&id) { - return true; - } - } - false - } -} - -struct Transform<'a> { - env: &'a Environment, -} - -impl<'a> ReactiveFunctionTransform for Transform<'a> { - type State = VisitorState; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_scope( - &mut self, - scope: &mut ReactiveScopeBlock, - state: &mut VisitorState, - ) -> Result<(), CompilerError> { - let scope_data = &self.env.scopes[scope.scope.0 as usize]; - let decl_ids: std::collections::HashSet<IdentifierId> = - scope_data.declarations.iter().map(|(id, _)| *id).collect(); - - // Add declared but not initialized variables - for (_, decl) in &scope_data.declarations { - state - .uninitialized - .insert(decl.identifier, UninitializedKind::UnknownKind); - } - - state.active_scopes.push(decl_ids); - self.traverse_scope(scope, state)?; - state.active_scopes.pop(); - - // Clean up uninitialized after scope - let scope_data = &self.env.scopes[scope.scope.0 as usize]; - for (_, decl) in &scope_data.declarations { - state.uninitialized.remove(&decl.identifier); - } - Ok(()) - } - - fn visit_place( - &mut self, - _id: EvaluationOrder, - place: &Place, - state: &mut VisitorState, - ) -> Result<(), CompilerError> { - if let Some(kind) = state.uninitialized.get(&place.identifier) { - if let UninitializedKind::Func { definition } = kind { - if *definition != Some(place.identifier) { - let mut err = CompilerError::new(); - err.push_error_detail( - CompilerErrorDetail::new( - ErrorCategory::Todo, - "[PruneHoistedContexts] Rewrite hoisted function references" - .to_string(), - ) - .with_loc(place.loc), - ); - return Err(err); - } - } - } - Ok(()) - } - - fn transform_instruction( - &mut self, - instruction: &mut ReactiveInstruction, - state: &mut VisitorState, - ) -> Result<Transformed<ReactiveStatement>, CompilerError> { - // Remove hoisted declarations to preserve TDZ - if let ReactiveValue::Instruction(InstructionValue::DeclareContext { lvalue, .. }) = - &instruction.value - { - let maybe_non_hoisted = convert_hoisted_lvalue_kind(lvalue.kind); - if let Some(non_hoisted) = maybe_non_hoisted { - if non_hoisted == InstructionKind::Function - && state.uninitialized.contains_key(&lvalue.place.identifier) - { - state.uninitialized.insert( - lvalue.place.identifier, - UninitializedKind::Func { definition: None }, - ); - } - return Ok(Transformed::Remove); - } - } - - if let ReactiveValue::Instruction(InstructionValue::StoreContext { lvalue, .. }) = - &mut instruction.value - { - if lvalue.kind != InstructionKind::Reassign { - let lvalue_id = lvalue.place.identifier; - let is_declared_by_scope = state.find_in_active_scopes(lvalue_id); - if is_declared_by_scope { - if lvalue.kind == InstructionKind::Let || lvalue.kind == InstructionKind::Const - { - lvalue.kind = InstructionKind::Reassign; - } else if lvalue.kind == InstructionKind::Function { - if let Some(kind) = state.uninitialized.get(&lvalue_id) { - if !matches!(kind, UninitializedKind::Func { .. }) { - let mut err = CompilerError::new(); - err.push_error_detail( - CompilerErrorDetail::new( - ErrorCategory::Invariant, - "[PruneHoistedContexts] Unexpected hoisted function" - .to_string(), - ) - .with_loc(instruction.loc), - ); - return Err(err); - } - // References to hoisted functions are now "safe" as - // variable assignments have finished. - state.uninitialized.remove(&lvalue_id); - } - } else { - let mut err = CompilerError::new(); - err.push_error_detail( - CompilerErrorDetail::new( - ErrorCategory::Todo, - "[PruneHoistedContexts] Unexpected kind".to_string(), - ) - .with_loc(instruction.loc), - ); - return Err(err); - } - } - } - } - - self.visit_instruction(instruction, state)?; - Ok(Transformed::Keep) - } -} - -/// Corresponds to TS `convertHoistedLValueKind` — returns None for non-hoisted kinds. -fn convert_hoisted_lvalue_kind(kind: InstructionKind) -> Option<InstructionKind> { - match kind { - InstructionKind::HoistedLet => Some(InstructionKind::Let), - InstructionKind::HoistedConst => Some(InstructionKind::Const), - InstructionKind::HoistedFunction => Some(InstructionKind::Function), - _ => None, - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/prune_non_escaping_scopes.rs b/compiler/crates/react_compiler_reactive_scopes/src/prune_non_escaping_scopes.rs deleted file mode 100644 index 598088945489..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/prune_non_escaping_scopes.rs +++ /dev/null @@ -1,1329 +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. - -//! PruneNonEscapingScopes — prunes reactive scopes that are not necessary -//! to bound downstream computation. -//! -//! Corresponds to `src/ReactiveScopes/PruneNonEscapingScopes.ts`. - -use std::collections::HashMap; -use std::collections::HashSet; - -use indexmap::IndexSet; -use react_compiler_hir::ArrayPatternElement; -use react_compiler_hir::DeclarationId; -use react_compiler_hir::Effect; -use react_compiler_hir::EvaluationOrder; -use react_compiler_hir::IdentifierId; -use react_compiler_hir::InstructionKind; -use react_compiler_hir::InstructionValue; -use react_compiler_hir::JsxAttribute; -use react_compiler_hir::JsxTag; -use react_compiler_hir::ObjectPropertyOrSpread; -use react_compiler_hir::Pattern; -use react_compiler_hir::Place; -use react_compiler_hir::PlaceOrSpread; -use react_compiler_hir::ReactiveFunction; -use react_compiler_hir::ReactiveInstruction; -use react_compiler_hir::ReactiveScopeBlock; -use react_compiler_hir::ReactiveStatement; -use react_compiler_hir::ReactiveTerminal; -use react_compiler_hir::ReactiveTerminalStatement; -use react_compiler_hir::ReactiveValue; -use react_compiler_hir::ScopeId; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors::each_instruction_value_operand; - -use crate::visitors::ReactiveFunctionTransform; -use crate::visitors::ReactiveFunctionVisitor; -use crate::visitors::Transformed; -use crate::visitors::transform_reactive_function; -use crate::visitors::visit_reactive_function; - -// ============================================================================= -// Public entry point -// ============================================================================= - -/// Prunes reactive scopes whose outputs don't escape. -/// TS: `pruneNonEscapingScopes` -pub fn prune_non_escaping_scopes( - func: &mut ReactiveFunction, - env: &mut Environment, -) -> Result<(), react_compiler_diagnostics::CompilerError> { - // First build up a map of which instructions are involved in creating which values, - // and which values are returned. - let mut state = CollectState::new(); - for param in &func.params { - let place = match param { - react_compiler_hir::ParamPattern::Place(p) => p, - react_compiler_hir::ParamPattern::Spread(s) => &s.place, - }; - let identifier = &env.identifiers[place.identifier.0 as usize]; - state.declare(identifier.declaration_id); - } - let visitor = CollectDependenciesVisitor::new(env); - let mut visitor_state = (state, Vec::<ScopeId>::new()); - visit_reactive_function(func, &visitor, &mut visitor_state); - let (state, _) = visitor_state; - - // Then walk outward from the returned values and find all captured operands. - let memoized = compute_memoized_identifiers(&state); - - // Prune scopes that do not declare/reassign any escaping values - let mut transform = PruneScopesTransform { - env, - pruned_scopes: HashSet::new(), - reassignments: HashMap::new(), - }; - let mut memoized_state = memoized; - transform_reactive_function(func, &mut transform, &mut memoized_state) -} - -// ============================================================================= -// MemoizationLevel -// ============================================================================= - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum MemoizationLevel { - /// The value should be memoized if it escapes - Memoized, - /// Values that are memoized if their dependencies are memoized - Conditional, - /// Values that cannot be compared with Object.is, but which by default don't need to be memoized - Unmemoized, - /// The value will never be memoized: used for values that can be cheaply compared w Object.is - Never, -} - -/// Given an identifier that appears as an lvalue multiple times with different memoization levels, -/// determines the final memoization level. -fn join_aliases(kind1: MemoizationLevel, kind2: MemoizationLevel) -> MemoizationLevel { - if kind1 == MemoizationLevel::Memoized || kind2 == MemoizationLevel::Memoized { - MemoizationLevel::Memoized - } else if kind1 == MemoizationLevel::Conditional || kind2 == MemoizationLevel::Conditional { - MemoizationLevel::Conditional - } else if kind1 == MemoizationLevel::Unmemoized || kind2 == MemoizationLevel::Unmemoized { - MemoizationLevel::Unmemoized - } else { - MemoizationLevel::Never - } -} - -// ============================================================================= -// Graph nodes -// ============================================================================= - -/// A node in the graph describing the memoization level of a given identifier -/// as well as its dependencies and scopes. -struct IdentifierNode { - level: MemoizationLevel, - memoized: bool, - dependencies: IndexSet<DeclarationId>, - scopes: IndexSet<ScopeId>, - seen: bool, -} - -/// A scope node describing its dependencies. -struct ScopeNode { - dependencies: Vec<DeclarationId>, - seen: bool, -} - -// ============================================================================= -// CollectState (TS: State class) -// ============================================================================= - -struct CollectState { - /// Maps lvalues for LoadLocal to the identifier being loaded, to resolve indirections. - definitions: HashMap<DeclarationId, DeclarationId>, - identifiers: HashMap<DeclarationId, IdentifierNode>, - scopes: HashMap<ScopeId, ScopeNode>, - escaping_values: IndexSet<DeclarationId>, -} - -impl CollectState { - fn new() -> Self { - CollectState { - definitions: HashMap::new(), - identifiers: HashMap::new(), - scopes: HashMap::new(), - escaping_values: IndexSet::new(), - } - } - - /// Declare a new identifier, used for function id and params. - fn declare(&mut self, id: DeclarationId) { - self.identifiers.insert( - id, - IdentifierNode { - level: MemoizationLevel::Never, - memoized: false, - dependencies: IndexSet::new(), - scopes: IndexSet::new(), - seen: false, - }, - ); - } - - /// Associates the identifier with its scope, if there is one and it is active for - /// the given instruction id. - fn visit_operand( - &mut self, - env: &Environment, - id: EvaluationOrder, - place: &Place, - identifier: DeclarationId, - ) { - if let Some(scope_id) = get_place_scope(env, id, place.identifier) { - let node = self.scopes.entry(scope_id).or_insert_with(|| { - let scope_data = &env.scopes[scope_id.0 as usize]; - let dependencies = scope_data - .dependencies - .iter() - .map(|dep| env.identifiers[dep.identifier.0 as usize].declaration_id) - .collect(); - ScopeNode { - dependencies, - seen: false, - } - }); - // Avoid unused variable warning — we needed the entry to exist - let _ = node; - let identifier_node = self - .identifiers - .get_mut(&identifier) - .expect("Expected identifier to be initialized"); - identifier_node.scopes.insert(scope_id); - } - } - - /// Resolve an identifier through definitions (LoadLocal indirections). - fn resolve(&self, id: DeclarationId) -> DeclarationId { - self.definitions.get(&id).copied().unwrap_or(id) - } -} - -// ============================================================================= -// MemoizationOptions -// ============================================================================= - -struct MemoizationOptions { - memoize_jsx_elements: bool, - force_memoize_primitives: bool, -} - -// ============================================================================= -// LValueMemoization -// ============================================================================= - -struct LValueMemoization { - place_identifier: IdentifierId, - level: MemoizationLevel, -} - -// ============================================================================= -// Helper: get_place_scope -// ============================================================================= - -fn get_place_scope( - env: &Environment, - id: EvaluationOrder, - identifier_id: IdentifierId, -) -> Option<ScopeId> { - let scope_id = env.identifiers[identifier_id.0 as usize].scope?; - if env.scopes[scope_id.0 as usize].range.contains(id) { - Some(scope_id) - } else { - None - } -} - -// ============================================================================= -// Helper: get_function_call_signature (for noAlias check) -// ============================================================================= - -// ============================================================================= -// Helper: compute pattern lvalues -// ============================================================================= - -fn compute_pattern_lvalues(pattern: &Pattern) -> Vec<LValueMemoization> { - let mut lvalues = Vec::new(); - match pattern { - Pattern::Array(array_pattern) => { - for item in &array_pattern.items { - match item { - ArrayPatternElement::Place(place) => { - lvalues.push(LValueMemoization { - place_identifier: place.identifier, - level: MemoizationLevel::Conditional, - }); - } - ArrayPatternElement::Spread(spread) => { - lvalues.push(LValueMemoization { - place_identifier: spread.place.identifier, - level: MemoizationLevel::Memoized, - }); - } - ArrayPatternElement::Hole => {} - } - } - } - Pattern::Object(object_pattern) => { - for property in &object_pattern.properties { - match property { - ObjectPropertyOrSpread::Property(prop) => { - lvalues.push(LValueMemoization { - place_identifier: prop.place.identifier, - level: MemoizationLevel::Conditional, - }); - } - ObjectPropertyOrSpread::Spread(spread) => { - lvalues.push(LValueMemoization { - place_identifier: spread.place.identifier, - level: MemoizationLevel::Memoized, - }); - } - } - } - } - } - lvalues -} - -// ============================================================================= -// CollectDependenciesVisitor -// ============================================================================= - -struct CollectDependenciesVisitor<'a> { - env: &'a Environment, - options: MemoizationOptions, -} - -impl<'a> CollectDependenciesVisitor<'a> { - fn new(env: &'a Environment) -> Self { - CollectDependenciesVisitor { - env, - options: MemoizationOptions { - memoize_jsx_elements: !env.config.enable_forest, - force_memoize_primitives: env.config.enable_forest - || env.enable_preserve_existing_memoization_guarantees, - }, - } - } - - /// Given a value, returns a description of how it should be memoized. - fn compute_memoization_inputs( - &self, - id: EvaluationOrder, - value: &ReactiveValue, - lvalue: Option<IdentifierId>, - state: &mut CollectState, - ) -> (Vec<LValueMemoization>, Vec<(IdentifierId, EvaluationOrder)>) { - match value { - ReactiveValue::ConditionalExpression { - consequent, - alternate, - .. - } => { - let (_, cons_rvalues) = - self.compute_memoization_inputs(id, consequent, None, state); - let (_, alt_rvalues) = self.compute_memoization_inputs(id, alternate, None, state); - let mut rvalues = cons_rvalues; - rvalues.extend(alt_rvalues); - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }] - } else { - vec![] - }; - (lvalues, rvalues) - } - ReactiveValue::LogicalExpression { left, right, .. } => { - let (_, left_rvalues) = self.compute_memoization_inputs(id, left, None, state); - let (_, right_rvalues) = self.compute_memoization_inputs(id, right, None, state); - let mut rvalues = left_rvalues; - rvalues.extend(right_rvalues); - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }] - } else { - vec![] - }; - (lvalues, rvalues) - } - ReactiveValue::SequenceExpression { - instructions, - value: inner, - .. - } => { - for instr in instructions { - self.visit_value_for_memoization( - instr.id, - &instr.value, - instr.lvalue.as_ref().map(|lv| lv.identifier), - state, - ); - } - let (_, rvalues) = self.compute_memoization_inputs(id, inner, None, state); - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }] - } else { - vec![] - }; - (lvalues, rvalues) - } - ReactiveValue::OptionalExpression { value: inner, .. } => { - let (_, rvalues) = self.compute_memoization_inputs(id, inner, None, state); - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }] - } else { - vec![] - }; - (lvalues, rvalues) - } - ReactiveValue::Instruction(instr_value) => { - self.compute_instruction_memoization_inputs(id, instr_value, lvalue) - } - } - } - - /// Compute memoization inputs for an InstructionValue. - fn compute_instruction_memoization_inputs( - &self, - id: EvaluationOrder, - value: &InstructionValue, - lvalue: Option<IdentifierId>, - ) -> (Vec<LValueMemoization>, Vec<(IdentifierId, EvaluationOrder)>) { - let env = self.env; - let options = &self.options; - - match value { - InstructionValue::JsxExpression { - tag, - props, - children, - .. - } => { - let mut rvalues: Vec<(IdentifierId, EvaluationOrder)> = Vec::new(); - if let JsxTag::Place(place) = tag { - rvalues.push((place.identifier, id)); - } - for prop in props { - match prop { - JsxAttribute::Attribute { place, .. } => { - rvalues.push((place.identifier, id)); - } - JsxAttribute::SpreadAttribute { argument, .. } => { - rvalues.push((argument.identifier, id)); - } - } - } - if let Some(children) = children { - for child in children { - rvalues.push((child.identifier, id)); - } - } - let level = if options.memoize_jsx_elements { - MemoizationLevel::Memoized - } else { - MemoizationLevel::Unmemoized - }; - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level, - }] - } else { - vec![] - }; - (lvalues, rvalues) - } - InstructionValue::JsxFragment { children, .. } => { - let level = if options.memoize_jsx_elements { - MemoizationLevel::Memoized - } else { - MemoizationLevel::Unmemoized - }; - let rvalues: Vec<(IdentifierId, EvaluationOrder)> = - children.iter().map(|c| (c.identifier, id)).collect(); - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level, - }] - } else { - vec![] - }; - (lvalues, rvalues) - } - InstructionValue::NextPropertyOf { .. } - | InstructionValue::StartMemoize { .. } - | InstructionValue::FinishMemoize { .. } - | InstructionValue::Debugger { .. } - | InstructionValue::ComputedDelete { .. } - | InstructionValue::PropertyDelete { .. } - | InstructionValue::LoadGlobal { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::TemplateLiteral { .. } - | InstructionValue::Primitive { .. } - | InstructionValue::JSXText { .. } - | InstructionValue::BinaryExpression { .. } - | InstructionValue::UnaryExpression { .. } => { - if options.force_memoize_primitives { - let level = MemoizationLevel::Conditional; - let operands = each_instruction_value_operand(value, env); - let rvalues: Vec<(IdentifierId, EvaluationOrder)> = - operands.iter().map(|p| (p.identifier, id)).collect(); - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level, - }] - } else { - vec![] - }; - (lvalues, rvalues) - } else { - let level = MemoizationLevel::Never; - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level, - }] - } else { - vec![] - }; - (lvalues, vec![]) - } - } - InstructionValue::Await { value: inner, .. } - | InstructionValue::TypeCastExpression { value: inner, .. } => { - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }] - } else { - vec![] - }; - (lvalues, vec![(inner.identifier, id)]) - } - InstructionValue::IteratorNext { - iterator, - collection, - .. - } => { - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }] - } else { - vec![] - }; - ( - lvalues, - vec![(iterator.identifier, id), (collection.identifier, id)], - ) - } - InstructionValue::GetIterator { collection, .. } => { - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }] - } else { - vec![] - }; - (lvalues, vec![(collection.identifier, id)]) - } - InstructionValue::LoadLocal { place, .. } => { - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }] - } else { - vec![] - }; - (lvalues, vec![(place.identifier, id)]) - } - InstructionValue::LoadContext { place, .. } => { - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }] - } else { - vec![] - }; - (lvalues, vec![(place.identifier, id)]) - } - InstructionValue::DeclareContext { - lvalue: decl_lvalue, - .. - } => { - let mut lvalues = vec![LValueMemoization { - place_identifier: decl_lvalue.place.identifier, - level: MemoizationLevel::Memoized, - }]; - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Unmemoized, - }); - } - (lvalues, vec![]) - } - InstructionValue::DeclareLocal { - lvalue: decl_lvalue, - .. - } => { - let mut lvalues = vec![LValueMemoization { - place_identifier: decl_lvalue.place.identifier, - level: MemoizationLevel::Unmemoized, - }]; - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Unmemoized, - }); - } - (lvalues, vec![]) - } - InstructionValue::PrefixUpdate { - lvalue: upd_lvalue, - value: upd_value, - .. - } - | InstructionValue::PostfixUpdate { - lvalue: upd_lvalue, - value: upd_value, - .. - } => { - let mut lvalues = vec![LValueMemoization { - place_identifier: upd_lvalue.identifier, - level: MemoizationLevel::Conditional, - }]; - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }); - } - (lvalues, vec![(upd_value.identifier, id)]) - } - InstructionValue::StoreLocal { - lvalue: store_lvalue, - value: store_value, - .. - } => { - let mut lvalues = vec![LValueMemoization { - place_identifier: store_lvalue.place.identifier, - level: MemoizationLevel::Conditional, - }]; - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }); - } - (lvalues, vec![(store_value.identifier, id)]) - } - InstructionValue::StoreContext { - lvalue: store_lvalue, - value: store_value, - .. - } => { - let mut lvalues = vec![LValueMemoization { - place_identifier: store_lvalue.place.identifier, - level: MemoizationLevel::Memoized, - }]; - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }); - } - (lvalues, vec![(store_value.identifier, id)]) - } - InstructionValue::StoreGlobal { - value: store_value, .. - } => { - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Unmemoized, - }] - } else { - vec![] - }; - (lvalues, vec![(store_value.identifier, id)]) - } - InstructionValue::Destructure { - lvalue: dest_lvalue, - value: dest_value, - .. - } => { - let mut lvalues = Vec::new(); - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }); - } - lvalues.extend(compute_pattern_lvalues(&dest_lvalue.pattern)); - (lvalues, vec![(dest_value.identifier, id)]) - } - InstructionValue::ComputedLoad { object, .. } - | InstructionValue::PropertyLoad { object, .. } => { - let level = MemoizationLevel::Conditional; - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level, - }] - } else { - vec![] - }; - (lvalues, vec![(object.identifier, id)]) - } - InstructionValue::ComputedStore { - object, - value: store_value, - .. - } => { - let mut lvalues = vec![LValueMemoization { - place_identifier: object.identifier, - level: MemoizationLevel::Conditional, - }]; - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Conditional, - }); - } - (lvalues, vec![(store_value.identifier, id)]) - } - InstructionValue::TaggedTemplateExpression { tag, .. } => { - let no_alias = env.has_no_alias_signature(tag.identifier); - let mut lvalues = Vec::new(); - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Memoized, - }); - } - if no_alias { - return (lvalues, vec![]); - } - let operands = each_instruction_value_operand(value, env); - for op in &operands { - if op.effect.is_mutable() { - lvalues.push(LValueMemoization { - place_identifier: op.identifier, - level: MemoizationLevel::Memoized, - }); - } - } - let rvalues: Vec<(IdentifierId, EvaluationOrder)> = - operands.iter().map(|p| (p.identifier, id)).collect(); - (lvalues, rvalues) - } - InstructionValue::CallExpression { callee, .. } => { - let no_alias = env.has_no_alias_signature(callee.identifier); - let mut lvalues = Vec::new(); - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Memoized, - }); - } - if no_alias { - return (lvalues, vec![]); - } - let operands = each_instruction_value_operand(value, env); - for op in &operands { - if op.effect.is_mutable() { - lvalues.push(LValueMemoization { - place_identifier: op.identifier, - level: MemoizationLevel::Memoized, - }); - } - } - let rvalues: Vec<(IdentifierId, EvaluationOrder)> = - operands.iter().map(|p| (p.identifier, id)).collect(); - (lvalues, rvalues) - } - InstructionValue::MethodCall { property, .. } => { - let no_alias = env.has_no_alias_signature(property.identifier); - let mut lvalues = Vec::new(); - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Memoized, - }); - } - if no_alias { - return (lvalues, vec![]); - } - let operands = each_instruction_value_operand(value, env); - for op in &operands { - if op.effect.is_mutable() { - lvalues.push(LValueMemoization { - place_identifier: op.identifier, - level: MemoizationLevel::Memoized, - }); - } - } - let rvalues: Vec<(IdentifierId, EvaluationOrder)> = - operands.iter().map(|p| (p.identifier, id)).collect(); - (lvalues, rvalues) - } - InstructionValue::RegExpLiteral { .. } - | InstructionValue::ArrayExpression { .. } - | InstructionValue::NewExpression { .. } - | InstructionValue::ObjectExpression { .. } - | InstructionValue::PropertyStore { .. } => { - let operands = each_instruction_value_operand(value, env); - let mut lvalues: Vec<LValueMemoization> = operands - .iter() - .filter(|op| op.effect.is_mutable()) - .map(|op| LValueMemoization { - place_identifier: op.identifier, - level: MemoizationLevel::Memoized, - }) - .collect(); - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Memoized, - }); - } - let rvalues: Vec<(IdentifierId, EvaluationOrder)> = - operands.iter().map(|p| (p.identifier, id)).collect(); - (lvalues, rvalues) - } - InstructionValue::ObjectMethod { .. } | InstructionValue::FunctionExpression { .. } => { - // The canonical each_instruction_value_operand already includes context - // (captured variables) for FunctionExpression/ObjectMethod. - let operands = each_instruction_value_operand(value, env); - let mut lvalues: Vec<LValueMemoization> = operands - .iter() - .filter(|op| op.effect.is_mutable()) - .map(|op| LValueMemoization { - place_identifier: op.identifier, - level: MemoizationLevel::Memoized, - }) - .collect(); - if let Some(lv) = lvalue { - lvalues.push(LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Memoized, - }); - } - let rvalues: Vec<(IdentifierId, EvaluationOrder)> = - operands.iter().map(|p| (p.identifier, id)).collect(); - (lvalues, rvalues) - } - InstructionValue::UnsupportedNode { .. } => { - let lvalues = if let Some(lv) = lvalue { - vec![LValueMemoization { - place_identifier: lv, - level: MemoizationLevel::Never, - }] - } else { - vec![] - }; - (lvalues, vec![]) - } - } - } - - fn visit_value_for_memoization( - &self, - id: EvaluationOrder, - value: &ReactiveValue, - lvalue: Option<IdentifierId>, - state: &mut CollectState, - ) { - let env = self.env; - // Determine the level of memoization for this value and the lvalues/rvalues - let (aliasing_lvalues, aliasing_rvalues) = - self.compute_memoization_inputs(id, value, lvalue, state); - - // Associate all the rvalues with the instruction's scope if it has one - // We need to collect rvalue data first to avoid borrow issues - let rvalue_data: Vec<(IdentifierId, DeclarationId)> = aliasing_rvalues - .iter() - .map(|(identifier_id, _)| { - let decl_id = env.identifiers[identifier_id.0 as usize].declaration_id; - let operand_id = state.resolve(decl_id); - (*identifier_id, operand_id) - }) - .collect(); - - for (identifier_id, operand_id) in &rvalue_data { - // Build the Place data needed for get_place_scope - state.visit_operand( - env, - id, - &Place { - identifier: *identifier_id, - effect: Effect::Read, - reactive: false, - loc: None, - }, - *operand_id, - ); - } - - // Add the operands as dependencies of all lvalues - for lv in &aliasing_lvalues { - let lvalue_decl_id = env.identifiers[lv.place_identifier.0 as usize].declaration_id; - let lvalue_id = state.resolve(lvalue_decl_id); - let node = state - .identifiers - .entry(lvalue_id) - .or_insert_with(|| IdentifierNode { - level: MemoizationLevel::Never, - memoized: false, - dependencies: IndexSet::new(), - scopes: IndexSet::new(), - seen: false, - }); - node.level = join_aliases(node.level, lv.level); - for (_, operand_id) in &rvalue_data { - if *operand_id == lvalue_id { - continue; - } - node.dependencies.insert(*operand_id); - } - - state.visit_operand( - env, - id, - &Place { - identifier: lv.place_identifier, - effect: Effect::Read, - reactive: false, - loc: None, - }, - lvalue_id, - ); - } - - // Handle LoadLocal definitions and hook calls - if let ReactiveValue::Instruction(instr_value) = value { - if let InstructionValue::LoadLocal { place, .. } = instr_value { - if let Some(lv_id) = lvalue { - let lv_decl = env.identifiers[lv_id.0 as usize].declaration_id; - let place_decl = env.identifiers[place.identifier.0 as usize].declaration_id; - state.definitions.insert(lv_decl, place_decl); - } - } else if let InstructionValue::CallExpression { callee, args, .. } = instr_value { - if env - .get_hook_kind_for_id(callee.identifier) - .ok() - .flatten() - .is_some() - { - let no_alias = env.has_no_alias_signature(callee.identifier); - if !no_alias { - for arg in args { - let place = match arg { - PlaceOrSpread::Spread(spread) => &spread.place, - PlaceOrSpread::Place(place) => place, - }; - let decl = env.identifiers[place.identifier.0 as usize].declaration_id; - state.escaping_values.insert(decl); - } - } - } - } else if let InstructionValue::MethodCall { property, args, .. } = instr_value { - if env - .get_hook_kind_for_id(property.identifier) - .ok() - .flatten() - .is_some() - { - let no_alias = env.has_no_alias_signature(property.identifier); - if !no_alias { - for arg in args { - let place = match arg { - PlaceOrSpread::Spread(spread) => &spread.place, - PlaceOrSpread::Place(place) => place, - }; - let decl = env.identifiers[place.identifier.0 as usize].declaration_id; - state.escaping_values.insert(decl); - } - } - } - } - } - } -} - -// ============================================================================= -// ReactiveFunctionVisitor impl for CollectDependenciesVisitor -// ============================================================================= - -impl<'a> ReactiveFunctionVisitor for CollectDependenciesVisitor<'a> { - type State = (CollectState, Vec<ScopeId>); - - fn env(&self) -> &Environment { - self.env - } - - fn visit_instruction(&self, instruction: &ReactiveInstruction, state: &mut Self::State) { - self.visit_value_for_memoization( - instruction.id, - &instruction.value, - instruction.lvalue.as_ref().map(|lv| lv.identifier), - &mut state.0, - ); - } - - fn visit_terminal(&self, stmt: &ReactiveTerminalStatement, state: &mut Self::State) { - // Traverse terminal blocks first (TS: this.traverseTerminal(stmt, scopes)) - self.traverse_terminal(stmt, state); - - // Handle return terminals - if let ReactiveTerminal::Return { value, .. } = &stmt.terminal { - let env = self.env; - let decl = env.identifiers[value.identifier.0 as usize].declaration_id; - state.0.escaping_values.insert(decl); - - // If the return is within a scope, associate those scopes with the returned value - let identifier_node = state - .0 - .identifiers - .get_mut(&decl) - .expect("Expected identifier to be initialized"); - for scope_id in &state.1 { - identifier_node.scopes.insert(*scope_id); - } - } - } - - fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut Self::State) { - let env = self.env; - let scope_id = scope.scope; - let scope_data = &env.scopes[scope_id.0 as usize]; - - // If a scope reassigns any variables, set the chain of active scopes as a dependency - // of those variables. - for reassignment_id in &scope_data.reassignments { - let decl = env.identifiers[reassignment_id.0 as usize].declaration_id; - let identifier_node = state - .0 - .identifiers - .get_mut(&decl) - .expect("Expected identifier to be initialized"); - for s in &state.1 { - identifier_node.scopes.insert(*s); - } - identifier_node.scopes.insert(scope_id); - } - - // TS: this.traverseScope(scope, [...scopes, scope.scope]) - state.1.push(scope_id); - self.traverse_scope(scope, state); - state.1.pop(); - } -} - -// ============================================================================= -// computeMemoizedIdentifiers -// ============================================================================= - -fn compute_memoized_identifiers(state: &CollectState) -> HashSet<DeclarationId> { - let mut memoized = HashSet::new(); - - // We need mutable access to the nodes, so we clone the state into mutable structures - let mut identifier_nodes: HashMap< - DeclarationId, - ( - MemoizationLevel, - bool, - IndexSet<DeclarationId>, - IndexSet<ScopeId>, - bool, - ), - > = state - .identifiers - .iter() - .map(|(id, node)| { - ( - *id, - ( - node.level, - node.memoized, - node.dependencies.clone(), - node.scopes.clone(), - node.seen, - ), - ) - }) - .collect(); - - let mut scope_nodes: HashMap<ScopeId, (Vec<DeclarationId>, bool)> = state - .scopes - .iter() - .map(|(id, node)| (*id, (node.dependencies.clone(), node.seen))) - .collect(); - - fn visit( - id: DeclarationId, - force_memoize: bool, - identifier_nodes: &mut HashMap< - DeclarationId, - ( - MemoizationLevel, - bool, - IndexSet<DeclarationId>, - IndexSet<ScopeId>, - bool, - ), - >, - scope_nodes: &mut HashMap<ScopeId, (Vec<DeclarationId>, bool)>, - memoized: &mut HashSet<DeclarationId>, - ) -> bool { - let Some(&(level, _, _, _, seen)) = identifier_nodes.get(&id) else { - return false; - }; - if seen { - return identifier_nodes.get(&id).unwrap().1; - } - - // Mark as seen, temporarily mark as non-memoized - identifier_nodes.get_mut(&id).unwrap().4 = true; // seen = true - identifier_nodes.get_mut(&id).unwrap().1 = false; // memoized = false - - // Visit dependencies - let deps: Vec<DeclarationId> = identifier_nodes - .get(&id) - .unwrap() - .2 - .iter() - .copied() - .collect(); - let mut has_memoized_dependency = false; - for dep in deps { - let is_dep_memoized = visit(dep, false, identifier_nodes, scope_nodes, memoized); - has_memoized_dependency |= is_dep_memoized; - } - - if level == MemoizationLevel::Memoized - || (level == MemoizationLevel::Conditional - && (has_memoized_dependency || force_memoize)) - || (level == MemoizationLevel::Unmemoized && force_memoize) - { - identifier_nodes.get_mut(&id).unwrap().1 = true; // memoized = true - memoized.insert(id); - let scopes: Vec<ScopeId> = identifier_nodes - .get(&id) - .unwrap() - .3 - .iter() - .copied() - .collect(); - for scope_id in scopes { - force_memoize_scope_dependencies(scope_id, identifier_nodes, scope_nodes, memoized); - } - } - identifier_nodes.get(&id).unwrap().1 - } - - fn force_memoize_scope_dependencies( - id: ScopeId, - identifier_nodes: &mut HashMap< - DeclarationId, - ( - MemoizationLevel, - bool, - IndexSet<DeclarationId>, - IndexSet<ScopeId>, - bool, - ), - >, - scope_nodes: &mut HashMap<ScopeId, (Vec<DeclarationId>, bool)>, - memoized: &mut HashSet<DeclarationId>, - ) { - let seen = scope_nodes - .get(&id) - .expect("Expected a node for all scopes") - .1; - if seen { - return; - } - scope_nodes.get_mut(&id).unwrap().1 = true; // seen = true - - let deps: Vec<DeclarationId> = scope_nodes.get(&id).unwrap().0.clone(); - for dep in deps { - visit(dep, true, identifier_nodes, scope_nodes, memoized); - } - } - - // Walk from the "roots" aka returned/escaping identifiers - let escaping: Vec<DeclarationId> = state.escaping_values.iter().copied().collect(); - for value in escaping { - visit( - value, - false, - &mut identifier_nodes, - &mut scope_nodes, - &mut memoized, - ); - } - - memoized -} - -// ============================================================================= -// PruneScopesTransform -// ============================================================================= - -struct PruneScopesTransform<'a> { - env: &'a Environment, - pruned_scopes: HashSet<ScopeId>, - reassignments: HashMap<DeclarationId, HashSet<IdentifierId>>, -} - -impl<'a> ReactiveFunctionTransform for PruneScopesTransform<'a> { - type State = HashSet<DeclarationId>; - - fn env(&self) -> &Environment { - self.env - } - - fn transform_scope( - &mut self, - scope: &mut ReactiveScopeBlock, - state: &mut HashSet<DeclarationId>, - ) -> Result<Transformed<ReactiveStatement>, react_compiler_diagnostics::CompilerError> { - self.visit_scope(scope, state)?; - - let scope_id = scope.scope; - let scope_data = &self.env.scopes[scope_id.0 as usize]; - - // Keep scopes that appear empty (value being memoized may be early-returned) - // or have early return values - if (scope_data.declarations.is_empty() && scope_data.reassignments.is_empty()) - || scope_data.early_return_value.is_some() - { - return Ok(Transformed::Keep); - } - - let has_memoized_output = scope_data.declarations.iter().any(|(_, decl)| { - let decl_id = self.env.identifiers[decl.identifier.0 as usize].declaration_id; - state.contains(&decl_id) - }) || scope_data.reassignments.iter().any(|reassign_id| { - let decl_id = self.env.identifiers[reassign_id.0 as usize].declaration_id; - state.contains(&decl_id) - }); - - if has_memoized_output { - Ok(Transformed::Keep) - } else { - self.pruned_scopes.insert(scope_id); - Ok(Transformed::ReplaceMany(std::mem::take( - &mut scope.instructions, - ))) - } - } - - fn transform_instruction( - &mut self, - instruction: &mut ReactiveInstruction, - state: &mut HashSet<DeclarationId>, - ) -> Result<Transformed<ReactiveStatement>, react_compiler_diagnostics::CompilerError> { - self.traverse_instruction(instruction, state)?; - - match &mut instruction.value { - ReactiveValue::Instruction(InstructionValue::StoreLocal { - value: store_value, - lvalue: store_lvalue, - .. - }) if store_lvalue.kind == InstructionKind::Reassign => { - let decl_id = - self.env.identifiers[store_lvalue.place.identifier.0 as usize].declaration_id; - let ids = self - .reassignments - .entry(decl_id) - .or_insert_with(HashSet::new); - ids.insert(store_value.identifier); - } - ReactiveValue::Instruction(InstructionValue::LoadLocal { place, .. }) => { - let has_scope = self.env.identifiers[place.identifier.0 as usize] - .scope - .is_some(); - let lvalue_no_scope = instruction - .lvalue - .as_ref() - .map(|lv| { - self.env.identifiers[lv.identifier.0 as usize] - .scope - .is_none() - }) - .unwrap_or(false); - if has_scope && lvalue_no_scope { - if let Some(lv) = &instruction.lvalue { - let decl_id = self.env.identifiers[lv.identifier.0 as usize].declaration_id; - let ids = self - .reassignments - .entry(decl_id) - .or_insert_with(HashSet::new); - ids.insert(place.identifier); - } - } - } - ReactiveValue::Instruction(InstructionValue::FinishMemoize { - decl, pruned, .. - }) => { - let decl_has_scope = self.env.identifiers[decl.identifier.0 as usize] - .scope - .is_some(); - if !decl_has_scope { - // If the manual memo was a useMemo that got inlined, iterate through - // all reassignments to the iife temporary to ensure they're memoized. - let decl_id = self.env.identifiers[decl.identifier.0 as usize].declaration_id; - let decls: Vec<IdentifierId> = self - .reassignments - .get(&decl_id) - .map(|ids| ids.iter().copied().collect()) - .unwrap_or_else(|| vec![decl.identifier]); - - if decls.iter().all(|d| { - let scope = self.env.identifiers[d.0 as usize].scope; - scope.is_none() || self.pruned_scopes.contains(&scope.unwrap()) - }) { - *pruned = true; - } - } else { - let scope = self.env.identifiers[decl.identifier.0 as usize].scope; - if let Some(scope_id) = scope { - if self.pruned_scopes.contains(&scope_id) { - *pruned = true; - } - } - } - } - _ => {} - } - - Ok(Transformed::Keep) - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/prune_non_reactive_dependencies.rs b/compiler/crates/react_compiler_reactive_scopes/src/prune_non_reactive_dependencies.rs deleted file mode 100644 index 943203a4b481..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/prune_non_reactive_dependencies.rs +++ /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. - -//! PruneNonReactiveDependencies + CollectReactiveIdentifiers -//! -//! Corresponds to `src/ReactiveScopes/PruneNonReactiveDependencies.ts` -//! and `src/ReactiveScopes/CollectReactiveIdentifiers.ts`. - -use std::collections::HashSet; - -use react_compiler_hir::{ - EvaluationOrder, IdentifierId, InstructionValue, Place, PrunedReactiveScopeBlock, - ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock, ReactiveValue, - environment::Environment, is_primitive_type, is_use_ref_type, object_shape, - visitors as hir_visitors, -}; - -use crate::visitors::{self, ReactiveFunctionTransform, ReactiveFunctionVisitor}; - -// ============================================================================= -// CollectReactiveIdentifiers -// ============================================================================= - -/// Collects identifiers that are reactive. -/// TS: `collectReactiveIdentifiers` -pub fn collect_reactive_identifiers( - func: &ReactiveFunction, - env: &Environment, -) -> HashSet<IdentifierId> { - let visitor = CollectVisitor { env }; - let mut state = HashSet::new(); - crate::visitors::visit_reactive_function(func, &visitor, &mut state); - state -} - -struct CollectVisitor<'a> { - env: &'a Environment, -} - -impl<'a> ReactiveFunctionVisitor for CollectVisitor<'a> { - type State = HashSet<IdentifierId>; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_lvalue(&self, id: EvaluationOrder, lvalue: &Place, state: &mut Self::State) { - // Visitors don't visit lvalues as places by default, but we want to visit all places - self.visit_place(id, lvalue, state); - } - - fn visit_place(&self, _id: EvaluationOrder, place: &Place, state: &mut Self::State) { - if place.reactive { - state.insert(place.identifier); - } - } - - fn visit_pruned_scope(&self, scope: &PrunedReactiveScopeBlock, state: &mut Self::State) { - self.traverse_pruned_scope(scope, state); - - let scope_data = &self.env.scopes[scope.scope.0 as usize]; - for (_id, decl) in &scope_data.declarations { - let identifier = &self.env.identifiers[decl.identifier.0 as usize]; - let ty = &self.env.types[identifier.type_.0 as usize]; - if !is_primitive_type(ty) && !is_stable_ref_type(ty, state, identifier.id) { - state.insert(*_id); - } - } - } -} - -/// TS: `isStableRefType` -fn is_stable_ref_type( - ty: &react_compiler_hir::Type, - reactive_identifiers: &HashSet<IdentifierId>, - id: IdentifierId, -) -> bool { - is_use_ref_type(ty) && !reactive_identifiers.contains(&id) -} - -// ============================================================================= -// isStableType (ported from HIR.ts) -// ============================================================================= - -/// TS: `isStableType` -fn is_stable_type(ty: &react_compiler_hir::Type) -> bool { - is_set_state_type(ty) - || is_set_action_state_type(ty) - || is_dispatcher_type(ty) - || is_use_ref_type(ty) - || is_start_transition_type(ty) - || is_set_optimistic_type(ty) -} - -fn is_set_state_type(ty: &react_compiler_hir::Type) -> bool { - matches!(ty, react_compiler_hir::Type::Function { shape_id: Some(id), .. } if id == object_shape::BUILT_IN_SET_STATE_ID) -} - -fn is_set_action_state_type(ty: &react_compiler_hir::Type) -> bool { - matches!(ty, react_compiler_hir::Type::Function { shape_id: Some(id), .. } if id == object_shape::BUILT_IN_SET_ACTION_STATE_ID) -} - -fn is_dispatcher_type(ty: &react_compiler_hir::Type) -> bool { - matches!(ty, react_compiler_hir::Type::Function { shape_id: Some(id), .. } if id == object_shape::BUILT_IN_DISPATCH_ID) -} - -fn is_start_transition_type(ty: &react_compiler_hir::Type) -> bool { - matches!(ty, react_compiler_hir::Type::Function { shape_id: Some(id), .. } if id == object_shape::BUILT_IN_START_TRANSITION_ID) -} - -fn is_set_optimistic_type(ty: &react_compiler_hir::Type) -> bool { - matches!(ty, react_compiler_hir::Type::Function { shape_id: Some(id), .. } if id == object_shape::BUILT_IN_SET_OPTIMISTIC_ID) -} - -// ============================================================================= -// PruneNonReactiveDependencies -// ============================================================================= - -/// Prunes dependencies that are guaranteed to be non-reactive. -/// TS: `pruneNonReactiveDependencies` -pub fn prune_non_reactive_dependencies(func: &mut ReactiveFunction, env: &mut Environment) { - let reactive_ids = collect_reactive_identifiers(func, env); - let mut visitor = PruneVisitor { env }; - let mut state = reactive_ids; - visitors::transform_reactive_function(func, &mut visitor, &mut state) - .expect("PruneNonReactiveDependencies should not fail"); -} - -struct PruneVisitor<'a> { - env: &'a mut Environment, -} - -impl<'a> ReactiveFunctionTransform for PruneVisitor<'a> { - type State = HashSet<IdentifierId>; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_instruction( - &mut self, - instruction: &mut ReactiveInstruction, - state: &mut Self::State, - ) -> Result<(), react_compiler_diagnostics::CompilerError> { - self.traverse_instruction(instruction, state)?; - - let lvalue = &instruction.lvalue; - match &instruction.value { - ReactiveValue::Instruction(InstructionValue::LoadLocal { place, .. }) => { - if let Some(lv) = lvalue { - if state.contains(&place.identifier) { - state.insert(lv.identifier); - } - } - } - ReactiveValue::Instruction(InstructionValue::StoreLocal { - value: store_value, - lvalue: store_lvalue, - .. - }) => { - if state.contains(&store_value.identifier) { - state.insert(store_lvalue.place.identifier); - if let Some(lv) = lvalue { - state.insert(lv.identifier); - } - } - } - ReactiveValue::Instruction(InstructionValue::Destructure { - value: destr_value, - lvalue: destr_lvalue, - .. - }) => { - if state.contains(&destr_value.identifier) { - for operand in hir_visitors::each_pattern_operand(&destr_lvalue.pattern) { - let ident = &self.env.identifiers[operand.identifier.0 as usize]; - let ty = &self.env.types[ident.type_.0 as usize]; - if is_stable_type(ty) { - continue; - } - state.insert(operand.identifier); - } - if let Some(lv) = lvalue { - state.insert(lv.identifier); - } - } - } - ReactiveValue::Instruction(InstructionValue::PropertyLoad { object, .. }) => { - if let Some(lv) = lvalue { - let ident = &self.env.identifiers[lv.identifier.0 as usize]; - let ty = &self.env.types[ident.type_.0 as usize]; - if state.contains(&object.identifier) && !is_stable_type(ty) { - state.insert(lv.identifier); - } - } - } - ReactiveValue::Instruction(InstructionValue::ComputedLoad { - object, property, .. - }) => { - if let Some(lv) = lvalue { - if state.contains(&object.identifier) || state.contains(&property.identifier) { - state.insert(lv.identifier); - } - } - } - _ => {} - } - Ok(()) - } - - fn visit_scope( - &mut self, - scope: &mut ReactiveScopeBlock, - state: &mut Self::State, - ) -> Result<(), react_compiler_diagnostics::CompilerError> { - self.traverse_scope(scope, state)?; - - let scope_id = scope.scope; - let scope_data = &mut self.env.scopes[scope_id.0 as usize]; - - // Remove non-reactive dependencies - scope_data - .dependencies - .retain(|dep| state.contains(&dep.identifier)); - - // If any deps remain, mark all declarations and reassignments as reactive - if !scope_data.dependencies.is_empty() { - let decl_ids: Vec<IdentifierId> = scope_data - .declarations - .iter() - .map(|(_, decl)| decl.identifier) - .collect(); - for id in decl_ids { - state.insert(id); - } - let reassign_ids: Vec<IdentifierId> = scope_data.reassignments.clone(); - for id in reassign_ids { - state.insert(id); - } - } - Ok(()) - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/prune_unused_labels.rs b/compiler/crates/react_compiler_reactive_scopes/src/prune_unused_labels.rs deleted file mode 100644 index 75a0efac98de..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/prune_unused_labels.rs +++ /dev/null @@ -1,91 +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. - -//! Flattens labeled terminals where the label is not reachable, and -//! nulls out labels for other terminals where the label is unused. -//! -//! Corresponds to `src/ReactiveScopes/PruneUnusedLabels.ts`. - -use std::collections::HashSet; - -use react_compiler_hir::{ - BlockId, ReactiveFunction, ReactiveStatement, ReactiveTerminal, ReactiveTerminalStatement, - ReactiveTerminalTargetKind, environment::Environment, -}; - -use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function}; - -/// Prune unused labels from a reactive function. -pub fn prune_unused_labels( - func: &mut ReactiveFunction, - env: &Environment, -) -> Result<(), react_compiler_diagnostics::CompilerError> { - let mut transform = Transform { env }; - let mut labels: HashSet<BlockId> = HashSet::new(); - transform_reactive_function(func, &mut transform, &mut labels) -} - -struct Transform<'a> { - env: &'a Environment, -} - -impl<'a> ReactiveFunctionTransform for Transform<'a> { - type State = HashSet<BlockId>; - - fn env(&self) -> &Environment { - self.env - } - - fn transform_terminal( - &mut self, - stmt: &mut ReactiveTerminalStatement, - state: &mut HashSet<BlockId>, - ) -> Result<Transformed<ReactiveStatement>, react_compiler_diagnostics::CompilerError> { - // Traverse children first - self.traverse_terminal(stmt, state)?; - - // Collect labeled break/continue targets - match &stmt.terminal { - ReactiveTerminal::Break { - target, - target_kind: ReactiveTerminalTargetKind::Labeled, - .. - } - | ReactiveTerminal::Continue { - target, - target_kind: ReactiveTerminalTargetKind::Labeled, - .. - } => { - state.insert(*target); - } - _ => {} - } - - // Is this terminal reachable via a break/continue to its label? - let is_reachable_label = stmt - .label - .as_ref() - .map_or(false, |label| state.contains(&label.id)); - - if let ReactiveTerminal::Label { block, .. } = &mut stmt.terminal { - if !is_reachable_label { - // Flatten labeled terminals where the label isn't necessary. - // Note: In TS, there's a check for `last.terminal.target === null` - // to pop a trailing break, but since target is always a BlockId (number), - // that check is always false, so the trailing break is never removed. - let flattened = std::mem::take(block); - return Ok(Transformed::ReplaceMany(flattened)); - } - } - - if !is_reachable_label { - if let Some(label) = &mut stmt.label { - label.implicit = true; - } - } - - Ok(Transformed::Keep) - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/prune_unused_lvalues.rs b/compiler/crates/react_compiler_reactive_scopes/src/prune_unused_lvalues.rs deleted file mode 100644 index f2997a373e1c..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/prune_unused_lvalues.rs +++ /dev/null @@ -1,237 +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. - -//! PruneUnusedLValues (PruneTemporaryLValues) -//! -//! Nulls out lvalues for temporary variables that are never accessed later. -//! -//! Corresponds to `src/ReactiveScopes/PruneTemporaryLValues.ts`. - -use std::collections::HashSet; - -use react_compiler_hir::{ - DeclarationId, EvaluationOrder, Place, ReactiveFunction, ReactiveInstruction, - ReactiveStatement, ReactiveValue, environment::Environment, -}; - -use crate::visitors::{self, ReactiveFunctionVisitor}; - -/// Nulls out lvalues for unnamed temporaries that are never used. -/// TS: `pruneUnusedLValues` -/// -/// Uses ReactiveFunctionVisitor to collect unnamed lvalue DeclarationIds, -/// removing them when referenced as operands. After the visitor pass, -/// a second pass nulls out the remaining unused lvalues. -/// -/// This uses a two-phase approach because Rust's ReactiveFunctionVisitor -/// takes immutable references, so we cannot modify lvalues during the visit. -/// The TS version stores mutable instruction references and modifies them -/// after the visitor completes. -pub fn prune_unused_lvalues(func: &mut ReactiveFunction, env: &Environment) { - // Phase 1: Use ReactiveFunctionVisitor to identify unused unnamed lvalues. - // When we see an unnamed lvalue on an instruction, we add its DeclarationId. - // When we see a place reference (operand), we remove its DeclarationId. - let visitor = Visitor { env }; - let mut lvalues: HashSet<DeclarationId> = HashSet::new(); - visitors::visit_reactive_function(func, &visitor, &mut lvalues); - - // Phase 2: Null out lvalues whose DeclarationId remains in the map. - // In the TS, this is done by iterating the stored instruction references. - // In Rust, we walk the tree to find instructions with matching DeclarationIds. - if !lvalues.is_empty() { - null_unused_lvalues(&mut func.body, env, &lvalues); - } -} - -/// TS: `type LValues = Map<DeclarationId, ReactiveInstruction>` -/// In Rust, we only need the set of DeclarationIds (not the instruction refs) -/// because we apply changes in a separate pass. -type LValues = HashSet<DeclarationId>; - -/// TS: `class Visitor extends ReactiveFunctionVisitor<LValues>` -struct Visitor<'a> { - env: &'a Environment, -} - -impl ReactiveFunctionVisitor for Visitor<'_> { - type State = LValues; - - fn env(&self) -> &Environment { - self.env - } - - /// TS: `visitPlace(_id, place, state) { state.delete(place.identifier.declarationId) }` - fn visit_place(&self, _id: EvaluationOrder, place: &Place, state: &mut LValues) { - let ident = &self.env.identifiers[place.identifier.0 as usize]; - state.remove(&ident.declaration_id); - } - - /// TS: `visitInstruction(instruction, state)` - /// Calls traverseInstruction first (visits operands via visitPlace), - /// then checks if the lvalue is unnamed and adds to map. - fn visit_instruction(&self, instruction: &ReactiveInstruction, state: &mut LValues) { - self.traverse_instruction(instruction, state); - if let Some(lv) = &instruction.lvalue { - let ident = &self.env.identifiers[lv.identifier.0 as usize]; - if ident.name.is_none() { - state.insert(ident.declaration_id); - } - } - } -} - -/// Phase 2: Walk the tree and null out lvalues whose DeclarationId is unused. -/// This is necessary because Rust's visitor takes immutable references. -fn null_unused_lvalues( - block: &mut Vec<ReactiveStatement>, - env: &Environment, - unused: &HashSet<DeclarationId>, -) { - for stmt in block.iter_mut() { - match stmt { - ReactiveStatement::Instruction(instr) => { - null_unused_in_instruction(instr, env, unused); - } - ReactiveStatement::Scope(scope) => { - null_unused_lvalues(&mut scope.instructions, env, unused); - } - ReactiveStatement::PrunedScope(scope) => { - null_unused_lvalues(&mut scope.instructions, env, unused); - } - ReactiveStatement::Terminal(stmt) => { - null_unused_in_terminal(&mut stmt.terminal, env, unused); - } - } - } -} - -fn null_unused_in_instruction( - instr: &mut ReactiveInstruction, - env: &Environment, - unused: &HashSet<DeclarationId>, -) { - if let Some(lv) = &instr.lvalue { - let ident = &env.identifiers[lv.identifier.0 as usize]; - if unused.contains(&ident.declaration_id) { - instr.lvalue = None; - } - } - null_unused_in_value(&mut instr.value, env, unused); -} - -fn null_unused_in_value( - value: &mut ReactiveValue, - env: &Environment, - unused: &HashSet<DeclarationId>, -) { - match value { - ReactiveValue::SequenceExpression { - instructions, - value: inner, - .. - } => { - for instr in instructions.iter_mut() { - null_unused_in_instruction(instr, env, unused); - } - null_unused_in_value(inner, env, unused); - } - ReactiveValue::LogicalExpression { left, right, .. } => { - null_unused_in_value(left, env, unused); - null_unused_in_value(right, env, unused); - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - .. - } => { - null_unused_in_value(test, env, unused); - null_unused_in_value(consequent, env, unused); - null_unused_in_value(alternate, env, unused); - } - ReactiveValue::OptionalExpression { value: inner, .. } => { - null_unused_in_value(inner, env, unused); - } - ReactiveValue::Instruction(_) => {} - } -} - -fn null_unused_in_terminal( - terminal: &mut react_compiler_hir::ReactiveTerminal, - env: &Environment, - unused: &HashSet<DeclarationId>, -) { - use react_compiler_hir::ReactiveTerminal; - match terminal { - ReactiveTerminal::Break { .. } | ReactiveTerminal::Continue { .. } => {} - ReactiveTerminal::Return { .. } | ReactiveTerminal::Throw { .. } => {} - ReactiveTerminal::For { - init, - test, - update, - loop_block, - .. - } => { - null_unused_in_value(init, env, unused); - null_unused_in_value(test, env, unused); - null_unused_lvalues(loop_block, env, unused); - if let Some(update) = update { - null_unused_in_value(update, env, unused); - } - } - ReactiveTerminal::ForOf { - init, - test, - loop_block, - .. - } => { - null_unused_in_value(init, env, unused); - null_unused_in_value(test, env, unused); - null_unused_lvalues(loop_block, env, unused); - } - ReactiveTerminal::ForIn { - init, loop_block, .. - } => { - null_unused_in_value(init, env, unused); - null_unused_lvalues(loop_block, env, unused); - } - ReactiveTerminal::DoWhile { - loop_block, test, .. - } => { - null_unused_lvalues(loop_block, env, unused); - null_unused_in_value(test, env, unused); - } - ReactiveTerminal::While { - test, loop_block, .. - } => { - null_unused_in_value(test, env, unused); - null_unused_lvalues(loop_block, env, unused); - } - ReactiveTerminal::If { - consequent, - alternate, - .. - } => { - null_unused_lvalues(consequent, env, unused); - if let Some(alt) = alternate { - null_unused_lvalues(alt, env, unused); - } - } - ReactiveTerminal::Switch { cases, .. } => { - for case in cases.iter_mut() { - if let Some(block) = &mut case.block { - null_unused_lvalues(block, env, unused); - } - } - } - ReactiveTerminal::Label { block, .. } => { - null_unused_lvalues(block, env, unused); - } - ReactiveTerminal::Try { block, handler, .. } => { - null_unused_lvalues(block, env, unused); - null_unused_lvalues(handler, env, unused); - } - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/prune_unused_scopes.rs b/compiler/crates/react_compiler_reactive_scopes/src/prune_unused_scopes.rs deleted file mode 100644 index 3a09aa8b3140..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/prune_unused_scopes.rs +++ /dev/null @@ -1,100 +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. - -//! PruneUnusedScopes — converts scopes without outputs into regular blocks. -//! -//! Corresponds to `src/ReactiveScopes/PruneUnusedScopes.ts`. - -use react_compiler_hir::{ - PrunedReactiveScopeBlock, ReactiveFunction, ReactiveScopeBlock, ReactiveStatement, - ReactiveTerminal, ReactiveTerminalStatement, environment::Environment, -}; - -use crate::visitors::{ReactiveFunctionTransform, Transformed, transform_reactive_function}; - -struct State { - has_return_statement: bool, -} - -/// Converts scopes without outputs into pruned-scopes (regular blocks). -/// TS: `pruneUnusedScopes` -pub fn prune_unused_scopes( - func: &mut ReactiveFunction, - env: &Environment, -) -> Result<(), react_compiler_diagnostics::CompilerError> { - let mut transform = Transform { env }; - let mut state = State { - has_return_statement: false, - }; - transform_reactive_function(func, &mut transform, &mut state) -} - -struct Transform<'a> { - env: &'a Environment, -} - -impl<'a> ReactiveFunctionTransform for Transform<'a> { - type State = State; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_terminal( - &mut self, - stmt: &mut ReactiveTerminalStatement, - state: &mut State, - ) -> Result<(), react_compiler_diagnostics::CompilerError> { - self.traverse_terminal(stmt, state)?; - if matches!(stmt.terminal, ReactiveTerminal::Return { .. }) { - state.has_return_statement = true; - } - Ok(()) - } - - fn transform_scope( - &mut self, - scope: &mut ReactiveScopeBlock, - _state: &mut State, - ) -> Result<Transformed<ReactiveStatement>, react_compiler_diagnostics::CompilerError> { - let mut scope_state = State { - has_return_statement: false, - }; - self.visit_scope(scope, &mut scope_state)?; - - let scope_id = scope.scope; - let scope_data = &self.env.scopes[scope_id.0 as usize]; - - if !scope_state.has_return_statement - && scope_data.reassignments.is_empty() - && (scope_data.declarations.is_empty() || !has_own_declaration(scope_data, scope_id)) - { - // Replace with pruned scope - Ok(Transformed::Replace(ReactiveStatement::PrunedScope( - PrunedReactiveScopeBlock { - scope: scope.scope, - instructions: std::mem::take(&mut scope.instructions), - }, - ))) - } else { - Ok(Transformed::Keep) - } - } -} - -/// Does the scope block declare any values of its own? -/// Returns false if all declarations are propagated from nested scopes. -/// TS: `hasOwnDeclaration` -fn has_own_declaration( - scope_data: &react_compiler_hir::ReactiveScope, - scope_id: react_compiler_hir::ScopeId, -) -> bool { - for (_, decl) in &scope_data.declarations { - if decl.scope == scope_id { - return true; - } - } - false -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/rename_variables.rs b/compiler/crates/react_compiler_reactive_scopes/src/rename_variables.rs deleted file mode 100644 index 682642059de0..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/rename_variables.rs +++ /dev/null @@ -1,445 +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. - -//! RenameVariables — renames variables for output, assigns unique names, -//! handles SSA renames. -//! -//! Corresponds to `src/ReactiveScopes/RenameVariables.ts`. - -use std::collections::HashMap; -use std::collections::HashSet; - -use react_compiler_hir::DeclarationId; -use react_compiler_hir::EvaluationOrder; -use react_compiler_hir::FunctionId; -use react_compiler_hir::IdentifierName; -use react_compiler_hir::InstructionValue; -use react_compiler_hir::ParamPattern; -use react_compiler_hir::Place; -use react_compiler_hir::PrunedReactiveScopeBlock; -use react_compiler_hir::ReactiveBlock; -use react_compiler_hir::ReactiveFunction; -use react_compiler_hir::ReactiveScopeBlock; -use react_compiler_hir::ReactiveValue; -use react_compiler_hir::environment::Environment; - -use crate::visitors::ReactiveFunctionVisitor; -use crate::visitors::{self}; - -// ============================================================================= -// Scopes -// ============================================================================= - -struct Scopes { - seen: HashMap<DeclarationId, IdentifierName>, - stack: Vec<HashMap<String, DeclarationId>>, - globals: HashSet<String>, - names: HashSet<String>, -} - -impl Scopes { - fn new(globals: HashSet<String>) -> Self { - Self { - seen: HashMap::new(), - stack: vec![HashMap::new()], - globals, - names: HashSet::new(), - } - } - - fn visit_identifier( - &mut self, - identifier_id: react_compiler_hir::IdentifierId, - env: &Environment, - ) { - let identifier = &env.identifiers[identifier_id.0 as usize]; - let original_name = match &identifier.name { - Some(name) => name.clone(), - None => return, - }; - let declaration_id = identifier.declaration_id; - - if self.seen.contains_key(&declaration_id) { - return; - } - - let original_value = original_name.value().to_string(); - let is_promoted = matches!(original_name, IdentifierName::Promoted(_)); - let is_promoted_temp = is_promoted && original_value.starts_with("#t"); - let is_promoted_jsx = is_promoted && original_value.starts_with("#T"); - - let mut name: String; - let mut id: u32 = 0; - if is_promoted_temp { - name = format!("t{}", id); - id += 1; - } else if is_promoted_jsx { - name = format!("T{}", id); - id += 1; - } else { - name = original_value.clone(); - } - - while self.lookup(&name).is_some() || self.globals.contains(&name) { - if is_promoted_temp { - name = format!("t{}", id); - id += 1; - } else if is_promoted_jsx { - name = format!("T{}", id); - id += 1; - } else { - name = format!("{}${}", original_value, id); - id += 1; - } - } - - let identifier_name = IdentifierName::Named(name.clone()); - self.seen.insert(declaration_id, identifier_name); - self.stack - .last_mut() - .unwrap() - .insert(name.clone(), declaration_id); - self.names.insert(name); - } - - fn lookup(&self, name: &str) -> Option<DeclarationId> { - for scope in self.stack.iter().rev() { - if let Some(id) = scope.get(name) { - return Some(*id); - } - } - None - } - - fn enter(&mut self) { - self.stack.push(HashMap::new()); - } - - fn leave(&mut self) { - self.stack.pop(); - } -} - -// ============================================================================= -// Visitor — TS: `class Visitor extends ReactiveFunctionVisitor<Scopes>` -// ============================================================================= - -struct Visitor<'a> { - env: &'a Environment, -} - -impl ReactiveFunctionVisitor for Visitor<'_> { - type State = Scopes; - - fn env(&self) -> &Environment { - self.env - } - - /// TS: `visitParam(place, state) { state.visit(place.identifier) }` - fn visit_param(&self, place: &Place, state: &mut Scopes) { - state.visit_identifier(place.identifier, self.env); - } - - /// TS: `visitLValue(_id, lvalue, state) { state.visit(lvalue.identifier) }` - fn visit_lvalue(&self, _id: EvaluationOrder, lvalue: &Place, state: &mut Scopes) { - state.visit_identifier(lvalue.identifier, self.env); - } - - /// TS: `visitPlace(_id, place, state) { state.visit(place.identifier) }` - fn visit_place(&self, _id: EvaluationOrder, place: &Place, state: &mut Scopes) { - state.visit_identifier(place.identifier, self.env); - } - - /// TS: `visitBlock(block, state) { state.enter(() => { this.traverseBlock(block, state) }) }` - fn visit_block(&self, block: &ReactiveBlock, state: &mut Scopes) { - state.enter(); - self.traverse_block(block, state); - state.leave(); - } - - /// TS: `visitPrunedScope(scopeBlock, state) { this.traverseBlock(scopeBlock.instructions, state) }` - /// No enter/leave — names assigned inside pruned scopes remain visible in - /// the enclosing scope, preventing name reuse. - fn visit_pruned_scope(&self, scope: &PrunedReactiveScopeBlock, state: &mut Scopes) { - self.traverse_block(&scope.instructions, state); - } - - /// TS: `visitScope(scope, state) { for (const [_, decl] of scope.scope.declarations) state.visit(decl.identifier); this.traverseScope(scope, state) }` - fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut Scopes) { - let scope_data = &self.env.scopes[scope.scope.0 as usize]; - let decl_ids: Vec<react_compiler_hir::IdentifierId> = scope_data - .declarations - .iter() - .map(|(_, d)| d.identifier) - .collect(); - for id in decl_ids { - state.visit_identifier(id, self.env); - } - self.traverse_scope(scope, state); - } - - /// TS: `visitValue(id, value, state) { this.traverseValue(id, value, state); if (value.kind === 'FunctionExpression' || value.kind === 'ObjectMethod') this.visitHirFunction(value.loweredFunc.func, state) }` - fn visit_value(&self, id: EvaluationOrder, value: &ReactiveValue, state: &mut Scopes) { - self.traverse_value(id, value, state); - if let ReactiveValue::Instruction(iv) = value { - match iv { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - self.visit_hir_function(lowered_func.func, state); - } - _ => {} - } - } - } -} - -// ============================================================================= -// Public entry point -// ============================================================================= - -/// Renames variables for output — assigns unique names, handles SSA renames. -/// Returns a Set of all unique variable names used. -/// TS: `renameVariables` -pub fn rename_variables(func: &mut ReactiveFunction, env: &mut Environment) -> HashSet<String> { - rename_variables_with_parent(func, env, None) -} - -fn rename_variables_with_parent( - func: &mut ReactiveFunction, - env: &mut Environment, - parent_names: Option<&HashSet<String>>, -) -> HashSet<String> { - let globals = collect_referenced_globals(&func.body, env); - - // Phase 1: Use ReactiveFunctionVisitor to compute the rename mapping. - // This collects DeclarationId -> IdentifierName without mutating env. - let mut scopes = Scopes::new(globals.clone()); - // If parent names are provided (for outlined functions), pre-populate - // the scope stack so that parameter names don't collide with parent - // variables. In the TS compiler, outlined functions are placed in the - // parent function body and processed within the parent's scope context. - if let Some(parent) = parent_names { - scopes.enter(); - for name in parent { - scopes - .stack - .last_mut() - .unwrap() - .insert(name.clone(), DeclarationId(u32::MAX)); - scopes.names.insert(name.clone()); - } - } - rename_variables_impl(func, &Visitor { env }, &mut scopes); - - // Phase 2: Apply the computed renames to all identifiers in env. - for identifier in env.identifiers.iter_mut() { - if let Some(mapped_name) = scopes.seen.get(&identifier.declaration_id) { - if identifier.name.is_some() { - identifier.name = Some(mapped_name.clone()); - } - } - } - - let mut result: HashSet<String> = scopes.names; - result.extend(globals); - result -} - -/// TS: `renameVariablesImpl` -fn rename_variables_impl(func: &ReactiveFunction, visitor: &Visitor, scopes: &mut Scopes) { - scopes.enter(); - for param in &func.params { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - visitor.visit_param(place, scopes); - } - visitors::visit_reactive_function(func, visitor, scopes); - scopes.leave(); -} - -// ============================================================================= -// CollectReferencedGlobals -// ============================================================================= - -/// Collects all globally referenced names from the reactive function. -/// TS: `collectReferencedGlobals` -fn collect_referenced_globals(block: &ReactiveBlock, env: &Environment) -> HashSet<String> { - let mut globals = HashSet::new(); - collect_globals_block(block, &mut globals, env); - globals -} - -fn collect_globals_block(block: &ReactiveBlock, globals: &mut HashSet<String>, env: &Environment) { - for stmt in block { - match stmt { - react_compiler_hir::ReactiveStatement::Instruction(instr) => { - collect_globals_value(&instr.value, globals, env); - } - react_compiler_hir::ReactiveStatement::Scope(scope) => { - collect_globals_block(&scope.instructions, globals, env); - } - react_compiler_hir::ReactiveStatement::PrunedScope(scope) => { - collect_globals_block(&scope.instructions, globals, env); - } - react_compiler_hir::ReactiveStatement::Terminal(terminal) => { - collect_globals_terminal(terminal, globals, env); - } - } - } -} - -fn collect_globals_value(value: &ReactiveValue, globals: &mut HashSet<String>, env: &Environment) { - match value { - ReactiveValue::Instruction(iv) => { - if let InstructionValue::LoadGlobal { binding, .. } = iv { - globals.insert(binding.name().to_string()); - } - // Visit inner functions - match iv { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - collect_globals_hir_function(lowered_func.func, globals, env); - } - _ => {} - } - } - ReactiveValue::SequenceExpression { - instructions, - value: inner, - .. - } => { - for instr in instructions { - collect_globals_value(&instr.value, globals, env); - } - collect_globals_value(inner, globals, env); - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - .. - } => { - collect_globals_value(test, globals, env); - collect_globals_value(consequent, globals, env); - collect_globals_value(alternate, globals, env); - } - ReactiveValue::LogicalExpression { left, right, .. } => { - collect_globals_value(left, globals, env); - collect_globals_value(right, globals, env); - } - ReactiveValue::OptionalExpression { value: inner, .. } => { - collect_globals_value(inner, globals, env); - } - } -} - -/// Recursively collects LoadGlobal names from an inner HIR function. -fn collect_globals_hir_function( - func_id: FunctionId, - globals: &mut HashSet<String>, - env: &Environment, -) { - let inner_func = &env.functions[func_id.0 as usize]; - let block_ids: Vec<_> = inner_func.body.blocks.keys().copied().collect(); - for block_id in block_ids { - let inner_func = &env.functions[func_id.0 as usize]; - let block = &inner_func.body.blocks[&block_id]; - for instr_id in &block.instructions { - let instr = &inner_func.instructions[instr_id.0 as usize]; - if let InstructionValue::LoadGlobal { binding, .. } = &instr.value { - globals.insert(binding.name().to_string()); - } - // Recurse into nested function expressions - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - collect_globals_hir_function(lowered_func.func, globals, env); - } - _ => {} - } - } - } -} - -fn collect_globals_terminal( - stmt: &react_compiler_hir::ReactiveTerminalStatement, - globals: &mut HashSet<String>, - env: &Environment, -) { - match &stmt.terminal { - react_compiler_hir::ReactiveTerminal::Break { .. } - | react_compiler_hir::ReactiveTerminal::Continue { .. } => {} - react_compiler_hir::ReactiveTerminal::Return { .. } - | react_compiler_hir::ReactiveTerminal::Throw { .. } => {} - react_compiler_hir::ReactiveTerminal::For { - init, - test, - update, - loop_block, - .. - } => { - collect_globals_value(init, globals, env); - collect_globals_value(test, globals, env); - collect_globals_block(loop_block, globals, env); - if let Some(update) = update { - collect_globals_value(update, globals, env); - } - } - react_compiler_hir::ReactiveTerminal::ForOf { - init, - test, - loop_block, - .. - } => { - collect_globals_value(init, globals, env); - collect_globals_value(test, globals, env); - collect_globals_block(loop_block, globals, env); - } - react_compiler_hir::ReactiveTerminal::ForIn { - init, loop_block, .. - } => { - collect_globals_value(init, globals, env); - collect_globals_block(loop_block, globals, env); - } - react_compiler_hir::ReactiveTerminal::DoWhile { - loop_block, test, .. - } => { - collect_globals_block(loop_block, globals, env); - collect_globals_value(test, globals, env); - } - react_compiler_hir::ReactiveTerminal::While { - test, loop_block, .. - } => { - collect_globals_value(test, globals, env); - collect_globals_block(loop_block, globals, env); - } - react_compiler_hir::ReactiveTerminal::If { - consequent, - alternate, - .. - } => { - collect_globals_block(consequent, globals, env); - if let Some(alt) = alternate { - collect_globals_block(alt, globals, env); - } - } - react_compiler_hir::ReactiveTerminal::Switch { cases, .. } => { - for case in cases { - if let Some(block) = &case.block { - collect_globals_block(block, globals, env); - } - } - } - react_compiler_hir::ReactiveTerminal::Label { block, .. } => { - collect_globals_block(block, globals, env); - } - react_compiler_hir::ReactiveTerminal::Try { block, handler, .. } => { - collect_globals_block(block, globals, env); - collect_globals_block(handler, globals, env); - } - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/stabilize_block_ids.rs b/compiler/crates/react_compiler_reactive_scopes/src/stabilize_block_ids.rs deleted file mode 100644 index 9d91dfffac7d..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/stabilize_block_ids.rs +++ /dev/null @@ -1,130 +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. - -//! StabilizeBlockIds -//! -//! Rewrites block IDs to sequential values so that the output is deterministic -//! regardless of the order in which blocks were created. -//! -//! Corresponds to `src/ReactiveScopes/StabilizeBlockIds.ts`. - -use std::collections::HashMap; - -use indexmap::IndexSet; -use react_compiler_hir::{ - BlockId, ReactiveFunction, ReactiveScopeBlock, ReactiveTerminal, ReactiveTerminalStatement, - environment::Environment, -}; - -use crate::visitors::{ - ReactiveFunctionTransform, ReactiveFunctionVisitor, transform_reactive_function, - visit_reactive_function, -}; - -/// Rewrites block IDs to sequential values. -/// TS: `stabilizeBlockIds` -pub fn stabilize_block_ids(func: &mut ReactiveFunction, env: &mut Environment) { - // Pass 1: Collect referenced labels (preserving insertion order to match TS Set behavior) - let mut referenced: IndexSet<BlockId> = IndexSet::new(); - let collector = CollectReferencedLabels { env: &*env }; - visit_reactive_function(func, &collector, &mut referenced); - - // Build mappings: referenced block IDs -> sequential IDs (insertion-order deterministic) - let mut mappings: HashMap<BlockId, BlockId> = HashMap::new(); - for block_id in &referenced { - let len = mappings.len() as u32; - mappings.entry(*block_id).or_insert(BlockId(len)); - } - - // Pass 2: Rewrite block IDs using ReactiveFunctionTransform - let mut rewriter = RewriteBlockIds { env }; - let _ = transform_reactive_function(func, &mut rewriter, &mut mappings); -} - -// ============================================================================= -// Pass 1: CollectReferencedLabels -// ============================================================================= - -struct CollectReferencedLabels<'a> { - env: &'a Environment, -} - -impl<'a> ReactiveFunctionVisitor for CollectReferencedLabels<'a> { - type State = IndexSet<BlockId>; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut Self::State) { - let scope_data = &self.env.scopes[scope.scope.0 as usize]; - if let Some(ref early_return) = scope_data.early_return_value { - state.insert(early_return.label); - } - self.traverse_scope(scope, state); - } - - fn visit_terminal(&self, stmt: &ReactiveTerminalStatement, state: &mut Self::State) { - if let Some(ref label) = stmt.label { - if !label.implicit { - state.insert(label.id); - } - } - self.traverse_terminal(stmt, state); - } -} - -// ============================================================================= -// Pass 2: RewriteBlockIds -// ============================================================================= - -fn get_or_insert_mapping(mappings: &mut HashMap<BlockId, BlockId>, id: BlockId) -> BlockId { - let len = mappings.len() as u32; - *mappings.entry(id).or_insert(BlockId(len)) -} - -/// TS: `class RewriteBlockIds extends ReactiveFunctionVisitor<Map<BlockId, BlockId>>` -struct RewriteBlockIds<'a> { - env: &'a mut Environment, -} - -impl<'a> ReactiveFunctionTransform for RewriteBlockIds<'a> { - type State = HashMap<BlockId, BlockId>; - - fn env(&self) -> &Environment { - self.env - } - - fn visit_scope( - &mut self, - scope: &mut ReactiveScopeBlock, - state: &mut Self::State, - ) -> Result<(), react_compiler_diagnostics::CompilerError> { - let scope_data = &mut self.env.scopes[scope.scope.0 as usize]; - if let Some(ref mut early_return) = scope_data.early_return_value { - early_return.label = get_or_insert_mapping(state, early_return.label); - } - self.traverse_scope(scope, state) - } - - fn visit_terminal( - &mut self, - stmt: &mut ReactiveTerminalStatement, - state: &mut Self::State, - ) -> Result<(), react_compiler_diagnostics::CompilerError> { - if let Some(ref mut label) = stmt.label { - label.id = get_or_insert_mapping(state, label.id); - } - - match &mut stmt.terminal { - ReactiveTerminal::Break { target, .. } | ReactiveTerminal::Continue { target, .. } => { - *target = get_or_insert_mapping(state, *target); - } - _ => {} - } - - self.traverse_terminal(stmt, state) - } -} diff --git a/compiler/crates/react_compiler_reactive_scopes/src/visitors.rs b/compiler/crates/react_compiler_reactive_scopes/src/visitors.rs deleted file mode 100644 index cdddf527c012..000000000000 --- a/compiler/crates/react_compiler_reactive_scopes/src/visitors.rs +++ /dev/null @@ -1,837 +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. - -//! Visitor and transform traits for ReactiveFunction. -//! -//! Corresponds to `src/ReactiveScopes/visitors.ts` in the TypeScript compiler. - -use react_compiler_diagnostics::CompilerError; -use react_compiler_hir::{ - EvaluationOrder, FunctionId, InstructionValue, ParamPattern, Place, PrunedReactiveScopeBlock, - ReactiveBlock, ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock, ReactiveStatement, - ReactiveTerminal, ReactiveTerminalStatement, ReactiveValue, environment::Environment, -}; - -// ============================================================================= -// ReactiveFunctionVisitor trait -// ============================================================================= - -/// Visitor trait for walking a ReactiveFunction tree. -/// -/// Override individual `visit_*` methods to customize behavior; call the -/// corresponding `traverse_*` to continue the default recursion. -/// -/// TS: `class ReactiveFunctionVisitor<TState>` -pub trait ReactiveFunctionVisitor { - type State; - - /// Provide Environment access. The default traversal uses this to include - /// FunctionExpression/ObjectMethod context places as operands (matching the - /// TS `eachInstructionValueOperand` behavior). - fn env(&self) -> &Environment; - - fn visit_id(&self, _id: EvaluationOrder, _state: &mut Self::State) {} - - fn visit_place(&self, _id: EvaluationOrder, _place: &Place, _state: &mut Self::State) {} - - fn visit_lvalue(&self, _id: EvaluationOrder, _lvalue: &Place, _state: &mut Self::State) {} - - fn visit_param(&self, _place: &Place, _state: &mut Self::State) {} - - /// Walk an inner HIR function, visiting params, instructions (with lvalues, - /// value-lvalues, operands, and nested functions), and terminal operands. - /// TS: `visitHirFunction` - fn visit_hir_function(&self, func_id: FunctionId, state: &mut Self::State) { - let inner_func = &self.env().functions[func_id.0 as usize]; - for param in &inner_func.params { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - self.visit_param(place, state); - } - let block_ids: Vec<_> = inner_func.body.blocks.keys().copied().collect(); - for block_id in block_ids { - let inner_func = &self.env().functions[func_id.0 as usize]; - let block = &inner_func.body.blocks[&block_id]; - let instr_ids: Vec<_> = block.instructions.clone(); - let terminal_operands: Vec<Place> = - react_compiler_hir::visitors::each_terminal_operand(&block.terminal); - let terminal_id = block.terminal.evaluation_order(); - - for instr_id in &instr_ids { - let inner_func = &self.env().functions[func_id.0 as usize]; - let instr = &inner_func.instructions[instr_id.0 as usize]; - // Build a temporary ReactiveInstruction for the visitor - let reactive_instr = ReactiveInstruction { - id: instr.id, - lvalue: Some(instr.lvalue.clone()), - value: ReactiveValue::Instruction(instr.value.clone()), - effects: None, - loc: instr.loc, - }; - self.visit_instruction(&reactive_instr, state); - // Recurse into nested functions - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - self.visit_hir_function(lowered_func.func, state); - } - _ => {} - } - } - for operand in &terminal_operands { - self.visit_place(terminal_id, operand, state); - } - } - } - - fn visit_value(&self, id: EvaluationOrder, value: &ReactiveValue, state: &mut Self::State) { - self.traverse_value(id, value, state); - } - - fn traverse_value(&self, id: EvaluationOrder, value: &ReactiveValue, state: &mut Self::State) { - match value { - ReactiveValue::OptionalExpression { value: inner, .. } => { - self.visit_value(id, inner, state); - } - ReactiveValue::LogicalExpression { left, right, .. } => { - self.visit_value(id, left, state); - self.visit_value(id, right, state); - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - .. - } => { - self.visit_value(id, test, state); - self.visit_value(id, consequent, state); - self.visit_value(id, alternate, state); - } - ReactiveValue::SequenceExpression { - instructions, - id: seq_id, - value: inner, - .. - } => { - for instr in instructions { - self.visit_instruction(instr, state); - } - self.visit_value(*seq_id, inner, state); - } - ReactiveValue::Instruction(instr_value) => { - let operands = react_compiler_hir::visitors::each_instruction_value_operand( - instr_value, - self.env(), - ); - for place in &operands { - self.visit_place(id, place, state); - } - } - } - } - - fn visit_instruction(&self, instruction: &ReactiveInstruction, state: &mut Self::State) { - self.traverse_instruction(instruction, state); - } - - fn traverse_instruction(&self, instruction: &ReactiveInstruction, state: &mut Self::State) { - self.visit_id(instruction.id, state); - // Visit instruction-level lvalue - if let Some(lvalue) = &instruction.lvalue { - self.visit_lvalue(instruction.id, lvalue, state); - } - // Visit value-level lvalues (TS: eachInstructionValueLValue) - if let ReactiveValue::Instruction(iv) = &instruction.value { - for place in react_compiler_hir::visitors::each_instruction_value_lvalue(iv) { - self.visit_lvalue(instruction.id, &place, state); - } - } - self.visit_value(instruction.id, &instruction.value, state); - } - - fn visit_terminal(&self, stmt: &ReactiveTerminalStatement, state: &mut Self::State) { - self.traverse_terminal(stmt, state); - } - - fn traverse_terminal(&self, stmt: &ReactiveTerminalStatement, state: &mut Self::State) { - let terminal = &stmt.terminal; - let id = terminal_id(terminal); - self.visit_id(id, state); - match terminal { - ReactiveTerminal::Break { .. } | ReactiveTerminal::Continue { .. } => {} - ReactiveTerminal::Return { value, id, .. } => { - self.visit_place(*id, value, state); - } - ReactiveTerminal::Throw { value, id, .. } => { - self.visit_place(*id, value, state); - } - ReactiveTerminal::For { - init, - test, - update, - loop_block, - id, - .. - } => { - self.visit_value(*id, init, state); - self.visit_value(*id, test, state); - self.visit_block(loop_block, state); - if let Some(update) = update { - self.visit_value(*id, update, state); - } - } - ReactiveTerminal::ForOf { - init, - test, - loop_block, - id, - .. - } => { - self.visit_value(*id, init, state); - self.visit_value(*id, test, state); - self.visit_block(loop_block, state); - } - ReactiveTerminal::ForIn { - init, - loop_block, - id, - .. - } => { - self.visit_value(*id, init, state); - self.visit_block(loop_block, state); - } - ReactiveTerminal::DoWhile { - loop_block, - test, - id, - .. - } => { - self.visit_block(loop_block, state); - self.visit_value(*id, test, state); - } - ReactiveTerminal::While { - test, - loop_block, - id, - .. - } => { - self.visit_value(*id, test, state); - self.visit_block(loop_block, state); - } - ReactiveTerminal::If { - test, - consequent, - alternate, - id, - .. - } => { - self.visit_place(*id, test, state); - self.visit_block(consequent, state); - if let Some(alt) = alternate { - self.visit_block(alt, state); - } - } - ReactiveTerminal::Switch { - test, cases, id, .. - } => { - self.visit_place(*id, test, state); - for case in cases { - if let Some(t) = &case.test { - self.visit_place(*id, t, state); - } - if let Some(block) = &case.block { - self.visit_block(block, state); - } - } - } - ReactiveTerminal::Label { block, .. } => { - self.visit_block(block, state); - } - ReactiveTerminal::Try { - block, - handler_binding, - handler, - id, - .. - } => { - self.visit_block(block, state); - if let Some(binding) = handler_binding { - self.visit_place(*id, binding, state); - } - self.visit_block(handler, state); - } - } - } - - fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut Self::State) { - self.traverse_scope(scope, state); - } - - fn traverse_scope(&self, scope: &ReactiveScopeBlock, state: &mut Self::State) { - self.visit_block(&scope.instructions, state); - } - - fn visit_pruned_scope(&self, scope: &PrunedReactiveScopeBlock, state: &mut Self::State) { - self.traverse_pruned_scope(scope, state); - } - - fn traverse_pruned_scope(&self, scope: &PrunedReactiveScopeBlock, state: &mut Self::State) { - self.visit_block(&scope.instructions, state); - } - - fn visit_block(&self, block: &ReactiveBlock, state: &mut Self::State) { - self.traverse_block(block, state); - } - - fn traverse_block(&self, block: &ReactiveBlock, state: &mut Self::State) { - for stmt in block { - match stmt { - ReactiveStatement::Instruction(instr) => { - self.visit_instruction(instr, state); - } - ReactiveStatement::Scope(scope) => { - self.visit_scope(scope, state); - } - ReactiveStatement::PrunedScope(scope) => { - self.visit_pruned_scope(scope, state); - } - ReactiveStatement::Terminal(terminal) => { - self.visit_terminal(terminal, state); - } - } - } - } -} - -/// Entry point for visiting a reactive function. -/// TS: `visitReactiveFunction` -pub fn visit_reactive_function<V: ReactiveFunctionVisitor>( - func: &ReactiveFunction, - visitor: &V, - state: &mut V::State, -) { - visitor.visit_block(&func.body, state); -} - -// ============================================================================= -// Transformed / TransformedValue enums -// ============================================================================= - -/// Result of transforming a ReactiveStatement. -/// TS: `Transformed<T>` -pub enum Transformed<T> { - Keep, - Remove, - Replace(T), - ReplaceMany(Vec<T>), -} - -/// Result of transforming a ReactiveValue. -/// TS: `TransformedValue` -#[allow(dead_code)] -pub enum TransformedValue { - Keep, - Replace(ReactiveValue), -} - -// ============================================================================= -// ReactiveFunctionTransform trait -// ============================================================================= - -/// Transform trait for modifying a ReactiveFunction tree in-place. -/// -/// Extends the visitor pattern with `transform_*` methods that can modify -/// or remove statements. The `traverse_block` implementation handles applying -/// transform results to the block. -/// -/// TS: `class ReactiveFunctionTransform<TState>` -pub trait ReactiveFunctionTransform { - type State; - - /// Provide Environment access. The default traversal uses this to include - /// FunctionExpression/ObjectMethod context places as operands (matching the - /// TS `eachInstructionValueOperand` behavior). - fn env(&self) -> &Environment; - - fn visit_id( - &mut self, - _id: EvaluationOrder, - _state: &mut Self::State, - ) -> Result<(), CompilerError> { - Ok(()) - } - - fn visit_place( - &mut self, - _id: EvaluationOrder, - _place: &Place, - _state: &mut Self::State, - ) -> Result<(), CompilerError> { - Ok(()) - } - - fn visit_lvalue( - &mut self, - _id: EvaluationOrder, - _lvalue: &Place, - _state: &mut Self::State, - ) -> Result<(), CompilerError> { - Ok(()) - } - - fn visit_value( - &mut self, - id: EvaluationOrder, - value: &mut ReactiveValue, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - self.traverse_value(id, value, state) - } - - fn traverse_value( - &mut self, - id: EvaluationOrder, - value: &mut ReactiveValue, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - match value { - ReactiveValue::OptionalExpression { value: inner, .. } => { - let next = self.transform_value(id, inner, state)?; - if let TransformedValue::Replace(new_value) = next { - **inner = new_value; - } - } - ReactiveValue::LogicalExpression { left, right, .. } => { - let next_left = self.transform_value(id, left, state)?; - if let TransformedValue::Replace(new_value) = next_left { - **left = new_value; - } - let next_right = self.transform_value(id, right, state)?; - if let TransformedValue::Replace(new_value) = next_right { - **right = new_value; - } - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - .. - } => { - let next_test = self.transform_value(id, test, state)?; - if let TransformedValue::Replace(new_value) = next_test { - **test = new_value; - } - let next_cons = self.transform_value(id, consequent, state)?; - if let TransformedValue::Replace(new_value) = next_cons { - **consequent = new_value; - } - let next_alt = self.transform_value(id, alternate, state)?; - if let TransformedValue::Replace(new_value) = next_alt { - **alternate = new_value; - } - } - ReactiveValue::SequenceExpression { - instructions, - id: seq_id, - value: inner, - .. - } => { - let seq_id = *seq_id; - for instr in instructions.iter_mut() { - self.visit_instruction(instr, state)?; - } - let next = self.transform_value(seq_id, inner, state)?; - if let TransformedValue::Replace(new_value) = next { - **inner = new_value; - } - } - ReactiveValue::Instruction(instr_value) => { - // Collect operands before visiting to avoid borrow conflict - // (self.env() borrows self immutably, self.visit_place() needs &mut self). - let operands = react_compiler_hir::visitors::each_instruction_value_operand( - instr_value, - self.env(), - ); - for place in &operands { - self.visit_place(id, place, state)?; - } - } - } - Ok(()) - } - - fn visit_instruction( - &mut self, - instruction: &mut ReactiveInstruction, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - self.traverse_instruction(instruction, state) - } - - fn transform_value( - &mut self, - id: EvaluationOrder, - value: &mut ReactiveValue, - state: &mut Self::State, - ) -> Result<TransformedValue, CompilerError> { - self.visit_value(id, value, state)?; - Ok(TransformedValue::Keep) - } - - fn traverse_instruction( - &mut self, - instruction: &mut ReactiveInstruction, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - self.visit_id(instruction.id, state)?; - // Visit instruction-level lvalue - if let Some(lvalue) = &instruction.lvalue { - self.visit_lvalue(instruction.id, lvalue, state)?; - } - // Visit value-level lvalues (TS: eachInstructionValueLValue) - if let ReactiveValue::Instruction(iv) = &instruction.value { - for place in react_compiler_hir::visitors::each_instruction_value_lvalue(iv) { - self.visit_lvalue(instruction.id, &place, state)?; - } - } - let next_value = self.transform_value(instruction.id, &mut instruction.value, state)?; - if let TransformedValue::Replace(new_value) = next_value { - instruction.value = new_value; - } - Ok(()) - } - - fn visit_terminal( - &mut self, - stmt: &mut ReactiveTerminalStatement, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - self.traverse_terminal(stmt, state) - } - - fn traverse_terminal( - &mut self, - stmt: &mut ReactiveTerminalStatement, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - let terminal = &mut stmt.terminal; - let id = terminal_id(terminal); - self.visit_id(id, state)?; - match terminal { - ReactiveTerminal::Break { .. } | ReactiveTerminal::Continue { .. } => {} - ReactiveTerminal::Return { value, id, .. } => { - self.visit_place(*id, value, state)?; - } - ReactiveTerminal::Throw { value, id, .. } => { - self.visit_place(*id, value, state)?; - } - ReactiveTerminal::For { - init, - test, - update, - loop_block, - id, - .. - } => { - let id = *id; - let next_init = self.transform_value(id, init, state)?; - if let TransformedValue::Replace(new_value) = next_init { - *init = new_value; - } - let next_test = self.transform_value(id, test, state)?; - if let TransformedValue::Replace(new_value) = next_test { - *test = new_value; - } - if let Some(update) = update { - let next_update = self.transform_value(id, update, state)?; - if let TransformedValue::Replace(new_value) = next_update { - *update = new_value; - } - } - self.visit_block(loop_block, state)?; - } - ReactiveTerminal::ForOf { - init, - test, - loop_block, - id, - .. - } => { - let id = *id; - let next_init = self.transform_value(id, init, state)?; - if let TransformedValue::Replace(new_value) = next_init { - *init = new_value; - } - let next_test = self.transform_value(id, test, state)?; - if let TransformedValue::Replace(new_value) = next_test { - *test = new_value; - } - self.visit_block(loop_block, state)?; - } - ReactiveTerminal::ForIn { - init, - loop_block, - id, - .. - } => { - let id = *id; - let next_init = self.transform_value(id, init, state)?; - if let TransformedValue::Replace(new_value) = next_init { - *init = new_value; - } - self.visit_block(loop_block, state)?; - } - ReactiveTerminal::DoWhile { - loop_block, - test, - id, - .. - } => { - let id = *id; - self.visit_block(loop_block, state)?; - let next_test = self.transform_value(id, test, state)?; - if let TransformedValue::Replace(new_value) = next_test { - *test = new_value; - } - } - ReactiveTerminal::While { - test, - loop_block, - id, - .. - } => { - let id = *id; - let next_test = self.transform_value(id, test, state)?; - if let TransformedValue::Replace(new_value) = next_test { - *test = new_value; - } - self.visit_block(loop_block, state)?; - } - ReactiveTerminal::If { - test, - consequent, - alternate, - id, - .. - } => { - self.visit_place(*id, test, state)?; - self.visit_block(consequent, state)?; - if let Some(alt) = alternate { - self.visit_block(alt, state)?; - } - } - ReactiveTerminal::Switch { - test, cases, id, .. - } => { - let id = *id; - self.visit_place(id, test, state)?; - for case in cases.iter_mut() { - if let Some(t) = &case.test { - self.visit_place(id, t, state)?; - } - if let Some(block) = &mut case.block { - self.visit_block(block, state)?; - } - } - } - ReactiveTerminal::Label { block, .. } => { - self.visit_block(block, state)?; - } - ReactiveTerminal::Try { - block, - handler_binding, - handler, - id, - .. - } => { - let id = *id; - self.visit_block(block, state)?; - if let Some(binding) = handler_binding { - self.visit_place(id, binding, state)?; - } - self.visit_block(handler, state)?; - } - } - Ok(()) - } - - fn visit_scope( - &mut self, - scope: &mut ReactiveScopeBlock, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - self.traverse_scope(scope, state) - } - - fn traverse_scope( - &mut self, - scope: &mut ReactiveScopeBlock, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - self.visit_block(&mut scope.instructions, state) - } - - fn visit_pruned_scope( - &mut self, - scope: &mut PrunedReactiveScopeBlock, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - self.traverse_pruned_scope(scope, state) - } - - fn traverse_pruned_scope( - &mut self, - scope: &mut PrunedReactiveScopeBlock, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - self.visit_block(&mut scope.instructions, state) - } - - fn visit_block( - &mut self, - block: &mut ReactiveBlock, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - self.traverse_block(block, state) - } - - fn transform_instruction( - &mut self, - instruction: &mut ReactiveInstruction, - state: &mut Self::State, - ) -> Result<Transformed<ReactiveStatement>, CompilerError> { - self.visit_instruction(instruction, state)?; - Ok(Transformed::Keep) - } - - fn transform_terminal( - &mut self, - stmt: &mut ReactiveTerminalStatement, - state: &mut Self::State, - ) -> Result<Transformed<ReactiveStatement>, CompilerError> { - self.visit_terminal(stmt, state)?; - Ok(Transformed::Keep) - } - - fn transform_scope( - &mut self, - scope: &mut ReactiveScopeBlock, - state: &mut Self::State, - ) -> Result<Transformed<ReactiveStatement>, CompilerError> { - self.visit_scope(scope, state)?; - Ok(Transformed::Keep) - } - - fn transform_pruned_scope( - &mut self, - scope: &mut PrunedReactiveScopeBlock, - state: &mut Self::State, - ) -> Result<Transformed<ReactiveStatement>, CompilerError> { - self.visit_pruned_scope(scope, state)?; - Ok(Transformed::Keep) - } - - fn traverse_block( - &mut self, - block: &mut ReactiveBlock, - state: &mut Self::State, - ) -> Result<(), CompilerError> { - let mut next_block: Option<Vec<ReactiveStatement>> = None; - let len = block.len(); - for i in 0..len { - // Take the statement out temporarily - let mut stmt = std::mem::replace( - &mut block[i], - // Placeholder — will be overwritten or discarded - ReactiveStatement::Instruction(ReactiveInstruction { - id: EvaluationOrder(0), - lvalue: None, - value: ReactiveValue::Instruction( - react_compiler_hir::InstructionValue::Debugger { loc: None }, - ), - effects: None, - loc: None, - }), - ); - let transformed = match &mut stmt { - ReactiveStatement::Instruction(instr) => { - self.transform_instruction(instr, state)? - } - ReactiveStatement::Scope(scope) => self.transform_scope(scope, state)?, - ReactiveStatement::PrunedScope(scope) => { - self.transform_pruned_scope(scope, state)? - } - ReactiveStatement::Terminal(terminal) => { - self.transform_terminal(terminal, state)? - } - }; - match transformed { - Transformed::Keep => { - if let Some(ref mut nb) = next_block { - nb.push(stmt); - } else { - // Put it back - block[i] = stmt; - } - } - Transformed::Remove => { - if next_block.is_none() { - next_block = Some(block[..i].to_vec()); - } - } - Transformed::Replace(replacement) => { - if next_block.is_none() { - next_block = Some(block[..i].to_vec()); - } - next_block.as_mut().unwrap().push(replacement); - } - Transformed::ReplaceMany(replacements) => { - if next_block.is_none() { - next_block = Some(block[..i].to_vec()); - } - next_block.as_mut().unwrap().extend(replacements); - } - } - } - if let Some(nb) = next_block { - *block = nb; - } - Ok(()) - } -} - -/// Entry point for transforming a reactive function. -/// TS: `visitReactiveFunction` (used with transforms too) -pub fn transform_reactive_function<T: ReactiveFunctionTransform>( - func: &mut ReactiveFunction, - transform: &mut T, - state: &mut T::State, -) -> Result<(), CompilerError> { - transform.visit_block(&mut func.body, state) -} - -// ============================================================================= -// Helper: extract terminal ID -// ============================================================================= - -fn terminal_id(terminal: &ReactiveTerminal) -> EvaluationOrder { - match terminal { - ReactiveTerminal::Break { id, .. } - | ReactiveTerminal::Continue { id, .. } - | ReactiveTerminal::Return { id, .. } - | ReactiveTerminal::Throw { id, .. } - | ReactiveTerminal::Switch { id, .. } - | ReactiveTerminal::DoWhile { id, .. } - | ReactiveTerminal::While { id, .. } - | ReactiveTerminal::For { id, .. } - | ReactiveTerminal::ForOf { id, .. } - | ReactiveTerminal::ForIn { id, .. } - | ReactiveTerminal::If { id, .. } - | ReactiveTerminal::Label { id, .. } - | ReactiveTerminal::Try { id, .. } => *id, - } -} diff --git a/compiler/crates/react_compiler_ssa/Cargo.toml b/compiler/crates/react_compiler_ssa/Cargo.toml deleted file mode 100644 index 3334d93d026d..000000000000 --- a/compiler/crates/react_compiler_ssa/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "react_compiler_ssa" -version = "0.1.0" -edition = "2024" - -[dependencies] -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" } -react_compiler_hir = { path = "../react_compiler_hir" } -react_compiler_lowering = { path = "../react_compiler_lowering" } -indexmap = "2" diff --git a/compiler/crates/react_compiler_ssa/src/eliminate_redundant_phi.rs b/compiler/crates/react_compiler_ssa/src/eliminate_redundant_phi.rs deleted file mode 100644 index 231ba4cecd0a..000000000000 --- a/compiler/crates/react_compiler_ssa/src/eliminate_redundant_phi.rs +++ /dev/null @@ -1,156 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors; -use react_compiler_hir::*; - -use crate::enter_ssa::placeholder_function; - -// ============================================================================= -// Helper: rewrite_place -// ============================================================================= - -fn rewrite_place(place: &mut Place, rewrites: &HashMap<IdentifierId, IdentifierId>) { - if let Some(&rewrite) = rewrites.get(&place.identifier) { - place.identifier = rewrite; - } -} - -// ============================================================================= -// Public entry point -// ============================================================================= - -pub fn eliminate_redundant_phi(func: &mut HirFunction, env: &mut Environment) { - let mut rewrites: HashMap<IdentifierId, IdentifierId> = HashMap::new(); - eliminate_redundant_phi_impl(func, env, &mut rewrites); -} - -// ============================================================================= -// Inner implementation -// ============================================================================= - -fn eliminate_redundant_phi_impl( - func: &mut HirFunction, - env: &mut Environment, - rewrites: &mut HashMap<IdentifierId, IdentifierId>, -) { - let ir = &mut func.body; - - let mut has_back_edge = false; - let mut visited: HashSet<BlockId> = HashSet::new(); - - let mut size; - loop { - size = rewrites.len(); - - let block_ids: Vec<BlockId> = ir.blocks.keys().copied().collect(); - for block_id in &block_ids { - let block_id = *block_id; - - if !has_back_edge { - let block = ir.blocks.get(&block_id).unwrap(); - for pred_id in &block.preds { - if !visited.contains(pred_id) { - has_back_edge = true; - } - } - } - visited.insert(block_id); - - // Find any redundant phis: rewrite operands, identify redundant phis, remove them. - // Matches TS behavior: each phi's operands are rewritten before checking redundancy, - // so that rewrites from earlier phis in the same block are visible to later phis. - let block = ir.blocks.get_mut(&block_id).unwrap(); - block.phis.retain_mut(|phi| { - // Remap phis in case operands are from eliminated phis - for (_, operand) in phi.operands.iter_mut() { - rewrite_place(operand, rewrites); - } - - // Find if the phi can be eliminated - let mut same: Option<IdentifierId> = None; - let mut is_redundant = true; - for (_, operand) in &phi.operands { - if (same.is_some() && operand.identifier == same.unwrap()) - || operand.identifier == phi.place.identifier - { - continue; - } else if same.is_some() { - is_redundant = false; - break; - } else { - same = Some(operand.identifier); - } - } - if is_redundant { - let same = same.expect("Expected phis to be non-empty"); - rewrites.insert(phi.place.identifier, same); - false // remove this phi - } else { - true // keep this phi - } - }); - - // Rewrite instructions - let instruction_ids: Vec<InstructionId> = - ir.blocks.get(&block_id).unwrap().instructions.clone(); - - for instr_id in &instruction_ids { - let instr_idx = instr_id.0 as usize; - let instr = &mut func.instructions[instr_idx]; - - // Rewrite all lvalues (matches TS eachInstructionLValue) - rewrite_place(&mut instr.lvalue, rewrites); - visitors::for_each_instruction_value_lvalue_mut(&mut instr.value, &mut |place| { - rewrite_place(place, rewrites); - }); - - // Rewrite operands using canonical visitor - visitors::for_each_instruction_value_operand_mut( - &mut func.instructions[instr_idx].value, - &mut |place| { - rewrite_place(place, rewrites); - }, - ); - - // Handle FunctionExpression/ObjectMethod context and recursion - let instr = &func.instructions[instr_idx]; - let func_expr_id = match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - Some(lowered_func.func) - } - _ => None, - }; - - if let Some(fid) = func_expr_id { - // Rewrite context places - let context = &mut env.functions[fid.0 as usize].context; - for place in context.iter_mut() { - rewrite_place(place, rewrites); - } - - // Take inner function out, process it, put it back - let mut inner_func = std::mem::replace( - &mut env.functions[fid.0 as usize], - placeholder_function(), - ); - - eliminate_redundant_phi_impl(&mut inner_func, env, rewrites); - - env.functions[fid.0 as usize] = inner_func; - } - } - - // Rewrite terminal operands using canonical visitor - let terminal = &mut ir.blocks.get_mut(&block_id).unwrap().terminal; - visitors::for_each_terminal_operand_mut(terminal, &mut |place| { - rewrite_place(place, rewrites); - }); - } - - if !(rewrites.len() > size && has_back_edge) { - break; - } - } -} diff --git a/compiler/crates/react_compiler_ssa/src/enter_ssa.rs b/compiler/crates/react_compiler_ssa/src/enter_ssa.rs deleted file mode 100644 index 70346fce4486..000000000000 --- a/compiler/crates/react_compiler_ssa/src/enter_ssa.rs +++ /dev/null @@ -1,531 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use indexmap::IndexMap; -use react_compiler_diagnostics::{CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors; -use react_compiler_hir::*; - -// ============================================================================= -// SSABuilder -// ============================================================================= - -struct IncompletePhi { - old_place: Place, - new_place: Place, -} - -struct State { - defs: HashMap<IdentifierId, IdentifierId>, - incomplete_phis: Vec<IncompletePhi>, -} - -struct SSABuilder { - states: HashMap<BlockId, State>, - current: Option<BlockId>, - unsealed_preds: HashMap<BlockId, u32>, - block_preds: HashMap<BlockId, Vec<BlockId>>, - unknown: HashSet<IdentifierId>, - context: HashSet<IdentifierId>, - pending_phis: HashMap<BlockId, Vec<Phi>>, - processed_functions: Vec<FunctionId>, -} - -impl SSABuilder { - fn new(blocks: &IndexMap<BlockId, BasicBlock>) -> Self { - let mut block_preds = HashMap::new(); - for (id, block) in blocks { - block_preds.insert(*id, block.preds.iter().copied().collect()); - } - SSABuilder { - states: HashMap::new(), - current: None, - unsealed_preds: HashMap::new(), - block_preds, - unknown: HashSet::new(), - context: HashSet::new(), - pending_phis: HashMap::new(), - processed_functions: Vec::new(), - } - } - - fn define_function(&mut self, func: &HirFunction) { - for (id, block) in &func.body.blocks { - self.block_preds - .insert(*id, block.preds.iter().copied().collect()); - } - } - - fn state_mut(&mut self) -> &mut State { - let current = self - .current - .expect("we need to be in a block to access state!"); - self.states - .get_mut(¤t) - .expect("state not found for current block") - } - - fn make_id(&mut self, old_id: IdentifierId, env: &mut Environment) -> IdentifierId { - let new_id = env.next_identifier_id(); - let old = &env.identifiers[old_id.0 as usize]; - let declaration_id = old.declaration_id; - let name = old.name.clone(); - let loc = old.loc; - let new_ident = &mut env.identifiers[new_id.0 as usize]; - new_ident.declaration_id = declaration_id; - new_ident.name = name; - new_ident.loc = loc; - new_id - } - - fn define_place( - &mut self, - old_place: &Place, - env: &mut Environment, - ) -> Result<Place, CompilerDiagnostic> { - let old_id = old_place.identifier; - - if self.unknown.contains(&old_id) { - let ident = &env.identifiers[old_id.0 as usize]; - let name = match &ident.name { - Some(name) => format!("{}${}", name.value(), old_id.0), - None => format!("${}", old_id.0), - }; - return Err(CompilerDiagnostic::new( - ErrorCategory::Todo, - "[hoisting] EnterSSA: Expected identifier to be defined before being used", - Some(format!("Identifier {} is undefined", name)), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: old_place.loc, - message: None, - identifier_name: None, - })); - } - - // Do not redefine context references. - if self.context.contains(&old_id) { - return Ok(self.get_place(old_place, env)); - } - - let new_id = self.make_id(old_id, env); - self.state_mut().defs.insert(old_id, new_id); - Ok(Place { - identifier: new_id, - effect: old_place.effect, - reactive: old_place.reactive, - loc: old_place.loc, - }) - } - - #[allow(dead_code)] - fn define_context( - &mut self, - old_place: &Place, - env: &mut Environment, - ) -> Result<Place, CompilerDiagnostic> { - let old_id = old_place.identifier; - let new_place = self.define_place(old_place, env)?; - self.context.insert(old_id); - Ok(new_place) - } - - /// A function's context places capture a *binding*, not a value: the - /// variable is only read when the function is later called, so a context - /// place may reference a binding that is declared after the function - /// expression itself (eg `const colgroup = useMemo(() => <colgroup>...)`, - /// where the JSX tag name resolves to the variable being assigned). Unmark - /// such identifiers so the later declaration doesn't error; if the function - /// body actually *reads* the variable before it is defined, visiting the - /// body re-marks it and the hoisting bailout in define_place still applies. - fn unmark_unknown(&mut self, id: IdentifierId) { - self.unknown.remove(&id); - } - - fn get_place(&mut self, old_place: &Place, env: &mut Environment) -> Place { - let current_id = self.current.expect("must be in a block"); - let new_id = self.get_id_at(old_place, current_id, env); - Place { - identifier: new_id, - effect: old_place.effect, - reactive: old_place.reactive, - loc: old_place.loc, - } - } - - fn get_id_at( - &mut self, - old_place: &Place, - block_id: BlockId, - env: &mut Environment, - ) -> IdentifierId { - if let Some(state) = self.states.get(&block_id) { - if let Some(&new_id) = state.defs.get(&old_place.identifier) { - return new_id; - } - } - - let preds = self.block_preds.get(&block_id).cloned().unwrap_or_default(); - - if preds.is_empty() { - self.unknown.insert(old_place.identifier); - return old_place.identifier; - } - - let unsealed = self.unsealed_preds.get(&block_id).copied().unwrap_or(0); - if unsealed > 0 { - let new_id = self.make_id(old_place.identifier, env); - let new_place = Place { - identifier: new_id, - effect: old_place.effect, - reactive: old_place.reactive, - loc: old_place.loc, - }; - let state = self.states.get_mut(&block_id).unwrap(); - state.incomplete_phis.push(IncompletePhi { - old_place: old_place.clone(), - new_place, - }); - state.defs.insert(old_place.identifier, new_id); - return new_id; - } - - if preds.len() == 1 { - let pred = preds[0]; - let new_id = self.get_id_at(old_place, pred, env); - self.states - .get_mut(&block_id) - .unwrap() - .defs - .insert(old_place.identifier, new_id); - return new_id; - } - - let new_id = self.make_id(old_place.identifier, env); - self.states - .get_mut(&block_id) - .unwrap() - .defs - .insert(old_place.identifier, new_id); - let new_place = Place { - identifier: new_id, - effect: old_place.effect, - reactive: old_place.reactive, - loc: old_place.loc, - }; - self.add_phi(block_id, old_place, &new_place, env); - new_id - } - - fn add_phi( - &mut self, - block_id: BlockId, - old_place: &Place, - new_place: &Place, - env: &mut Environment, - ) { - let preds = self.block_preds.get(&block_id).cloned().unwrap_or_default(); - - let mut pred_defs: IndexMap<BlockId, Place> = IndexMap::new(); - for pred_block_id in &preds { - let pred_id = self.get_id_at(old_place, *pred_block_id, env); - pred_defs.insert( - *pred_block_id, - Place { - identifier: pred_id, - effect: old_place.effect, - reactive: old_place.reactive, - loc: old_place.loc, - }, - ); - } - - let phi = Phi { - place: new_place.clone(), - operands: pred_defs, - }; - - self.pending_phis.entry(block_id).or_default().push(phi); - } - - fn fix_incomplete_phis(&mut self, block_id: BlockId, env: &mut Environment) { - let incomplete_phis: Vec<IncompletePhi> = self - .states - .get_mut(&block_id) - .unwrap() - .incomplete_phis - .drain(..) - .collect(); - for phi in &incomplete_phis { - self.add_phi(block_id, &phi.old_place, &phi.new_place, env); - } - } - - fn start_block(&mut self, block_id: BlockId) { - self.current = Some(block_id); - self.states.insert( - block_id, - State { - defs: HashMap::new(), - incomplete_phis: Vec::new(), - }, - ); - } -} - -// ============================================================================= -// Public entry point -// ============================================================================= - -pub fn enter_ssa(func: &mut HirFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> { - let mut builder = SSABuilder::new(&func.body.blocks); - let root_entry = func.body.entry; - enter_ssa_impl(func, &mut builder, env, root_entry)?; - - // Apply all pending phis to the actual blocks - apply_pending_phis(func, env, &mut builder); - - Ok(()) -} - -fn apply_pending_phis(func: &mut HirFunction, env: &mut Environment, builder: &mut SSABuilder) { - for (block_id, block) in func.body.blocks.iter_mut() { - if let Some(phis) = builder.pending_phis.remove(block_id) { - block.phis.extend(phis); - } - } - for fid in &builder.processed_functions.clone() { - let inner_func = &mut env.functions[fid.0 as usize]; - for (block_id, block) in inner_func.body.blocks.iter_mut() { - if let Some(phis) = builder.pending_phis.remove(block_id) { - block.phis.extend(phis); - } - } - } -} - -fn enter_ssa_impl( - func: &mut HirFunction, - builder: &mut SSABuilder, - env: &mut Environment, - root_entry: BlockId, -) -> Result<(), CompilerDiagnostic> { - let mut visited_blocks: HashSet<BlockId> = HashSet::new(); - let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect(); - - for block_id in &block_ids { - let block_id = *block_id; - - if visited_blocks.contains(&block_id) { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!("found a cycle! visiting bb{} again", block_id.0), - None, - )); - } - - visited_blocks.insert(block_id); - builder.start_block(block_id); - - // Handle params at the root entry - if block_id == root_entry { - if !func.context.is_empty() { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected function context to be empty for outer function declarations", - None, - )); - } - let params = std::mem::take(&mut func.params); - let mut new_params = Vec::with_capacity(params.len()); - for param in params { - new_params.push(match param { - ParamPattern::Place(p) => ParamPattern::Place(builder.define_place(&p, env)?), - ParamPattern::Spread(s) => ParamPattern::Spread(SpreadPattern { - place: builder.define_place(&s.place, env)?, - }), - }); - } - func.params = new_params; - } - - // Process instructions - let instruction_ids: Vec<InstructionId> = func - .body - .blocks - .get(&block_id) - .unwrap() - .instructions - .clone(); - - for instr_id in &instruction_ids { - let instr_idx = instr_id.0 as usize; - let instr = &mut func.instructions[instr_idx]; - - // For FunctionExpression/ObjectMethod, we need to handle context - // mapping specially because env.functions is borrowed by the closure. - // First, check if this is a FunctionExpression/ObjectMethod and handle - // context mapping separately. - let func_expr_id = match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => Some(lowered_func.func), - _ => None, - }; - - // Map context places for function expressions before other operands - if let Some(fid) = func_expr_id { - let context = std::mem::take(&mut env.functions[fid.0 as usize].context); - env.functions[fid.0 as usize].context = context - .into_iter() - .map(|place| builder.get_place(&place, env)) - .collect(); - } - - // Map non-context operands - visitors::for_each_instruction_value_operand_mut(&mut instr.value, &mut |place| { - *place = builder.get_place(place, env); - }); - - // Map lvalues (skip DeclareContext/StoreContext — context variables - // don't participate in SSA renaming) - let instr = &mut func.instructions[instr_idx]; - let mut lvalue_err: Option<CompilerDiagnostic> = None; - visitors::for_each_instruction_lvalue_mut(instr, &mut |place| { - if lvalue_err.is_none() { - match builder.define_place(place, env) { - Ok(new_place) => *place = new_place, - Err(e) => lvalue_err = Some(e), - } - } - }); - if let Some(e) = lvalue_err { - return Err(e); - } - - // Handle inner function SSA - if let Some(fid) = func_expr_id { - let context_ids: Vec<IdentifierId> = env.functions[fid.0 as usize] - .context - .iter() - .map(|place| place.identifier) - .collect(); - for id in context_ids { - builder.unmark_unknown(id); - } - builder.processed_functions.push(fid); - let inner_func = &mut env.functions[fid.0 as usize]; - let inner_entry = inner_func.body.entry; - let entry_block = inner_func.body.blocks.get_mut(&inner_entry).unwrap(); - - if !entry_block.preds.is_empty() { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected function expression entry block to have zero predecessors", - None, - )); - } - entry_block.preds.insert(block_id); - - builder.define_function(inner_func); - - let saved_current = builder.current; - - // Map inner function params - let inner_params = std::mem::take(&mut env.functions[fid.0 as usize].params); - let mut new_inner_params = Vec::with_capacity(inner_params.len()); - for param in inner_params { - new_inner_params.push(match param { - ParamPattern::Place(p) => { - ParamPattern::Place(builder.define_place(&p, env)?) - } - ParamPattern::Spread(s) => ParamPattern::Spread(SpreadPattern { - place: builder.define_place(&s.place, env)?, - }), - }); - } - env.functions[fid.0 as usize].params = new_inner_params; - - // Take the inner function out of the arena to process it - let mut inner_func = - std::mem::replace(&mut env.functions[fid.0 as usize], placeholder_function()); - - enter_ssa_impl(&mut inner_func, builder, env, root_entry)?; - - // Put it back - env.functions[fid.0 as usize] = inner_func; - - builder.current = saved_current; - - // Clear entry preds - env.functions[fid.0 as usize] - .body - .blocks - .get_mut(&inner_entry) - .unwrap() - .preds - .clear(); - builder.block_preds.insert(inner_entry, Vec::new()); - } - } - - // Map terminal operands - let terminal = &mut func.body.blocks.get_mut(&block_id).unwrap().terminal; - visitors::for_each_terminal_operand_mut(terminal, &mut |place| { - *place = builder.get_place(place, env); - }); - - // Handle successors - let terminal_ref = &func.body.blocks.get(&block_id).unwrap().terminal; - let successors = visitors::each_terminal_successor(terminal_ref); - for output_id in successors { - let output_preds_len = builder - .block_preds - .get(&output_id) - .map(|p| p.len() as u32) - .unwrap_or(0); - - let count = if builder.unsealed_preds.contains_key(&output_id) { - builder.unsealed_preds[&output_id] - 1 - } else { - output_preds_len - 1 - }; - builder.unsealed_preds.insert(output_id, count); - - if count == 0 && visited_blocks.contains(&output_id) { - builder.fix_incomplete_phis(output_id, env); - } - } - } - - Ok(()) -} - -/// Create a placeholder HirFunction for temporarily swapping an inner function -/// out of `env.functions` via `std::mem::replace`. The placeholder is never -/// read — the real function is swapped back immediately after processing. -pub fn placeholder_function() -> HirFunction { - HirFunction { - loc: None, - id: None, - name_hint: None, - fn_type: ReactFunctionType::Other, - params: Vec::new(), - return_type_annotation: None, - returns: Place { - identifier: IdentifierId(0), - effect: Effect::Unknown, - reactive: false, - loc: None, - }, - context: Vec::new(), - body: HIR { - entry: BlockId(0), - blocks: IndexMap::new(), - }, - instructions: Vec::new(), - generator: false, - is_async: false, - directives: Vec::new(), - aliasing_effects: None, - } -} diff --git a/compiler/crates/react_compiler_ssa/src/lib.rs b/compiler/crates/react_compiler_ssa/src/lib.rs deleted file mode 100644 index 592e5c59034b..000000000000 --- a/compiler/crates/react_compiler_ssa/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod eliminate_redundant_phi; -pub mod enter_ssa; -mod rewrite_instruction_kinds_based_on_reassignment; - -pub use eliminate_redundant_phi::eliminate_redundant_phi; -pub use enter_ssa::enter_ssa; -pub use rewrite_instruction_kinds_based_on_reassignment::rewrite_instruction_kinds_based_on_reassignment; diff --git a/compiler/crates/react_compiler_ssa/src/rewrite_instruction_kinds_based_on_reassignment.rs b/compiler/crates/react_compiler_ssa/src/rewrite_instruction_kinds_based_on_reassignment.rs deleted file mode 100644 index f4dc5e9694e7..000000000000 --- a/compiler/crates/react_compiler_ssa/src/rewrite_instruction_kinds_based_on_reassignment.rs +++ /dev/null @@ -1,389 +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. - -//! Rewrites InstructionKind of instructions which declare/assign variables, -//! converting the first declaration to Const/Let depending on whether it is -//! subsequently reassigned, and ensuring that subsequent reassignments are -//! marked as Reassign. -//! -//! Ported from TypeScript `src/SSA/RewriteInstructionKindsBasedOnReassignment.ts`. -//! -//! Note that declarations which were const in the original program cannot become -//! `let`, but the inverse is not true: a `let` which was reassigned in the source -//! may be converted to a `const` if the reassignment is not used and was removed -//! by dead code elimination. - -use std::collections::HashMap; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory, SourceLocation, -}; -use react_compiler_hir::visitors::each_pattern_operand; -use react_compiler_hir::{ - BlockKind, DeclarationId, HirFunction, InstructionKind, InstructionValue, ParamPattern, Place, -}; - -use react_compiler_hir::environment::Environment; - -/// Create an invariant CompilerError (matches TS CompilerError.invariant). -/// When a loc is provided, creates a CompilerDiagnostic with an error detail item -/// (matching TS CompilerError.invariant which uses .withDetails()). -fn invariant_error(reason: &str, description: Option<String>) -> CompilerError { - invariant_error_with_loc(reason, description, None) -} - -fn invariant_error_with_loc( - reason: &str, - description: Option<String>, - loc: Option<SourceLocation>, -) -> CompilerError { - let mut err = CompilerError::new(); - let diagnostic = CompilerDiagnostic::new(ErrorCategory::Invariant, reason, description) - .with_detail(CompilerDiagnosticDetail::Error { - loc, - message: Some(reason.to_string()), - identifier_name: None, - }); - err.push_diagnostic(diagnostic); - err -} - -/// Format an InstructionKind variant name (matches TS `${kind}` interpolation). -fn format_kind(kind: Option<InstructionKind>) -> String { - match kind { - Some(InstructionKind::Const) => "Const".to_string(), - Some(InstructionKind::Let) => "Let".to_string(), - Some(InstructionKind::Reassign) => "Reassign".to_string(), - Some(InstructionKind::Catch) => "Catch".to_string(), - Some(InstructionKind::HoistedConst) => "HoistedConst".to_string(), - Some(InstructionKind::HoistedLet) => "HoistedLet".to_string(), - Some(InstructionKind::HoistedFunction) => "HoistedFunction".to_string(), - Some(InstructionKind::Function) => "Function".to_string(), - None => "null".to_string(), - } -} - -/// Format a Place like TS `printPlace()`: `<effect> <name>$<id>[<range>]{reactive}` -fn format_place(place: &Place, env: &Environment) -> String { - let ident = &env.identifiers[place.identifier.0 as usize]; - let name = match &ident.name { - Some(n) => n.value().to_string(), - None => String::new(), - }; - let scope = match ident.scope { - Some(scope_id) => format!("_@{}", scope_id.0), - None => String::new(), - }; - let mutable_range = if ident.mutable_range.end.0 > ident.mutable_range.start.0 + 1 { - format!( - "[{}:{}]", - ident.mutable_range.start.0, ident.mutable_range.end.0 - ) - } else { - String::new() - }; - let reactive = if place.reactive { "{reactive}" } else { "" }; - format!( - "{} {}${}{}{}{}", - place.effect, name, place.identifier.0, scope, mutable_range, reactive - ) -} - -/// Index into a collected list of declaration mutations to apply. -/// -/// We use a two-phase approach: first collect which declarations exist, -/// then apply mutations. This is because in the TS code, `declarations` -/// map stores references to LValue/LValuePattern and mutates `kind` through them. -/// In Rust, we track instruction indices and apply changes in a second pass. -enum DeclarationLoc { - /// An LValue from DeclareLocal or StoreLocal — identified by (block_index, instr_index_in_block) - Instruction { - block_index: usize, - instr_local_index: usize, - }, - /// A parameter or context variable (seeded as Let, may be upgraded to Let on reassignment — already Let) - ParamOrContext, -} - -pub fn rewrite_instruction_kinds_based_on_reassignment( - func: &mut HirFunction, - env: &Environment, -) -> Result<(), CompilerError> { - // Phase 1: Collect all information about which declarations need updates. - // - // Track: for each DeclarationId, the location of its first declaration, - // and whether it needs to be changed to Let (because of reassignment). - let mut declarations: HashMap<DeclarationId, DeclarationLoc> = HashMap::new(); - // Track which (block_index, instr_local_index) should have their lvalue.kind set to Reassign - let mut reassign_locs: Vec<(usize, usize)> = Vec::new(); - // Track which declaration locations need to be set to Let - let mut let_locs: Vec<(usize, usize)> = Vec::new(); - // Track which (block_index, instr_local_index) should have their lvalue.kind set to Const - let mut const_locs: Vec<(usize, usize)> = Vec::new(); - // Track which (block_index, instr_local_index) Destructure instructions get a specific kind - let mut destructure_kind_locs: Vec<(usize, usize, InstructionKind)> = Vec::new(); - - // Seed with parameters - for param in &func.params { - let place: &Place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - let ident = &env.identifiers[place.identifier.0 as usize]; - if ident.name.is_some() { - declarations.insert(ident.declaration_id, DeclarationLoc::ParamOrContext); - } - } - - // Seed with context variables - for place in &func.context { - let ident = &env.identifiers[place.identifier.0 as usize]; - if ident.name.is_some() { - declarations.insert(ident.declaration_id, DeclarationLoc::ParamOrContext); - } - } - - // Process all blocks - let block_keys: Vec<_> = func.body.blocks.keys().cloned().collect(); - for (block_index, block_id) in block_keys.iter().enumerate() { - let block = &func.body.blocks[block_id]; - let block_kind = block.kind; - for (local_idx, instr_id) in block.instructions.iter().enumerate() { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::DeclareLocal { lvalue, .. } => { - let decl_id = - env.identifiers[lvalue.place.identifier.0 as usize].declaration_id; - if declarations.contains_key(&decl_id) { - return Err(invariant_error_with_loc( - "Expected variable not to be defined prior to declaration", - Some(format!( - "{} was already defined", - format_place(&lvalue.place, env), - )), - lvalue.place.loc, - )); - } - declarations.insert( - decl_id, - DeclarationLoc::Instruction { - block_index, - instr_local_index: local_idx, - }, - ); - } - InstructionValue::StoreLocal { lvalue, .. } => { - let ident = &env.identifiers[lvalue.place.identifier.0 as usize]; - if ident.name.is_some() { - let decl_id = ident.declaration_id; - if let Some(existing) = declarations.get(&decl_id) { - // Reassignment: mark existing declaration as Let, current as Reassign - match existing { - DeclarationLoc::Instruction { - block_index: bi, - instr_local_index: ili, - } => { - let_locs.push((*bi, *ili)); - } - DeclarationLoc::ParamOrContext => { - // Already Let, no-op - } - } - reassign_locs.push((block_index, local_idx)); - } else { - // First store — mark as Const - // Mirrors TS: CompilerError.invariant(!declarations.has(...)) - if declarations.contains_key(&decl_id) { - return Err(invariant_error_with_loc( - "Expected variable not to be defined prior to declaration", - Some(format!( - "{} was already defined", - format_place(&lvalue.place, env), - )), - lvalue.place.loc, - )); - } - declarations.insert( - decl_id, - DeclarationLoc::Instruction { - block_index, - instr_local_index: local_idx, - }, - ); - const_locs.push((block_index, local_idx)); - } - } - } - InstructionValue::Destructure { lvalue, .. } => { - let mut kind: Option<InstructionKind> = None; - for place in each_pattern_operand(&lvalue.pattern) { - let ident = &env.identifiers[place.identifier.0 as usize]; - if ident.name.is_none() { - if !(kind.is_none() || kind == Some(InstructionKind::Const)) { - return Err(invariant_error_with_loc( - "Expected consistent kind for destructuring", - Some(format!( - "other places were `{}` but '{}' is const", - format_kind(kind), - format_place(&place, env), - )), - place.loc, - )); - } - kind = Some(InstructionKind::Const); - } else { - let decl_id = ident.declaration_id; - if let Some(existing) = declarations.get(&decl_id) { - // Reassignment - if !(kind.is_none() || kind == Some(InstructionKind::Reassign)) { - return Err(invariant_error_with_loc( - "Expected consistent kind for destructuring", - Some(format!( - "Other places were `{}` but '{}' is reassigned", - format_kind(kind), - format_place(&place, env), - )), - place.loc, - )); - } - kind = Some(InstructionKind::Reassign); - match existing { - DeclarationLoc::Instruction { - block_index: bi, - instr_local_index: ili, - } => { - let_locs.push((*bi, *ili)); - } - DeclarationLoc::ParamOrContext => { - // Already Let - } - } - } else { - // New declaration - if block_kind == BlockKind::Value { - return Err(invariant_error_with_loc( - "TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)", - None, - place.loc, - )); - } - declarations.insert( - decl_id, - DeclarationLoc::Instruction { - block_index, - instr_local_index: local_idx, - }, - ); - if !(kind.is_none() || kind == Some(InstructionKind::Const)) { - return Err(invariant_error_with_loc( - "Expected consistent kind for destructuring", - Some(format!( - "Other places were `{}` but '{}' is const", - format_kind(kind), - format_place(&place, env), - )), - place.loc, - )); - } - kind = Some(InstructionKind::Const); - } - } - } - let kind = - kind.ok_or_else(|| invariant_error("Expected at least one operand", None))?; - destructure_kind_locs.push((block_index, local_idx, kind)); - } - InstructionValue::PostfixUpdate { lvalue, .. } - | InstructionValue::PrefixUpdate { lvalue, .. } => { - let ident = &env.identifiers[lvalue.identifier.0 as usize]; - let decl_id = ident.declaration_id; - let Some(existing) = declarations.get(&decl_id) else { - return Err(invariant_error_with_loc( - "Expected variable to have been defined", - Some(format!("No declaration for {}", format_place(lvalue, env),)), - lvalue.loc, - )); - }; - match existing { - DeclarationLoc::Instruction { - block_index: bi, - instr_local_index: ili, - } => { - let_locs.push((*bi, *ili)); - } - DeclarationLoc::ParamOrContext => { - // Already Let - } - } - } - _ => {} - } - } - } - - // Phase 2: Apply all collected mutations. - - // Helper: given (block_index, instr_local_index), get the InstructionId - // and mutate the instruction's lvalue kind. - for (bi, ili) in const_locs { - let block_id = &block_keys[bi]; - let instr_id = func.body.blocks[block_id].instructions[ili]; - let instr = &mut func.instructions[instr_id.0 as usize]; - match &mut instr.value { - InstructionValue::StoreLocal { lvalue, .. } => { - lvalue.kind = InstructionKind::Const; - } - _ => {} - } - } - - for (bi, ili) in reassign_locs { - let block_id = &block_keys[bi]; - let instr_id = func.body.blocks[block_id].instructions[ili]; - let instr = &mut func.instructions[instr_id.0 as usize]; - match &mut instr.value { - InstructionValue::StoreLocal { lvalue, .. } => { - lvalue.kind = InstructionKind::Reassign; - } - _ => {} - } - } - - // Apply destructure_kind_locs BEFORE let_locs: a Destructure that first - // declares a variable gets kind=Const here, but if a later instruction - // reassigns that variable the Destructure must become Let. Applying - // let_locs afterwards allows it to override the Const set here, matching - // the TS behaviour where `declaration.kind = Let` mutates the original - // lvalue reference after the Destructure's own `lvalue.kind = kind`. - for (bi, ili, kind) in destructure_kind_locs { - let block_id = &block_keys[bi]; - let instr_id = func.body.blocks[block_id].instructions[ili]; - let instr = &mut func.instructions[instr_id.0 as usize]; - match &mut instr.value { - InstructionValue::Destructure { lvalue, .. } => { - lvalue.kind = kind; - } - _ => {} - } - } - - for (bi, ili) in let_locs { - let block_id = &block_keys[bi]; - let instr_id = func.body.blocks[block_id].instructions[ili]; - let instr = &mut func.instructions[instr_id.0 as usize]; - match &mut instr.value { - InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::StoreLocal { lvalue, .. } => { - lvalue.kind = InstructionKind::Let; - } - InstructionValue::Destructure { lvalue, .. } => { - lvalue.kind = InstructionKind::Let; - } - _ => {} - } - } - - Ok(()) -} diff --git a/compiler/crates/react_compiler_typeinference/Cargo.toml b/compiler/crates/react_compiler_typeinference/Cargo.toml deleted file mode 100644 index 79fdfe37d8ec..000000000000 --- a/compiler/crates/react_compiler_typeinference/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "react_compiler_typeinference" -version = "0.1.0" -edition = "2024" - -[dependencies] -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" } -react_compiler_hir = { path = "../react_compiler_hir" } -react_compiler_ssa = { path = "../react_compiler_ssa" } diff --git a/compiler/crates/react_compiler_typeinference/src/infer_types.rs b/compiler/crates/react_compiler_typeinference/src/infer_types.rs deleted file mode 100644 index 9cde716d5c17..000000000000 --- a/compiler/crates/react_compiler_typeinference/src/infer_types.rs +++ /dev/null @@ -1,1642 +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. - -//! Type inference pass. -//! -//! Generates type equations from the HIR, unifies them, and applies the -//! resolved types back to identifiers. Analogous to TS `InferTypes.ts`. - -use std::collections::HashMap; - -use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory}; -use react_compiler_hir::environment::{Environment, is_hook_name}; -use react_compiler_hir::object_shape::{ - BUILT_IN_ARRAY_ID, BUILT_IN_FUNCTION_ID, BUILT_IN_JSX_ID, BUILT_IN_MIXED_READONLY_ID, - BUILT_IN_OBJECT_ID, BUILT_IN_PROPS_ID, BUILT_IN_REF_VALUE_ID, BUILT_IN_SET_STATE_ID, - BUILT_IN_USE_REF_ID, ShapeRegistry, -}; -use react_compiler_hir::{ - ArrayPatternElement, BinaryOperator, FunctionId, HirFunction, Identifier, IdentifierId, - IdentifierName, InstructionId, InstructionKind, InstructionValue, JsxAttribute, - LoweredFunction, ManualMemoDependencyRoot, NonLocalBinding, ObjectPropertyKey, - ObjectPropertyOrSpread, ParamPattern, Pattern, PropertyLiteral, PropertyNameKind, - ReactFunctionType, SourceLocation, Terminal, Type, TypeId, -}; -use react_compiler_ssa::enter_ssa::placeholder_function; - -// ============================================================================= -// Public API -// ============================================================================= - -pub fn infer_types( - func: &mut HirFunction, - env: &mut Environment, -) -> Result<(), CompilerDiagnostic> { - let enable_treat_ref_like_identifiers_as_refs = - env.config.enable_treat_ref_like_identifiers_as_refs; - let enable_treat_set_identifiers_as_state_setters = - env.config.enable_treat_set_identifiers_as_state_setters; - // Pre-compute custom hook type for property resolution fallback - let custom_hook_type = env.get_custom_hook_type_opt(); - let mut unifier = Unifier::new( - enable_treat_ref_like_identifiers_as_refs, - custom_hook_type, - enable_treat_set_identifiers_as_state_setters, - ); - generate(func, env, &mut unifier)?; - - apply_function( - func, - &env.functions, - &mut env.identifiers, - &mut env.types, - &mut unifier, - ); - Ok(()) -} - -// ============================================================================= -// Helpers -// ============================================================================= - -/// Get the type for an identifier as a TypeVar referencing its type slot. -fn get_type(id: IdentifierId, identifiers: &[Identifier]) -> Type { - let type_id = identifiers[id.0 as usize].type_; - Type::TypeVar { id: type_id } -} - -/// Allocate a new TypeVar in the types arena (standalone, no &mut Environment needed). -fn make_type(types: &mut Vec<Type>) -> Type { - let id = TypeId(types.len() as u32); - types.push(Type::TypeVar { id }); - Type::TypeVar { id } -} - -/// Pre-resolve LoadGlobal types for a single function's instructions. -fn pre_resolve_globals( - func: &HirFunction, - function_key: u32, - env: &mut Environment, - global_types: &mut HashMap<(u32, InstructionId), Type>, -) { - for &instr_id in func.body.blocks.values().flat_map(|b| &b.instructions) { - let instr = &func.instructions[instr_id.0 as usize]; - if let InstructionValue::LoadGlobal { binding, loc, .. } = &instr.value { - if let Some(global_type) = env.get_global_declaration(binding, *loc).ok().flatten() { - global_types.insert((function_key, instr_id), global_type); - } - } - } -} - -/// Recursively pre-resolve LoadGlobal types for an inner function and its children. -fn pre_resolve_globals_recursive( - func_id: FunctionId, - env: &mut Environment, - global_types: &mut HashMap<(u32, InstructionId), Type>, -) { - // Collect LoadGlobal bindings and child function IDs in one pass to avoid - // borrow conflicts (we need &env.functions to read, then &mut env for - // get_global_declaration). - let inner = &env.functions[func_id.0 as usize]; - let mut load_globals: Vec<(InstructionId, NonLocalBinding, Option<SourceLocation>)> = - Vec::new(); - let mut child_func_ids: Vec<FunctionId> = Vec::new(); - - for block in inner.body.blocks.values() { - for &instr_id in &block.instructions { - let instr = &inner.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::LoadGlobal { binding, loc, .. } => { - load_globals.push((instr_id, binding.clone(), *loc)); - } - InstructionValue::FunctionExpression { - lowered_func: LoweredFunction { func: fid }, - .. - } - | InstructionValue::ObjectMethod { - lowered_func: LoweredFunction { func: fid }, - .. - } => { - child_func_ids.push(*fid); - } - _ => {} - } - } - } - - // Now resolve globals (no longer borrowing env.functions) - for (instr_id, binding, loc) in load_globals { - if let Some(global_type) = env.get_global_declaration(&binding, loc).ok().flatten() { - global_types.insert((func_id.0, instr_id), global_type); - } - } - - // Recurse into child functions - for child_id in child_func_ids { - pre_resolve_globals_recursive(child_id, env, global_types); - } -} - -fn is_primitive_binary_op(op: &BinaryOperator) -> bool { - matches!( - op, - BinaryOperator::Add - | BinaryOperator::Subtract - | BinaryOperator::Divide - | BinaryOperator::Modulo - | BinaryOperator::Multiply - | BinaryOperator::Exponent - | BinaryOperator::BitwiseAnd - | BinaryOperator::BitwiseOr - | BinaryOperator::ShiftRight - | BinaryOperator::ShiftLeft - | BinaryOperator::BitwiseXor - | BinaryOperator::GreaterThan - | BinaryOperator::LessThan - | BinaryOperator::GreaterEqual - | BinaryOperator::LessEqual - ) -} - -/// Resolve a property type from the shapes registry. -/// If `custom_hook_type` is provided and the property name looks like a hook, -/// it will be used as a fallback when no matching property is found (matching -/// TS `getPropertyType` behavior). -fn resolve_property_type( - shapes: &ShapeRegistry, - resolved_object: &Type, - property_name: &PropertyNameKind, - custom_hook_type: Option<&Type>, -) -> Option<Type> { - let shape_id = match resolved_object { - Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(), - _ => { - // No shape, but if property name is hook-like, return hook type - if let Some(hook_type) = custom_hook_type { - if let PropertyNameKind::Literal { - value: PropertyLiteral::String(s), - } = property_name - { - if is_hook_name(s) { - return Some(hook_type.clone()); - } - } - } - return None; - } - }; - let shape_id = match shape_id { - Some(id) => id, - None => { - // Object/Function with no shapeId: TS getPropertyType falls through - // to hook-name check, TS getFallthroughPropertyType returns null - if let PropertyNameKind::Literal { - value: PropertyLiteral::String(s), - } = property_name - { - if is_hook_name(s) { - return custom_hook_type.cloned(); - } - } - return None; - } - }; - let shape = shapes.get(shape_id)?; - - match property_name { - PropertyNameKind::Literal { value } => match value { - PropertyLiteral::String(s) => shape - .properties - .get(s.as_str()) - .or_else(|| shape.properties.get("*")) - .cloned() - // Hook-name fallback: if property is not found in shape but looks - // like a hook name, return the custom hook type - .or_else(|| { - if is_hook_name(s) { - custom_hook_type.cloned() - } else { - None - } - }), - PropertyLiteral::Number(_) => shape.properties.get("*").cloned(), - }, - PropertyNameKind::Computed { .. } => shape.properties.get("*").cloned(), - } -} - -/// Check if a property access looks like a ref pattern (e.g. `ref.current`, `fooRef.current`). -/// Matches TS `isRefLikeName` in InferTypes.ts. -fn is_ref_like_name(object_name: &str, property_name: &PropertyNameKind) -> bool { - let is_current = match property_name { - PropertyNameKind::Literal { - value: PropertyLiteral::String(s), - } => s == "current", - _ => false, - }; - if !is_current { - return false; - } - // Match TS regex: /^(?:[a-zA-Z$_][a-zA-Z$_0-9]*)Ref$|^ref$/ - // "Ref" alone does NOT match — requires at least one character before "Ref" - // (e.g., "fooRef", "aRef" match, but bare "Ref" does not). - object_name == "ref" - || (object_name.len() > 3 - && object_name.ends_with("Ref") - && object_name[..1] - .chars() - .next() - .is_some_and(|c| c.is_ascii_alphabetic() || c == '$' || c == '_')) -} - -/// Type equality matching TS `typeEquals`. -/// -/// Note: Function equality only compares return types (matching TS `funcTypeEquals` -/// which ignores `shapeId` and `isConstructor`). Phi equality always returns false -/// because the TS `phiTypeEquals` has a bug where `return false` is outside the -/// `if` block, so it unconditionally returns false. -fn type_equals(a: &Type, b: &Type) -> bool { - match (a, b) { - (Type::TypeVar { id: id_a }, Type::TypeVar { id: id_b }) => id_a == id_b, - (Type::Primitive, Type::Primitive) => true, - (Type::Poly, Type::Poly) => true, - (Type::ObjectMethod, Type::ObjectMethod) => true, - (Type::Object { shape_id: sa }, Type::Object { shape_id: sb }) => sa == sb, - ( - Type::Function { - return_type: ra, .. - }, - Type::Function { - return_type: rb, .. - }, - ) => type_equals(ra, rb), - _ => false, - } -} - -fn set_name(names: &mut HashMap<IdentifierId, String>, id: IdentifierId, source: &Identifier) { - if let Some(IdentifierName::Named(ref name)) = source.name { - names.insert(id, name.clone()); - } -} - -fn get_name(names: &HashMap<IdentifierId, String>, id: IdentifierId) -> String { - names.get(&id).cloned().unwrap_or_default() -} - -// ============================================================================= -// Generate equations -// ============================================================================= - -/// Generate type equations from a top-level function. -/// -/// Takes `&mut Environment` for convenience. Inner functions use -/// `generate_for_function_id` with split borrows instead, because the -/// take/replace pattern on `env.functions` requires separate `&mut` access -/// to different fields. -fn generate( - func: &HirFunction, - env: &mut Environment, - unifier: &mut Unifier, -) -> Result<(), CompilerDiagnostic> { - // Component params - if func.fn_type == ReactFunctionType::Component { - if let Some(first) = func.params.first() { - if let ParamPattern::Place(place) = first { - let ty = get_type(place.identifier, &env.identifiers); - unifier.unify( - ty, - Type::Object { - shape_id: Some(BUILT_IN_PROPS_ID.to_string()), - }, - &env.shapes, - )?; - } - } - if let Some(second) = func.params.get(1) { - if let ParamPattern::Place(place) = second { - let ty = get_type(place.identifier, &env.identifiers); - unifier.unify( - ty, - Type::Object { - shape_id: Some(BUILT_IN_USE_REF_ID.to_string()), - }, - &env.shapes, - )?; - } - } - } - - // Pre-resolve LoadGlobal types for all functions (outer + inner). We do - // this before the instruction loop because get_global_declaration needs - // &mut env, but generate_instruction_types takes split borrows on env fields. - // The key is (function_key, InstructionId) where function_key is u32::MAX - // for the outer function and FunctionId.0 for inner functions. - let mut global_types: HashMap<(u32, InstructionId), Type> = HashMap::new(); - pre_resolve_globals(func, u32::MAX, env, &mut global_types); - // Also pre-resolve inner functions recursively - for &instr_id in func.body.blocks.values().flat_map(|b| &b.instructions) { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::FunctionExpression { - lowered_func: LoweredFunction { func: func_id }, - .. - } - | InstructionValue::ObjectMethod { - lowered_func: LoweredFunction { func: func_id }, - .. - } => { - pre_resolve_globals_recursive(*func_id, env, &mut global_types); - } - _ => {} - } - } - - let mut names: HashMap<IdentifierId, String> = HashMap::new(); - let mut return_types: Vec<Type> = Vec::new(); - - for (_block_id, block) in &func.body.blocks { - // Phis - for phi in &block.phis { - let left = get_type(phi.place.identifier, &env.identifiers); - let operands: Vec<Type> = phi - .operands - .values() - .map(|p| get_type(p.identifier, &env.identifiers)) - .collect(); - unifier.unify(left, Type::Phi { operands }, &env.shapes)?; - } - - // Instructions — use split borrows: &env.identifiers, &env.shapes - // are immutable, while &mut env.types and &mut env.functions are mutable. - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - generate_instruction_types( - instr, - instr_id, - u32::MAX, - &env.identifiers, - &mut env.types, - &mut env.functions, - &mut names, - &global_types, - &env.shapes, - unifier, - )?; - } - - // Return terminals - if let Terminal::Return { ref value, .. } = block.terminal { - return_types.push(get_type(value.identifier, &env.identifiers)); - } - } - - // Unify return types - let returns_type = get_type(func.returns.identifier, &env.identifiers); - if return_types.len() > 1 { - unifier.unify( - returns_type, - Type::Phi { - operands: return_types, - }, - &env.shapes, - )?; - } else if return_types.len() == 1 { - unifier.unify( - returns_type, - return_types.into_iter().next().unwrap(), - &env.shapes, - )?; - } - Ok(()) -} - -/// Recursively generate equations for an inner function (accessed via FunctionId). -fn generate_for_function_id( - func_id: FunctionId, - identifiers: &[Identifier], - types: &mut Vec<Type>, - functions: &mut Vec<HirFunction>, - global_types: &HashMap<(u32, InstructionId), Type>, - shapes: &ShapeRegistry, - unifier: &mut Unifier, -) -> Result<(), CompilerDiagnostic> { - // Take the function out temporarily to avoid borrow conflicts - let inner = std::mem::replace(&mut functions[func_id.0 as usize], placeholder_function()); - - // Process params for component inner functions - if inner.fn_type == ReactFunctionType::Component { - if let Some(first) = inner.params.first() { - if let ParamPattern::Place(place) = first { - let ty = get_type(place.identifier, identifiers); - unifier.unify( - ty, - Type::Object { - shape_id: Some(BUILT_IN_PROPS_ID.to_string()), - }, - shapes, - )?; - } - } - if let Some(second) = inner.params.get(1) { - if let ParamPattern::Place(place) = second { - let ty = get_type(place.identifier, identifiers); - unifier.unify( - ty, - Type::Object { - shape_id: Some(BUILT_IN_USE_REF_ID.to_string()), - }, - shapes, - )?; - } - } - } - - // TS creates a fresh `names` Map per recursive `generate` call, so inner - // functions don't inherit or pollute the outer function's name mappings. - let mut inner_names: HashMap<IdentifierId, String> = HashMap::new(); - let mut inner_return_types: Vec<Type> = Vec::new(); - - for (_block_id, block) in &inner.body.blocks { - for phi in &block.phis { - let left = get_type(phi.place.identifier, identifiers); - let operands: Vec<Type> = phi - .operands - .values() - .map(|p| get_type(p.identifier, identifiers)) - .collect(); - unifier.unify(left, Type::Phi { operands }, shapes)?; - } - - for &instr_id in &block.instructions { - let instr = &inner.instructions[instr_id.0 as usize]; - generate_instruction_types( - instr, - instr_id, - func_id.0, - identifiers, - types, - functions, - &mut inner_names, - global_types, - shapes, - unifier, - )?; - } - - if let Terminal::Return { ref value, .. } = block.terminal { - inner_return_types.push(get_type(value.identifier, identifiers)); - } - } - - let returns_type = get_type(inner.returns.identifier, identifiers); - if inner_return_types.len() > 1 { - unifier.unify( - returns_type, - Type::Phi { - operands: inner_return_types, - }, - shapes, - )?; - } else if inner_return_types.len() == 1 { - unifier.unify( - returns_type, - inner_return_types.into_iter().next().unwrap(), - shapes, - )?; - } - - // Put the function back - functions[func_id.0 as usize] = inner; - Ok(()) -} - -fn generate_instruction_types( - instr: &react_compiler_hir::Instruction, - instr_id: InstructionId, - function_key: u32, - identifiers: &[Identifier], - types: &mut Vec<Type>, - functions: &mut Vec<HirFunction>, - names: &mut HashMap<IdentifierId, String>, - global_types: &HashMap<(u32, InstructionId), Type>, - shapes: &ShapeRegistry, - unifier: &mut Unifier, -) -> Result<(), CompilerDiagnostic> { - let left = get_type(instr.lvalue.identifier, identifiers); - - match &instr.value { - InstructionValue::TemplateLiteral { .. } - | InstructionValue::JSXText { .. } - | InstructionValue::Primitive { .. } => { - unifier.unify(left, Type::Primitive, shapes)?; - } - - InstructionValue::UnaryExpression { .. } => { - unifier.unify(left, Type::Primitive, shapes)?; - } - - InstructionValue::LoadLocal { place, .. } => { - set_name( - names, - instr.lvalue.identifier, - &identifiers[place.identifier.0 as usize], - ); - let place_type = get_type(place.identifier, identifiers); - unifier.unify(left, place_type, shapes)?; - } - - InstructionValue::DeclareContext { .. } | InstructionValue::LoadContext { .. } => { - // Intentionally skip type inference for most context variables - } - - InstructionValue::StoreContext { lvalue, value, .. } => { - if lvalue.kind == InstructionKind::Const { - let lvalue_type = get_type(lvalue.place.identifier, identifiers); - let value_type = get_type(value.identifier, identifiers); - unifier.unify(lvalue_type, value_type, shapes)?; - } - } - - InstructionValue::StoreLocal { lvalue, value, .. } => { - let value_type = get_type(value.identifier, identifiers); - unifier.unify(left, value_type.clone(), shapes)?; - let lvalue_type = get_type(lvalue.place.identifier, identifiers); - unifier.unify(lvalue_type, value_type, shapes)?; - } - - InstructionValue::StoreGlobal { value, .. } => { - let value_type = get_type(value.identifier, identifiers); - unifier.unify(left, value_type, shapes)?; - } - - InstructionValue::BinaryExpression { - operator, - left: bin_left, - right: bin_right, - .. - } => { - if is_primitive_binary_op(operator) { - let left_operand_type = get_type(bin_left.identifier, identifiers); - unifier.unify(left_operand_type, Type::Primitive, shapes)?; - let right_operand_type = get_type(bin_right.identifier, identifiers); - unifier.unify(right_operand_type, Type::Primitive, shapes)?; - } - unifier.unify(left, Type::Primitive, shapes)?; - } - - InstructionValue::PostfixUpdate { value, lvalue, .. } - | InstructionValue::PrefixUpdate { value, lvalue, .. } => { - let value_type = get_type(value.identifier, identifiers); - unifier.unify(value_type, Type::Primitive, shapes)?; - let lvalue_type = get_type(lvalue.identifier, identifiers); - unifier.unify(lvalue_type, Type::Primitive, shapes)?; - unifier.unify(left, Type::Primitive, shapes)?; - } - - InstructionValue::LoadGlobal { .. } => { - // Type was pre-resolved in generate() via env.get_global_declaration() - if let Some(global_type) = global_types.get(&(function_key, instr_id)) { - unifier.unify(left, global_type.clone(), shapes)?; - } - } - - InstructionValue::CallExpression { callee, .. } => { - let return_type = make_type(types); - let mut shape_id = None; - if unifier.enable_treat_set_identifiers_as_state_setters { - let name = get_name(names, callee.identifier); - if name.starts_with("set") { - shape_id = Some(BUILT_IN_SET_STATE_ID.to_string()); - } - } - let callee_type = get_type(callee.identifier, identifiers); - unifier.unify( - callee_type, - Type::Function { - shape_id, - return_type: Box::new(return_type.clone()), - is_constructor: false, - }, - shapes, - )?; - unifier.unify(left, return_type, shapes)?; - } - - InstructionValue::TaggedTemplateExpression { tag, .. } => { - let return_type = make_type(types); - let tag_type = get_type(tag.identifier, identifiers); - unifier.unify( - tag_type, - Type::Function { - shape_id: None, - return_type: Box::new(return_type.clone()), - is_constructor: false, - }, - shapes, - )?; - unifier.unify(left, return_type, shapes)?; - } - - InstructionValue::ObjectExpression { properties, .. } => { - for prop in properties { - if let ObjectPropertyOrSpread::Property(obj_prop) = prop { - if let ObjectPropertyKey::Computed { name } = &obj_prop.key { - let name_type = get_type(name.identifier, identifiers); - unifier.unify(name_type, Type::Primitive, shapes)?; - } - } - } - unifier.unify( - left, - Type::Object { - shape_id: Some(BUILT_IN_OBJECT_ID.to_string()), - }, - shapes, - )?; - } - - InstructionValue::ArrayExpression { .. } => { - unifier.unify( - left, - Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - shapes, - )?; - } - - InstructionValue::PropertyLoad { - object, property, .. - } => { - let object_type = get_type(object.identifier, identifiers); - let object_name = get_name(names, object.identifier); - unifier.unify( - left, - Type::Property { - object_type: Box::new(object_type), - object_name, - property_name: PropertyNameKind::Literal { - value: property.clone(), - }, - }, - shapes, - )?; - } - - InstructionValue::ComputedLoad { - object, property, .. - } => { - let object_type = get_type(object.identifier, identifiers); - let object_name = get_name(names, object.identifier); - let prop_type = get_type(property.identifier, identifiers); - unifier.unify( - left, - Type::Property { - object_type: Box::new(object_type), - object_name, - property_name: PropertyNameKind::Computed { - value: Box::new(prop_type), - }, - }, - shapes, - )?; - } - - InstructionValue::MethodCall { property, .. } => { - let return_type = make_type(types); - let prop_type = get_type(property.identifier, identifiers); - unifier.unify( - prop_type, - Type::Function { - return_type: Box::new(return_type.clone()), - shape_id: None, - is_constructor: false, - }, - shapes, - )?; - unifier.unify(left, return_type, shapes)?; - } - - InstructionValue::Destructure { lvalue, value, .. } => match &lvalue.pattern { - Pattern::Array(array_pattern) => { - for (i, item) in array_pattern.items.iter().enumerate() { - match item { - ArrayPatternElement::Place(place) => { - let item_type = get_type(place.identifier, identifiers); - let value_type = get_type(value.identifier, identifiers); - let object_name = get_name(names, value.identifier); - unifier.unify( - item_type, - Type::Property { - object_type: Box::new(value_type), - object_name, - property_name: PropertyNameKind::Literal { - value: PropertyLiteral::String(i.to_string()), - }, - }, - shapes, - )?; - } - ArrayPatternElement::Spread(spread) => { - let spread_type = get_type(spread.place.identifier, identifiers); - unifier.unify( - spread_type, - Type::Object { - shape_id: Some(BUILT_IN_ARRAY_ID.to_string()), - }, - shapes, - )?; - } - ArrayPatternElement::Hole => { - continue; - } - } - } - } - Pattern::Object(object_pattern) => { - for prop in &object_pattern.properties { - if let ObjectPropertyOrSpread::Property(obj_prop) = prop { - match &obj_prop.key { - ObjectPropertyKey::Identifier { name } - | ObjectPropertyKey::String { name } => { - let prop_place_type = - get_type(obj_prop.place.identifier, identifiers); - let value_type = get_type(value.identifier, identifiers); - let object_name = get_name(names, value.identifier); - unifier.unify( - prop_place_type, - Type::Property { - object_type: Box::new(value_type), - object_name, - property_name: PropertyNameKind::Literal { - value: PropertyLiteral::String(name.clone()), - }, - }, - shapes, - )?; - } - _ => {} - } - } - } - } - }, - - InstructionValue::TypeCastExpression { value, .. } => { - let value_type = get_type(value.identifier, identifiers); - unifier.unify(left, value_type, shapes)?; - } - - InstructionValue::PropertyDelete { .. } | InstructionValue::ComputedDelete { .. } => { - unifier.unify(left, Type::Primitive, shapes)?; - } - - InstructionValue::FunctionExpression { - lowered_func: LoweredFunction { func: func_id }, - .. - } => { - // Recurse into inner function first - generate_for_function_id( - *func_id, - identifiers, - types, - functions, - global_types, - shapes, - unifier, - )?; - // Get the inner function's return type - let inner_func = &functions[func_id.0 as usize]; - let inner_return_type = get_type(inner_func.returns.identifier, identifiers); - unifier.unify( - left, - Type::Function { - shape_id: Some(BUILT_IN_FUNCTION_ID.to_string()), - return_type: Box::new(inner_return_type), - is_constructor: false, - }, - shapes, - )?; - } - - InstructionValue::NextPropertyOf { .. } => { - unifier.unify(left, Type::Primitive, shapes)?; - } - - InstructionValue::ObjectMethod { - lowered_func: LoweredFunction { func: func_id }, - .. - } => { - generate_for_function_id( - *func_id, - identifiers, - types, - functions, - global_types, - shapes, - unifier, - )?; - unifier.unify(left, Type::ObjectMethod, shapes)?; - } - - InstructionValue::JsxExpression { props, .. } => { - if unifier.enable_treat_ref_like_identifiers_as_refs { - for prop in props { - if let JsxAttribute::Attribute { name, place } = prop { - if name == "ref" { - let ref_type = get_type(place.identifier, identifiers); - unifier.unify( - ref_type, - Type::Object { - shape_id: Some(BUILT_IN_USE_REF_ID.to_string()), - }, - shapes, - )?; - } - } - } - } - unifier.unify( - left, - Type::Object { - shape_id: Some(BUILT_IN_JSX_ID.to_string()), - }, - shapes, - )?; - } - - InstructionValue::JsxFragment { .. } => { - unifier.unify( - left, - Type::Object { - shape_id: Some(BUILT_IN_JSX_ID.to_string()), - }, - shapes, - )?; - } - - InstructionValue::NewExpression { callee, .. } => { - let return_type = make_type(types); - let callee_type = get_type(callee.identifier, identifiers); - unifier.unify( - callee_type, - Type::Function { - return_type: Box::new(return_type.clone()), - shape_id: None, - is_constructor: true, - }, - shapes, - )?; - unifier.unify(left, return_type, shapes)?; - } - - InstructionValue::PropertyStore { - object, property, .. - } => { - let dummy = make_type(types); - let object_type = get_type(object.identifier, identifiers); - let object_name = get_name(names, object.identifier); - unifier.unify( - dummy, - Type::Property { - object_type: Box::new(object_type), - object_name, - property_name: PropertyNameKind::Literal { - value: property.clone(), - }, - }, - shapes, - )?; - } - - InstructionValue::DeclareLocal { .. } - | InstructionValue::RegExpLiteral { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::ComputedStore { .. } - | InstructionValue::Await { .. } - | InstructionValue::GetIterator { .. } - | InstructionValue::IteratorNext { .. } - | InstructionValue::UnsupportedNode { .. } - | InstructionValue::Debugger { .. } - | InstructionValue::FinishMemoize { .. } => { - // No type equations for these - } - - InstructionValue::StartMemoize { .. } => { - // No type equations for StartMemoize itself - } - } - Ok(()) -} - -// ============================================================================= -// Apply resolved types -// ============================================================================= - -fn apply_function( - func: &HirFunction, - functions: &[HirFunction], - identifiers: &mut [Identifier], - types: &mut Vec<Type>, - unifier: &Unifier, -) { - for (_block_id, block) in &func.body.blocks { - // Phi places - for phi in &block.phis { - resolve_identifier(phi.place.identifier, identifiers, types, unifier); - } - - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - - // Instruction lvalue - resolve_identifier(instr.lvalue.identifier, identifiers, types, unifier); - - // LValues from instruction values (StoreLocal, StoreContext, DeclareLocal, DeclareContext, Destructure) - apply_instruction_lvalues(&instr.value, identifiers, types, unifier); - - // Operands - apply_instruction_operands(&instr.value, identifiers, types, unifier); - - // Recurse into inner functions - match &instr.value { - InstructionValue::FunctionExpression { - lowered_func: LoweredFunction { func: func_id }, - .. - } - | InstructionValue::ObjectMethod { - lowered_func: LoweredFunction { func: func_id }, - .. - } => { - let inner_func = &functions[func_id.0 as usize]; - // Resolve types for captured context variable places (matching TS - // where eachInstructionValueOperand yields func.context places) - for ctx in &inner_func.context { - resolve_identifier(ctx.identifier, identifiers, types, unifier); - } - apply_function(inner_func, functions, identifiers, types, unifier); - } - _ => {} - } - } - } - - // Resolve return type - resolve_identifier(func.returns.identifier, identifiers, types, unifier); -} - -fn resolve_identifier( - id: IdentifierId, - identifiers: &mut [Identifier], - types: &mut Vec<Type>, - unifier: &Unifier, -) { - let type_id = identifiers[id.0 as usize].type_; - let current_type = types[type_id.0 as usize].clone(); - let resolved = unifier.get(¤t_type); - types[type_id.0 as usize] = resolved; -} - -/// Resolve types for instruction lvalues (mirrors TS eachInstructionLValue). -fn apply_instruction_lvalues( - value: &InstructionValue, - identifiers: &mut [Identifier], - types: &mut Vec<Type>, - unifier: &Unifier, -) { - match value { - InstructionValue::StoreLocal { lvalue, .. } - | InstructionValue::StoreContext { lvalue, .. } => { - resolve_identifier(lvalue.place.identifier, identifiers, types, unifier); - } - InstructionValue::DeclareLocal { lvalue, .. } - | InstructionValue::DeclareContext { lvalue, .. } => { - resolve_identifier(lvalue.place.identifier, identifiers, types, unifier); - } - InstructionValue::Destructure { lvalue, .. } => match &lvalue.pattern { - Pattern::Array(array_pattern) => { - for item in &array_pattern.items { - match item { - ArrayPatternElement::Place(place) => { - resolve_identifier(place.identifier, identifiers, types, unifier); - } - ArrayPatternElement::Spread(spread) => { - resolve_identifier( - spread.place.identifier, - identifiers, - types, - unifier, - ); - } - ArrayPatternElement::Hole => {} - } - } - } - Pattern::Object(object_pattern) => { - for prop in &object_pattern.properties { - match prop { - ObjectPropertyOrSpread::Property(obj_prop) => { - resolve_identifier( - obj_prop.place.identifier, - identifiers, - types, - unifier, - ); - } - ObjectPropertyOrSpread::Spread(spread) => { - resolve_identifier( - spread.place.identifier, - identifiers, - types, - unifier, - ); - } - } - } - } - }, - _ => {} - } -} - -/// Resolve types for instruction operands (mirrors TS eachInstructionOperand). -fn apply_instruction_operands( - value: &InstructionValue, - identifiers: &mut [Identifier], - types: &mut Vec<Type>, - unifier: &Unifier, -) { - match value { - InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => { - resolve_identifier(place.identifier, identifiers, types, unifier); - } - InstructionValue::StoreLocal { value: val, .. } => { - resolve_identifier(val.identifier, identifiers, types, unifier); - } - InstructionValue::StoreContext { value: val, .. } => { - resolve_identifier(val.identifier, identifiers, types, unifier); - } - InstructionValue::StoreGlobal { value: val, .. } => { - resolve_identifier(val.identifier, identifiers, types, unifier); - } - InstructionValue::Destructure { value: val, .. } => { - resolve_identifier(val.identifier, identifiers, types, unifier); - } - InstructionValue::BinaryExpression { left, right, .. } => { - resolve_identifier(left.identifier, identifiers, types, unifier); - resolve_identifier(right.identifier, identifiers, types, unifier); - } - InstructionValue::UnaryExpression { value: val, .. } => { - resolve_identifier(val.identifier, identifiers, types, unifier); - } - InstructionValue::TypeCastExpression { value: val, .. } => { - resolve_identifier(val.identifier, identifiers, types, unifier); - } - InstructionValue::CallExpression { callee, args, .. } => { - resolve_identifier(callee.identifier, identifiers, types, unifier); - for arg in args { - match arg { - react_compiler_hir::PlaceOrSpread::Place(p) => { - resolve_identifier(p.identifier, identifiers, types, unifier); - } - react_compiler_hir::PlaceOrSpread::Spread(s) => { - resolve_identifier(s.place.identifier, identifiers, types, unifier); - } - } - } - } - InstructionValue::MethodCall { - receiver, - property, - args, - .. - } => { - resolve_identifier(receiver.identifier, identifiers, types, unifier); - resolve_identifier(property.identifier, identifiers, types, unifier); - for arg in args { - match arg { - react_compiler_hir::PlaceOrSpread::Place(p) => { - resolve_identifier(p.identifier, identifiers, types, unifier); - } - react_compiler_hir::PlaceOrSpread::Spread(s) => { - resolve_identifier(s.place.identifier, identifiers, types, unifier); - } - } - } - } - InstructionValue::NewExpression { callee, args, .. } => { - resolve_identifier(callee.identifier, identifiers, types, unifier); - for arg in args { - match arg { - react_compiler_hir::PlaceOrSpread::Place(p) => { - resolve_identifier(p.identifier, identifiers, types, unifier); - } - react_compiler_hir::PlaceOrSpread::Spread(s) => { - resolve_identifier(s.place.identifier, identifiers, types, unifier); - } - } - } - } - InstructionValue::TaggedTemplateExpression { tag, .. } => { - resolve_identifier(tag.identifier, identifiers, types, unifier); - // The template quasi's subexpressions are not separate operands in this HIR - } - InstructionValue::PropertyLoad { object, .. } => { - resolve_identifier(object.identifier, identifiers, types, unifier); - } - InstructionValue::PropertyStore { - object, value: val, .. - } => { - resolve_identifier(object.identifier, identifiers, types, unifier); - resolve_identifier(val.identifier, identifiers, types, unifier); - } - InstructionValue::PropertyDelete { object, .. } => { - resolve_identifier(object.identifier, identifiers, types, unifier); - } - InstructionValue::ComputedLoad { - object, property, .. - } => { - resolve_identifier(object.identifier, identifiers, types, unifier); - resolve_identifier(property.identifier, identifiers, types, unifier); - } - InstructionValue::ComputedStore { - object, - property, - value: val, - .. - } => { - resolve_identifier(object.identifier, identifiers, types, unifier); - resolve_identifier(property.identifier, identifiers, types, unifier); - resolve_identifier(val.identifier, identifiers, types, unifier); - } - InstructionValue::ComputedDelete { - object, property, .. - } => { - resolve_identifier(object.identifier, identifiers, types, unifier); - resolve_identifier(property.identifier, identifiers, types, unifier); - } - InstructionValue::ObjectExpression { properties, .. } => { - for prop in properties { - match prop { - ObjectPropertyOrSpread::Property(obj_prop) => { - resolve_identifier(obj_prop.place.identifier, identifiers, types, unifier); - if let ObjectPropertyKey::Computed { name } = &obj_prop.key { - resolve_identifier(name.identifier, identifiers, types, unifier); - } - } - ObjectPropertyOrSpread::Spread(spread) => { - resolve_identifier(spread.place.identifier, identifiers, types, unifier); - } - } - } - } - InstructionValue::ArrayExpression { elements, .. } => { - for elem in elements { - match elem { - react_compiler_hir::ArrayElement::Place(p) => { - resolve_identifier(p.identifier, identifiers, types, unifier); - } - react_compiler_hir::ArrayElement::Spread(s) => { - resolve_identifier(s.place.identifier, identifiers, types, unifier); - } - react_compiler_hir::ArrayElement::Hole => {} - } - } - } - InstructionValue::JsxExpression { - tag, - props, - children, - .. - } => { - if let react_compiler_hir::JsxTag::Place(p) = tag { - resolve_identifier(p.identifier, identifiers, types, unifier); - } - for attr in props { - match attr { - JsxAttribute::Attribute { place, .. } => { - resolve_identifier(place.identifier, identifiers, types, unifier); - } - JsxAttribute::SpreadAttribute { argument } => { - resolve_identifier(argument.identifier, identifiers, types, unifier); - } - } - } - if let Some(children) = children { - for child in children { - resolve_identifier(child.identifier, identifiers, types, unifier); - } - } - } - InstructionValue::JsxFragment { children, .. } => { - for child in children { - resolve_identifier(child.identifier, identifiers, types, unifier); - } - } - InstructionValue::FunctionExpression { .. } | InstructionValue::ObjectMethod { .. } => { - // Inner functions are handled separately via recursion in apply_function - } - InstructionValue::TemplateLiteral { subexprs, .. } => { - for sub in subexprs { - resolve_identifier(sub.identifier, identifiers, types, unifier); - } - } - InstructionValue::PrefixUpdate { - value: val, lvalue, .. - } - | InstructionValue::PostfixUpdate { - value: val, lvalue, .. - } => { - resolve_identifier(val.identifier, identifiers, types, unifier); - resolve_identifier(lvalue.identifier, identifiers, types, unifier); - } - InstructionValue::Await { value: val, .. } => { - resolve_identifier(val.identifier, identifiers, types, unifier); - } - InstructionValue::GetIterator { collection, .. } => { - resolve_identifier(collection.identifier, identifiers, types, unifier); - } - InstructionValue::IteratorNext { - iterator, - collection, - .. - } => { - resolve_identifier(iterator.identifier, identifiers, types, unifier); - resolve_identifier(collection.identifier, identifiers, types, unifier); - } - InstructionValue::NextPropertyOf { value: val, .. } => { - resolve_identifier(val.identifier, identifiers, types, unifier); - } - InstructionValue::FinishMemoize { decl, .. } => { - resolve_identifier(decl.identifier, identifiers, types, unifier); - } - InstructionValue::StartMemoize { deps, .. } => { - // Resolve types for deps with NamedLocal kind (matching TS - // eachInstructionOperand which yields dep.root.value for NamedLocal deps) - if let Some(deps) = deps { - for dep in deps { - if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &dep.root { - resolve_identifier(value.identifier, identifiers, types, unifier); - } - } - } - } - InstructionValue::Primitive { .. } - | InstructionValue::JSXText { .. } - | InstructionValue::LoadGlobal { .. } - | InstructionValue::DeclareLocal { .. } - | InstructionValue::DeclareContext { .. } - | InstructionValue::RegExpLiteral { .. } - | InstructionValue::MetaProperty { .. } - | InstructionValue::Debugger { .. } - | InstructionValue::UnsupportedNode { .. } => { - // No operand places - } - } -} - -// ============================================================================= -// Unifier -// ============================================================================= - -struct Unifier { - substitutions: HashMap<TypeId, Type>, - enable_treat_ref_like_identifiers_as_refs: bool, - enable_treat_set_identifiers_as_state_setters: bool, - custom_hook_type: Option<Type>, -} - -impl Unifier { - fn new( - enable_treat_ref_like_identifiers_as_refs: bool, - custom_hook_type: Option<Type>, - enable_treat_set_identifiers_as_state_setters: bool, - ) -> Self { - Unifier { - substitutions: HashMap::new(), - enable_treat_ref_like_identifiers_as_refs, - enable_treat_set_identifiers_as_state_setters, - custom_hook_type, - } - } - - fn unify( - &mut self, - t_a: Type, - t_b: Type, - shapes: &ShapeRegistry, - ) -> Result<(), CompilerDiagnostic> { - self.unify_impl(t_a, t_b, shapes) - } - - fn unify_impl( - &mut self, - t_a: Type, - t_b: Type, - shapes: &ShapeRegistry, - ) -> Result<(), CompilerDiagnostic> { - // Handle Property in the RHS position - if let Type::Property { - ref object_type, - ref object_name, - ref property_name, - } = t_b - { - // Check enableTreatRefLikeIdentifiersAsRefs - if self.enable_treat_ref_like_identifiers_as_refs - && is_ref_like_name(object_name, property_name) - { - self.unify_impl( - *object_type.clone(), - Type::Object { - shape_id: Some(BUILT_IN_USE_REF_ID.to_string()), - }, - shapes, - )?; - self.unify_impl( - t_a, - Type::Object { - shape_id: Some(BUILT_IN_REF_VALUE_ID.to_string()), - }, - shapes, - )?; - return Ok(()); - } - - // Resolve property type via the shapes registry - let resolved_object = self.get(object_type); - let property_type = resolve_property_type( - shapes, - &resolved_object, - property_name, - self.custom_hook_type.as_ref(), - ); - if let Some(property_type) = property_type { - self.unify_impl(t_a, property_type, shapes)?; - } - return Ok(()); - } - - if type_equals(&t_a, &t_b) { - return Ok(()); - } - - if let Type::TypeVar { .. } = &t_a { - self.bind_variable_to(t_a, t_b, shapes)?; - return Ok(()); - } - - if let Type::TypeVar { .. } = &t_b { - self.bind_variable_to(t_b, t_a, shapes)?; - return Ok(()); - } - - if let ( - Type::Function { - return_type: ret_a, - is_constructor: con_a, - .. - }, - Type::Function { - return_type: ret_b, - is_constructor: con_b, - .. - }, - ) = (&t_a, &t_b) - { - if con_a == con_b { - self.unify_impl(*ret_a.clone(), *ret_b.clone(), shapes)?; - } - } - Ok(()) - } - - fn bind_variable_to( - &mut self, - v: Type, - ty: Type, - shapes: &ShapeRegistry, - ) -> Result<(), CompilerDiagnostic> { - let v_id = match &v { - Type::TypeVar { id } => *id, - _ => return Ok(()), - }; - - if let Type::Poly = &ty { - // Ignore PolyType - return Ok(()); - } - - if let Some(existing) = self.substitutions.get(&v_id).cloned() { - self.unify_impl(existing, ty, shapes)?; - return Ok(()); - } - - if let Type::TypeVar { id: ty_id } = &ty { - if let Some(existing) = self.substitutions.get(ty_id).cloned() { - self.unify_impl(v, existing, shapes)?; - return Ok(()); - } - } - - if let Type::Phi { ref operands } = ty { - if operands.is_empty() { - return Err(CompilerDiagnostic { - category: ErrorCategory::Invariant, - reason: "there should be at least one operand".to_string(), - description: None, - details: vec![], - suggestions: None, - }); - } - - let mut candidate_type: Option<Type> = None; - for operand in operands { - let resolved = self.get(operand); - match &candidate_type { - None => { - candidate_type = Some(resolved); - } - Some(candidate) => { - if !type_equals(&resolved, candidate) { - let union_type = try_union_types(&resolved, candidate); - if let Some(union) = union_type { - candidate_type = Some(union); - } else { - candidate_type = None; - break; - } - } - // else same type, continue - } - } - } - - if let Some(candidate) = candidate_type { - self.unify_impl(v, candidate, shapes)?; - return Ok(()); - } - } - - if self.occurs_check(&v, &ty) { - let resolved_type = self.try_resolve_type(&v, &ty); - if let Some(resolved) = resolved_type { - self.substitutions.insert(v_id, resolved); - return Ok(()); - } - return Err(CompilerDiagnostic { - category: ErrorCategory::Invariant, - reason: "cycle detected".to_string(), - description: None, - details: vec![], - suggestions: None, - }); - } - - self.substitutions.insert(v_id, ty); - Ok(()) - } - - fn try_resolve_type(&mut self, v: &Type, ty: &Type) -> Option<Type> { - match ty { - Type::Phi { operands } => { - let mut new_operands = Vec::new(); - for operand in operands { - if let Type::TypeVar { id } = operand { - if let Type::TypeVar { id: v_id } = v { - if id == v_id { - continue; // skip self-reference - } - } - } - let resolved = self.try_resolve_type(v, operand)?; - new_operands.push(resolved); - } - Some(Type::Phi { - operands: new_operands, - }) - } - Type::TypeVar { id } => { - let substitution = self.get(ty); - if !type_equals(&substitution, ty) { - let resolved = self.try_resolve_type(v, &substitution)?; - self.substitutions.insert(*id, resolved.clone()); - Some(resolved) - } else { - Some(ty.clone()) - } - } - Type::Property { - object_type, - object_name, - property_name, - } => { - let resolved_obj = self.get(object_type); - let object_type = self.try_resolve_type(v, &resolved_obj)?; - Some(Type::Property { - object_type: Box::new(object_type), - object_name: object_name.clone(), - property_name: property_name.clone(), - }) - } - Type::Function { - shape_id, - return_type, - is_constructor, - } => { - let resolved_ret = self.get(return_type); - let return_type = self.try_resolve_type(v, &resolved_ret)?; - Some(Type::Function { - shape_id: shape_id.clone(), - return_type: Box::new(return_type), - is_constructor: *is_constructor, - }) - } - Type::ObjectMethod | Type::Object { .. } | Type::Primitive | Type::Poly => { - Some(ty.clone()) - } - } - } - - fn occurs_check(&self, v: &Type, ty: &Type) -> bool { - if type_equals(v, ty) { - return true; - } - - if let Type::TypeVar { id } = ty { - if let Some(sub) = self.substitutions.get(id) { - return self.occurs_check(v, sub); - } - } - - if let Type::Phi { operands } = ty { - return operands.iter().any(|o| self.occurs_check(v, o)); - } - - if let Type::Function { return_type, .. } = ty { - return self.occurs_check(v, return_type); - } - - false - } - - fn get(&self, ty: &Type) -> Type { - if let Type::TypeVar { id } = ty { - if let Some(sub) = self.substitutions.get(id) { - return self.get(sub); - } - } - - if let Type::Phi { operands } = ty { - return Type::Phi { - operands: operands.iter().map(|o| self.get(o)).collect(), - }; - } - - if let Type::Function { - is_constructor, - shape_id, - return_type, - } = ty - { - return Type::Function { - is_constructor: *is_constructor, - shape_id: shape_id.clone(), - return_type: Box::new(self.get(return_type)), - }; - } - - ty.clone() - } -} - -// ============================================================================= -// Union types helper -// ============================================================================= - -fn try_union_types(ty1: &Type, ty2: &Type) -> Option<Type> { - let (readonly_type, other_type) = if matches!(ty1, Type::Object { shape_id } if shape_id.as_deref() == Some(BUILT_IN_MIXED_READONLY_ID)) - { - (ty1, ty2) - } else if matches!(ty2, Type::Object { shape_id } if shape_id.as_deref() == Some(BUILT_IN_MIXED_READONLY_ID)) - { - (ty2, ty1) - } else { - return None; - }; - - if matches!(other_type, Type::Primitive) { - // Union(Primitive | MixedReadonly) = MixedReadonly - return Some(readonly_type.clone()); - } else if matches!(other_type, Type::Object { shape_id } if shape_id.as_deref() == Some(BUILT_IN_ARRAY_ID)) - { - // Union(Array | MixedReadonly) = Array - return Some(other_type.clone()); - } - - None -} diff --git a/compiler/crates/react_compiler_typeinference/src/lib.rs b/compiler/crates/react_compiler_typeinference/src/lib.rs deleted file mode 100644 index d4fe86603374..000000000000 --- a/compiler/crates/react_compiler_typeinference/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod infer_types; - -pub use infer_types::infer_types; diff --git a/compiler/crates/react_compiler_utils/Cargo.toml b/compiler/crates/react_compiler_utils/Cargo.toml deleted file mode 100644 index 06b93a5b9d39..000000000000 --- a/compiler/crates/react_compiler_utils/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "react_compiler_utils" -version = "0.1.0" -edition = "2024" - -[dependencies] -indexmap = "2" diff --git a/compiler/crates/react_compiler_utils/src/disjoint_set.rs b/compiler/crates/react_compiler_utils/src/disjoint_set.rs deleted file mode 100644 index fc8758a35a0d..000000000000 --- a/compiler/crates/react_compiler_utils/src/disjoint_set.rs +++ /dev/null @@ -1,148 +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. - -//! A generic disjoint-set (union-find) data structure. -//! -//! Ported from TypeScript `src/Utils/DisjointSet.ts`. - -use std::collections::HashSet; -use std::hash::Hash; - -use indexmap::IndexMap; - -/// A Union-Find data structure for grouping items into disjoint sets. -/// -/// Corresponds to TS `DisjointSet<T>` in `src/Utils/DisjointSet.ts`. -/// Uses `IndexMap` to preserve insertion order (matching TS `Map` behavior). -pub struct DisjointSet<K: Copy + Eq + Hash> { - entries: IndexMap<K, K>, -} - -impl<K: Copy + Eq + Hash> DisjointSet<K> { - pub fn new() -> Self { - DisjointSet { - entries: IndexMap::new(), - } - } - - /// Updates the graph to reflect that the given items form a set, - /// linking any previous sets that the items were part of into a single set. - /// - /// Corresponds to TS `union(items: Array<T>): void`. - pub fn union(&mut self, items: &[K]) { - if items.is_empty() { - return; - } - let root = self.find(items[0]); - for &item in &items[1..] { - let item_root = self.find(item); - if item_root != root { - self.entries.insert(item_root, root); - } - } - } - - /// Find the root of the set containing `item`, with path compression. - /// If `item` is not in the set, it is inserted as its own root. - /// - /// Note: callers that need null/None semantics for missing items should - /// use `find_opt()` instead. - pub fn find(&mut self, item: K) -> K { - let parent = match self.entries.get(&item) { - Some(&p) => p, - None => { - self.entries.insert(item, item); - return item; - } - }; - if parent == item { - return item; - } - let root = self.find(parent); - self.entries.insert(item, root); - root - } - - /// Find the root of the set containing `item`, returning `None` if the item - /// was never added to the set. - /// - /// Corresponds to TS `find(item: T): T | null`. - pub fn find_opt(&mut self, item: K) -> Option<K> { - if !self.entries.contains_key(&item) { - return None; - } - Some(self.find(item)) - } - - /// Returns true if the item is present in the set. - /// - /// Corresponds to TS `has(item: T): boolean`. - pub fn has(&self, item: K) -> bool { - self.entries.contains_key(&item) - } - - /// Forces the set into canonical form (all items pointing directly to their - /// root) and returns a map of items to their roots. - /// - /// Corresponds to TS `canonicalize(): Map<T, T>`. - pub fn canonicalize(&mut self) -> IndexMap<K, K> { - let mut result = IndexMap::new(); - let keys: Vec<K> = self.entries.keys().copied().collect(); - for item in keys { - let root = self.find(item); - result.insert(item, root); - } - result - } - - /// Calls the provided callback once for each item in the disjoint set, - /// passing the item and the group root to which it belongs. - /// - /// Corresponds to TS `forEach(fn: (item: T, group: T) => void): void`. - pub fn for_each<F>(&mut self, mut f: F) - where - F: FnMut(K, K), - { - let keys: Vec<K> = self.entries.keys().copied().collect(); - for item in keys { - let group = self.find(item); - f(item, group); - } - } - - /// Groups all items by their root and returns the groups as a list of sets. - /// - /// Corresponds to TS `buildSets(): Array<Set<T>>`. - pub fn build_sets(&mut self) -> Vec<HashSet<K>> { - let mut group_to_index: IndexMap<K, usize> = IndexMap::new(); - let mut sets: Vec<HashSet<K>> = Vec::new(); - let keys: Vec<K> = self.entries.keys().copied().collect(); - for item in keys { - let group = self.find(item); - let idx = match group_to_index.get(&group) { - Some(&idx) => idx, - None => { - let idx = sets.len(); - group_to_index.insert(group, idx); - sets.push(HashSet::new()); - idx - } - }; - sets[idx].insert(item); - } - sets - } - - /// Returns the number of items in the set. - /// - /// Corresponds to TS `get size(): number`. - pub fn len(&self) -> usize { - self.entries.len() - } - - pub fn is_empty(&self) -> bool { - self.entries.is_empty() - } -} diff --git a/compiler/crates/react_compiler_utils/src/lib.rs b/compiler/crates/react_compiler_utils/src/lib.rs deleted file mode 100644 index f2944740df24..000000000000 --- a/compiler/crates/react_compiler_utils/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod disjoint_set; - -pub use disjoint_set::DisjointSet; diff --git a/compiler/crates/react_compiler_validation/Cargo.toml b/compiler/crates/react_compiler_validation/Cargo.toml deleted file mode 100644 index f30d13246cf4..000000000000 --- a/compiler/crates/react_compiler_validation/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "react_compiler_validation" -version = "0.1.0" -edition = "2024" - -[dependencies] -indexmap = "2" -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" } -react_compiler_hir = { path = "../react_compiler_hir" } diff --git a/compiler/crates/react_compiler_validation/src/lib.rs b/compiler/crates/react_compiler_validation/src/lib.rs deleted file mode 100644 index f6afec6b69d1..000000000000 --- a/compiler/crates/react_compiler_validation/src/lib.rs +++ /dev/null @@ -1,32 +0,0 @@ -pub mod validate_context_variable_lvalues; -pub mod validate_exhaustive_dependencies; -pub mod validate_hooks_usage; -pub mod validate_locals_not_reassigned_after_render; -pub mod validate_no_capitalized_calls; -pub mod validate_no_derived_computations_in_effects; -pub mod validate_no_freezing_known_mutable_functions; -pub mod validate_no_jsx_in_try_statement; -pub mod validate_no_ref_access_in_render; -pub mod validate_no_set_state_in_effects; -pub mod validate_no_set_state_in_render; -pub mod validate_preserved_manual_memoization; -pub mod validate_static_components; -pub mod validate_use_memo; - -pub use validate_context_variable_lvalues::{ - validate_context_variable_lvalues, validate_context_variable_lvalues_with_errors, -}; -pub use validate_exhaustive_dependencies::validate_exhaustive_dependencies; -pub use validate_hooks_usage::validate_hooks_usage; -pub use validate_locals_not_reassigned_after_render::validate_locals_not_reassigned_after_render; -pub use validate_no_capitalized_calls::validate_no_capitalized_calls; -pub use validate_no_derived_computations_in_effects::validate_no_derived_computations_in_effects; -pub use validate_no_derived_computations_in_effects::validate_no_derived_computations_in_effects_exp; -pub use validate_no_freezing_known_mutable_functions::validate_no_freezing_known_mutable_functions; -pub use validate_no_jsx_in_try_statement::validate_no_jsx_in_try_statement; -pub use validate_no_ref_access_in_render::validate_no_ref_access_in_render; -pub use validate_no_set_state_in_effects::validate_no_set_state_in_effects; -pub use validate_no_set_state_in_render::validate_no_set_state_in_render; -pub use validate_preserved_manual_memoization::validate_preserved_manual_memoization; -pub use validate_static_components::validate_static_components; -pub use validate_use_memo::validate_use_memo; diff --git a/compiler/crates/react_compiler_validation/src/validate_context_variable_lvalues.rs b/compiler/crates/react_compiler_validation/src/validate_context_variable_lvalues.rs deleted file mode 100644 index 82a3e280a29b..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_context_variable_lvalues.rs +++ /dev/null @@ -1,241 +0,0 @@ -use std::collections::HashMap; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory, -}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors::{each_instruction_value_lvalue, each_pattern_operand}; -use react_compiler_hir::{ - FunctionId, HirFunction, Identifier, IdentifierId, InstructionValue, Place, -}; - -/// Variable reference kind: local, context, or destructure. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum VarRefKind { - Local, - Context, - Destructure, -} - -impl std::fmt::Display for VarRefKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - VarRefKind::Local => write!(f, "local"), - VarRefKind::Context => write!(f, "context"), - VarRefKind::Destructure => write!(f, "destructure"), - } - } -} - -type IdentifierKinds = HashMap<IdentifierId, (Place, VarRefKind)>; - -/// Validates that context variable lvalues are used consistently. -/// -/// Port of ValidateContextVariableLValues.ts -pub fn validate_context_variable_lvalues( - func: &HirFunction, - env: &mut Environment, -) -> Result<(), CompilerDiagnostic> { - validate_context_variable_lvalues_with_errors( - func, - &env.functions, - &env.identifiers, - &mut env.errors, - ) -} - -/// Like [`validate_context_variable_lvalues`], but writes diagnostics into the -/// provided `errors` instead of `env.errors`. Useful when the caller wants to -/// discard the diagnostics (e.g. when lowering is incomplete). -pub fn validate_context_variable_lvalues_with_errors( - func: &HirFunction, - functions: &[HirFunction], - identifiers: &[Identifier], - errors: &mut CompilerError, -) -> Result<(), CompilerDiagnostic> { - let mut identifier_kinds: IdentifierKinds = HashMap::new(); - validate_context_variable_lvalues_impl( - func, - &mut identifier_kinds, - functions, - identifiers, - errors, - ) -} - -fn validate_context_variable_lvalues_impl( - func: &HirFunction, - identifier_kinds: &mut IdentifierKinds, - functions: &[HirFunction], - identifiers: &[Identifier], - errors: &mut CompilerError, -) -> Result<(), CompilerDiagnostic> { - let mut inner_function_ids: Vec<FunctionId> = Vec::new(); - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let value = &instr.value; - - match value { - InstructionValue::DeclareContext { lvalue, .. } - | InstructionValue::StoreContext { lvalue, .. } => { - visit( - identifier_kinds, - &lvalue.place, - VarRefKind::Context, - identifiers, - errors, - )?; - } - InstructionValue::LoadContext { place, .. } => { - visit( - identifier_kinds, - place, - VarRefKind::Context, - identifiers, - errors, - )?; - } - InstructionValue::StoreLocal { lvalue, .. } - | InstructionValue::DeclareLocal { lvalue, .. } => { - visit( - identifier_kinds, - &lvalue.place, - VarRefKind::Local, - identifiers, - errors, - )?; - } - InstructionValue::LoadLocal { place, .. } => { - visit( - identifier_kinds, - place, - VarRefKind::Local, - identifiers, - errors, - )?; - } - InstructionValue::PostfixUpdate { lvalue, .. } - | InstructionValue::PrefixUpdate { lvalue, .. } => { - visit( - identifier_kinds, - lvalue, - VarRefKind::Local, - identifiers, - errors, - )?; - } - InstructionValue::Destructure { lvalue, .. } => { - for place in each_pattern_operand(&lvalue.pattern) { - visit( - identifier_kinds, - &place, - VarRefKind::Destructure, - identifiers, - errors, - )?; - } - } - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - inner_function_ids.push(lowered_func.func); - } - _ => { - for _ in each_instruction_value_lvalue(value) { - errors.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::Todo, - "ValidateContextVariableLValues: unhandled instruction variant", - None, - ) - .with_detail( - CompilerDiagnosticDetail::Error { - loc: value.loc().copied(), - message: None, - identifier_name: None, - }, - ), - ); - } - } - } - } - } - - // Process inner functions after the block loop to avoid borrow conflicts - for func_id in inner_function_ids { - let inner_func = &functions[func_id.0 as usize]; - validate_context_variable_lvalues_impl( - inner_func, - identifier_kinds, - functions, - identifiers, - errors, - )?; - } - - Ok(()) -} - -/// Format a place like TS `printPlace()`: `<effect> <name>$<id>` -fn format_place(place: &Place, identifiers: &[Identifier]) -> String { - let id = place.identifier; - let ident = &identifiers[id.0 as usize]; - let name = match &ident.name { - Some(n) => n.value().to_string(), - None => String::new(), - }; - format!("{} {}${}", place.effect, name, id.0) -} - -fn visit( - identifiers: &mut IdentifierKinds, - place: &Place, - kind: VarRefKind, - env_identifiers: &[Identifier], - errors: &mut CompilerError, -) -> Result<(), CompilerDiagnostic> { - if let Some((prev_place, prev_kind)) = identifiers.get(&place.identifier) { - let was_context = *prev_kind == VarRefKind::Context; - let is_context = kind == VarRefKind::Context; - if was_context != is_context { - if *prev_kind == VarRefKind::Destructure || kind == VarRefKind::Destructure { - let loc = if kind == VarRefKind::Destructure { - place.loc - } else { - prev_place.loc - }; - errors.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::Todo, - "Support destructuring of context variables", - None, - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc, - message: None, - identifier_name: None, - }), - ); - return Ok(()); - } - let place_str = format_place(place, env_identifiers); - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Expected all references to a variable to be consistently local or context references", - Some(format!( - "Identifier {} is referenced as a {} variable, but was previously referenced as a {} variable", - place_str, kind, prev_kind - )), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: place.loc, - message: Some(format!("this is {}", prev_kind)), - identifier_name: None, - })); - } - } - identifiers.insert(place.identifier, (place.clone(), kind)); - Ok(()) -} diff --git a/compiler/crates/react_compiler_validation/src/validate_exhaustive_dependencies.rs b/compiler/crates/react_compiler_validation/src/validate_exhaustive_dependencies.rs deleted file mode 100644 index a0977e978ba3..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_exhaustive_dependencies.rs +++ /dev/null @@ -1,1798 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, CompilerSuggestion, CompilerSuggestionOperation, - ErrorCategory, SourceLocation, -}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::environment_config::ExhaustiveEffectDepsMode; -use react_compiler_hir::visitors::{ - each_instruction_value_lvalue, each_instruction_value_operand_with_functions, - each_terminal_operand, -}; -use react_compiler_hir::{ - ArrayElement, BlockId, DependencyPathEntry, HirFunction, Identifier, IdentifierId, - InstructionKind, InstructionValue, ManualMemoDependency, ManualMemoDependencyRoot, - NonLocalBinding, ParamPattern, Place, PlaceOrSpread, PropertyLiteral, Terminal, Type, -}; - -/// Port of ValidateExhaustiveDependencies.ts -/// -/// Validates that existing manual memoization is exhaustive and does not -/// have extraneous dependencies. The goal is to ensure auto-memoization -/// will not substantially change program behavior. -/// -/// Note: takes `&mut HirFunction` (deviating from the read-only validation convention) -/// because it sets `has_invalid_deps` on StartMemoize instructions when validation -/// errors are found, so that ValidatePreservedManualMemoization can skip those blocks. -pub fn validate_exhaustive_dependencies( - func: &mut HirFunction, - env: &mut Environment, -) -> Result<(), CompilerDiagnostic> { - let reactive = collect_reactive_identifiers(func, &env.functions); - let validate_memo = env.config.validate_exhaustive_memoization_dependencies; - let validate_effect = env.config.validate_exhaustive_effect_dependencies.clone(); - - let mut temporaries: HashMap<IdentifierId, Temporary> = HashMap::new(); - for param in &func.params { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - temporaries.insert( - place.identifier, - Temporary::Local { - identifier: place.identifier, - path: Vec::new(), - context: false, - loc: place.loc, - }, - ); - } - - let mut start_memo: Option<StartMemoInfo> = None; - let mut memo_locals: HashSet<IdentifierId> = HashSet::new(); - - // Callbacks struct holding the mutable state - let mut callbacks = Callbacks { - start_memo: &mut start_memo, - memo_locals: &mut memo_locals, - validate_memo, - validate_effect: validate_effect.clone(), - reactive: &reactive, - diagnostics: Vec::new(), - invalid_memo_ids: HashSet::new(), - }; - - collect_dependencies( - func, - &env.identifiers, - &env.types, - &env.functions, - &mut temporaries, - &mut Some(&mut callbacks), - false, - )?; - - // Set has_invalid_deps on StartMemoize instructions that had validation errors - if !callbacks.invalid_memo_ids.is_empty() { - for instr in func.instructions.iter_mut() { - if let InstructionValue::StartMemoize { - manual_memo_id, - has_invalid_deps, - .. - } = &mut instr.value - { - if callbacks.invalid_memo_ids.contains(manual_memo_id) { - *has_invalid_deps = true; - } - } - } - } - - // Record all diagnostics on the environment - for diagnostic in callbacks.diagnostics { - env.record_diagnostic(diagnostic); - } - Ok(()) -} - -// ============================================================================= -// Internal types -// ============================================================================= - -/// Info extracted from a StartMemoize instruction -struct StartMemoInfo { - manual_memo_id: u32, - deps: Option<Vec<ManualMemoDependency>>, - deps_loc: Option<Option<SourceLocation>>, - #[allow(dead_code)] - loc: Option<SourceLocation>, -} - -/// A temporary value tracked during dependency collection -#[derive(Debug, Clone)] -enum Temporary { - Local { - identifier: IdentifierId, - path: Vec<DependencyPathEntry>, - context: bool, - loc: Option<SourceLocation>, - }, - Global { - binding: NonLocalBinding, - }, - Aggregate { - dependencies: Vec<InferredDependency>, - loc: Option<SourceLocation>, - }, -} - -/// An inferred dependency (Local or Global) -#[derive(Debug, Clone)] -enum InferredDependency { - Local { - identifier: IdentifierId, - path: Vec<DependencyPathEntry>, - #[allow(dead_code)] - context: bool, - loc: Option<SourceLocation>, - }, - Global { - binding: NonLocalBinding, - }, -} - -/// Hashable key for deduplicating inferred dependencies in a Set -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum InferredDependencyKey { - Local { - identifier: IdentifierId, - path_key: String, - }, - Global { - name: String, - }, -} - -fn dep_to_key(dep: &InferredDependency) -> InferredDependencyKey { - match dep { - InferredDependency::Local { - identifier, path, .. - } => InferredDependencyKey::Local { - identifier: *identifier, - path_key: path_to_string(path), - }, - InferredDependency::Global { binding } => InferredDependencyKey::Global { - name: binding.name().to_string(), - }, - } -} - -fn path_to_string(path: &[DependencyPathEntry]) -> String { - path.iter() - .map(|p| format!("{}{}", if p.optional { "?." } else { "." }, p.property)) - .collect::<Vec<_>>() - .join("") -} - -/// Callbacks for StartMemoize/FinishMemoize/Effect events -struct Callbacks<'a> { - start_memo: &'a mut Option<StartMemoInfo>, - #[allow(dead_code)] - memo_locals: &'a mut HashSet<IdentifierId>, - validate_memo: bool, - validate_effect: ExhaustiveEffectDepsMode, - reactive: &'a HashSet<IdentifierId>, - diagnostics: Vec<CompilerDiagnostic>, - /// manual_memo_ids that had validation errors (to set has_invalid_deps) - invalid_memo_ids: HashSet<u32>, -} - -// ============================================================================= -// Helper: type checking functions -// ============================================================================= - -fn is_effect_event_function_type(ty: &Type) -> bool { - matches!(ty, Type::Function { shape_id: Some(id), .. } if id == "BuiltInEffectEventFunction") -} - -fn is_stable_type(ty: &Type) -> bool { - match ty { - Type::Function { - shape_id: Some(id), .. - } => matches!( - id.as_str(), - "BuiltInSetState" - | "BuiltInSetActionState" - | "BuiltInDispatch" - | "BuiltInStartTransition" - | "BuiltInSetOptimistic" - ), - Type::Object { shape_id: Some(id) } => matches!(id.as_str(), "BuiltInUseRefId"), - _ => false, - } -} - -fn is_effect_hook(ty: &Type) -> bool { - matches!(ty, Type::Function { shape_id: Some(id), .. } - if id == "BuiltInUseEffectHook" - || id == "BuiltInUseLayoutEffectHook" - || id == "BuiltInUseInsertionEffectHook" - ) -} - -fn is_primitive_type(ty: &Type) -> bool { - matches!(ty, Type::Primitive) -} - -fn is_use_ref_type(ty: &Type) -> bool { - matches!(ty, Type::Object { shape_id: Some(id) } if id == "BuiltInUseRefId") -} - -fn get_identifier_type<'a>( - id: IdentifierId, - identifiers: &'a [Identifier], - types: &'a [Type], -) -> &'a Type { - let ident = &identifiers[id.0 as usize]; - &types[ident.type_.0 as usize] -} - -fn get_identifier_name(id: IdentifierId, identifiers: &[Identifier]) -> Option<String> { - identifiers[id.0 as usize] - .name - .as_ref() - .map(|n| n.value().to_string()) -} - -// ============================================================================= -// Path helpers (matching TS areEqualPaths, isSubPath, isSubPathIgnoringOptionals) -// ============================================================================= - -fn are_equal_paths(a: &[DependencyPathEntry], b: &[DependencyPathEntry]) -> bool { - a.len() == b.len() - && a.iter() - .zip(b.iter()) - .all(|(ai, bi)| ai.property == bi.property && ai.optional == bi.optional) -} - -fn is_sub_path(subpath: &[DependencyPathEntry], path: &[DependencyPathEntry]) -> bool { - subpath.len() <= path.len() - && subpath - .iter() - .zip(path.iter()) - .all(|(a, b)| a.property == b.property && a.optional == b.optional) -} - -fn is_sub_path_ignoring_optionals( - subpath: &[DependencyPathEntry], - path: &[DependencyPathEntry], -) -> bool { - subpath.len() <= path.len() - && subpath - .iter() - .zip(path.iter()) - .all(|(a, b)| a.property == b.property) -} - -// ============================================================================= -// Collect reactive identifiers -// ============================================================================= - -fn collect_reactive_identifiers( - func: &HirFunction, - functions: &[HirFunction], -) -> HashSet<IdentifierId> { - let mut reactive = HashSet::new(); - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - // Check instruction lvalue - if instr.lvalue.reactive { - reactive.insert(instr.lvalue.identifier); - } - // Check inner lvalues (Destructure patterns, StoreLocal, DeclareLocal, etc.) - // Matches TS eachInstructionLValue which yields both instr.lvalue and - // eachInstructionValueLValue(instr.value) - for lvalue in each_instruction_value_lvalue(&instr.value) { - if lvalue.reactive { - reactive.insert(lvalue.identifier); - } - } - for operand in each_instruction_value_operand_with_functions(&instr.value, functions) { - if operand.reactive { - reactive.insert(operand.identifier); - } - } - } - for operand in each_terminal_operand(&block.terminal) { - if operand.reactive { - reactive.insert(operand.identifier); - } - } - } - reactive -} - -// ============================================================================= -// findOptionalPlaces -// ============================================================================= - -fn find_optional_places(func: &HirFunction) -> HashMap<IdentifierId, bool> { - let mut optionals: HashMap<IdentifierId, bool> = HashMap::new(); - let mut visited: HashSet<BlockId> = HashSet::new(); - - for (_block_id, block) in &func.body.blocks { - if visited.contains(&block.id) { - continue; - } - if let Terminal::Optional { - test, - fallthrough: optional_fallthrough, - optional, - .. - } = &block.terminal - { - visited.insert(block.id); - let mut test_block_id = *test; - let mut queue: Vec<Option<bool>> = vec![Some(*optional)]; - - 'outer: loop { - let test_block = &func.body.blocks[&test_block_id]; - visited.insert(test_block.id); - match &test_block.terminal { - Terminal::Branch { - test: test_place, - consequent, - fallthrough, - .. - } => { - let is_optional = queue - .pop() - .expect("Expected an optional value for each optional test condition"); - if let Some(opt) = is_optional { - optionals.insert(test_place.identifier, opt); - } - if fallthrough == optional_fallthrough { - // Found the end of the optional chain - let consequent_block = &func.body.blocks[consequent]; - if let Some(last_id) = consequent_block.instructions.last() { - let last_instr = &func.instructions[last_id.0 as usize]; - if let InstructionValue::StoreLocal { value, .. } = - &last_instr.value - { - if let Some(opt) = is_optional { - optionals.insert(value.identifier, opt); - } - } - } - break 'outer; - } else { - test_block_id = *fallthrough; - } - } - Terminal::Optional { - optional: opt, - test: inner_test, - .. - } => { - queue.push(Some(*opt)); - test_block_id = *inner_test; - } - Terminal::Logical { - test: inner_test, .. - } - | Terminal::Ternary { - test: inner_test, .. - } => { - queue.push(None); - test_block_id = *inner_test; - } - Terminal::Sequence { - block: seq_block, .. - } => { - test_block_id = *seq_block; - } - Terminal::MaybeThrow { continuation, .. } => { - test_block_id = *continuation; - } - _ => { - // Unexpected terminal in optional — skip rather than panic - break 'outer; - } - } - } - // TS asserts queue.length === 0 here, but we skip the assertion - // to avoid panicking on edge cases. - } - } - - optionals -} - -// ============================================================================= -// Dependency collection -// ============================================================================= - -fn add_dependency( - dep: &Temporary, - dependencies: &mut Vec<InferredDependency>, - dep_keys: &mut HashSet<InferredDependencyKey>, - locals: &HashSet<IdentifierId>, -) { - match dep { - Temporary::Aggregate { - dependencies: agg_deps, - .. - } => { - for d in agg_deps { - add_dependency_inferred(d, dependencies, dep_keys, locals); - } - } - Temporary::Global { binding } => { - let inferred = InferredDependency::Global { - binding: binding.clone(), - }; - let key = dep_to_key(&inferred); - if dep_keys.insert(key) { - dependencies.push(inferred); - } - } - Temporary::Local { - identifier, - path, - context, - loc, - } => { - if !locals.contains(identifier) { - let inferred = InferredDependency::Local { - identifier: *identifier, - path: path.clone(), - context: *context, - loc: *loc, - }; - let key = dep_to_key(&inferred); - if dep_keys.insert(key) { - dependencies.push(inferred); - } - } - } - } -} - -fn add_dependency_inferred( - dep: &InferredDependency, - dependencies: &mut Vec<InferredDependency>, - dep_keys: &mut HashSet<InferredDependencyKey>, - locals: &HashSet<IdentifierId>, -) { - match dep { - InferredDependency::Global { .. } => { - let key = dep_to_key(dep); - if dep_keys.insert(key) { - dependencies.push(dep.clone()); - } - } - InferredDependency::Local { identifier, .. } => { - if !locals.contains(identifier) { - let key = dep_to_key(dep); - if dep_keys.insert(key) { - dependencies.push(dep.clone()); - } - } - } - } -} - -fn visit_candidate_dependency( - place: &Place, - temporaries: &HashMap<IdentifierId, Temporary>, - dependencies: &mut Vec<InferredDependency>, - dep_keys: &mut HashSet<InferredDependencyKey>, - locals: &HashSet<IdentifierId>, -) { - if let Some(dep) = temporaries.get(&place.identifier) { - add_dependency(dep, dependencies, dep_keys, locals); - } -} - -fn collect_dependencies( - func: &HirFunction, - identifiers: &[Identifier], - types: &[Type], - functions: &[HirFunction], - temporaries: &mut HashMap<IdentifierId, Temporary>, - callbacks: &mut Option<&mut Callbacks<'_>>, - is_function_expression: bool, -) -> Result<Temporary, CompilerDiagnostic> { - let optionals = find_optional_places(func); - let mut locals: HashSet<IdentifierId> = HashSet::new(); - - if is_function_expression { - for param in &func.params { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - locals.insert(place.identifier); - } - } - - let mut dependencies: Vec<InferredDependency> = Vec::new(); - let mut dep_keys: HashSet<InferredDependencyKey> = HashSet::new(); - - // Saved state for when we're inside a memo block (StartMemoize..FinishMemoize). - // In TS, `dependencies` and `locals` are shared by reference between the main - // collection loop and the callbacks — StartMemoize clears them, FinishMemoize - // reads and clears them. We simulate this by saving/restoring. - let mut saved_dependencies: Option<Vec<InferredDependency>> = None; - let mut saved_dep_keys: Option<HashSet<InferredDependencyKey>> = None; - let mut saved_locals: Option<HashSet<IdentifierId>> = None; - - for (_block_id, block) in &func.body.blocks { - // Process phis - for phi in &block.phis { - let mut deps: Vec<InferredDependency> = Vec::new(); - for (_pred_id, operand) in &phi.operands { - if let Some(dep) = temporaries.get(&operand.identifier) { - match dep { - Temporary::Aggregate { - dependencies: agg, .. - } => { - deps.extend(agg.iter().cloned()); - } - Temporary::Local { - identifier, - path, - context, - loc, - } => { - deps.push(InferredDependency::Local { - identifier: *identifier, - path: path.clone(), - context: *context, - loc: *loc, - }); - } - Temporary::Global { binding } => { - deps.push(InferredDependency::Global { - binding: binding.clone(), - }); - } - } - } - } - if deps.is_empty() { - continue; - } else if deps.len() == 1 { - let dep = &deps[0]; - match dep { - InferredDependency::Local { - identifier, - path, - context, - loc, - } => { - temporaries.insert( - phi.place.identifier, - Temporary::Local { - identifier: *identifier, - path: path.clone(), - context: *context, - loc: *loc, - }, - ); - } - InferredDependency::Global { binding } => { - temporaries.insert( - phi.place.identifier, - Temporary::Global { - binding: binding.clone(), - }, - ); - } - } - } else { - temporaries.insert( - phi.place.identifier, - Temporary::Aggregate { - dependencies: deps, - loc: None, - }, - ); - } - } - - // Process instructions - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - - match &instr.value { - InstructionValue::LoadGlobal { binding, .. } => { - temporaries.insert( - lvalue_id, - Temporary::Global { - binding: binding.clone(), - }, - ); - } - InstructionValue::LoadContext { place, .. } - | InstructionValue::LoadLocal { place, .. } => { - if let Some(temp) = temporaries.get(&place.identifier).cloned() { - match &temp { - Temporary::Local { .. } => { - // Update loc to the load site - let mut updated = temp.clone(); - if let Temporary::Local { loc, .. } = &mut updated { - *loc = place.loc; - } - temporaries.insert(lvalue_id, updated); - } - _ => { - temporaries.insert(lvalue_id, temp); - } - } - if locals.contains(&place.identifier) { - locals.insert(lvalue_id); - } - } - } - InstructionValue::DeclareLocal { - lvalue: decl_lv, .. - } => { - temporaries.insert( - decl_lv.place.identifier, - Temporary::Local { - identifier: decl_lv.place.identifier, - path: Vec::new(), - context: false, - loc: decl_lv.place.loc, - }, - ); - locals.insert(decl_lv.place.identifier); - } - InstructionValue::StoreLocal { - lvalue: store_lv, - value: store_val, - .. - } => { - let has_name = identifiers[store_lv.place.identifier.0 as usize] - .name - .is_some(); - if !has_name { - // Unnamed: propagate temporary - if let Some(temp) = temporaries.get(&store_val.identifier).cloned() { - temporaries.insert(store_lv.place.identifier, temp); - } - } else { - // Named: visit the value and create a new local - visit_candidate_dependency( - store_val, - temporaries, - &mut dependencies, - &mut dep_keys, - &locals, - ); - if store_lv.kind != InstructionKind::Reassign { - temporaries.insert( - store_lv.place.identifier, - Temporary::Local { - identifier: store_lv.place.identifier, - path: Vec::new(), - context: false, - loc: store_lv.place.loc, - }, - ); - locals.insert(store_lv.place.identifier); - } - } - } - InstructionValue::DeclareContext { - lvalue: decl_lv, .. - } => { - temporaries.insert( - decl_lv.place.identifier, - Temporary::Local { - identifier: decl_lv.place.identifier, - path: Vec::new(), - context: true, - loc: decl_lv.place.loc, - }, - ); - } - InstructionValue::StoreContext { - lvalue: store_lv, - value: store_val, - .. - } => { - visit_candidate_dependency( - store_val, - temporaries, - &mut dependencies, - &mut dep_keys, - &locals, - ); - if store_lv.kind != InstructionKind::Reassign { - temporaries.insert( - store_lv.place.identifier, - Temporary::Local { - identifier: store_lv.place.identifier, - path: Vec::new(), - context: true, - loc: store_lv.place.loc, - }, - ); - locals.insert(store_lv.place.identifier); - } - } - InstructionValue::Destructure { - value: destr_val, - lvalue: destr_lv, - .. - } => { - visit_candidate_dependency( - destr_val, - temporaries, - &mut dependencies, - &mut dep_keys, - &locals, - ); - if destr_lv.kind != InstructionKind::Reassign { - for lv_place in each_instruction_value_lvalue(&instr.value) { - temporaries.insert( - lv_place.identifier, - Temporary::Local { - identifier: lv_place.identifier, - path: Vec::new(), - context: false, - loc: lv_place.loc, - }, - ); - locals.insert(lv_place.identifier); - } - } - } - InstructionValue::PropertyLoad { - object, property, .. - } => { - // Number properties or ref.current: visit the object directly - let is_numeric = matches!(property, PropertyLiteral::Number(_)); - let is_ref_current = - is_use_ref_type(get_identifier_type(object.identifier, identifiers, types)) - && *property == PropertyLiteral::String("current".to_string()); - - if is_numeric || is_ref_current { - visit_candidate_dependency( - object, - temporaries, - &mut dependencies, - &mut dep_keys, - &locals, - ); - } else { - // Extend path - let obj_temp = temporaries.get(&object.identifier).cloned(); - if let Some(Temporary::Local { - identifier, - path, - context, - .. - }) = obj_temp - { - let optional = - optionals.get(&object.identifier).copied().unwrap_or(false); - let mut new_path = path.clone(); - new_path.push(DependencyPathEntry { - optional, - property: property.clone(), - loc: instr.value.loc().copied(), - }); - temporaries.insert( - lvalue_id, - Temporary::Local { - identifier, - path: new_path, - context, - loc: instr.value.loc().copied(), - }, - ); - } - } - } - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - let inner_func = &functions[lowered_func.func.0 as usize]; - let function_deps = collect_dependencies( - inner_func, - identifiers, - types, - functions, - temporaries, - &mut None, - true, - )?; - temporaries.insert(lvalue_id, function_deps.clone()); - add_dependency(&function_deps, &mut dependencies, &mut dep_keys, &locals); - } - InstructionValue::StartMemoize { - manual_memo_id, - deps, - deps_loc, - loc, - .. - } => { - if let Some(cb) = callbacks.as_mut() { - // onStartMemoize — mirrors TS behavior of clearing dependencies and locals - *cb.start_memo = Some(StartMemoInfo { - manual_memo_id: *manual_memo_id, - deps: deps.clone(), - deps_loc: *deps_loc, - loc: *loc, - }); - // Save current state and clear, matching TS which clears the shared - // dependencies/locals sets on StartMemoize - saved_dependencies = Some(std::mem::take(&mut dependencies)); - saved_dep_keys = Some(std::mem::take(&mut dep_keys)); - saved_locals = Some(std::mem::take(&mut locals)); - } - } - InstructionValue::FinishMemoize { - manual_memo_id, - decl, - .. - } => { - if let Some(cb) = callbacks.as_mut() { - // onFinishMemoize — mirrors TS behavior - let sm = cb.start_memo.take(); - if let Some(sm) = sm { - assert_eq!( - sm.manual_memo_id, *manual_memo_id, - "Found FinishMemoize without corresponding StartMemoize" - ); - - if cb.validate_memo { - // Visit the decl to add it as a dependency candidate - // (matches TS: visitCandidateDependency(value.decl, ...)) - visit_candidate_dependency( - decl, - temporaries, - &mut dependencies, - &mut dep_keys, - &locals, - ); - - // Use ALL dependencies collected since StartMemoize cleared the set. - // This matches TS: `const inferred = Array.from(dependencies)` - let inferred: Vec<InferredDependency> = dependencies.clone(); - - let diagnostic = validate_dependencies( - inferred, - &sm.deps.unwrap_or_default(), - cb.reactive, - sm.deps_loc.unwrap_or(None), - ErrorCategory::MemoDependencies, - "all", - identifiers, - types, - )?; - if let Some(diag) = diagnostic { - cb.diagnostics.push(diag); - cb.invalid_memo_ids.insert(sm.manual_memo_id); - } - } - - // Restore saved state (matching TS: dependencies.clear(), locals.clear()) - // We restore instead of just clearing because we need the outer deps back - if let Some(saved) = saved_dependencies.take() { - // Merge current memo-block deps into the restored outer deps - let memo_deps = std::mem::replace(&mut dependencies, saved); - let _memo_keys = std::mem::replace( - &mut dep_keys, - saved_dep_keys.take().unwrap_or_default(), - ); - locals = saved_locals.take().unwrap_or_default(); - // Add memo deps to outer deps (they're still valid outer deps) - for d in memo_deps { - let key = dep_to_key(&d); - if dep_keys.insert(key) { - dependencies.push(d); - } - } - } - } - } - } - InstructionValue::ArrayExpression { elements, loc, .. } => { - let mut array_deps: Vec<InferredDependency> = Vec::new(); - let mut array_keys: HashSet<InferredDependencyKey> = HashSet::new(); - let empty_locals = HashSet::new(); - for elem in elements { - let place = match elem { - ArrayElement::Place(p) => Some(p), - ArrayElement::Spread(s) => Some(&s.place), - ArrayElement::Hole => None, - }; - if let Some(place) = place { - // Visit with empty locals for manual deps - visit_candidate_dependency( - place, - temporaries, - &mut array_deps, - &mut array_keys, - &empty_locals, - ); - // Visit normally - visit_candidate_dependency( - place, - temporaries, - &mut dependencies, - &mut dep_keys, - &locals, - ); - } - } - temporaries.insert( - lvalue_id, - Temporary::Aggregate { - dependencies: array_deps, - loc: *loc, - }, - ); - } - InstructionValue::CallExpression { callee, args, .. } => { - // Check if this is an effect hook call - if let Some(cb) = callbacks.as_mut() { - let callee_ty = get_identifier_type(callee.identifier, identifiers, types); - if is_effect_hook(callee_ty) - && !matches!(cb.validate_effect, ExhaustiveEffectDepsMode::Off) - { - if args.len() >= 2 { - let fn_arg = match &args[0] { - PlaceOrSpread::Place(p) => Some(p), - _ => None, - }; - let deps_arg = match &args[1] { - PlaceOrSpread::Place(p) => Some(p), - _ => None, - }; - if let (Some(fn_place), Some(deps_place)) = (fn_arg, deps_arg) { - let fn_deps = temporaries.get(&fn_place.identifier).cloned(); - let manual_deps = - temporaries.get(&deps_place.identifier).cloned(); - if let ( - Some(Temporary::Aggregate { - dependencies: fn_dep_list, - .. - }), - Some(Temporary::Aggregate { - dependencies: manual_dep_list, - loc: manual_loc, - }), - ) = (fn_deps, manual_deps) - { - let effect_report_mode = match &cb.validate_effect { - ExhaustiveEffectDepsMode::All => "all", - ExhaustiveEffectDepsMode::MissingOnly => "missing-only", - ExhaustiveEffectDepsMode::ExtraOnly => "extra-only", - ExhaustiveEffectDepsMode::Off => unreachable!(), - }; - // Convert manual deps to ManualMemoDependency format - let manual_memo_deps: Vec<ManualMemoDependency> = - manual_dep_list - .iter() - .map(|dep| match dep { - InferredDependency::Local { - identifier, - path, - loc, - .. - } => ManualMemoDependency { - root: ManualMemoDependencyRoot::NamedLocal { - value: Place { - identifier: *identifier, - effect: - react_compiler_hir::Effect::Read, - reactive: cb - .reactive - .contains(identifier), - loc: *loc, - }, - constant: false, - }, - path: path.clone(), - loc: *loc, - }, - InferredDependency::Global { binding } => { - ManualMemoDependency { - root: - ManualMemoDependencyRoot::Global { - identifier_name: binding - .name() - .to_string(), - }, - path: Vec::new(), - loc: None, - } - } - }) - .collect(); - - let diagnostic = validate_dependencies( - fn_dep_list, - &manual_memo_deps, - cb.reactive, - manual_loc, - ErrorCategory::EffectExhaustiveDependencies, - effect_report_mode, - identifiers, - types, - )?; - if let Some(diag) = diagnostic { - cb.diagnostics.push(diag); - } - } - } - } - } - } - - // Visit all operands except for MethodCall's property - for operand in - each_instruction_value_operand_with_functions(&instr.value, functions) - { - visit_candidate_dependency( - &operand, - temporaries, - &mut dependencies, - &mut dep_keys, - &locals, - ); - } - } - InstructionValue::MethodCall { - receiver, - property, - args, - .. - } => { - // Check if this is an effect hook call - if let Some(cb) = callbacks.as_mut() { - let prop_ty = get_identifier_type(property.identifier, identifiers, types); - if is_effect_hook(prop_ty) - && !matches!(cb.validate_effect, ExhaustiveEffectDepsMode::Off) - { - if args.len() >= 2 { - let fn_arg = match &args[0] { - PlaceOrSpread::Place(p) => Some(p), - _ => None, - }; - let deps_arg = match &args[1] { - PlaceOrSpread::Place(p) => Some(p), - _ => None, - }; - if let (Some(fn_place), Some(deps_place)) = (fn_arg, deps_arg) { - let fn_deps = temporaries.get(&fn_place.identifier).cloned(); - let manual_deps = - temporaries.get(&deps_place.identifier).cloned(); - if let ( - Some(Temporary::Aggregate { - dependencies: fn_dep_list, - .. - }), - Some(Temporary::Aggregate { - dependencies: manual_dep_list, - loc: manual_loc, - }), - ) = (fn_deps, manual_deps) - { - let effect_report_mode = match &cb.validate_effect { - ExhaustiveEffectDepsMode::All => "all", - ExhaustiveEffectDepsMode::MissingOnly => "missing-only", - ExhaustiveEffectDepsMode::ExtraOnly => "extra-only", - ExhaustiveEffectDepsMode::Off => unreachable!(), - }; - let manual_memo_deps: Vec<ManualMemoDependency> = - manual_dep_list - .iter() - .map(|dep| match dep { - InferredDependency::Local { - identifier, - path, - loc, - .. - } => ManualMemoDependency { - root: ManualMemoDependencyRoot::NamedLocal { - value: Place { - identifier: *identifier, - effect: - react_compiler_hir::Effect::Read, - reactive: cb - .reactive - .contains(identifier), - loc: *loc, - }, - constant: false, - }, - path: path.clone(), - loc: *loc, - }, - InferredDependency::Global { binding } => { - ManualMemoDependency { - root: - ManualMemoDependencyRoot::Global { - identifier_name: binding - .name() - .to_string(), - }, - path: Vec::new(), - loc: None, - } - } - }) - .collect(); - - let diagnostic = validate_dependencies( - fn_dep_list, - &manual_memo_deps, - cb.reactive, - manual_loc, - ErrorCategory::EffectExhaustiveDependencies, - effect_report_mode, - identifiers, - types, - )?; - if let Some(diag) = diagnostic { - cb.diagnostics.push(diag); - } - } - } - } - } - } - - // Visit operands, skipping the method property itself - visit_candidate_dependency( - receiver, - temporaries, - &mut dependencies, - &mut dep_keys, - &locals, - ); - // Skip property — matches TS behavior - for arg in args { - let place = match arg { - PlaceOrSpread::Place(p) => p, - PlaceOrSpread::Spread(s) => &s.place, - }; - visit_candidate_dependency( - place, - temporaries, - &mut dependencies, - &mut dep_keys, - &locals, - ); - } - } - _ => { - // Default: visit all operands - for operand in - each_instruction_value_operand_with_functions(&instr.value, functions) - { - visit_candidate_dependency( - &operand, - temporaries, - &mut dependencies, - &mut dep_keys, - &locals, - ); - } - // Track lvalues as locals - for lv in each_instruction_lvalue_ids(&instr.value, lvalue_id) { - locals.insert(lv); - } - } - } - } - - // Terminal operands - for operand in &each_terminal_operand(&block.terminal) { - if optionals.contains_key(&operand.identifier) { - continue; - } - visit_candidate_dependency( - operand, - temporaries, - &mut dependencies, - &mut dep_keys, - &locals, - ); - } - } - - Ok(Temporary::Aggregate { - dependencies, - loc: None, - }) -} - -// ============================================================================= -// validateDependencies -// ============================================================================= - -fn validate_dependencies( - mut inferred: Vec<InferredDependency>, - manual_dependencies: &[ManualMemoDependency], - reactive: &HashSet<IdentifierId>, - manual_memo_loc: Option<SourceLocation>, - category: ErrorCategory, - exhaustive_deps_report_mode: &str, - identifiers: &[Identifier], - types: &[Type], -) -> Result<Option<CompilerDiagnostic>, CompilerDiagnostic> { - // Sort dependencies by name and path - inferred.sort_by(|a, b| { - match (a, b) { - ( - InferredDependency::Global { binding: ab }, - InferredDependency::Global { binding: bb }, - ) => ab.name().cmp(bb.name()), - ( - InferredDependency::Local { - identifier: a_id, - path: a_path, - .. - }, - InferredDependency::Local { - identifier: b_id, - path: b_path, - .. - }, - ) => { - let a_name = get_identifier_name(*a_id, identifiers); - let b_name = get_identifier_name(*b_id, identifiers); - match (a_name.as_deref(), b_name.as_deref()) { - (Some(an), Some(bn)) => { - if *a_id != *b_id { - an.cmp(bn) - } else if a_path.len() != b_path.len() { - a_path.len().cmp(&b_path.len()) - } else { - // Compare path entries - for (ap, bp) in a_path.iter().zip(b_path.iter()) { - let a_opt = if ap.optional { 0i32 } else { 1 }; - let b_opt = if bp.optional { 0i32 } else { 1 }; - if a_opt != b_opt { - return a_opt.cmp(&b_opt); - } - let prop_cmp = - ap.property.to_string().cmp(&bp.property.to_string()); - if prop_cmp != std::cmp::Ordering::Equal { - return prop_cmp; - } - } - std::cmp::Ordering::Equal - } - } - _ => std::cmp::Ordering::Equal, - } - } - ( - InferredDependency::Global { binding: ab }, - InferredDependency::Local { - identifier: b_id, .. - }, - ) => { - let a_name = ab.name(); - let b_name = get_identifier_name(*b_id, identifiers); - match b_name.as_deref() { - Some(bn) => a_name.cmp(bn), - None => std::cmp::Ordering::Equal, - } - } - ( - InferredDependency::Local { - identifier: a_id, .. - }, - InferredDependency::Global { binding: bb }, - ) => { - let a_name = get_identifier_name(*a_id, identifiers); - let b_name = bb.name(); - match a_name.as_deref() { - Some(an) => an.cmp(b_name), - None => std::cmp::Ordering::Equal, - } - } - } - }); - - // Remove redundant inferred dependencies - // retainWhere logic: keep dep[ix] only if no earlier entry is equal or a subpath prefix - // Mirrors TS: retainWhere(inferred, (dep, ix) => { - // const match = inferred.findIndex(prevDep => isEqualTemporary(prevDep, dep) || ...); - // return match === -1 || match >= ix; - // }) - { - let snapshot = inferred.clone(); - let mut write_index = 0; - for ix in 0..snapshot.len() { - let dep = &snapshot[ix]; - let first_match = snapshot.iter().position(|prev_dep| { - is_equal_temporary(prev_dep, dep) - || (matches!( - (prev_dep, dep), - ( - InferredDependency::Local { .. }, - InferredDependency::Local { .. } - ) - ) && { - if let ( - InferredDependency::Local { - identifier: prev_id, - path: prev_path, - .. - }, - InferredDependency::Local { - identifier: dep_id, - path: dep_path, - .. - }, - ) = (prev_dep, dep) - { - prev_id == dep_id && is_sub_path(prev_path, dep_path) - } else { - false - } - }) - }); - - let keep = match first_match { - None => true, - Some(m) => m >= ix, - }; - if keep { - inferred[write_index] = snapshot[ix].clone(); - write_index += 1; - } - } - inferred.truncate(write_index); - } - - // Validate manual deps - let mut matched: HashSet<usize> = HashSet::new(); // indices into manual_dependencies - let mut missing: Vec<&InferredDependency> = Vec::new(); - let mut extra: Vec<&ManualMemoDependency> = Vec::new(); - - for inferred_dep in &inferred { - match inferred_dep { - InferredDependency::Global { binding } => { - for (i, manual_dep) in manual_dependencies.iter().enumerate() { - if let ManualMemoDependencyRoot::Global { identifier_name } = &manual_dep.root { - if identifier_name == binding.name() { - matched.insert(i); - extra.push(manual_dep); - } - } - } - continue; - } - InferredDependency::Local { - identifier, - path, - loc: _, - .. - } => { - // Skip effect event functions - let ty = get_identifier_type(*identifier, identifiers, types); - if is_effect_event_function_type(ty) { - continue; - } - - let mut has_matching = false; - for (i, manual_dep) in manual_dependencies.iter().enumerate() { - if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &manual_dep.root { - if value.identifier == *identifier - && (are_equal_paths(&manual_dep.path, path) - || is_sub_path_ignoring_optionals(&manual_dep.path, path)) - { - has_matching = true; - matched.insert(i); - } - } - } - - if has_matching || is_optional_dependency(*identifier, reactive, identifiers, types) - { - continue; - } - - missing.push(inferred_dep); - } - } - } - - // Check for extra dependencies - for (i, dep) in manual_dependencies.iter().enumerate() { - if matched.contains(&i) { - continue; - } - if let ManualMemoDependencyRoot::NamedLocal { - constant, value, .. - } = &dep.root - { - if *constant { - let dep_ty = get_identifier_type(value.identifier, identifiers, types); - // Constant-folded primitives: skip - if !value.reactive && is_primitive_type(dep_ty) { - continue; - } - } - } - extra.push(dep); - } - - // Filter based on report mode - let filtered_missing: Vec<&InferredDependency> = if exhaustive_deps_report_mode == "extra-only" - { - Vec::new() - } else { - missing - }; - let filtered_extra: Vec<&ManualMemoDependency> = - if exhaustive_deps_report_mode == "missing-only" { - Vec::new() - } else { - extra - }; - - if filtered_missing.is_empty() && filtered_extra.is_empty() { - return Ok(None); - } - - // Build suggestion when we have valid index info (matches TS behavior) - let suggestion = manual_memo_loc.and_then(|loc| { - let start_index = loc.start.index?; - let end_index = loc.end.index?; - let text = format!( - "[{}]", - inferred - .iter() - .filter(|dep| { - match dep { - InferredDependency::Local { identifier, .. } => { - let ty = get_identifier_type(*identifier, identifiers, types); - !is_optional_dependency(*identifier, reactive, identifiers, types) - && !is_effect_event_function_type(ty) - } - InferredDependency::Global { .. } => false, - } - }) - .map(|dep| print_inferred_dependency(dep, identifiers)) - .collect::<Vec<_>>() - .join(", ") - ); - Some(CompilerSuggestion { - op: CompilerSuggestionOperation::Replace, - range: (start_index as usize, end_index as usize), - description: "Update dependencies".to_string(), - text: Some(text), - }) - }); - - let mut diagnostic = create_diagnostic( - category, - &filtered_missing, - &filtered_extra, - suggestion, - identifiers, - )?; - - // Add detail items for missing deps - for dep in &filtered_missing { - if let InferredDependency::Local { - identifier, - path: _, - loc, - .. - } = dep - { - let mut hint = String::new(); - let ty = get_identifier_type(*identifier, identifiers, types); - if is_stable_type(ty) { - hint = ". Refs, setState functions, and other \"stable\" values generally do not need to be added as dependencies, but this variable may change over time to point to different values".to_string(); - } - let dep_str = print_inferred_dependency(dep, identifiers); - diagnostic.details.push(CompilerDiagnosticDetail::Error { - loc: *loc, - message: Some(format!("Missing dependency `{dep_str}`{hint}")), - identifier_name: None, - }); - } - } - - // Add detail items for extra deps - for dep in &filtered_extra { - match &dep.root { - ManualMemoDependencyRoot::Global { .. } => { - let dep_str = print_manual_memo_dependency(dep, identifiers); - diagnostic.details.push(CompilerDiagnosticDetail::Error { - loc: dep.loc.or(manual_memo_loc), - message: Some(format!( - "Unnecessary dependency `{dep_str}`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change" - )), - identifier_name: None, - }); - } - ManualMemoDependencyRoot::NamedLocal { value, .. } => { - // Check if there's a matching inferred dep - let matching_inferred = inferred.iter().find(|inf_dep| { - if let InferredDependency::Local { - identifier: inf_id, - path: inf_path, - .. - } = inf_dep - { - *inf_id == value.identifier - && is_sub_path_ignoring_optionals(inf_path, &dep.path) - } else { - false - } - }); - - if let Some(matching) = matching_inferred { - if let InferredDependency::Local { identifier, .. } = matching { - let matching_ty = get_identifier_type(*identifier, identifiers, types); - if is_effect_event_function_type(matching_ty) { - let dep_str = print_manual_memo_dependency(dep, identifiers); - diagnostic.details.push(CompilerDiagnosticDetail::Error { - loc: dep.loc.or(manual_memo_loc), - message: Some(format!( - "Functions returned from `useEffectEvent` must not be included in the dependency array. Remove `{dep_str}` from the dependencies." - )), - identifier_name: None, - }); - } else if !is_optional_dependency_inferred( - matching, - reactive, - identifiers, - types, - ) { - let dep_str = print_manual_memo_dependency(dep, identifiers); - let inferred_str = print_inferred_dependency(matching, identifiers); - diagnostic.details.push(CompilerDiagnosticDetail::Error { - loc: dep.loc.or(manual_memo_loc), - message: Some(format!( - "Overly precise dependency `{dep_str}`, use `{inferred_str}` instead" - )), - identifier_name: None, - }); - } else { - let dep_str = print_manual_memo_dependency(dep, identifiers); - diagnostic.details.push(CompilerDiagnosticDetail::Error { - loc: dep.loc.or(manual_memo_loc), - message: Some(format!("Unnecessary dependency `{dep_str}`")), - identifier_name: None, - }); - } - } - } else { - let dep_str = print_manual_memo_dependency(dep, identifiers); - diagnostic.details.push(CompilerDiagnosticDetail::Error { - loc: dep.loc.or(manual_memo_loc), - message: Some(format!("Unnecessary dependency `{dep_str}`")), - identifier_name: None, - }); - } - } - } - } - - // Add hint showing inferred dependencies when a suggestion was generated - // (matches TS: only adds hint when suggestion != null, using suggestion.text) - if let Some(ref suggestions) = diagnostic.suggestions { - if let Some(suggestion) = suggestions.first() { - if let Some(ref text) = suggestion.text { - diagnostic.details.push(CompilerDiagnosticDetail::Hint { - message: format!("Inferred dependencies: `{text}`"), - }); - } - } - } - - Ok(Some(diagnostic)) -} - -// ============================================================================= -// Printing helpers -// ============================================================================= - -fn print_inferred_dependency(dep: &InferredDependency, identifiers: &[Identifier]) -> String { - match dep { - InferredDependency::Global { binding } => binding.name().to_string(), - InferredDependency::Local { - identifier, path, .. - } => { - let name = get_identifier_name(*identifier, identifiers) - .unwrap_or_else(|| "<unnamed>".to_string()); - let path_str: String = path - .iter() - .map(|p| format!("{}.{}", if p.optional { "?" } else { "" }, p.property)) - .collect(); - format!("{name}{path_str}") - } - } -} - -fn print_manual_memo_dependency(dep: &ManualMemoDependency, identifiers: &[Identifier]) -> String { - let name = match &dep.root { - ManualMemoDependencyRoot::Global { identifier_name } => identifier_name.clone(), - ManualMemoDependencyRoot::NamedLocal { value, .. } => { - get_identifier_name(value.identifier, identifiers) - .unwrap_or_else(|| "<unnamed>".to_string()) - } - }; - let path_str: String = dep - .path - .iter() - .map(|p| format!("{}.{}", if p.optional { "?" } else { "" }, p.property)) - .collect(); - format!("{name}{path_str}") -} - -// ============================================================================= -// Optional dependency check -// ============================================================================= - -fn is_optional_dependency( - identifier: IdentifierId, - reactive: &HashSet<IdentifierId>, - identifiers: &[Identifier], - types: &[Type], -) -> bool { - if reactive.contains(&identifier) { - return false; - } - let ty = get_identifier_type(identifier, identifiers, types); - is_stable_type(ty) || is_primitive_type(ty) -} - -fn is_optional_dependency_inferred( - dep: &InferredDependency, - reactive: &HashSet<IdentifierId>, - identifiers: &[Identifier], - types: &[Type], -) -> bool { - match dep { - InferredDependency::Local { identifier, .. } => { - is_optional_dependency(*identifier, reactive, identifiers, types) - } - InferredDependency::Global { .. } => false, - } -} - -// ============================================================================= -// Equality check for temporaries -// ============================================================================= - -fn is_equal_temporary(a: &InferredDependency, b: &InferredDependency) -> bool { - match (a, b) { - ( - InferredDependency::Global { binding: ab }, - InferredDependency::Global { binding: bb }, - ) => ab.name() == bb.name(), - ( - InferredDependency::Local { - identifier: a_id, - path: a_path, - .. - }, - InferredDependency::Local { - identifier: b_id, - path: b_path, - .. - }, - ) => a_id == b_id && are_equal_paths(a_path, b_path), - _ => false, - } -} - -// ============================================================================= -// createDiagnostic -// ============================================================================= - -fn create_diagnostic( - category: ErrorCategory, - missing: &[&InferredDependency], - extra: &[&ManualMemoDependency], - suggestion: Option<CompilerSuggestion>, - _identifiers: &[Identifier], -) -> Result<CompilerDiagnostic, CompilerDiagnostic> { - let missing_str = if !missing.is_empty() { - Some("missing") - } else { - None - }; - let extra_str = if !extra.is_empty() { - Some("extra") - } else { - None - }; - - let (reason, description) = match category { - ErrorCategory::MemoDependencies => { - let reason_parts: Vec<&str> = - [missing_str, extra_str].iter().filter_map(|x| *x).collect(); - let reason = format!("Found {} memoization dependencies", reason_parts.join("/")); - - let desc_parts: Vec<&str> = [ - if !missing.is_empty() { - Some("Missing dependencies can cause a value to update less often than it should, resulting in stale UI") - } else { - None - }, - if !extra.is_empty() { - Some("Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often") - } else { - None - }, - ] - .iter() - .filter_map(|x| *x) - .collect(); - let description = desc_parts.join(". "); - (reason, description) - } - ErrorCategory::EffectExhaustiveDependencies => { - let reason_parts: Vec<&str> = - [missing_str, extra_str].iter().filter_map(|x| *x).collect(); - let reason = format!("Found {} effect dependencies", reason_parts.join("/")); - - let desc_parts: Vec<&str> = [ - if !missing.is_empty() { - Some("Missing dependencies can cause an effect to fire less often than it should") - } else { - None - }, - if !extra.is_empty() { - Some("Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects") - } else { - None - }, - ] - .iter() - .filter_map(|x| *x) - .collect(); - let description = desc_parts.join(". "); - (reason, description) - } - _ => { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - format!("Unexpected error category: {:?}", category), - None, - )); - } - }; - - Ok(CompilerDiagnostic { - category, - reason, - description: Some(description), - details: Vec::new(), - suggestions: suggestion.map(|s| vec![s]), - }) -} - -/// Collect lvalue identifier ids from instruction value (for the default branch). -/// Thin wrapper around canonical `each_instruction_value_lvalue` that maps to ids. -fn each_instruction_lvalue_ids( - value: &InstructionValue, - lvalue_id: IdentifierId, -) -> Vec<IdentifierId> { - let mut ids = vec![lvalue_id]; - for place in each_instruction_value_lvalue(value) { - ids.push(place.identifier); - } - ids -} diff --git a/compiler/crates/react_compiler_validation/src/validate_hooks_usage.rs b/compiler/crates/react_compiler_validation/src/validate_hooks_usage.rs deleted file mode 100644 index 7d24e59883cf..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_hooks_usage.rs +++ /dev/null @@ -1,524 +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. - -//! Validates hooks usage rules. -//! -//! Port of ValidateHooksUsage.ts. -//! Ensures hooks are called unconditionally, not passed as values, -//! and not called dynamically. Also validates that hooks are not -//! called inside function expressions. - -use std::collections::HashMap; - -use indexmap::IndexMap; -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerError, CompilerErrorDetail, ErrorCategory, SourceLocation, -}; -use react_compiler_hir::dominator::compute_unconditional_blocks; -use react_compiler_hir::environment::{Environment, is_hook_name}; -use react_compiler_hir::object_shape::HookKind; -use react_compiler_hir::visitors::{each_pattern_operand, each_terminal_operand}; -use react_compiler_hir::{ - FunctionId, HirFunction, Identifier, IdentifierId, InstructionValue, ParamPattern, Place, - PropertyLiteral, Type, visitors, -}; - -/// Value classification for hook validation. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Kind { - Error, - KnownHook, - PotentialHook, - Global, - Local, -} - -fn join_kinds(a: Kind, b: Kind) -> Kind { - if a == Kind::Error || b == Kind::Error { - Kind::Error - } else if a == Kind::KnownHook || b == Kind::KnownHook { - Kind::KnownHook - } else if a == Kind::PotentialHook || b == Kind::PotentialHook { - Kind::PotentialHook - } else if a == Kind::Global || b == Kind::Global { - Kind::Global - } else { - Kind::Local - } -} - -fn get_kind_for_place( - place: &Place, - value_kinds: &HashMap<IdentifierId, Kind>, - identifiers: &[Identifier], -) -> Kind { - let known_kind = value_kinds.get(&place.identifier).copied(); - let ident = &identifiers[place.identifier.0 as usize]; - if let Some(ref name) = ident.name { - if is_hook_name(name.value()) { - return join_kinds(known_kind.unwrap_or(Kind::Local), Kind::PotentialHook); - } - } - known_kind.unwrap_or(Kind::Local) -} - -fn ident_is_hook_name(identifier_id: IdentifierId, identifiers: &[Identifier]) -> bool { - let ident = &identifiers[identifier_id.0 as usize]; - if let Some(ref name) = ident.name { - is_hook_name(name.value()) - } else { - false - } -} - -fn get_hook_kind_for_id<'a>( - identifier_id: IdentifierId, - identifiers: &[Identifier], - types: &[Type], - env: &'a Environment, -) -> Result<Option<&'a HookKind>, CompilerDiagnostic> { - let identifier = &identifiers[identifier_id.0 as usize]; - let ty = &types[identifier.type_.0 as usize]; - env.get_hook_kind_for_type(ty) -} - -fn visit_place( - place: &Place, - value_kinds: &HashMap<IdentifierId, Kind>, - errors_by_loc: &mut IndexMap<SourceLocation, CompilerErrorDetail>, - env: &mut Environment, -) -> Result<(), CompilerError> { - let kind = value_kinds.get(&place.identifier).copied(); - if kind == Some(Kind::KnownHook) { - record_invalid_hook_usage_error(place, errors_by_loc, env)?; - } - Ok(()) -} - -fn record_conditional_hook_error( - place: &Place, - value_kinds: &mut HashMap<IdentifierId, Kind>, - errors_by_loc: &mut IndexMap<SourceLocation, CompilerErrorDetail>, - env: &mut Environment, -) -> Result<(), CompilerError> { - value_kinds.insert(place.identifier, Kind::Error); - let reason = "Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)".to_string(); - if let Some(loc) = place.loc { - let previous = errors_by_loc.get(&loc); - if previous.is_none() || previous.unwrap().reason != reason { - errors_by_loc.insert( - loc, - CompilerErrorDetail { - category: ErrorCategory::Hooks, - reason, - description: None, - loc: Some(loc), - suggestions: None, - }, - ); - } - } else { - env.record_error(CompilerErrorDetail { - category: ErrorCategory::Hooks, - reason, - description: None, - loc: None, - suggestions: None, - })?; - } - Ok(()) -} - -fn record_invalid_hook_usage_error( - place: &Place, - errors_by_loc: &mut IndexMap<SourceLocation, CompilerErrorDetail>, - env: &mut Environment, -) -> Result<(), CompilerError> { - let reason = "Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values".to_string(); - if let Some(loc) = place.loc { - if !errors_by_loc.contains_key(&loc) { - errors_by_loc.insert( - loc, - CompilerErrorDetail { - category: ErrorCategory::Hooks, - reason, - description: None, - loc: Some(loc), - suggestions: None, - }, - ); - } - } else { - env.record_error(CompilerErrorDetail { - category: ErrorCategory::Hooks, - reason, - description: None, - loc: None, - suggestions: None, - })?; - } - Ok(()) -} - -fn record_dynamic_hook_usage_error( - place: &Place, - errors_by_loc: &mut IndexMap<SourceLocation, CompilerErrorDetail>, - env: &mut Environment, -) -> Result<(), CompilerError> { - let reason = "Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks".to_string(); - if let Some(loc) = place.loc { - if !errors_by_loc.contains_key(&loc) { - errors_by_loc.insert( - loc, - CompilerErrorDetail { - category: ErrorCategory::Hooks, - reason, - description: None, - loc: Some(loc), - suggestions: None, - }, - ); - } - } else { - env.record_error(CompilerErrorDetail { - category: ErrorCategory::Hooks, - reason, - description: None, - loc: None, - suggestions: None, - })?; - } - Ok(()) -} - -/// Validates hooks usage rules for a function. -pub fn validate_hooks_usage( - func: &HirFunction, - env: &mut Environment, -) -> Result<(), react_compiler_diagnostics::CompilerDiagnostic> { - let unconditional_blocks = compute_unconditional_blocks(func, env.next_block_id().0)?; - let mut errors_by_loc: IndexMap<SourceLocation, CompilerErrorDetail> = IndexMap::new(); - let mut value_kinds: HashMap<IdentifierId, Kind> = HashMap::new(); - - // Process params - for param in &func.params { - let place = match param { - ParamPattern::Place(p) => p, - ParamPattern::Spread(s) => &s.place, - }; - let kind = get_kind_for_place(place, &value_kinds, &env.identifiers); - value_kinds.insert(place.identifier, kind); - } - - // Process blocks - for (_block_id, block) in &func.body.blocks { - // Process phis - for phi in &block.phis { - let mut kind = if ident_is_hook_name(phi.place.identifier, &env.identifiers) { - Kind::PotentialHook - } else { - Kind::Local - }; - for (_, operand) in &phi.operands { - if let Some(&operand_kind) = value_kinds.get(&operand.identifier) { - kind = join_kinds(kind, operand_kind); - } - } - value_kinds.insert(phi.place.identifier, kind); - } - - // Process instructions - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - - match &instr.value { - InstructionValue::LoadGlobal { .. } => { - if get_hook_kind_for_id(lvalue_id, &env.identifiers, &env.types, env)?.is_some() - { - value_kinds.insert(lvalue_id, Kind::KnownHook); - } else { - value_kinds.insert(lvalue_id, Kind::Global); - } - } - InstructionValue::LoadContext { place, .. } - | InstructionValue::LoadLocal { place, .. } => { - visit_place(place, &value_kinds, &mut errors_by_loc, env)?; - let kind = get_kind_for_place(place, &value_kinds, &env.identifiers); - value_kinds.insert(lvalue_id, kind); - } - InstructionValue::StoreLocal { lvalue, value, .. } - | InstructionValue::StoreContext { lvalue, value, .. } => { - visit_place(value, &value_kinds, &mut errors_by_loc, env)?; - let kind = join_kinds( - get_kind_for_place(value, &value_kinds, &env.identifiers), - get_kind_for_place(&lvalue.place, &value_kinds, &env.identifiers), - ); - value_kinds.insert(lvalue.place.identifier, kind); - value_kinds.insert(lvalue_id, kind); - } - InstructionValue::ComputedLoad { object, .. } => { - visit_place(object, &value_kinds, &mut errors_by_loc, env)?; - let kind = get_kind_for_place(object, &value_kinds, &env.identifiers); - let lvalue_kind = - get_kind_for_place(&instr.lvalue, &value_kinds, &env.identifiers); - value_kinds.insert(lvalue_id, join_kinds(lvalue_kind, kind)); - } - InstructionValue::PropertyLoad { - object, property, .. - } => { - let object_kind = get_kind_for_place(object, &value_kinds, &env.identifiers); - let is_hook_property = match property { - PropertyLiteral::String(s) => is_hook_name(s), - PropertyLiteral::Number(_) => false, - }; - let kind = match object_kind { - Kind::Error => Kind::Error, - Kind::KnownHook => { - if is_hook_property { - Kind::KnownHook - } else { - Kind::Local - } - } - Kind::PotentialHook => Kind::PotentialHook, - Kind::Global => { - if is_hook_property { - Kind::KnownHook - } else { - Kind::Global - } - } - Kind::Local => { - if is_hook_property { - Kind::PotentialHook - } else { - Kind::Local - } - } - }; - value_kinds.insert(lvalue_id, kind); - } - InstructionValue::CallExpression { callee, args, .. } => { - let callee_kind = get_kind_for_place(callee, &value_kinds, &env.identifiers); - let is_hook_callee = - callee_kind == Kind::KnownHook || callee_kind == Kind::PotentialHook; - if is_hook_callee && !unconditional_blocks.contains(&block.id) { - record_conditional_hook_error( - callee, - &mut value_kinds, - &mut errors_by_loc, - env, - )?; - } else if callee_kind == Kind::PotentialHook { - record_dynamic_hook_usage_error(callee, &mut errors_by_loc, env)?; - } - // Visit all operands except callee - for arg in args { - let place = match arg { - react_compiler_hir::PlaceOrSpread::Place(p) => p, - react_compiler_hir::PlaceOrSpread::Spread(s) => &s.place, - }; - visit_place(place, &value_kinds, &mut errors_by_loc, env)?; - } - } - InstructionValue::MethodCall { - receiver, - property, - args, - .. - } => { - let callee_kind = get_kind_for_place(property, &value_kinds, &env.identifiers); - let is_hook_callee = - callee_kind == Kind::KnownHook || callee_kind == Kind::PotentialHook; - if is_hook_callee && !unconditional_blocks.contains(&block.id) { - record_conditional_hook_error( - property, - &mut value_kinds, - &mut errors_by_loc, - env, - )?; - } else if callee_kind == Kind::PotentialHook { - record_dynamic_hook_usage_error(property, &mut errors_by_loc, env)?; - } - // Visit receiver and args (not property) - visit_place(receiver, &value_kinds, &mut errors_by_loc, env)?; - for arg in args { - let place = match arg { - react_compiler_hir::PlaceOrSpread::Place(p) => p, - react_compiler_hir::PlaceOrSpread::Spread(s) => &s.place, - }; - visit_place(place, &value_kinds, &mut errors_by_loc, env)?; - } - } - InstructionValue::Destructure { lvalue, value, .. } => { - visit_place(value, &value_kinds, &mut errors_by_loc, env)?; - let object_kind = get_kind_for_place(value, &value_kinds, &env.identifiers); - // Process instr.lvalue and all pattern operands (matching TS eachInstructionLValue) - let pattern_places = each_pattern_operand(&lvalue.pattern); - let all_lvalues = - std::iter::once(instr.lvalue.clone()).chain(pattern_places.into_iter()); - for place in all_lvalues { - let is_hook_property = - ident_is_hook_name(place.identifier, &env.identifiers); - let kind = match object_kind { - Kind::Error => Kind::Error, - Kind::KnownHook => Kind::KnownHook, - Kind::PotentialHook => Kind::PotentialHook, - Kind::Global => { - if is_hook_property { - Kind::KnownHook - } else { - Kind::Global - } - } - Kind::Local => { - if is_hook_property { - Kind::PotentialHook - } else { - Kind::Local - } - } - }; - value_kinds.insert(place.identifier, kind); - } - } - InstructionValue::ObjectMethod { lowered_func, .. } - | InstructionValue::FunctionExpression { lowered_func, .. } => { - visit_function_expression(env, lowered_func.func)?; - } - _ => { - // For all other instructions: visit operands, set lvalue kinds - // Matches TS which uses eachInstructionOperand + eachInstructionLValue - visit_all_operands(&instr.value, &value_kinds, &mut errors_by_loc, env)?; - // Set kind for instr.lvalue - let kind = get_kind_for_place(&instr.lvalue, &value_kinds, &env.identifiers); - value_kinds.insert(lvalue_id, kind); - // Also set kind for value-level lvalues (e.g. DeclareLocal, PrefixUpdate, PostfixUpdate) - for lv in visitors::each_instruction_value_lvalue(&instr.value) { - let lv_kind = get_kind_for_place(&lv, &value_kinds, &env.identifiers); - value_kinds.insert(lv.identifier, lv_kind); - } - } - } - } - - // Visit terminal operands - for place in each_terminal_operand(&block.terminal) { - visit_place(&place, &value_kinds, &mut errors_by_loc, env)?; - } - } - - // Record all accumulated errors (in insertion order, matching TS Map iteration) - for (_, error_detail) in errors_by_loc { - env.record_error(error_detail)?; - } - Ok(()) -} - -/// Visit a function expression to check for hook calls inside it. -/// Processes instructions in order, visiting nested functions immediately -/// (before processing subsequent calls) to match TS error ordering. -fn visit_function_expression( - env: &mut Environment, - func_id: FunctionId, -) -> Result<(), CompilerError> { - // Collect items in instruction order to process them sequentially. - // Each item is either a call to check or a nested function to visit. - enum Item { - Call(IdentifierId, Option<SourceLocation>), - NestedFunc(FunctionId), - } - - let func = &env.functions[func_id.0 as usize]; - let mut items: Vec<Item> = Vec::new(); - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::ObjectMethod { lowered_func, .. } - | InstructionValue::FunctionExpression { lowered_func, .. } => { - items.push(Item::NestedFunc(lowered_func.func)); - } - InstructionValue::CallExpression { callee, .. } => { - items.push(Item::Call(callee.identifier, callee.loc)); - } - InstructionValue::MethodCall { property, .. } => { - items.push(Item::Call(property.identifier, property.loc)); - } - _ => {} - } - } - } - - // Process items in instruction order (matching TS which visits nested - // functions immediately before processing subsequent calls) - for item in items { - match item { - Item::Call(identifier_id, loc) => { - let identifier = &env.identifiers[identifier_id.0 as usize]; - let ty = &env.types[identifier.type_.0 as usize]; - let hook_kind = env.get_hook_kind_for_type(ty).ok().flatten().cloned(); - if let Some(hook_kind) = hook_kind { - let description = format!( - "Cannot call {} within a function expression", - if hook_kind == HookKind::Custom { - "hook" - } else { - hook_kind_display(&hook_kind) - } - ); - env.record_error(CompilerErrorDetail { - category: ErrorCategory::Hooks, - reason: "Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)".to_string(), - description: Some(description), - loc, - suggestions: None, - })?; - } - } - Item::NestedFunc(nested_func_id) => { - visit_function_expression(env, nested_func_id)?; - } - } - } - Ok(()) -} - -fn hook_kind_display(kind: &HookKind) -> &'static str { - match kind { - HookKind::UseContext => "useContext", - HookKind::UseState => "useState", - HookKind::UseActionState => "useActionState", - HookKind::UseReducer => "useReducer", - HookKind::UseRef => "useRef", - HookKind::UseEffect => "useEffect", - HookKind::UseLayoutEffect => "useLayoutEffect", - HookKind::UseInsertionEffect => "useInsertionEffect", - HookKind::UseMemo => "useMemo", - HookKind::UseCallback => "useCallback", - HookKind::UseTransition => "useTransition", - HookKind::UseImperativeHandle => "useImperativeHandle", - HookKind::UseEffectEvent => "useEffectEvent", - HookKind::UseOptimistic => "useOptimistic", - HookKind::Custom => "hook", - } -} - -/// Visit all operands of an instruction value (generic fallback). -/// Uses the canonical `each_instruction_value_operand` from visitors. -fn visit_all_operands( - value: &InstructionValue, - value_kinds: &HashMap<IdentifierId, Kind>, - errors_by_loc: &mut IndexMap<SourceLocation, CompilerErrorDetail>, - env: &mut Environment, -) -> Result<(), CompilerError> { - let operands = visitors::each_instruction_value_operand(value, &*env); - for place in &operands { - visit_place(place, value_kinds, errors_by_loc, env)?; - } - Ok(()) -} diff --git a/compiler/crates/react_compiler_validation/src/validate_locals_not_reassigned_after_render.rs b/compiler/crates/react_compiler_validation/src/validate_locals_not_reassigned_after_render.rs deleted file mode 100644 index 38207f7d0d25..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_locals_not_reassigned_after_render.rs +++ /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. - */ - -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::{CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors::{ - each_instruction_lvalue_ids, each_instruction_value_operand, each_terminal_operand, -}; -use react_compiler_hir::{ - Effect, HirFunction, Identifier, IdentifierId, IdentifierName, InstructionValue, Place, Type, -}; - -/// Validates that local variables cannot be reassigned after render. -/// This prevents a category of bugs in which a closure captures a -/// binding from one render but does not update. -pub fn validate_locals_not_reassigned_after_render(func: &HirFunction, env: &mut Environment) { - let mut context_variables: HashSet<IdentifierId> = HashSet::new(); - let mut diagnostics: Vec<CompilerDiagnostic> = Vec::new(); - - let reassignment = get_context_reassignment( - func, - &env.identifiers, - &env.types, - &env.functions, - env, - &mut context_variables, - false, - false, - &mut diagnostics, - ); - - // Record accumulated errors (from async function checks in inner functions) first - for diagnostic in diagnostics { - env.record_diagnostic(diagnostic); - } - - // Then record the top-level reassignment error if any - if let Some(reassignment_place) = reassignment { - let variable_name = format_variable_name(&reassignment_place, &env.identifiers); - env.record_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::Immutability, - "Cannot reassign variable after render completes", - Some(format!( - "Reassigning {} after render has completed can cause inconsistent \ - behavior on subsequent renders. Consider using state instead", - variable_name - )), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: reassignment_place.loc, - message: Some(format!( - "Cannot reassign {} after render completes", - variable_name - )), - identifier_name: None, - }), - ); - } -} - -/// Format a variable name for error messages. Uses the named identifier if -/// available, otherwise falls back to "variable". -fn format_variable_name(place: &Place, identifiers: &[Identifier]) -> String { - let identifier = &identifiers[place.identifier.0 as usize]; - match &identifier.name { - Some(IdentifierName::Named(name)) => format!("`{}`", name), - _ => "variable".to_string(), - } -} - -/// Recursively checks whether a function (or its dependencies) reassigns a -/// context variable. Returns the reassigned place if found, or None. -/// -/// Side effects: accumulates async-function reassignment diagnostics into `diagnostics`. -fn get_context_reassignment( - func: &HirFunction, - identifiers: &[Identifier], - types: &[Type], - functions: &[HirFunction], - env: &Environment, - context_variables: &mut HashSet<IdentifierId>, - is_function_expression: bool, - is_async: bool, - diagnostics: &mut Vec<CompilerDiagnostic>, -) -> Option<Place> { - // Maps identifiers to the place that they reassign - let mut reassigning_functions: HashMap<IdentifierId, Place> = HashMap::new(); - - for (_block_id, block) in &func.body.blocks { - for &instruction_id in &block.instructions { - let instr = &func.instructions[instruction_id.0 as usize]; - - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } - | InstructionValue::ObjectMethod { lowered_func, .. } => { - let inner_function = &functions[lowered_func.func.0 as usize]; - let inner_is_async = is_async || inner_function.is_async; - - // Recursively check the inner function - let mut reassignment = get_context_reassignment( - inner_function, - identifiers, - types, - functions, - env, - context_variables, - true, - inner_is_async, - diagnostics, - ); - - // If the function itself doesn't reassign, check if one of its - // dependencies (operands) is a reassigning function - if reassignment.is_none() { - for context_place in &inner_function.context { - if let Some(reassignment_place) = - reassigning_functions.get(&context_place.identifier) - { - reassignment = Some(reassignment_place.clone()); - break; - } - } - } - - // If the function or its dependencies reassign, handle it - if let Some(ref reassignment_place) = reassignment { - if inner_is_async { - // Async functions that reassign get an immediate error - let variable_name = - format_variable_name(reassignment_place, identifiers); - diagnostics.push( - CompilerDiagnostic::new( - ErrorCategory::Immutability, - "Cannot reassign variable in async function", - Some( - "Reassigning a variable in an async function can cause \ - inconsistent behavior on subsequent renders. \ - Consider using state instead" - .to_string(), - ), - ) - .with_detail( - CompilerDiagnosticDetail::Error { - loc: reassignment_place.loc, - message: Some(format!("Cannot reassign {}", variable_name)), - identifier_name: None, - }, - ), - ); - // Return null (don't propagate further) — matches TS behavior - return None; - } else { - // Propagate reassignment info on the lvalue - reassigning_functions - .insert(instr.lvalue.identifier, reassignment_place.clone()); - } - } - } - - InstructionValue::StoreLocal { lvalue, value, .. } => { - if let Some(reassignment_place) = reassigning_functions.get(&value.identifier) { - let reassignment_place = reassignment_place.clone(); - reassigning_functions - .insert(lvalue.place.identifier, reassignment_place.clone()); - reassigning_functions.insert(instr.lvalue.identifier, reassignment_place); - } - } - - InstructionValue::LoadLocal { place, .. } => { - if let Some(reassignment_place) = reassigning_functions.get(&place.identifier) { - reassigning_functions - .insert(instr.lvalue.identifier, reassignment_place.clone()); - } - } - - InstructionValue::DeclareContext { lvalue, .. } => { - if !is_function_expression { - context_variables.insert(lvalue.place.identifier); - } - } - - InstructionValue::StoreContext { lvalue, value, .. } => { - // If we're inside a function expression and the target is a - // context variable from the outer scope, this is a reassignment - if is_function_expression - && context_variables.contains(&lvalue.place.identifier) - { - return Some(lvalue.place.clone()); - } - - // In the outer function, track context variables - if !is_function_expression { - context_variables.insert(lvalue.place.identifier); - } - - // Propagate reassigning function info through StoreContext - if let Some(reassignment_place) = reassigning_functions.get(&value.identifier) { - let reassignment_place = reassignment_place.clone(); - reassigning_functions - .insert(lvalue.place.identifier, reassignment_place.clone()); - reassigning_functions.insert(instr.lvalue.identifier, reassignment_place); - } - } - - _ => { - // For calls with noAlias signatures, only check the callee/receiver - // (not args) to avoid false positives from callbacks that reassign - // context variables. - let operands: Vec<Place> = match &instr.value { - InstructionValue::CallExpression { callee, .. } => { - if env.has_no_alias_signature(callee.identifier) { - vec![callee.clone()] - } else { - each_instruction_value_operand(&instr.value, env) - } - } - InstructionValue::MethodCall { - receiver, property, .. - } => { - if env.has_no_alias_signature(property.identifier) { - vec![receiver.clone(), property.clone()] - } else { - each_instruction_value_operand(&instr.value, env) - } - } - InstructionValue::TaggedTemplateExpression { tag, .. } => { - if env.has_no_alias_signature(tag.identifier) { - vec![tag.clone()] - } else { - each_instruction_value_operand(&instr.value, env) - } - } - _ => each_instruction_value_operand(&instr.value, env), - }; - - for operand in &operands { - // Invariant: effects must be inferred before this pass runs - assert!( - operand.effect != Effect::Unknown, - "Expected effects to be inferred prior to \ - ValidateLocalsNotReassignedAfterRender" - ); - - if let Some(reassignment_place) = - reassigning_functions.get(&operand.identifier).cloned() - { - if operand.effect == Effect::Freeze { - // Functions that reassign local variables are inherently - // mutable and unsafe to pass where a frozen value is expected. - return Some(reassignment_place); - } else { - // If the operand is not frozen but does reassign, then the - // lvalues of the instruction could also be reassigning - for lvalue_id in each_instruction_lvalue_ids(instr) { - reassigning_functions - .insert(lvalue_id, reassignment_place.clone()); - } - } - } - } - } - } - } - - // Check terminal operands for reassigning functions - for operand in each_terminal_operand(&block.terminal) { - if let Some(reassignment_place) = reassigning_functions.get(&operand.identifier) { - return Some(reassignment_place.clone()); - } - } - } - - None -} diff --git a/compiler/crates/react_compiler_validation/src/validate_no_capitalized_calls.rs b/compiler/crates/react_compiler_validation/src/validate_no_capitalized_calls.rs deleted file mode 100644 index 8fd19fe58a08..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_no_capitalized_calls.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::{CompilerError, CompilerErrorDetail, ErrorCategory}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{HirFunction, IdentifierId, InstructionValue, PropertyLiteral}; - -/// Validates that capitalized functions are not called directly (they should be rendered as JSX). -/// -/// Port of ValidateNoCapitalizedCalls.ts. -pub fn validate_no_capitalized_calls( - func: &HirFunction, - env: &mut Environment, -) -> Result<(), CompilerError> { - // Build the allow list from global registry keys + config entries - let mut allow_list: HashSet<String> = env.globals().keys().cloned().collect(); - if let Some(config_entries) = &env.config.validate_no_capitalized_calls { - for entry in config_entries { - allow_list.insert(entry.clone()); - } - } - - let mut capital_load_globals: HashMap<IdentifierId, String> = HashMap::new(); - let mut capitalized_properties: HashMap<IdentifierId, String> = HashMap::new(); - - let reason = "Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config"; - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - let value = &instr.value; - - match value { - InstructionValue::LoadGlobal { binding, .. } => { - let name = binding.name(); - if !name.is_empty() - && name.starts_with(|c: char| c.is_ascii_uppercase()) - // We don't want to flag CONSTANTS() - && name != name.to_uppercase() - && !allow_list.contains(name) - { - capital_load_globals.insert(lvalue_id, name.to_string()); - } - } - InstructionValue::CallExpression { callee, loc, .. } => { - let callee_id = callee.identifier; - if let Some(callee_name) = capital_load_globals.get(&callee_id) { - env.record_error(CompilerErrorDetail { - category: ErrorCategory::CapitalizedCalls, - reason: reason.to_string(), - description: Some(format!("{callee_name} may be a component")), - loc: *loc, - suggestions: None, - })?; - continue; - } - } - InstructionValue::PropertyLoad { property, .. } => { - if let PropertyLiteral::String(prop_name) = property { - if prop_name.starts_with(|c: char| c.is_ascii_uppercase()) { - capitalized_properties.insert(lvalue_id, prop_name.clone()); - } - } - } - InstructionValue::MethodCall { property, loc, .. } => { - let property_id = property.identifier; - if let Some(prop_name) = capitalized_properties.get(&property_id) { - env.record_error(CompilerErrorDetail { - category: ErrorCategory::CapitalizedCalls, - reason: reason.to_string(), - description: Some(format!("{prop_name} may be a component")), - loc: *loc, - suggestions: None, - })?; - } - } - _ => {} - } - } - } - Ok(()) -} diff --git a/compiler/crates/react_compiler_validation/src/validate_no_derived_computations_in_effects.rs b/compiler/crates/react_compiler_validation/src/validate_no_derived_computations_in_effects.rs deleted file mode 100644 index b86df5953a38..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_no_derived_computations_in_effects.rs +++ /dev/null @@ -1,1455 +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. - -//! Validates that useEffect is not used for derived computations which could/should -//! be performed in render. -//! -//! See https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state -//! -//! Port of ValidateNoDerivedComputationsInEffects_exp.ts. - -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, CompilerErrorDetail, ErrorCategory, -}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors::{ - each_instruction_lvalue_ids, each_instruction_operand as canonical_each_instruction_operand, -}; -use react_compiler_hir::{ - ArrayElement, BlockId, Effect, EvaluationOrder, FunctionId, HirFunction, Identifier, - IdentifierId, IdentifierName, InstructionValue, ParamPattern, PlaceOrSpread, ReactFunctionType, - ReturnVariant, SourceLocation, Type, is_set_state_type, is_use_effect_hook_type, - is_use_ref_type, is_use_state_type, -}; - -/// Get the user-visible name for an identifier, matching Babel's -/// loc.identifierName behavior. First checks the identifier's own name, -/// then falls back to extracting the name from the source code at the -/// given source location. This handles SSA identifiers whose names were -/// lost during compiler passes. -fn get_identifier_name_with_loc( - id: IdentifierId, - identifiers: &[Identifier], - loc: &Option<SourceLocation>, - source_code: Option<&str>, -) -> Option<String> { - let ident = &identifiers[id.0 as usize]; - match &ident.name { - Some(IdentifierName::Named(name)) | Some(IdentifierName::Promoted(name)) => { - return Some(name.clone()); - } - _ => {} - } - // Fall back: find another identifier with the same declaration_id that has a name. - let decl_id = ident.declaration_id; - for other in identifiers { - if other.declaration_id == decl_id { - match &other.name { - Some(IdentifierName::Named(name)) | Some(IdentifierName::Promoted(name)) => { - return Some(name.clone()); - } - _ => {} - } - } - } - // Fall back to extracting from source code using UTF-16 code unit indices. - // Babel/JS positions use UTF-16 code unit offsets, but Rust strings are UTF-8, - // so we need to convert between the two. - if let (Some(loc), Some(code)) = (loc, source_code) { - let start_utf16 = loc.start.index? as usize; - let end_utf16 = loc.end.index? as usize; - if start_utf16 < end_utf16 { - // Convert UTF-16 code unit offsets to UTF-8 byte offsets - let mut utf16_pos = 0usize; - let mut byte_start = None; - let mut byte_end = None; - for (byte_idx, ch) in code.char_indices() { - if utf16_pos == start_utf16 { - byte_start = Some(byte_idx); - } - if utf16_pos == end_utf16 { - byte_end = Some(byte_idx); - break; - } - utf16_pos += ch.len_utf16(); - } - // Handle end at the very end of string - if utf16_pos == end_utf16 && byte_end.is_none() { - byte_end = Some(code.len()); - } - if let (Some(start), Some(end)) = (byte_start, byte_end) { - let slice = &code[start..end]; - if !slice.is_empty() - && slice - .chars() - .all(|c| c.is_alphanumeric() || c == '_' || c == '$') - { - return Some(slice.to_string()); - } - } - } - } - None -} - -const MAX_FIXPOINT_ITERATIONS: usize = 100; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum TypeOfValue { - Ignored, - FromProps, - FromState, - FromPropsAndState, -} - -#[derive(Debug, Clone)] -struct DerivationMetadata { - type_of_value: TypeOfValue, - place_identifier: IdentifierId, - place_name: Option<IdentifierName>, - source_ids: indexmap::IndexSet<IdentifierId>, - is_state_source: bool, -} - -/// Metadata about a useEffect call site. -struct EffectMetadata { - effect_func_id: FunctionId, - dep_elements: Vec<DepElement>, -} - -#[derive(Debug, Clone)] -struct DepElement { - identifier: IdentifierId, - loc: Option<SourceLocation>, -} - -struct ValidationContext { - /// Map from lvalue identifier to the FunctionId of function expressions - functions: HashMap<IdentifierId, FunctionId>, - /// Map from lvalue identifier to ArrayExpression elements (candidate deps) - candidate_dependencies: HashMap<IdentifierId, Vec<DepElement>>, - derivation_cache: DerivationCache, - effects_cache: HashMap<IdentifierId, EffectMetadata>, - set_state_loads: HashMap<IdentifierId, Option<IdentifierId>>, - set_state_usages: HashMap<IdentifierId, HashSet<LocKey>>, -} - -/// A hashable key for SourceLocation to use in HashSet -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct LocKey { - start_line: u32, - start_col: u32, - end_line: u32, - end_col: u32, -} - -impl LocKey { - fn from_loc(loc: &Option<SourceLocation>) -> Self { - match loc { - Some(loc) => LocKey { - start_line: loc.start.line, - start_col: loc.start.column, - end_line: loc.end.line, - end_col: loc.end.column, - }, - None => LocKey { - start_line: 0, - start_col: 0, - end_line: 0, - end_col: 0, - }, - } - } -} - -#[derive(Debug, Clone)] -struct DerivationCache { - has_changes: bool, - cache: HashMap<IdentifierId, DerivationMetadata>, - previous_cache: Option<HashMap<IdentifierId, DerivationMetadata>>, -} - -impl DerivationCache { - fn new() -> Self { - DerivationCache { - has_changes: false, - cache: HashMap::new(), - previous_cache: None, - } - } - - fn take_snapshot(&mut self) { - let mut prev = HashMap::new(); - for (key, value) in &self.cache { - prev.insert( - *key, - DerivationMetadata { - place_identifier: value.place_identifier, - place_name: value.place_name.clone(), - source_ids: value.source_ids.clone(), - type_of_value: value.type_of_value, - is_state_source: value.is_state_source, - }, - ); - } - self.previous_cache = Some(prev); - } - - fn check_for_changes(&mut self) { - let prev = match &self.previous_cache { - Some(p) => p, - None => { - self.has_changes = true; - return; - } - }; - - for (key, value) in &self.cache { - match prev.get(key) { - None => { - self.has_changes = true; - return; - } - Some(prev_value) => { - if !is_derivation_equal(prev_value, value) { - self.has_changes = true; - return; - } - } - } - } - - if self.cache.len() != prev.len() { - self.has_changes = true; - return; - } - - self.has_changes = false; - } - - fn snapshot(&mut self) -> bool { - let has_changes = self.has_changes; - self.has_changes = false; - has_changes - } - - fn add_derivation_entry( - &mut self, - derived_id: IdentifierId, - derived_name: Option<IdentifierName>, - source_ids: indexmap::IndexSet<IdentifierId>, - type_of_value: TypeOfValue, - is_state_source: bool, - ) { - let mut final_is_source = is_state_source; - if !final_is_source { - for source_id in &source_ids { - if let Some(source_metadata) = self.cache.get(source_id) { - if source_metadata.is_state_source - && !matches!(&source_metadata.place_name, Some(IdentifierName::Named(_))) - { - final_is_source = true; - break; - } - } - } - } - - self.cache.insert( - derived_id, - DerivationMetadata { - place_identifier: derived_id, - place_name: derived_name, - source_ids, - type_of_value, - is_state_source: final_is_source, - }, - ); - } -} - -fn is_derivation_equal(a: &DerivationMetadata, b: &DerivationMetadata) -> bool { - if a.type_of_value != b.type_of_value { - return false; - } - if a.source_ids.len() != b.source_ids.len() { - return false; - } - for id in &a.source_ids { - if !b.source_ids.contains(id) { - return false; - } - } - true -} - -fn join_value(lvalue_type: TypeOfValue, value_type: TypeOfValue) -> TypeOfValue { - if lvalue_type == TypeOfValue::Ignored { - return value_type; - } - if value_type == TypeOfValue::Ignored { - return lvalue_type; - } - if lvalue_type == value_type { - return lvalue_type; - } - TypeOfValue::FromPropsAndState -} - -fn get_root_set_state( - key: IdentifierId, - loads: &HashMap<IdentifierId, Option<IdentifierId>>, - visited: &mut HashSet<IdentifierId>, -) -> Option<IdentifierId> { - if visited.contains(&key) { - return None; - } - visited.insert(key); - - match loads.get(&key) { - None => None, - Some(None) => Some(key), - Some(Some(parent_id)) => get_root_set_state(*parent_id, loads, visited), - } -} - -fn maybe_record_set_state_for_instr( - instr: &react_compiler_hir::Instruction, - env: &Environment, - set_state_loads: &mut HashMap<IdentifierId, Option<IdentifierId>>, - set_state_usages: &mut HashMap<IdentifierId, HashSet<LocKey>>, -) { - let identifiers = &env.identifiers; - let types = &env.types; - - let all_lvalues = each_instruction_lvalue_ids(instr); - for &lvalue_id in &all_lvalues { - // Check if this is a LoadLocal from a known setState - if let InstructionValue::LoadLocal { place, .. } = &instr.value { - if set_state_loads.contains_key(&place.identifier) { - set_state_loads.insert(lvalue_id, Some(place.identifier)); - } else { - // Only check root setState if not a LoadLocal from a known chain - let lvalue_ident = &identifiers[lvalue_id.0 as usize]; - let lvalue_ty = &types[lvalue_ident.type_.0 as usize]; - if is_set_state_type(lvalue_ty) { - set_state_loads.insert(lvalue_id, None); - } - } - } else { - // Check if lvalue is a setState type (root setState) - let lvalue_ident = &identifiers[lvalue_id.0 as usize]; - let lvalue_ty = &types[lvalue_ident.type_.0 as usize]; - if is_set_state_type(lvalue_ty) { - set_state_loads.insert(lvalue_id, None); - } - } - - let root = get_root_set_state(lvalue_id, set_state_loads, &mut HashSet::new()); - if let Some(root_id) = root { - set_state_usages.entry(root_id).or_insert_with(|| { - let mut set = HashSet::new(); - set.insert(LocKey::from_loc(&instr.lvalue.loc)); - set - }); - } - } -} - -fn is_mutable_at( - env: &Environment, - eval_order: EvaluationOrder, - identifier_id: IdentifierId, -) -> bool { - env.identifiers[identifier_id.0 as usize] - .mutable_range - .contains(eval_order) -} - -pub fn validate_no_derived_computations_in_effects_exp( - func: &HirFunction, - env: &Environment, -) -> Result<CompilerError, CompilerDiagnostic> { - let identifiers = &env.identifiers; - - let mut context = ValidationContext { - functions: HashMap::new(), - candidate_dependencies: HashMap::new(), - derivation_cache: DerivationCache::new(), - effects_cache: HashMap::new(), - set_state_loads: HashMap::new(), - set_state_usages: HashMap::new(), - }; - - // Initialize derivation cache based on function type - if func.fn_type == ReactFunctionType::Hook { - for param in &func.params { - if let ParamPattern::Place(place) = param { - let name = identifiers[place.identifier.0 as usize].name.clone(); - context.derivation_cache.cache.insert( - place.identifier, - DerivationMetadata { - place_identifier: place.identifier, - place_name: name, - source_ids: indexmap::IndexSet::new(), - type_of_value: TypeOfValue::FromProps, - is_state_source: true, - }, - ); - } - } - } else if func.fn_type == ReactFunctionType::Component { - if let Some(param) = func.params.first() { - if let ParamPattern::Place(place) = param { - let name = identifiers[place.identifier.0 as usize].name.clone(); - context.derivation_cache.cache.insert( - place.identifier, - DerivationMetadata { - place_identifier: place.identifier, - place_name: name, - source_ids: indexmap::IndexSet::new(), - type_of_value: TypeOfValue::FromProps, - is_state_source: true, - }, - ); - } - } - } - - // Fixpoint iteration - let mut is_first_pass = true; - let mut iteration_count = 0; - loop { - context.derivation_cache.take_snapshot(); - - for (_block_id, block) in &func.body.blocks { - record_phi_derivations(block, &mut context, env); - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - record_instruction_derivations(instr, &mut context, is_first_pass, func, env)?; - } - } - - context.derivation_cache.check_for_changes(); - is_first_pass = false; - iteration_count += 1; - assert!( - iteration_count < MAX_FIXPOINT_ITERATIONS, - "[ValidateNoDerivedComputationsInEffects] Fixpoint iteration failed to converge." - ); - - if !context.derivation_cache.snapshot() { - break; - } - } - - // Validate all effect sites - let mut errors = CompilerError::new(); - let effects_cache: Vec<(IdentifierId, FunctionId, Vec<DepElement>)> = context - .effects_cache - .iter() - .map(|(k, v)| (*k, v.effect_func_id, v.dep_elements.clone())) - .collect(); - - for (_key, effect_func_id, dep_elements) in &effects_cache { - validate_effect( - *effect_func_id, - dep_elements, - &mut context, - func, - env, - &mut errors, - ); - } - - Ok(errors) -} - -fn record_phi_derivations( - block: &react_compiler_hir::BasicBlock, - context: &mut ValidationContext, - env: &Environment, -) { - let identifiers = &env.identifiers; - for phi in &block.phis { - let mut type_of_value = TypeOfValue::Ignored; - let mut source_ids: indexmap::IndexSet<IdentifierId> = indexmap::IndexSet::new(); - - for (_block_id, operand) in &phi.operands { - if let Some(operand_metadata) = context.derivation_cache.cache.get(&operand.identifier) - { - type_of_value = join_value(type_of_value, operand_metadata.type_of_value); - source_ids.insert(operand.identifier); - } - } - - if type_of_value != TypeOfValue::Ignored { - let name = identifiers[phi.place.identifier.0 as usize].name.clone(); - context.derivation_cache.add_derivation_entry( - phi.place.identifier, - name, - source_ids, - type_of_value, - false, - ); - } - } -} - -fn record_instruction_derivations( - instr: &react_compiler_hir::Instruction, - context: &mut ValidationContext, - is_first_pass: bool, - _outer_func: &HirFunction, - env: &Environment, -) -> Result<(), CompilerDiagnostic> { - let identifiers = &env.identifiers; - let types = &env.types; - let functions = &env.functions; - let lvalue_id = instr.lvalue.identifier; - - // maybeRecordSetState - maybe_record_set_state_for_instr( - instr, - env, - &mut context.set_state_loads, - &mut context.set_state_usages, - ); - - let mut type_of_value = TypeOfValue::Ignored; - let is_source = false; - let mut sources: indexmap::IndexSet<IdentifierId> = indexmap::IndexSet::new(); - - match &instr.value { - InstructionValue::FunctionExpression { lowered_func, .. } => { - context.functions.insert(lvalue_id, lowered_func.func); - // Recurse into the inner function - let inner_func = &functions[lowered_func.func.0 as usize]; - for (_block_id, block) in &inner_func.body.blocks { - record_phi_derivations(block, context, env); - for &inner_instr_id in &block.instructions { - let inner_instr = &inner_func.instructions[inner_instr_id.0 as usize]; - record_instruction_derivations( - inner_instr, - context, - is_first_pass, - inner_func, - env, - )?; - } - } - } - InstructionValue::CallExpression { callee, args, .. } => { - let callee_type = &types[identifiers[callee.identifier.0 as usize].type_.0 as usize]; - if is_use_effect_hook_type(callee_type) && args.len() == 2 { - if let ( - react_compiler_hir::PlaceOrSpread::Place(arg0), - react_compiler_hir::PlaceOrSpread::Place(arg1), - ) = (&args[0], &args[1]) - { - let effect_function = context.functions.get(&arg0.identifier).copied(); - let deps = context - .candidate_dependencies - .get(&arg1.identifier) - .cloned(); - if let (Some(effect_func_id), Some(dep_elements)) = (effect_function, deps) { - context.effects_cache.insert( - arg0.identifier, - EffectMetadata { - effect_func_id, - dep_elements, - }, - ); - } - } - } - - // Check if lvalue is useState type - let lvalue_type = &types[identifiers[lvalue_id.0 as usize].type_.0 as usize]; - if is_use_state_type(lvalue_type) { - let name = identifiers[lvalue_id.0 as usize].name.clone(); - context.derivation_cache.add_derivation_entry( - lvalue_id, - name, - indexmap::IndexSet::new(), - TypeOfValue::FromState, - true, - ); - return Ok(()); - } - } - InstructionValue::MethodCall { property, args, .. } => { - let prop_type = &types[identifiers[property.identifier.0 as usize].type_.0 as usize]; - if is_use_effect_hook_type(prop_type) && args.len() == 2 { - if let ( - react_compiler_hir::PlaceOrSpread::Place(arg0), - react_compiler_hir::PlaceOrSpread::Place(arg1), - ) = (&args[0], &args[1]) - { - let effect_function = context.functions.get(&arg0.identifier).copied(); - let deps = context - .candidate_dependencies - .get(&arg1.identifier) - .cloned(); - if let (Some(effect_func_id), Some(dep_elements)) = (effect_function, deps) { - context.effects_cache.insert( - arg0.identifier, - EffectMetadata { - effect_func_id, - dep_elements, - }, - ); - } - } - } - - // Check if lvalue is useState type - let lvalue_type = &types[identifiers[lvalue_id.0 as usize].type_.0 as usize]; - if is_use_state_type(lvalue_type) { - let name = identifiers[lvalue_id.0 as usize].name.clone(); - context.derivation_cache.add_derivation_entry( - lvalue_id, - name, - indexmap::IndexSet::new(), - TypeOfValue::FromState, - true, - ); - return Ok(()); - } - } - InstructionValue::ArrayExpression { elements, .. } => { - let dep_elements: Vec<DepElement> = elements - .iter() - .filter_map(|el| match el { - ArrayElement::Place(p) => Some(DepElement { - identifier: p.identifier, - loc: p.loc, - }), - _ => None, - }) - .collect(); - context - .candidate_dependencies - .insert(lvalue_id, dep_elements); - } - _ => {} - } - - // Collect operand derivations - for (operand_id, operand_loc) in each_instruction_operand(instr, env) { - // Track setState usages - if context.set_state_loads.contains_key(&operand_id) { - let root = - get_root_set_state(operand_id, &context.set_state_loads, &mut HashSet::new()); - if let Some(root_id) = root { - if let Some(usages) = context.set_state_usages.get_mut(&root_id) { - usages.insert(LocKey::from_loc(&operand_loc)); - } - } - } - - if let Some(operand_metadata) = context.derivation_cache.cache.get(&operand_id) { - type_of_value = join_value(type_of_value, operand_metadata.type_of_value); - sources.insert(operand_id); - } - } - - if type_of_value == TypeOfValue::Ignored { - return Ok(()); - } - - // Record derivation for ALL lvalue places (including destructured variables) - for &lv_id in &each_instruction_lvalue_ids(instr) { - let name = identifiers[lv_id.0 as usize].name.clone(); - context.derivation_cache.add_derivation_entry( - lv_id, - name, - sources.clone(), - type_of_value, - is_source, - ); - } - - if matches!(&instr.value, InstructionValue::FunctionExpression { .. }) { - // Don't record mutation effects for FunctionExpressions - return Ok(()); - } - - // Handle mutable operands - for operand in each_instruction_operand_with_effect(instr, env) { - if operand.effect.is_mutable() { - if is_mutable_at(env, instr.id, operand.id) { - if let Some(existing) = context.derivation_cache.cache.get_mut(&operand.id) { - existing.type_of_value = join_value(type_of_value, existing.type_of_value); - } else { - let name = identifiers[operand.id.0 as usize].name.clone(); - context.derivation_cache.add_derivation_entry( - operand.id, - name, - sources.clone(), - type_of_value, - false, - ); - } - } - } else if matches!(operand.effect, Effect::Unknown) { - return Err(CompilerDiagnostic::new( - ErrorCategory::Invariant, - "Unexpected unknown effect", - None, - )); - } - // Freeze | Read => no-op - } - Ok(()) -} - -struct OperandWithEffect { - id: IdentifierId, - effect: Effect, -} - -/// Collects operand (IdentifierId, loc) pairs from an instruction. -/// Thin wrapper around canonical `each_instruction_operand` that maps Places to (id, loc) pairs. -fn each_instruction_operand( - instr: &react_compiler_hir::Instruction, - env: &Environment, -) -> Vec<(IdentifierId, Option<SourceLocation>)> { - canonical_each_instruction_operand(instr, env) - .into_iter() - .map(|place| (place.identifier, place.loc)) - .collect() -} - -/// Collects operands with their effects. -/// Thin wrapper around canonical `each_instruction_operand` that maps Places to OperandWithEffect. -fn each_instruction_operand_with_effect( - instr: &react_compiler_hir::Instruction, - env: &Environment, -) -> Vec<OperandWithEffect> { - canonical_each_instruction_operand(instr, env) - .into_iter() - .map(|place| OperandWithEffect { - id: place.identifier, - effect: place.effect, - }) - .collect() -} - -// ============================================================================= -// Tree building and rendering (for error messages) -// ============================================================================= - -struct TreeNode { - name: String, - type_of_value: TypeOfValue, - is_source: bool, - children: Vec<TreeNode>, -} - -fn build_tree_node( - source_id: IdentifierId, - context: &ValidationContext, - visited: &HashSet<String>, -) -> Vec<TreeNode> { - let source_metadata = match context.derivation_cache.cache.get(&source_id) { - Some(m) => m, - None => return Vec::new(), - }; - - if source_metadata.is_state_source { - if let Some(IdentifierName::Named(name)) = &source_metadata.place_name { - return vec![TreeNode { - name: name.clone(), - type_of_value: source_metadata.type_of_value, - is_source: true, - children: Vec::new(), - }]; - } - } - - let mut children: Vec<TreeNode> = Vec::new(); - let mut named_siblings: indexmap::IndexSet<String> = indexmap::IndexSet::new(); - - for child_id in &source_metadata.source_ids { - assert_ne!( - *child_id, source_id, - "Unexpected self-reference: a value should not have itself as a source" - ); - - let mut new_visited = visited.clone(); - if let Some(IdentifierName::Named(name)) = &source_metadata.place_name { - new_visited.insert(name.clone()); - } - - let child_nodes = build_tree_node(*child_id, context, &new_visited); - for child_node in child_nodes { - if !named_siblings.contains(&child_node.name) { - named_siblings.insert(child_node.name.clone()); - children.push(child_node); - } - } - } - - if let Some(IdentifierName::Named(name)) = &source_metadata.place_name { - if !visited.contains(name) { - return vec![TreeNode { - name: name.clone(), - type_of_value: source_metadata.type_of_value, - is_source: source_metadata.is_state_source, - children, - }]; - } - } - - children -} - -fn render_tree( - node: &TreeNode, - indent: &str, - is_last: bool, - props_set: &mut indexmap::IndexSet<String>, - state_set: &mut indexmap::IndexSet<String>, -) -> String { - let prefix = format!( - "{}{}", - indent, - if is_last { - "\u{2514}\u{2500}\u{2500} " - } else { - "\u{251c}\u{2500}\u{2500} " - } - ); - let child_indent = format!("{}{}", indent, if is_last { " " } else { "\u{2502} " }); - - let mut result = format!("{}{}", prefix, node.name); - - if node.is_source { - let type_label = match node.type_of_value { - TypeOfValue::FromProps => { - props_set.insert(node.name.clone()); - "Prop" - } - TypeOfValue::FromState => { - state_set.insert(node.name.clone()); - "State" - } - _ => { - props_set.insert(node.name.clone()); - state_set.insert(node.name.clone()); - "Prop and State" - } - }; - result += &format!(" ({})", type_label); - } - - if !node.children.is_empty() { - result += "\n"; - for (index, child) in node.children.iter().enumerate() { - let is_last_child = index == node.children.len() - 1; - result += &render_tree(child, &child_indent, is_last_child, props_set, state_set); - if index < node.children.len() - 1 { - result += "\n"; - } - } - } - - result -} - -fn get_fn_local_deps( - func_id: Option<FunctionId>, - env: &Environment, -) -> Option<HashSet<IdentifierId>> { - let func_id = func_id?; - let inner = &env.functions[func_id.0 as usize]; - let mut deps: HashSet<IdentifierId> = HashSet::new(); - - for (_block_id, block) in &inner.body.blocks { - for &instr_id in &block.instructions { - let instr = &inner.instructions[instr_id.0 as usize]; - if let InstructionValue::LoadLocal { place, .. } = &instr.value { - deps.insert(place.identifier); - } - } - } - - Some(deps) -} - -fn validate_effect( - effect_func_id: FunctionId, - dependencies: &[DepElement], - context: &mut ValidationContext, - _outer_func: &HirFunction, - env: &Environment, - errors: &mut CompilerError, -) { - let identifiers = &env.identifiers; - let types = &env.types; - let functions = &env.functions; - let effect_function = &functions[effect_func_id.0 as usize]; - let mut seen_blocks: HashSet<BlockId> = HashSet::new(); - - struct DerivedSetStateCall { - callee_loc: Option<SourceLocation>, - callee_id: IdentifierId, - callee_identifier_name: Option<String>, - source_ids: indexmap::IndexSet<IdentifierId>, - } - - let mut effect_derived_set_state_calls: Vec<DerivedSetStateCall> = Vec::new(); - let mut effect_set_state_usages: HashMap<IdentifierId, HashSet<LocKey>> = HashMap::new(); - - // Consider setStates in the effect's dependency array as being part of effectSetStateUsages - for dep in dependencies { - let root = get_root_set_state( - dep.identifier, - &context.set_state_loads, - &mut HashSet::new(), - ); - if let Some(root_id) = root { - let mut set = HashSet::new(); - set.insert(LocKey::from_loc(&dep.loc)); - effect_set_state_usages.insert(root_id, set); - } - } - - let mut cleanup_function_deps: Option<HashSet<IdentifierId>> = None; - let mut globals: HashSet<IdentifierId> = HashSet::new(); - - for (_block_id, block) in &effect_function.body.blocks { - // Check for return -> cleanup function - if let react_compiler_hir::Terminal::Return { - value, - return_variant: ReturnVariant::Explicit, - .. - } = &block.terminal - { - let func_id = context.functions.get(&value.identifier).copied(); - cleanup_function_deps = get_fn_local_deps(func_id, env); - } - - // Skip if block has a back edge (pred not yet seen) - let has_back_edge = block.preds.iter().any(|pred| !seen_blocks.contains(pred)); - if has_back_edge { - return; - } - - for &instr_id in &block.instructions { - let instr = &effect_function.instructions[instr_id.0 as usize]; - - // Early return if any instruction derives from a ref - let lvalue_type = - &types[identifiers[instr.lvalue.identifier.0 as usize].type_.0 as usize]; - if is_use_ref_type(lvalue_type) { - return; - } - - // maybeRecordSetState for effect instructions - maybe_record_set_state_for_instr( - instr, - env, - &mut context.set_state_loads, - &mut effect_set_state_usages, - ); - - // Track setState usages for operands - for (operand_id, operand_loc) in each_instruction_operand(instr, env) { - if context.set_state_loads.contains_key(&operand_id) { - let root = get_root_set_state( - operand_id, - &context.set_state_loads, - &mut HashSet::new(), - ); - if let Some(root_id) = root { - if let Some(usages) = effect_set_state_usages.get_mut(&root_id) { - usages.insert(LocKey::from_loc(&operand_loc)); - } - } - } - } - - match &instr.value { - InstructionValue::CallExpression { callee, args, .. } => { - let callee_type = - &types[identifiers[callee.identifier.0 as usize].type_.0 as usize]; - if is_set_state_type(callee_type) && args.len() == 1 { - if let react_compiler_hir::PlaceOrSpread::Place(arg0) = &args[0] { - let callee_metadata = - context.derivation_cache.cache.get(&callee.identifier); - - // If the setState comes from a source other than local state, skip - if let Some(cm) = callee_metadata { - if cm.type_of_value != TypeOfValue::FromState { - continue; - } - } else { - continue; - } - - let arg_metadata = context.derivation_cache.cache.get(&arg0.identifier); - if let Some(am) = arg_metadata { - // Get the user-visible identifier name, matching Babel's - // loc.identifierName. Falls back to extracting from source code. - let callee_ident_name = get_identifier_name_with_loc( - callee.identifier, - identifiers, - &callee.loc, - env.code.as_deref(), - ); - effect_derived_set_state_calls.push(DerivedSetStateCall { - callee_loc: callee.loc, - callee_id: callee.identifier, - callee_identifier_name: callee_ident_name, - source_ids: am.source_ids.clone(), - }); - } - } - } else { - // Check if callee is from props/propsAndState -> bail - let callee_metadata = - context.derivation_cache.cache.get(&callee.identifier); - if let Some(cm) = callee_metadata { - if cm.type_of_value == TypeOfValue::FromProps - || cm.type_of_value == TypeOfValue::FromPropsAndState - { - return; - } - } - - if globals.contains(&callee.identifier) { - return; - } - } - } - InstructionValue::LoadGlobal { .. } => { - globals.insert(instr.lvalue.identifier); - for (operand_id, _) in each_instruction_operand(instr, env) { - globals.insert(operand_id); - } - } - _ => {} - } - } - seen_blocks.insert(block.id); - } - - // Emit errors for derived setState calls - for derived in &effect_derived_set_state_calls { - let root_set_state_call = get_root_set_state( - derived.callee_id, - &context.set_state_loads, - &mut HashSet::new(), - ); - if let Some(root_id) = root_set_state_call { - let effect_usage_count = effect_set_state_usages - .get(&root_id) - .map(|s| s.len()) - .unwrap_or(0); - let total_usage_count = context - .set_state_usages - .get(&root_id) - .map(|s| s.len()) - .unwrap_or(0); - if effect_set_state_usages.contains_key(&root_id) - && context.set_state_usages.contains_key(&root_id) - && effect_usage_count == total_usage_count - 1 - { - let mut props_set: indexmap::IndexSet<String> = indexmap::IndexSet::new(); - let mut state_set: indexmap::IndexSet<String> = indexmap::IndexSet::new(); - - let mut root_nodes_map: indexmap::IndexMap<String, TreeNode> = - indexmap::IndexMap::new(); - for id in &derived.source_ids { - let nodes = build_tree_node(*id, context, &HashSet::new()); - for node in nodes { - if !root_nodes_map.contains_key(&node.name) { - root_nodes_map.insert(node.name.clone(), node); - } - } - } - let root_nodes: Vec<&TreeNode> = root_nodes_map.values().collect(); - - let trees: Vec<String> = root_nodes - .iter() - .enumerate() - .map(|(index, node)| { - render_tree( - node, - "", - index == root_nodes.len() - 1, - &mut props_set, - &mut state_set, - ) - }) - .collect(); - - // Check cleanup function dependencies - let should_skip = if let Some(ref cleanup_deps) = cleanup_function_deps { - derived - .source_ids - .iter() - .any(|dep| cleanup_deps.contains(dep)) - } else { - false - }; - if should_skip { - return; - } - - let mut root_sources = String::new(); - if !props_set.is_empty() { - let props_list: Vec<&str> = props_set.iter().map(|s| s.as_str()).collect(); - root_sources += &format!("Props: [{}]", props_list.join(", ")); - } - if !state_set.is_empty() { - if !root_sources.is_empty() { - root_sources += "\n"; - } - let state_list: Vec<&str> = state_set.iter().map(|s| s.as_str()).collect(); - root_sources += &format!("State: [{}]", state_list.join(", ")); - } - - let description = format!( - "Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\n\ - This setState call is setting a derived value that depends on the following reactive sources:\n\n\ - {}\n\n\ - Data Flow Tree:\n\ - {}\n\n\ - See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state", - root_sources, - trees.join("\n"), - ); - - errors.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::EffectDerivationsOfState, - "You might not need an effect. Derive values in render, not effects.", - Some(description), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: derived.callee_loc, - message: Some( - "This should be computed during render, not in an effect".to_string(), - ), - identifier_name: derived.callee_identifier_name.clone(), - }), - ); - } - } - } -} - -// ============================================================================= -// Non-exp version: ValidateNoDerivedComputationsInEffects -// Port of ValidateNoDerivedComputationsInEffects.ts -// ============================================================================= - -/// Non-experimental version of the derived-computations-in-effects validation. -/// Records errors directly on the Environment (matching TS `env.recordError()` behavior). -pub fn validate_no_derived_computations_in_effects( - func: &HirFunction, - env: &mut Environment, -) -> Result<(), CompilerError> { - // Phase 1: Collect effect call sites (func_id + resolved deps). - // Done with only immutable borrows of env fields. - let effects_to_validate: Vec<(FunctionId, Vec<IdentifierId>)> = { - let ids = &env.identifiers; - let tys = &env.types; - let mut candidate_deps: HashMap<IdentifierId, Vec<IdentifierId>> = HashMap::new(); - let mut functions_map: HashMap<IdentifierId, FunctionId> = HashMap::new(); - let mut locals_map: HashMap<IdentifierId, IdentifierId> = HashMap::new(); - let mut result = Vec::new(); - - for (_, block) in &func.body.blocks { - for &iid in &block.instructions { - let instr = &func.instructions[iid.0 as usize]; - match &instr.value { - InstructionValue::LoadLocal { place, .. } => { - locals_map.insert(instr.lvalue.identifier, place.identifier); - } - InstructionValue::ArrayExpression { elements, .. } => { - let elem_ids: Vec<IdentifierId> = elements - .iter() - .filter_map(|e| match e { - ArrayElement::Place(p) => Some(p.identifier), - _ => None, - }) - .collect(); - if elem_ids.len() == elements.len() { - candidate_deps.insert(instr.lvalue.identifier, elem_ids); - } - } - InstructionValue::FunctionExpression { lowered_func, .. } => { - functions_map.insert(instr.lvalue.identifier, lowered_func.func); - } - InstructionValue::CallExpression { callee, args, .. } => { - let callee_ty = &tys[ids[callee.identifier.0 as usize].type_.0 as usize]; - if is_use_effect_hook_type(callee_ty) && args.len() == 2 { - if let (PlaceOrSpread::Place(arg0), PlaceOrSpread::Place(arg1)) = - (&args[0], &args[1]) - { - if let (Some(&func_id), Some(dep_elements)) = ( - functions_map.get(&arg0.identifier), - candidate_deps.get(&arg1.identifier), - ) { - if !dep_elements.is_empty() { - let resolved: Vec<IdentifierId> = dep_elements - .iter() - .map(|d| locals_map.get(d).copied().unwrap_or(*d)) - .collect(); - result.push((func_id, resolved)); - } - } - } - } - } - InstructionValue::MethodCall { property, args, .. } => { - let callee_ty = &tys[ids[property.identifier.0 as usize].type_.0 as usize]; - if is_use_effect_hook_type(callee_ty) && args.len() == 2 { - if let (PlaceOrSpread::Place(arg0), PlaceOrSpread::Place(arg1)) = - (&args[0], &args[1]) - { - if let (Some(&func_id), Some(dep_elements)) = ( - functions_map.get(&arg0.identifier), - candidate_deps.get(&arg1.identifier), - ) { - if !dep_elements.is_empty() { - let resolved: Vec<IdentifierId> = dep_elements - .iter() - .map(|d| locals_map.get(d).copied().unwrap_or(*d)) - .collect(); - result.push((func_id, resolved)); - } - } - } - } - } - _ => {} - } - } - } - result - }; - - // Phase 2: Validate each collected effect and record error details. - // Uses ErrorDetail (flat loc format) to match TS behavior where - // env.recordError(new CompilerErrorDetail({...})) is used. - for (func_id, resolved_deps) in effects_to_validate { - let details = validate_effect_non_exp( - &env.functions[func_id.0 as usize], - &resolved_deps, - &env.identifiers, - &env.types, - ); - for detail in details { - env.record_error(detail)?; - } - } - Ok(()) -} - -fn validate_effect_non_exp( - effect_func: &HirFunction, - effect_deps: &[IdentifierId], - ids: &[Identifier], - tys: &[Type], -) -> Vec<CompilerErrorDetail> { - // Check that the effect function only captures effect deps and setState - for ctx in &effect_func.context { - let ctx_ty = &tys[ids[ctx.identifier.0 as usize].type_.0 as usize]; - if is_set_state_type(ctx_ty) { - continue; - } else if effect_deps.iter().any(|d| *d == ctx.identifier) { - continue; - } else { - return Vec::new(); - } - } - - // Check that all effect deps are actually used in the function - for dep in effect_deps { - if !effect_func.context.iter().any(|c| c.identifier == *dep) { - return Vec::new(); - } - } - - let mut seen_blocks: HashSet<BlockId> = HashSet::new(); - let mut dep_values: HashMap<IdentifierId, Vec<IdentifierId>> = HashMap::new(); - for dep in effect_deps { - dep_values.insert(*dep, vec![*dep]); - } - - let mut set_state_locs: Vec<SourceLocation> = Vec::new(); - - for (_, block) in &effect_func.body.blocks { - for &pred in &block.preds { - if !seen_blocks.contains(&pred) { - return Vec::new(); - } - } - - for phi in &block.phis { - let mut aggregate: HashSet<IdentifierId> = HashSet::new(); - for operand in phi.operands.values() { - if let Some(deps) = dep_values.get(&operand.identifier) { - for d in deps { - aggregate.insert(*d); - } - } - } - if !aggregate.is_empty() { - dep_values.insert(phi.place.identifier, aggregate.into_iter().collect()); - } - } - - for &iid in &block.instructions { - let instr = &effect_func.instructions[iid.0 as usize]; - match &instr.value { - InstructionValue::Primitive { .. } - | InstructionValue::JSXText { .. } - | InstructionValue::LoadGlobal { .. } => {} - InstructionValue::LoadLocal { place, .. } => { - if let Some(deps) = dep_values.get(&place.identifier) { - dep_values.insert(instr.lvalue.identifier, deps.clone()); - } - } - InstructionValue::ComputedLoad { .. } - | InstructionValue::PropertyLoad { .. } - | InstructionValue::BinaryExpression { .. } - | InstructionValue::TemplateLiteral { .. } - | InstructionValue::CallExpression { .. } - | InstructionValue::MethodCall { .. } => { - let mut aggregate: HashSet<IdentifierId> = HashSet::new(); - for operand in non_exp_value_operands(&instr.value) { - if let Some(deps) = dep_values.get(&operand) { - for d in deps { - aggregate.insert(*d); - } - } - } - if !aggregate.is_empty() { - dep_values.insert(instr.lvalue.identifier, aggregate.into_iter().collect()); - } - - if let InstructionValue::CallExpression { callee, args, .. } = &instr.value { - let callee_ty = &tys[ids[callee.identifier.0 as usize].type_.0 as usize]; - if is_set_state_type(callee_ty) && args.len() == 1 { - if let PlaceOrSpread::Place(arg) = &args[0] { - if let Some(deps) = dep_values.get(&arg.identifier) { - let dep_set: HashSet<_> = deps.iter().collect(); - if dep_set.len() == effect_deps.len() { - if let Some(loc) = callee.loc { - set_state_locs.push(loc); - } - } else { - return Vec::new(); - } - } else { - return Vec::new(); - } - } - } - } - } - _ => { - return Vec::new(); - } - } - } - - match &block.terminal { - react_compiler_hir::Terminal::Return { value, .. } - | react_compiler_hir::Terminal::Throw { value, .. } => { - if dep_values.contains_key(&value.identifier) { - return Vec::new(); - } - } - react_compiler_hir::Terminal::If { test, .. } - | react_compiler_hir::Terminal::Branch { test, .. } => { - if dep_values.contains_key(&test.identifier) { - return Vec::new(); - } - } - react_compiler_hir::Terminal::Switch { test, .. } => { - if dep_values.contains_key(&test.identifier) { - return Vec::new(); - } - } - _ => {} - } - - seen_blocks.insert(block.id); - } - - set_state_locs - .into_iter() - .map(|loc| { - CompilerErrorDetail { - category: ErrorCategory::EffectDerivationsOfState, - reason: "Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)".to_string(), - description: None, - loc: Some(loc), - suggestions: None, - } - }) - .collect() -} - -/// Collects operand IdentifierIds for a subset of instruction variants used -/// by `validate_effect_non_exp`. -/// -/// NOTE: This intentionally does NOT use the canonical `each_instruction_value_operand` -/// because: (1) `validate_effect_non_exp` only matches specific variants -/// (ComputedLoad, PropertyLoad, BinaryExpression, TemplateLiteral, CallExpression, -/// MethodCall), so FunctionExpression/ObjectMethod context handling is unnecessary; -/// and (2) the caller does not have access to `env` which the canonical function requires -/// for resolving function expression context captures. -fn non_exp_value_operands(value: &InstructionValue) -> Vec<IdentifierId> { - match value { - InstructionValue::ComputedLoad { - object, property, .. - } => { - vec![object.identifier, property.identifier] - } - InstructionValue::PropertyLoad { object, .. } => vec![object.identifier], - InstructionValue::BinaryExpression { left, right, .. } => { - vec![left.identifier, right.identifier] - } - InstructionValue::TemplateLiteral { subexprs, .. } => { - subexprs.iter().map(|s| s.identifier).collect() - } - InstructionValue::CallExpression { callee, args, .. } => { - let mut op_ids = vec![callee.identifier]; - for a in args { - match a { - PlaceOrSpread::Place(p) => op_ids.push(p.identifier), - PlaceOrSpread::Spread(s) => op_ids.push(s.place.identifier), - } - } - op_ids - } - InstructionValue::MethodCall { - receiver, - property, - args, - .. - } => { - let mut op_ids = vec![receiver.identifier, property.identifier]; - for a in args { - match a { - PlaceOrSpread::Place(p) => op_ids.push(p.identifier), - PlaceOrSpread::Spread(s) => op_ids.push(s.place.identifier), - } - } - op_ids - } - _ => Vec::new(), - } -} diff --git a/compiler/crates/react_compiler_validation/src/validate_no_freezing_known_mutable_functions.rs b/compiler/crates/react_compiler_validation/src/validate_no_freezing_known_mutable_functions.rs deleted file mode 100644 index 10f173da912b..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_no_freezing_known_mutable_functions.rs +++ /dev/null @@ -1,222 +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. - */ - -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, SourceLocation, -}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors::{each_instruction_value_operand, each_terminal_operand}; -use react_compiler_hir::{ - AliasingEffect, Effect, HirFunction, Identifier, IdentifierId, IdentifierName, - InstructionValue, Place, Type, -}; - -/// Information about a known mutation effect: which identifier is mutated, and -/// the source location of the mutation. -#[derive(Debug, Clone)] -struct MutationInfo { - value_identifier: IdentifierId, - value_loc: Option<SourceLocation>, -} - -/// Validates that functions with known mutations (ie due to types) cannot be passed -/// where a frozen value is expected. -/// -/// Because a function that mutates a captured variable is equivalent to a mutable value, -/// and the receiver has no way to avoid calling the function, this pass detects functions -/// with *known* mutations (Mutate or MutateTransitive, not conditional) that are passed -/// where a frozen value is expected and reports an error. -pub fn validate_no_freezing_known_mutable_functions(func: &HirFunction, env: &mut Environment) { - let diagnostics = check_no_freezing_known_mutable_functions( - func, - &env.identifiers, - &env.types, - &env.functions, - env, - ); - for diagnostic in diagnostics { - env.record_diagnostic(diagnostic); - } -} - -fn check_no_freezing_known_mutable_functions( - func: &HirFunction, - identifiers: &[Identifier], - types: &[Type], - functions: &[HirFunction], - env: &Environment, -) -> Vec<CompilerDiagnostic> { - // Maps an identifier to the mutation effect that makes it "known mutable" - let mut context_mutation_effects: HashMap<IdentifierId, MutationInfo> = HashMap::new(); - let mut diagnostics: Vec<CompilerDiagnostic> = Vec::new(); - - for (_block_id, block) in &func.body.blocks { - for &instruction_id in &block.instructions { - let instr = &func.instructions[instruction_id.0 as usize]; - - match &instr.value { - InstructionValue::LoadLocal { place, .. } => { - // Propagate known mutation from the loaded place to the lvalue - if let Some(mutation_info) = context_mutation_effects.get(&place.identifier) { - context_mutation_effects - .insert(instr.lvalue.identifier, mutation_info.clone()); - } - } - - InstructionValue::StoreLocal { lvalue, value, .. } => { - // Propagate known mutation from the stored value to both the - // instruction lvalue and the StoreLocal's target lvalue - if let Some(mutation_info) = context_mutation_effects.get(&value.identifier) { - let mutation_info = mutation_info.clone(); - context_mutation_effects - .insert(instr.lvalue.identifier, mutation_info.clone()); - context_mutation_effects.insert(lvalue.place.identifier, mutation_info); - } - } - - InstructionValue::FunctionExpression { lowered_func, .. } => { - let inner_function = &functions[lowered_func.func.0 as usize]; - if let Some(ref aliasing_effects) = inner_function.aliasing_effects { - let context_ids: HashSet<IdentifierId> = inner_function - .context - .iter() - .map(|place| place.identifier) - .collect(); - - 'effects: for effect in aliasing_effects { - match effect { - AliasingEffect::Mutate { value, .. } - | AliasingEffect::MutateTransitive { value, .. } => { - // If the mutated value is already known-mutable, propagate - if let Some(known_mutation) = - context_mutation_effects.get(&value.identifier) - { - context_mutation_effects.insert( - instr.lvalue.identifier, - known_mutation.clone(), - ); - } else if context_ids.contains(&value.identifier) - && !is_ref_or_ref_like_mutable_type( - value.identifier, - identifiers, - types, - ) - { - // New known mutation of a context variable - context_mutation_effects.insert( - instr.lvalue.identifier, - MutationInfo { - value_identifier: value.identifier, - value_loc: value.loc, - }, - ); - break 'effects; - } - } - - AliasingEffect::MutateConditionally { value, .. } - | AliasingEffect::MutateTransitiveConditionally { value, .. } => { - // Only propagate existing known mutations for conditional effects - if let Some(known_mutation) = - context_mutation_effects.get(&value.identifier) - { - context_mutation_effects.insert( - instr.lvalue.identifier, - known_mutation.clone(), - ); - } - } - - _ => {} - } - } - } - } - - _ => { - // For all other instruction kinds, check operands for freeze violations - for operand in each_instruction_value_operand(&instr.value, env) { - check_operand_for_freeze_violation( - &operand, - &context_mutation_effects, - identifiers, - &mut diagnostics, - ); - } - } - } - } - - // Also check terminal operands - for operand in each_terminal_operand(&block.terminal) { - check_operand_for_freeze_violation( - &operand, - &context_mutation_effects, - identifiers, - &mut diagnostics, - ); - } - } - - diagnostics -} - -/// If an operand with Effect::Freeze is a known-mutable function, emit a diagnostic. -fn check_operand_for_freeze_violation( - operand: &Place, - context_mutation_effects: &HashMap<IdentifierId, MutationInfo>, - identifiers: &[Identifier], - diagnostics: &mut Vec<CompilerDiagnostic>, -) { - if operand.effect == Effect::Freeze { - if let Some(mutation_info) = context_mutation_effects.get(&operand.identifier) { - let identifier = &identifiers[mutation_info.value_identifier.0 as usize]; - let variable_name = match &identifier.name { - Some(IdentifierName::Named(name)) => format!("`{}`", name), - _ => "a local variable".to_string(), - }; - - diagnostics.push( - CompilerDiagnostic::new( - ErrorCategory::Immutability, - "Cannot modify local variables after render completes", - Some(format!( - "This argument is a function which may reassign or mutate {} after render, \ - which can cause inconsistent behavior on subsequent renders. \ - Consider using state instead", - variable_name - )), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: operand.loc, - message: Some(format!( - "This function may (indirectly) reassign or modify {} after render", - variable_name - )), - identifier_name: None, - }) - .with_detail(CompilerDiagnosticDetail::Error { - loc: mutation_info.value_loc, - message: Some(format!("This modifies {}", variable_name)), - identifier_name: None, - }), - ); - } - } -} - -/// Check if an identifier's type is a ref or ref-like mutable type. -fn is_ref_or_ref_like_mutable_type( - identifier_id: IdentifierId, - identifiers: &[Identifier], - types: &[Type], -) -> bool { - let identifier = &identifiers[identifier_id.0 as usize]; - react_compiler_hir::is_ref_or_ref_like_mutable_type(&types[identifier.type_.0 as usize]) -} diff --git a/compiler/crates/react_compiler_validation/src/validate_no_jsx_in_try_statement.rs b/compiler/crates/react_compiler_validation/src/validate_no_jsx_in_try_statement.rs deleted file mode 100644 index a37b6206f2ed..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_no_jsx_in_try_statement.rs +++ /dev/null @@ -1,65 +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. - -//! Validates against constructing JSX within try/catch blocks. -//! -//! Developers may not be aware of error boundaries and lazy evaluation of JSX, leading them -//! to use patterns such as `let el; try { el = <Component /> } 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 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. -//! -//! Port of ValidateNoJSXInTryStatement.ts. - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory, -}; -use react_compiler_hir::{BlockId, HirFunction, InstructionValue, Terminal}; - -pub fn validate_no_jsx_in_try_statement(func: &HirFunction) -> CompilerError { - let mut active_try_blocks: Vec<BlockId> = Vec::new(); - let mut error = CompilerError::new(); - - for (_block_id, block) in &func.body.blocks { - // Remove completed try blocks (retainWhere equivalent) - active_try_blocks.retain(|id| *id != block.id); - - if !active_try_blocks.is_empty() { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::JsxExpression { loc, .. } - | InstructionValue::JsxFragment { loc, .. } => { - error.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::ErrorBoundaries, - "Avoid constructing JSX within try/catch", - Some( - "React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. 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)".to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: *loc, - message: Some( - "Avoid constructing JSX within try/catch".to_string(), - ), - identifier_name: None, - }), - ); - } - _ => {} - } - } - } - - if let Terminal::Try { handler, .. } = &block.terminal { - active_try_blocks.push(*handler); - } - } - - error -} diff --git a/compiler/crates/react_compiler_validation/src/validate_no_ref_access_in_render.rs b/compiler/crates/react_compiler_validation/src/validate_no_ref_access_in_render.rs deleted file mode 100644 index 0a0ac6591ec8..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_no_ref_access_in_render.rs +++ /dev/null @@ -1,1256 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, SourceLocation, -}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::object_shape::HookKind; -use react_compiler_hir::visitors::{ - each_instruction_value_operand as canonical_each_instruction_value_operand, - each_pattern_operand, each_terminal_operand, -}; -use react_compiler_hir::{ - AliasingEffect, BlockId, HirFunction, Identifier, IdentifierId, InstructionValue, Place, - PrimitiveValue, PropertyLiteral, Terminal, Type, UnaryOperator, -}; - -const ERROR_DESCRIPTION: &str = "React refs are values that are not needed for rendering. \ - Refs should only be accessed outside of render, such as in event handlers or effects. \ - Accessing a ref value (the `current` property) during render can cause your component \ - not to update as expected (https://react.dev/reference/react/useRef)"; - -// --- RefId --- - -type RefId = u32; - -static REF_ID_COUNTER: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); - -fn next_ref_id() -> RefId { - REF_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed) -} - -// --- RefAccessType / RefAccessRefType / RefFnType --- - -/// Corresponds to TS `RefAccessType`. -/// -/// PartialEq matches the TS `tyEqual` semantics: Ref ignores ref_id, -/// RefValue compares loc but ignores ref_id. This is critical for fixpoint -/// convergence — join creates fresh ref_ids, and comparing them would -/// prevent the environment from stabilizing. -#[derive(Debug, Clone)] -enum RefAccessType { - None, - Nullable, - Guard { - ref_id: RefId, - }, - Ref { - ref_id: RefId, - }, - RefValue { - loc: Option<SourceLocation>, - ref_id: Option<RefId>, - }, - Structure { - value: Option<Box<RefAccessRefType>>, - fn_type: Option<RefFnType>, - }, -} - -impl PartialEq for RefAccessType { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (RefAccessType::None, RefAccessType::None) => true, - (RefAccessType::Nullable, RefAccessType::Nullable) => true, - (RefAccessType::Guard { ref_id: a }, RefAccessType::Guard { ref_id: b }) => a == b, - (RefAccessType::Ref { .. }, RefAccessType::Ref { .. }) => true, - (RefAccessType::RefValue { loc: a, .. }, RefAccessType::RefValue { loc: b, .. }) => { - a == b - } - ( - RefAccessType::Structure { - value: a_val, - fn_type: a_fn, - }, - RefAccessType::Structure { - value: b_val, - fn_type: b_fn, - }, - ) => a_val == b_val && a_fn == b_fn, - _ => false, - } - } -} - -/// Corresponds to TS `RefAccessRefType` — the subset of `RefAccessType` that can appear -/// inside `Structure.value` and be joined via `join_ref_access_ref_types`. -/// -/// PartialEq mirrors RefAccessType: Ref ignores ref_id, RefValue compares -/// loc only. -#[derive(Debug, Clone)] -enum RefAccessRefType { - Ref { - ref_id: RefId, - }, - RefValue { - loc: Option<SourceLocation>, - ref_id: Option<RefId>, - }, - Structure { - value: Option<Box<RefAccessRefType>>, - fn_type: Option<RefFnType>, - }, -} - -impl PartialEq for RefAccessRefType { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (RefAccessRefType::Ref { .. }, RefAccessRefType::Ref { .. }) => true, - ( - RefAccessRefType::RefValue { loc: a, .. }, - RefAccessRefType::RefValue { loc: b, .. }, - ) => a == b, - ( - RefAccessRefType::Structure { - value: a_val, - fn_type: a_fn, - }, - RefAccessRefType::Structure { - value: b_val, - fn_type: b_fn, - }, - ) => a_val == b_val && a_fn == b_fn, - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -struct RefFnType { - read_ref_effect: bool, - return_type: Box<RefAccessType>, -} - -impl RefAccessType { - /// Try to convert a `RefAccessType` to a `RefAccessRefType` (the Ref/RefValue/Structure subset). - fn to_ref_type(&self) -> Option<RefAccessRefType> { - match self { - RefAccessType::Ref { ref_id } => Some(RefAccessRefType::Ref { ref_id: *ref_id }), - RefAccessType::RefValue { loc, ref_id } => Some(RefAccessRefType::RefValue { - loc: *loc, - ref_id: *ref_id, - }), - RefAccessType::Structure { value, fn_type } => Some(RefAccessRefType::Structure { - value: value.clone(), - fn_type: fn_type.clone(), - }), - _ => None, - } - } - - /// Convert a `RefAccessRefType` back to a `RefAccessType`. - fn from_ref_type(ref_type: &RefAccessRefType) -> Self { - match ref_type { - RefAccessRefType::Ref { ref_id } => RefAccessType::Ref { ref_id: *ref_id }, - RefAccessRefType::RefValue { loc, ref_id } => RefAccessType::RefValue { - loc: *loc, - ref_id: *ref_id, - }, - RefAccessRefType::Structure { value, fn_type } => RefAccessType::Structure { - value: value.clone(), - fn_type: fn_type.clone(), - }, - } - } -} - -// --- Join operations --- - -fn join_ref_access_ref_types(a: &RefAccessRefType, b: &RefAccessRefType) -> RefAccessRefType { - match (a, b) { - ( - RefAccessRefType::RefValue { ref_id: a_id, .. }, - RefAccessRefType::RefValue { ref_id: b_id, .. }, - ) => { - if a_id == b_id { - a.clone() - } else { - RefAccessRefType::RefValue { - loc: None, - ref_id: None, - } - } - } - (RefAccessRefType::RefValue { .. }, _) => RefAccessRefType::RefValue { - loc: None, - ref_id: None, - }, - (_, RefAccessRefType::RefValue { .. }) => RefAccessRefType::RefValue { - loc: None, - ref_id: None, - }, - (RefAccessRefType::Ref { ref_id: a_id }, RefAccessRefType::Ref { ref_id: b_id }) => { - if a_id == b_id { - a.clone() - } else { - RefAccessRefType::Ref { - ref_id: next_ref_id(), - } - } - } - (RefAccessRefType::Ref { .. }, _) | (_, RefAccessRefType::Ref { .. }) => { - RefAccessRefType::Ref { - ref_id: next_ref_id(), - } - } - ( - RefAccessRefType::Structure { - value: a_value, - fn_type: a_fn, - }, - RefAccessRefType::Structure { - value: b_value, - fn_type: b_fn, - }, - ) => { - let fn_type = match (a_fn, b_fn) { - (None, other) | (other, None) => other.clone(), - (Some(a_fn), Some(b_fn)) => Some(RefFnType { - read_ref_effect: a_fn.read_ref_effect || b_fn.read_ref_effect, - return_type: Box::new(join_ref_access_types( - &a_fn.return_type, - &b_fn.return_type, - )), - }), - }; - let value = match (a_value, b_value) { - (None, other) | (other, None) => other.clone(), - (Some(a_val), Some(b_val)) => { - Some(Box::new(join_ref_access_ref_types(a_val, b_val))) - } - }; - RefAccessRefType::Structure { value, fn_type } - } - } -} - -fn join_ref_access_types(a: &RefAccessType, b: &RefAccessType) -> RefAccessType { - match (a, b) { - (RefAccessType::None, other) | (other, RefAccessType::None) => other.clone(), - (RefAccessType::Guard { ref_id: a_id }, RefAccessType::Guard { ref_id: b_id }) => { - if a_id == b_id { - a.clone() - } else { - RefAccessType::None - } - } - (RefAccessType::Guard { .. }, RefAccessType::Nullable) - | (RefAccessType::Nullable, RefAccessType::Guard { .. }) => RefAccessType::None, - (RefAccessType::Guard { .. }, other) | (other, RefAccessType::Guard { .. }) => { - other.clone() - } - (RefAccessType::Nullable, other) | (other, RefAccessType::Nullable) => other.clone(), - _ => match (a.to_ref_type(), b.to_ref_type()) { - (Some(a_ref), Some(b_ref)) => { - RefAccessType::from_ref_type(&join_ref_access_ref_types(&a_ref, &b_ref)) - } - (Some(r), None) | (None, Some(r)) => RefAccessType::from_ref_type(&r), - _ => RefAccessType::None, - }, - } -} - -fn join_ref_access_types_many(types: &[RefAccessType]) -> RefAccessType { - types - .iter() - .fold(RefAccessType::None, |acc, t| join_ref_access_types(&acc, t)) -} - -// --- Env --- - -struct Env { - changed: bool, - data: HashMap<IdentifierId, RefAccessType>, - temporaries: HashMap<IdentifierId, Place>, -} - -impl Env { - fn new() -> Self { - Self { - changed: false, - data: HashMap::new(), - temporaries: HashMap::new(), - } - } - - fn define(&mut self, key: IdentifierId, value: Place) { - self.temporaries.insert(key, value); - } - - fn reset_changed(&mut self) { - self.changed = false; - } - - fn has_changed(&self) -> bool { - self.changed - } - - fn get(&self, key: IdentifierId) -> Option<&RefAccessType> { - let operand_id = self - .temporaries - .get(&key) - .map(|p| p.identifier) - .unwrap_or(key); - self.data.get(&operand_id) - } - - fn set(&mut self, key: IdentifierId, value: RefAccessType) { - let operand_id = self - .temporaries - .get(&key) - .map(|p| p.identifier) - .unwrap_or(key); - let current = self.data.get(&operand_id); - let widened_value = join_ref_access_types(&value, current.unwrap_or(&RefAccessType::None)); - if current.is_none() && widened_value == RefAccessType::None { - // No change needed - } else if current.map_or(true, |c| c != &widened_value) { - self.changed = true; - } - self.data.insert(operand_id, widened_value); - } -} - -// --- Helper functions --- - -fn ref_type_of_type(id: IdentifierId, identifiers: &[Identifier], types: &[Type]) -> RefAccessType { - let identifier = &identifiers[id.0 as usize]; - let ty = &types[identifier.type_.0 as usize]; - if react_compiler_hir::is_ref_value_type(ty) { - RefAccessType::RefValue { - loc: None, - ref_id: None, - } - } else if react_compiler_hir::is_use_ref_type(ty) { - RefAccessType::Ref { - ref_id: next_ref_id(), - } - } else { - RefAccessType::None - } -} - -fn is_ref_type(id: IdentifierId, identifiers: &[Identifier], types: &[Type]) -> bool { - let identifier = &identifiers[id.0 as usize]; - react_compiler_hir::is_use_ref_type(&types[identifier.type_.0 as usize]) -} - -fn is_ref_value_type(id: IdentifierId, identifiers: &[Identifier], types: &[Type]) -> bool { - let identifier = &identifiers[id.0 as usize]; - react_compiler_hir::is_ref_value_type(&types[identifier.type_.0 as usize]) -} - -fn destructure(ty: &RefAccessType) -> RefAccessType { - match ty { - RefAccessType::Structure { - value: Some(inner), .. - } => destructure(&RefAccessType::from_ref_type(inner)), - other => other.clone(), - } -} - -// --- Validation helpers --- - -fn validate_no_direct_ref_value_access( - errors: &mut Vec<CompilerDiagnostic>, - operand: &Place, - env: &Env, -) { - if let Some(ty) = env.get(operand.identifier) { - let ty = destructure(ty); - if let RefAccessType::RefValue { loc, .. } = &ty { - errors.push( - CompilerDiagnostic::new( - ErrorCategory::Refs, - "Cannot access refs during render", - Some(ERROR_DESCRIPTION.to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: loc.or(operand.loc), - message: Some("Cannot access ref value during render".to_string()), - identifier_name: None, - }), - ); - } - } -} - -fn validate_no_ref_value_access(errors: &mut Vec<CompilerDiagnostic>, env: &Env, operand: &Place) { - if let Some(ty) = env.get(operand.identifier) { - let ty = destructure(ty); - match &ty { - RefAccessType::RefValue { loc, .. } => { - errors.push( - CompilerDiagnostic::new( - ErrorCategory::Refs, - "Cannot access refs during render", - Some(ERROR_DESCRIPTION.to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: loc.or(operand.loc), - message: Some("Cannot access ref value during render".to_string()), - identifier_name: None, - }), - ); - } - RefAccessType::Structure { - fn_type: Some(fn_type), - .. - } if fn_type.read_ref_effect => { - errors.push( - CompilerDiagnostic::new( - ErrorCategory::Refs, - "Cannot access refs during render", - Some(ERROR_DESCRIPTION.to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: operand.loc, - message: Some("Cannot access ref value during render".to_string()), - identifier_name: None, - }), - ); - } - _ => {} - } - } -} - -fn validate_no_ref_passed_to_function( - errors: &mut Vec<CompilerDiagnostic>, - env: &Env, - operand: &Place, - loc: Option<SourceLocation>, -) { - if let Some(ty) = env.get(operand.identifier) { - let ty = destructure(ty); - match &ty { - RefAccessType::Ref { .. } | RefAccessType::RefValue { .. } => { - let error_loc = if let RefAccessType::RefValue { loc: ref_loc, .. } = &ty { - ref_loc.or(loc) - } else { - loc - }; - errors.push( - CompilerDiagnostic::new( - ErrorCategory::Refs, - "Cannot access refs during render", - Some(ERROR_DESCRIPTION.to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: error_loc, - message: Some( - "Passing a ref to a function may read its value during render" - .to_string(), - ), - identifier_name: None, - }), - ); - } - RefAccessType::Structure { - fn_type: Some(fn_type), - .. - } if fn_type.read_ref_effect => { - errors.push( - CompilerDiagnostic::new( - ErrorCategory::Refs, - "Cannot access refs during render", - Some(ERROR_DESCRIPTION.to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc, - message: Some( - "Passing a ref to a function may read its value during render" - .to_string(), - ), - identifier_name: None, - }), - ); - } - _ => {} - } - } -} - -fn validate_no_ref_update( - errors: &mut Vec<CompilerDiagnostic>, - env: &Env, - operand: &Place, - loc: Option<SourceLocation>, -) { - if let Some(ty) = env.get(operand.identifier) { - let ty = destructure(ty); - match &ty { - RefAccessType::Ref { .. } | RefAccessType::RefValue { .. } => { - let error_loc = if let RefAccessType::RefValue { loc: ref_loc, .. } = &ty { - ref_loc.or(loc) - } else { - loc - }; - errors.push( - CompilerDiagnostic::new( - ErrorCategory::Refs, - "Cannot access refs during render", - Some(ERROR_DESCRIPTION.to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: error_loc, - message: Some("Cannot update ref during render".to_string()), - identifier_name: None, - }), - ); - } - _ => {} - } - } -} - -fn guard_check(errors: &mut Vec<CompilerDiagnostic>, operand: &Place, env: &Env) { - if matches!( - env.get(operand.identifier), - Some(RefAccessType::Guard { .. }) - ) { - errors.push( - CompilerDiagnostic::new( - ErrorCategory::Refs, - "Cannot access refs during render", - Some(ERROR_DESCRIPTION.to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: operand.loc, - message: Some("Cannot access ref value during render".to_string()), - identifier_name: None, - }), - ); - } -} - -// --- Main entry point --- - -pub fn validate_no_ref_access_in_render(func: &HirFunction, env: &mut Environment) { - let mut ref_env = Env::new(); - collect_temporaries_sidemap(func, &mut ref_env, &env.identifiers, &env.types); - let mut errors: Vec<CompilerDiagnostic> = Vec::new(); - validate_no_ref_access_in_render_impl( - func, - &env.identifiers, - &env.types, - &env.functions, - &*env, - &mut ref_env, - &mut errors, - ); - for diagnostic in errors { - env.record_diagnostic(diagnostic); - } -} - -fn collect_temporaries_sidemap( - func: &HirFunction, - env: &mut Env, - identifiers: &[Identifier], - types: &[Type], -) { - for (_, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::LoadLocal { place, .. } => { - let temp = env - .temporaries - .get(&place.identifier) - .cloned() - .unwrap_or_else(|| place.clone()); - env.define(instr.lvalue.identifier, temp); - } - InstructionValue::StoreLocal { lvalue, value, .. } => { - let temp = env - .temporaries - .get(&value.identifier) - .cloned() - .unwrap_or_else(|| value.clone()); - env.define(instr.lvalue.identifier, temp.clone()); - env.define(lvalue.place.identifier, temp); - } - InstructionValue::PropertyLoad { - object, property, .. - } => { - if is_ref_type(object.identifier, identifiers, types) - && *property == PropertyLiteral::String("current".to_string()) - { - continue; - } - let temp = env - .temporaries - .get(&object.identifier) - .cloned() - .unwrap_or_else(|| object.clone()); - env.define(instr.lvalue.identifier, temp); - } - _ => {} - } - } - } -} - -fn validate_no_ref_access_in_render_impl( - func: &HirFunction, - identifiers: &[Identifier], - types: &[Type], - functions: &[HirFunction], - env: &Environment, - ref_env: &mut Env, - errors: &mut Vec<CompilerDiagnostic>, -) -> RefAccessType { - let mut return_values: Vec<RefAccessType> = Vec::new(); - - // Process params - for param in &func.params { - let place = match param { - react_compiler_hir::ParamPattern::Place(p) => p, - react_compiler_hir::ParamPattern::Spread(s) => &s.place, - }; - ref_env.set( - place.identifier, - ref_type_of_type(place.identifier, identifiers, types), - ); - } - - // Collect identifiers that are interpolated as JSX children - let mut interpolated_as_jsx: HashSet<IdentifierId> = HashSet::new(); - for (_, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::JsxExpression { - children: Some(children), - .. - } => { - for child in children { - interpolated_as_jsx.insert(child.identifier); - } - } - InstructionValue::JsxFragment { children, .. } => { - for child in children { - interpolated_as_jsx.insert(child.identifier); - } - } - _ => {} - } - } - } - - // Fixed-point iteration (up to 10 iterations) - for iteration in 0..10 { - if iteration > 0 && !ref_env.has_changed() { - break; - } - ref_env.reset_changed(); - return_values.clear(); - let mut safe_blocks: Vec<(BlockId, RefId)> = Vec::new(); - - for (_, block) in &func.body.blocks { - safe_blocks.retain(|(block_id, _)| *block_id != block.id); - - // Process phis - for phi in &block.phis { - let phi_types: Vec<RefAccessType> = phi - .operands - .values() - .map(|operand| { - ref_env - .get(operand.identifier) - .cloned() - .unwrap_or(RefAccessType::None) - }) - .collect(); - ref_env.set(phi.place.identifier, join_ref_access_types_many(&phi_types)); - } - - // Process instructions - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::JsxExpression { .. } - | InstructionValue::JsxFragment { .. } => { - for operand in &canonical_each_instruction_value_operand(&instr.value, env) - { - validate_no_direct_ref_value_access(errors, operand, ref_env); - } - } - InstructionValue::ComputedLoad { - object, property, .. - } => { - validate_no_direct_ref_value_access(errors, property, ref_env); - let obj_type = ref_env.get(object.identifier).cloned(); - let lookup_type = match &obj_type { - Some(RefAccessType::Structure { - value: Some(value), .. - }) => Some(RefAccessType::from_ref_type(value)), - Some(RefAccessType::Ref { ref_id }) => Some(RefAccessType::RefValue { - loc: instr.loc, - ref_id: Some(*ref_id), - }), - _ => None, - }; - ref_env.set( - instr.lvalue.identifier, - lookup_type.unwrap_or_else(|| { - ref_type_of_type(instr.lvalue.identifier, identifiers, types) - }), - ); - } - InstructionValue::PropertyLoad { object, .. } => { - let obj_type = ref_env.get(object.identifier).cloned(); - let lookup_type = match &obj_type { - Some(RefAccessType::Structure { - value: Some(value), .. - }) => Some(RefAccessType::from_ref_type(value)), - Some(RefAccessType::Ref { ref_id }) => Some(RefAccessType::RefValue { - loc: instr.loc, - ref_id: Some(*ref_id), - }), - _ => None, - }; - ref_env.set( - instr.lvalue.identifier, - lookup_type.unwrap_or_else(|| { - ref_type_of_type(instr.lvalue.identifier, identifiers, types) - }), - ); - } - InstructionValue::TypeCastExpression { value, .. } => { - ref_env.set( - instr.lvalue.identifier, - ref_env.get(value.identifier).cloned().unwrap_or_else(|| { - ref_type_of_type(instr.lvalue.identifier, identifiers, types) - }), - ); - } - InstructionValue::LoadContext { place, .. } - | InstructionValue::LoadLocal { place, .. } => { - ref_env.set( - instr.lvalue.identifier, - ref_env.get(place.identifier).cloned().unwrap_or_else(|| { - ref_type_of_type(instr.lvalue.identifier, identifiers, types) - }), - ); - } - InstructionValue::StoreContext { lvalue, value, .. } - | InstructionValue::StoreLocal { lvalue, value, .. } => { - ref_env.set( - lvalue.place.identifier, - ref_env.get(value.identifier).cloned().unwrap_or_else(|| { - ref_type_of_type(lvalue.place.identifier, identifiers, types) - }), - ); - ref_env.set( - instr.lvalue.identifier, - ref_env.get(value.identifier).cloned().unwrap_or_else(|| { - ref_type_of_type(instr.lvalue.identifier, identifiers, types) - }), - ); - } - InstructionValue::Destructure { value, lvalue, .. } => { - let obj_type = ref_env.get(value.identifier).cloned(); - let lookup_type = match &obj_type { - Some(RefAccessType::Structure { - value: Some(value), .. - }) => Some(RefAccessType::from_ref_type(value)), - _ => None, - }; - ref_env.set( - instr.lvalue.identifier, - lookup_type.clone().unwrap_or_else(|| { - ref_type_of_type(instr.lvalue.identifier, identifiers, types) - }), - ); - for pattern_place in each_pattern_operand(&lvalue.pattern) { - ref_env.set( - pattern_place.identifier, - lookup_type.clone().unwrap_or_else(|| { - ref_type_of_type(pattern_place.identifier, identifiers, types) - }), - ); - } - } - InstructionValue::ObjectMethod { lowered_func, .. } - | InstructionValue::FunctionExpression { lowered_func, .. } => { - let inner = &functions[lowered_func.func.0 as usize]; - let mut inner_errors: Vec<CompilerDiagnostic> = Vec::new(); - let result = validate_no_ref_access_in_render_impl( - inner, - identifiers, - types, - functions, - env, - ref_env, - &mut inner_errors, - ); - let (return_type, read_ref_effect) = if inner_errors.is_empty() { - (result, false) - } else { - (RefAccessType::None, true) - }; - ref_env.set( - instr.lvalue.identifier, - RefAccessType::Structure { - value: None, - fn_type: Some(RefFnType { - read_ref_effect, - return_type: Box::new(return_type), - }), - }, - ); - } - InstructionValue::MethodCall { property, .. } - | InstructionValue::CallExpression { - callee: property, .. - } => { - let callee = property; - let mut return_type = RefAccessType::None; - let fn_type = ref_env.get(callee.identifier).cloned(); - let mut did_error = false; - - if let Some(RefAccessType::Structure { - fn_type: Some(fn_ty), - .. - }) = &fn_type - { - return_type = *fn_ty.return_type.clone(); - if fn_ty.read_ref_effect { - did_error = true; - errors.push( - CompilerDiagnostic::new( - ErrorCategory::Refs, - "Cannot access refs during render", - Some(ERROR_DESCRIPTION.to_string()), - ) - .with_detail( - CompilerDiagnosticDetail::Error { - loc: callee.loc, - message: Some( - "This function accesses a ref value".to_string(), - ), - identifier_name: None, - }, - ), - ); - } - } - - /* - * If we already reported an error on this instruction, don't report - * duplicate errors - */ - if !did_error { - let is_ref_lvalue = - is_ref_type(instr.lvalue.identifier, identifiers, types); - let callee_identifier = &identifiers[callee.identifier.0 as usize]; - let callee_type = &types[callee_identifier.type_.0 as usize]; - let hook_kind = env.get_hook_kind_for_type(callee_type).ok().flatten(); - - if is_ref_lvalue - || (hook_kind.is_some() - && !matches!(hook_kind, Some(&HookKind::UseState)) - && !matches!(hook_kind, Some(&HookKind::UseReducer))) - { - for operand in - &canonical_each_instruction_value_operand(&instr.value, env) - { - /* - * Allow passing refs or ref-accessing functions when: - * 1. lvalue is a ref (mergeRefs pattern) - * 2. calling hooks (independently validated) - */ - validate_no_direct_ref_value_access(errors, operand, ref_env); - } - } else if interpolated_as_jsx.contains(&instr.lvalue.identifier) { - for operand in - &canonical_each_instruction_value_operand(&instr.value, env) - { - /* - * Special case: the lvalue is passed as a jsx child - */ - validate_no_ref_value_access(errors, ref_env, operand); - } - } else if hook_kind.is_none() { - if let Some(ref effects) = instr.effects { - /* - * For non-hook functions with known aliasing effects, - * use the effects to determine what validation to apply. - * Track visited id:kind pairs to avoid duplicate errors. - */ - let mut visited_effects: HashSet<String> = HashSet::new(); - for effect in effects { - let (place, validation) = match effect { - AliasingEffect::Freeze { value, .. } => { - (Some(value), "direct-ref") - } - AliasingEffect::Mutate { value, .. } - | AliasingEffect::MutateTransitive { value, .. } - | AliasingEffect::MutateConditionally { - value, .. - } - | AliasingEffect::MutateTransitiveConditionally { - value, - .. - } => (Some(value), "ref-passed"), - AliasingEffect::Render { place, .. } => { - (Some(place), "ref-passed") - } - AliasingEffect::Capture { from, .. } - | AliasingEffect::Alias { from, .. } - | AliasingEffect::MaybeAlias { from, .. } - | AliasingEffect::Assign { from, .. } - | AliasingEffect::CreateFrom { from, .. } => { - (Some(from), "ref-passed") - } - AliasingEffect::ImmutableCapture { from, .. } => { - /* - * ImmutableCapture: check whether the same - * operand also has a Freeze effect to - * distinguish known signatures from - * downgraded defaults. - */ - let is_frozen = effects.iter().any(|e| { - matches!( - e, - AliasingEffect::Freeze { value, .. } - if value.identifier == from.identifier - ) - }); - ( - Some(from), - if is_frozen { - "direct-ref" - } else { - "ref-passed" - }, - ) - } - _ => (None, "none"), - }; - if let Some(place) = place { - if validation != "none" { - let key = format!( - "{}:{}", - place.identifier.0, validation - ); - if visited_effects.insert(key) { - if validation == "direct-ref" { - validate_no_direct_ref_value_access( - errors, place, ref_env, - ); - } else { - validate_no_ref_passed_to_function( - errors, ref_env, place, place.loc, - ); - } - } - } - } - } - } else { - for operand in - &canonical_each_instruction_value_operand(&instr.value, env) - { - validate_no_ref_passed_to_function( - errors, - ref_env, - operand, - operand.loc, - ); - } - } - } else { - for operand in - &canonical_each_instruction_value_operand(&instr.value, env) - { - validate_no_ref_passed_to_function( - errors, - ref_env, - operand, - operand.loc, - ); - } - } - } - ref_env.set(instr.lvalue.identifier, return_type); - } - InstructionValue::ObjectExpression { .. } - | InstructionValue::ArrayExpression { .. } => { - let operands = canonical_each_instruction_value_operand(&instr.value, env); - let mut types_vec: Vec<RefAccessType> = Vec::new(); - for operand in &operands { - validate_no_direct_ref_value_access(errors, operand, ref_env); - types_vec.push( - ref_env - .get(operand.identifier) - .cloned() - .unwrap_or(RefAccessType::None), - ); - } - let value = join_ref_access_types_many(&types_vec); - match &value { - RefAccessType::None - | RefAccessType::Guard { .. } - | RefAccessType::Nullable => { - ref_env.set(instr.lvalue.identifier, RefAccessType::None); - } - _ => { - ref_env.set( - instr.lvalue.identifier, - RefAccessType::Structure { - value: value.to_ref_type().map(Box::new), - fn_type: None, - }, - ); - } - } - } - InstructionValue::PropertyDelete { object, .. } - | InstructionValue::PropertyStore { object, .. } - | InstructionValue::ComputedDelete { object, .. } - | InstructionValue::ComputedStore { object, .. } => { - let target = ref_env.get(object.identifier).cloned(); - let mut found_safe = false; - if matches!(&instr.value, InstructionValue::PropertyStore { .. }) { - if let Some(RefAccessType::Ref { ref_id }) = &target { - if let Some(pos) = safe_blocks.iter().position(|(_, r)| r == ref_id) - { - safe_blocks.remove(pos); - found_safe = true; - } - } - } - if !found_safe { - validate_no_ref_update(errors, ref_env, object, instr.loc); - } - match &instr.value { - InstructionValue::ComputedDelete { property, .. } - | InstructionValue::ComputedStore { property, .. } => { - validate_no_ref_value_access(errors, ref_env, property); - } - _ => {} - } - match &instr.value { - InstructionValue::ComputedStore { value, .. } - | InstructionValue::PropertyStore { value, .. } => { - validate_no_direct_ref_value_access(errors, value, ref_env); - let value_type = ref_env.get(value.identifier).cloned(); - if let Some(RefAccessType::Structure { .. }) = &value_type { - let mut object_type = value_type.unwrap(); - if let Some(t) = &target { - object_type = join_ref_access_types(&object_type, t); - } - ref_env.set(object.identifier, object_type); - } - } - _ => {} - } - } - InstructionValue::StartMemoize { .. } - | InstructionValue::FinishMemoize { .. } => {} - InstructionValue::LoadGlobal { binding, .. } => { - if binding.name() == "undefined" { - ref_env.set(instr.lvalue.identifier, RefAccessType::Nullable); - } - } - InstructionValue::Primitive { value, .. } => { - if matches!(value, PrimitiveValue::Null | PrimitiveValue::Undefined) { - ref_env.set(instr.lvalue.identifier, RefAccessType::Nullable); - } - } - InstructionValue::UnaryExpression { - operator, value, .. - } => { - if *operator == UnaryOperator::Not { - if let Some(RefAccessType::RefValue { - ref_id: Some(ref_id), - .. - }) = ref_env.get(value.identifier).cloned().as_ref() - { - /* - * Record an error suggesting the `if (ref.current == null)` pattern, - * but also record the lvalue as a guard so that we don't emit a - * second error for the write to the ref - */ - ref_env.set( - instr.lvalue.identifier, - RefAccessType::Guard { ref_id: *ref_id }, - ); - errors.push( - CompilerDiagnostic::new( - ErrorCategory::Refs, - "Cannot access refs during render", - Some(ERROR_DESCRIPTION.to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: value.loc, - message: Some( - "Cannot access ref value during render" - .to_string(), - ), - identifier_name: None, - }) - .with_detail(CompilerDiagnosticDetail::Hint { - message: "To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`".to_string(), - }), - ); - } else { - validate_no_ref_value_access(errors, ref_env, value); - } - } else { - validate_no_ref_value_access(errors, ref_env, value); - } - } - InstructionValue::BinaryExpression { left, right, .. } => { - let left_type = ref_env.get(left.identifier).cloned(); - let right_type = ref_env.get(right.identifier).cloned(); - let mut nullish = false; - let mut found_ref_id: Option<RefId> = None; - - if let Some(RefAccessType::RefValue { - ref_id: Some(id), .. - }) = &left_type - { - found_ref_id = Some(*id); - } else if let Some(RefAccessType::RefValue { - ref_id: Some(id), .. - }) = &right_type - { - found_ref_id = Some(*id); - } - - if matches!(&left_type, Some(RefAccessType::Nullable)) { - nullish = true; - } else if matches!(&right_type, Some(RefAccessType::Nullable)) { - nullish = true; - } - - if let Some(ref_id) = found_ref_id { - if nullish { - ref_env - .set(instr.lvalue.identifier, RefAccessType::Guard { ref_id }); - } else { - validate_no_ref_value_access(errors, ref_env, left); - validate_no_ref_value_access(errors, ref_env, right); - } - } else { - validate_no_ref_value_access(errors, ref_env, left); - validate_no_ref_value_access(errors, ref_env, right); - } - } - _ => { - for operand in &canonical_each_instruction_value_operand(&instr.value, env) - { - validate_no_ref_value_access(errors, ref_env, operand); - } - } - } - - // Guard values are derived from ref.current, so they can only be used - // in if statement targets - for operand in &canonical_each_instruction_value_operand(&instr.value, env) { - guard_check(errors, operand, ref_env); - } - - if is_ref_type(instr.lvalue.identifier, identifiers, types) - && !matches!( - ref_env.get(instr.lvalue.identifier), - Some(RefAccessType::Ref { .. }) - ) - { - let existing = ref_env - .get(instr.lvalue.identifier) - .cloned() - .unwrap_or(RefAccessType::None); - ref_env.set( - instr.lvalue.identifier, - join_ref_access_types( - &existing, - &RefAccessType::Ref { - ref_id: next_ref_id(), - }, - ), - ); - } - if is_ref_value_type(instr.lvalue.identifier, identifiers, types) - && !matches!( - ref_env.get(instr.lvalue.identifier), - Some(RefAccessType::RefValue { .. }) - ) - { - let existing = ref_env - .get(instr.lvalue.identifier) - .cloned() - .unwrap_or(RefAccessType::None); - ref_env.set( - instr.lvalue.identifier, - join_ref_access_types( - &existing, - &RefAccessType::RefValue { - loc: instr.loc, - ref_id: None, - }, - ), - ); - } - } - - // Check if terminal is an `if` — push safe block for guard - if let Terminal::If { - test, fallthrough, .. - } = &block.terminal - { - if let Some(RefAccessType::Guard { ref_id }) = ref_env.get(test.identifier) { - if !safe_blocks.iter().any(|(_, r)| r == ref_id) { - safe_blocks.push((*fallthrough, *ref_id)); - } - } - } - - // Process terminal operands - for operand in &each_terminal_operand(&block.terminal) { - if !matches!(&block.terminal, Terminal::Return { .. }) { - validate_no_ref_value_access(errors, ref_env, operand); - if !matches!(&block.terminal, Terminal::If { .. }) { - guard_check(errors, operand, ref_env); - } - } else { - // Allow functions containing refs to be returned, but not direct ref values - validate_no_direct_ref_value_access(errors, operand, ref_env); - guard_check(errors, operand, ref_env); - if let Some(ty) = ref_env.get(operand.identifier) { - return_values.push(ty.clone()); - } - } - } - } - - if !errors.is_empty() { - return RefAccessType::None; - } - } - - if ref_env.has_changed() { - errors.push(CompilerDiagnostic::new( - react_compiler_diagnostics::ErrorCategory::Invariant, - "Ref type environment did not converge", - None, - )); - return RefAccessType::None; - } - - join_ref_access_types_many(&return_values) -} diff --git a/compiler/crates/react_compiler_validation/src/validate_no_set_state_in_effects.rs b/compiler/crates/react_compiler_validation/src/validate_no_set_state_in_effects.rs deleted file mode 100644 index 926d85232594..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_no_set_state_in_effects.rs +++ /dev/null @@ -1,577 +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. - -//! Validates against calling setState in the body of an effect (useEffect and friends), -//! while allowing calling setState in callbacks scheduled by the effect. -//! -//! Calling setState during execution of a useEffect triggers a re-render, which is -//! often bad for performance and frequently has more efficient and straightforward -//! alternatives. See https://react.dev/learn/you-might-not-need-an-effect for examples. -//! -//! Port of ValidateNoSetStateInEffects.ts. - -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory, -}; -use react_compiler_hir::dominator::{compute_post_dominator_tree, post_dominator_frontier}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{ - BlockId, HirFunction, Identifier, IdentifierId, IdentifierName, InstructionValue, - PlaceOrSpread, PropertyLiteral, SourceLocation, Terminal, Type, is_ref_value_type, - is_set_state_type, is_use_effect_event_type, is_use_effect_hook_type, - is_use_insertion_effect_hook_type, is_use_layout_effect_hook_type, is_use_ref_type, visitors, -}; - -pub fn validate_no_set_state_in_effects( - func: &HirFunction, - env: &Environment, -) -> Result<CompilerError, CompilerDiagnostic> { - let identifiers = &env.identifiers; - let types = &env.types; - let functions = &env.functions; - let enable_verbose = env.config.enable_verbose_no_set_state_in_effect; - let enable_allow_set_state_from_refs = env.config.enable_allow_set_state_from_refs_in_effects; - - // Map from IdentifierId to the Place where the setState originated - let mut set_state_functions: HashMap<IdentifierId, SetStateInfo> = HashMap::new(); - let mut errors = CompilerError::new(); - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::LoadLocal { place, .. } => { - if set_state_functions.contains_key(&place.identifier) { - let info = set_state_functions[&place.identifier].clone(); - set_state_functions.insert(instr.lvalue.identifier, info); - } - } - InstructionValue::StoreLocal { lvalue, value, .. } => { - if set_state_functions.contains_key(&value.identifier) { - let info = set_state_functions[&value.identifier].clone(); - set_state_functions.insert(lvalue.place.identifier, info.clone()); - set_state_functions.insert(instr.lvalue.identifier, info); - } - } - InstructionValue::FunctionExpression { lowered_func, .. } => { - // Check if any context capture references a setState - let inner_func = &functions[lowered_func.func.0 as usize]; - let has_set_state_operand = inner_func.context.iter().any(|ctx_place| { - is_set_state_type_by_id(ctx_place.identifier, identifiers, types) - || set_state_functions.contains_key(&ctx_place.identifier) - }); - - if has_set_state_operand { - let callee = get_set_state_call( - inner_func, - &mut set_state_functions, - identifiers, - types, - functions, - enable_allow_set_state_from_refs, - env.next_block_id_counter, - env.code.as_deref(), - )?; - if let Some(info) = callee { - set_state_functions.insert(instr.lvalue.identifier, info); - } - } - } - InstructionValue::MethodCall { property, args, .. } => { - let prop_type = - &types[identifiers[property.identifier.0 as usize].type_.0 as usize]; - if is_use_effect_event_type(prop_type) { - if let Some(first_arg) = args.first() { - if let PlaceOrSpread::Place(arg_place) = first_arg { - if let Some(info) = set_state_functions.get(&arg_place.identifier) { - set_state_functions - .insert(instr.lvalue.identifier, info.clone()); - } - } - } - } else if is_use_effect_hook_type(prop_type) - || is_use_layout_effect_hook_type(prop_type) - || is_use_insertion_effect_hook_type(prop_type) - { - if let Some(first_arg) = args.first() { - if let PlaceOrSpread::Place(arg_place) = first_arg { - if let Some(info) = set_state_functions.get(&arg_place.identifier) { - push_error(&mut errors, info, enable_verbose); - } - } - } - } - } - InstructionValue::CallExpression { callee, args, .. } => { - let callee_type = - &types[identifiers[callee.identifier.0 as usize].type_.0 as usize]; - if is_use_effect_event_type(callee_type) { - if let Some(first_arg) = args.first() { - if let PlaceOrSpread::Place(arg_place) = first_arg { - if let Some(info) = set_state_functions.get(&arg_place.identifier) { - set_state_functions - .insert(instr.lvalue.identifier, info.clone()); - } - } - } - } else if is_use_effect_hook_type(callee_type) - || is_use_layout_effect_hook_type(callee_type) - || is_use_insertion_effect_hook_type(callee_type) - { - if let Some(first_arg) = args.first() { - if let PlaceOrSpread::Place(arg_place) = first_arg { - if let Some(info) = set_state_functions.get(&arg_place.identifier) { - push_error(&mut errors, info, enable_verbose); - } - } - } - } - } - _ => {} - } - } - } - - Ok(errors) -} - -#[derive(Debug, Clone)] -struct SetStateInfo { - loc: Option<SourceLocation>, - identifier_name: Option<String>, -} - -/// Get the user-visible name for an identifier, matching Babel's -/// loc.identifierName behavior. First checks the identifier's own name, -/// then falls back to extracting the name from the source code at the -/// given source location (the callee's loc). This handles SSA identifiers -/// whose names were lost during compiler passes. -fn get_identifier_name_with_loc( - id: IdentifierId, - identifiers: &[Identifier], - loc: &Option<SourceLocation>, - source_code: Option<&str>, -) -> Option<String> { - let ident = &identifiers[id.0 as usize]; - if let Some(IdentifierName::Named(name)) = &ident.name { - return Some(name.clone()); - } - // Fall back to extracting from source code - if let (Some(loc), Some(code)) = (loc, source_code) { - let start_idx = loc.start.index? as usize; - let end_idx = loc.end.index? as usize; - if start_idx < code.len() && end_idx <= code.len() && start_idx < end_idx { - let slice = &code[start_idx..end_idx]; - if !slice.is_empty() - && slice - .chars() - .all(|c| c.is_alphanumeric() || c == '_' || c == '$') - { - return Some(slice.to_string()); - } - } - } - None -} - -fn is_set_state_type_by_id( - identifier_id: IdentifierId, - identifiers: &[Identifier], - types: &[Type], -) -> bool { - let ident = &identifiers[identifier_id.0 as usize]; - let ty = &types[ident.type_.0 as usize]; - is_set_state_type(ty) -} - -fn push_error(errors: &mut CompilerError, info: &SetStateInfo, enable_verbose: bool) { - if enable_verbose { - errors.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::EffectSetState, - "Calling setState synchronously within an effect can trigger cascading renders", - Some( - "Effects are intended to synchronize state between React and external systems. \ - Calling setState synchronously causes cascading renders that hurt performance.\n\n\ - This pattern may indicate one of several issues:\n\n\ - **1. Non-local derived data**: If the value being set could be computed from props/state \ - but requires data from a parent component, consider restructuring state ownership so the \ - derivation can happen during render in the component that owns the relevant state.\n\n\ - **2. Derived event pattern**: If you're detecting when a prop changes (e.g., `isPlaying` \ - transitioning from false to true), this often indicates the parent should provide an event \ - callback (like `onPlay`) instead of just the current state. Request access to the original event.\n\n\ - **3. Force update / external sync**: If you're forcing a re-render to sync with an external \ - data source (mutable values outside React), use `useSyncExternalStore` to properly subscribe \ - to external state changes.\n\n\ - See: https://react.dev/learn/you-might-not-need-an-effect".to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: info.loc, - message: Some( - "Avoid calling setState() directly within an effect".to_string(), - ), - identifier_name: info.identifier_name.clone(), - }), - ); - } else { - errors.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::EffectSetState, - "Calling setState synchronously within an effect can trigger cascading renders", - Some( - "Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. \ - In general, the body of an effect should do one or both of the following:\n\ - * Update external systems with the latest state from React.\n\ - * Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n\ - Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. \ - (https://react.dev/learn/you-might-not-need-an-effect)".to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: info.loc, - message: Some( - "Avoid calling setState() directly within an effect".to_string(), - ), - identifier_name: info.identifier_name.clone(), - }), - ); - } -} - -/// Recursively collect all Place identifiers from a destructure pattern. -fn collect_destructure_places( - pattern: &react_compiler_hir::Pattern, - ref_derived_values: &mut HashSet<IdentifierId>, -) { - match pattern { - react_compiler_hir::Pattern::Array(arr) => { - for item in &arr.items { - match item { - react_compiler_hir::ArrayPatternElement::Place(p) => { - ref_derived_values.insert(p.identifier); - } - react_compiler_hir::ArrayPatternElement::Spread(s) => { - ref_derived_values.insert(s.place.identifier); - } - react_compiler_hir::ArrayPatternElement::Hole => {} - } - } - } - react_compiler_hir::Pattern::Object(obj) => { - for prop in &obj.properties { - match prop { - react_compiler_hir::ObjectPropertyOrSpread::Property(p) => { - ref_derived_values.insert(p.place.identifier); - } - react_compiler_hir::ObjectPropertyOrSpread::Spread(s) => { - ref_derived_values.insert(s.place.identifier); - } - } - } - } - } -} - -fn is_derived_from_ref( - id: IdentifierId, - ref_derived_values: &HashSet<IdentifierId>, - identifiers: &[Identifier], - types: &[Type], -) -> bool { - if ref_derived_values.contains(&id) { - return true; - } - let ident = &identifiers[id.0 as usize]; - let ty = &types[ident.type_.0 as usize]; - is_use_ref_type(ty) || is_ref_value_type(ty) -} - -/// Collects all operand IdentifierIds from an instruction value. -/// Uses the canonical `each_instruction_value_operand_with_functions` from visitors. -fn collect_operands(value: &InstructionValue, functions: &[HirFunction]) -> Vec<IdentifierId> { - visitors::each_instruction_value_operand_with_functions(value, functions) - .into_iter() - .map(|p| p.identifier) - .collect() -} - -/// Creates a function that checks whether a block is "control-dominated" by -/// a ref-derived condition. A block is ref-controlled if its post-dominator -/// frontier contains a block whose terminal tests a ref-derived value. -fn create_ref_controlled_block_checker( - func: &HirFunction, - next_block_id_counter: u32, - ref_derived_values: &HashSet<IdentifierId>, - identifiers: &[Identifier], - types: &[Type], -) -> Result<HashMap<BlockId, bool>, CompilerDiagnostic> { - let post_dominators = compute_post_dominator_tree(func, next_block_id_counter, false)?; - let mut cache: HashMap<BlockId, bool> = HashMap::new(); - - for (block_id, _block) in &func.body.blocks { - let frontier = post_dominator_frontier(func, &post_dominators, *block_id); - let mut is_controlled = false; - - for frontier_block_id in &frontier { - let control_block = &func.body.blocks[frontier_block_id]; - match &control_block.terminal { - Terminal::If { test, .. } | Terminal::Branch { test, .. } => { - if is_derived_from_ref(test.identifier, ref_derived_values, identifiers, types) - { - is_controlled = true; - break; - } - } - Terminal::Switch { test, cases, .. } => { - if is_derived_from_ref(test.identifier, ref_derived_values, identifiers, types) - { - is_controlled = true; - break; - } - for case in cases { - if let Some(case_test) = &case.test { - if is_derived_from_ref( - case_test.identifier, - ref_derived_values, - identifiers, - types, - ) { - is_controlled = true; - break; - } - } - } - if is_controlled { - break; - } - } - _ => {} - } - } - - cache.insert(*block_id, is_controlled); - } - - Ok(cache) -} - -/// Checks inner function body for direct setState calls. Returns the callee Place info -/// if a setState call is found in the function body. -/// Tracks ref-derived values to allow setState when the value being set comes from a ref. -fn get_set_state_call( - func: &HirFunction, - set_state_functions: &mut HashMap<IdentifierId, SetStateInfo>, - identifiers: &[Identifier], - types: &[Type], - functions: &[HirFunction], - enable_allow_set_state_from_refs: bool, - next_block_id_counter: u32, - source_code: Option<&str>, -) -> Result<Option<SetStateInfo>, CompilerDiagnostic> { - let mut ref_derived_values: HashSet<IdentifierId> = HashSet::new(); - - // First pass: collect ref-derived values (needed before building control dominator checker) - // We do a pre-pass to seed ref_derived_values so the control dominator checker has them. - if enable_allow_set_state_from_refs { - for (_block_id, block) in &func.body.blocks { - for phi in &block.phis { - let is_phi_derived = phi.operands.values().any(|operand| { - is_derived_from_ref(operand.identifier, &ref_derived_values, identifiers, types) - }); - if is_phi_derived { - ref_derived_values.insert(phi.place.identifier); - } - } - - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - - let operands = collect_operands(&instr.value, functions); - let has_ref_operand = operands.iter().any(|op_id| { - is_derived_from_ref(*op_id, &ref_derived_values, identifiers, types) - }); - - if has_ref_operand { - ref_derived_values.insert(instr.lvalue.identifier); - if let InstructionValue::Destructure { lvalue, .. } = &instr.value { - collect_destructure_places(&lvalue.pattern, &mut ref_derived_values); - } - if let InstructionValue::StoreLocal { lvalue, .. } = &instr.value { - ref_derived_values.insert(lvalue.place.identifier); - } - } - - if let InstructionValue::PropertyLoad { - object, property, .. - } = &instr.value - { - if *property == PropertyLiteral::String("current".to_string()) { - let obj_ident = &identifiers[object.identifier.0 as usize]; - let obj_ty = &types[obj_ident.type_.0 as usize]; - if is_use_ref_type(obj_ty) || is_ref_value_type(obj_ty) { - ref_derived_values.insert(instr.lvalue.identifier); - } - } - } - } - } - } - - // Build control dominator checker after collecting ref-derived values - let ref_controlled_blocks = if enable_allow_set_state_from_refs { - create_ref_controlled_block_checker( - func, - next_block_id_counter, - &ref_derived_values, - identifiers, - types, - )? - } else { - HashMap::new() - }; - - let is_ref_controlled_block = |block_id: BlockId| -> bool { - ref_controlled_blocks - .get(&block_id) - .copied() - .unwrap_or(false) - }; - - // Reset and redo: second pass with control dominator info available - ref_derived_values.clear(); - - for (_block_id, block) in &func.body.blocks { - // Track ref-derived values through phis - if enable_allow_set_state_from_refs { - for phi in &block.phis { - if is_derived_from_ref( - phi.place.identifier, - &ref_derived_values, - identifiers, - types, - ) { - continue; - } - let is_phi_derived = phi.operands.values().any(|operand| { - is_derived_from_ref(operand.identifier, &ref_derived_values, identifiers, types) - }); - if is_phi_derived { - ref_derived_values.insert(phi.place.identifier); - } else { - // Fallback: check if any predecessor block is ref-controlled - let mut found = false; - for pred in phi.operands.keys() { - if is_ref_controlled_block(*pred) { - ref_derived_values.insert(phi.place.identifier); - found = true; - break; - } - } - if found { - continue; - } - } - } - } - - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - - // Track ref-derived values through instructions - if enable_allow_set_state_from_refs { - let operands = collect_operands(&instr.value, functions); - let has_ref_operand = operands.iter().any(|op_id| { - is_derived_from_ref(*op_id, &ref_derived_values, identifiers, types) - }); - - if has_ref_operand { - ref_derived_values.insert(instr.lvalue.identifier); - // For Destructure, also mark all pattern places as ref-derived - if let InstructionValue::Destructure { lvalue, .. } = &instr.value { - collect_destructure_places(&lvalue.pattern, &mut ref_derived_values); - } - // For StoreLocal, propagate to the local variable - if let InstructionValue::StoreLocal { lvalue, .. } = &instr.value { - ref_derived_values.insert(lvalue.place.identifier); - } - } - - // Special case: PropertyLoad of .current on ref/refValue - if let InstructionValue::PropertyLoad { - object, property, .. - } = &instr.value - { - if *property == PropertyLiteral::String("current".to_string()) { - let obj_ident = &identifiers[object.identifier.0 as usize]; - let obj_ty = &types[obj_ident.type_.0 as usize]; - if is_use_ref_type(obj_ty) || is_ref_value_type(obj_ty) { - ref_derived_values.insert(instr.lvalue.identifier); - } - } - } - } - - match &instr.value { - InstructionValue::LoadLocal { place, .. } => { - if set_state_functions.contains_key(&place.identifier) { - let info = set_state_functions[&place.identifier].clone(); - set_state_functions.insert(instr.lvalue.identifier, info); - } - } - InstructionValue::StoreLocal { lvalue, value, .. } => { - if set_state_functions.contains_key(&value.identifier) { - let info = set_state_functions[&value.identifier].clone(); - set_state_functions.insert(lvalue.place.identifier, info.clone()); - set_state_functions.insert(instr.lvalue.identifier, info); - } - } - InstructionValue::CallExpression { callee, args, .. } => { - if is_set_state_type_by_id(callee.identifier, identifiers, types) - || set_state_functions.contains_key(&callee.identifier) - { - if enable_allow_set_state_from_refs { - // Check if the first argument is ref-derived - if let Some(first_arg) = args.first() { - if let PlaceOrSpread::Place(arg_place) = first_arg { - if is_derived_from_ref( - arg_place.identifier, - &ref_derived_values, - identifiers, - types, - ) { - // Allow setState when value is derived from ref - return Ok(None); - } - } - } - // Check if the current block is controlled by a ref-derived condition - if is_ref_controlled_block(block.id) { - continue; - } - } - // Get the user-visible identifier name, matching Babel's - // loc.identifierName behavior. Uses declaration_id to find - // the original named identifier when SSA creates unnamed copies. - let callee_name = get_identifier_name_with_loc( - callee.identifier, - identifiers, - &callee.loc, - source_code, - ); - return Ok(Some(SetStateInfo { - loc: callee.loc, - identifier_name: callee_name, - })); - } - } - _ => {} - } - } - } - Ok(None) -} diff --git a/compiler/crates/react_compiler_validation/src/validate_no_set_state_in_render.rs b/compiler/crates/react_compiler_validation/src/validate_no_set_state_in_render.rs deleted file mode 100644 index 19a7c5ea2a79..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_no_set_state_in_render.rs +++ /dev/null @@ -1,181 +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. - -//! Validates that the function does not unconditionally call setState during render. -//! -//! Port of ValidateNoSetStateInRender.ts. - -use std::collections::HashSet; - -use react_compiler_diagnostics::{CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory}; -use react_compiler_hir::dominator::compute_unconditional_blocks; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{BlockId, HirFunction, Identifier, IdentifierId, InstructionValue, Type}; - -pub fn validate_no_set_state_in_render( - func: &HirFunction, - env: &mut Environment, -) -> Result<(), CompilerDiagnostic> { - let mut unconditional_set_state_functions: HashSet<IdentifierId> = HashSet::new(); - let next_block_id = env.next_block_id().0; - let diagnostics = validate_impl( - func, - &env.identifiers, - &env.types, - &env.functions, - next_block_id, - env.config.enable_use_keyed_state, - &mut unconditional_set_state_functions, - )?; - for diag in diagnostics { - env.record_diagnostic(diag); - } - Ok(()) -} - -fn is_set_state_id( - identifier_id: IdentifierId, - identifiers: &[Identifier], - types: &[Type], -) -> bool { - let ident = &identifiers[identifier_id.0 as usize]; - let ty = &types[ident.type_.0 as usize]; - react_compiler_hir::is_set_state_type(ty) -} - -fn validate_impl( - func: &HirFunction, - identifiers: &[Identifier], - types: &[Type], - functions: &[HirFunction], - next_block_id_counter: u32, - enable_use_keyed_state: bool, - unconditional_set_state_functions: &mut HashSet<IdentifierId>, -) -> Result<Vec<CompilerDiagnostic>, CompilerDiagnostic> { - let unconditional_blocks: HashSet<BlockId> = - compute_unconditional_blocks(func, next_block_id_counter)?; - let mut active_manual_memo_id: Option<u32> = None; - let mut errors: Vec<CompilerDiagnostic> = Vec::new(); - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - match &instr.value { - InstructionValue::LoadLocal { place, .. } => { - if unconditional_set_state_functions.contains(&place.identifier) { - unconditional_set_state_functions.insert(instr.lvalue.identifier); - } - } - InstructionValue::StoreLocal { lvalue, value, .. } => { - if unconditional_set_state_functions.contains(&value.identifier) { - unconditional_set_state_functions.insert(lvalue.place.identifier); - unconditional_set_state_functions.insert(instr.lvalue.identifier); - } - } - InstructionValue::ObjectMethod { lowered_func, .. } - | InstructionValue::FunctionExpression { lowered_func, .. } => { - let inner_func = &functions[lowered_func.func.0 as usize]; - - // Check if any operand references a setState. - // For FunctionExpression/ObjectMethod, operands are the context captures. - let has_set_state_operand = inner_func.context.iter().any(|ctx_place| { - is_set_state_id(ctx_place.identifier, identifiers, types) - || unconditional_set_state_functions.contains(&ctx_place.identifier) - }); - - if has_set_state_operand { - let inner_errors = validate_impl( - inner_func, - identifiers, - types, - functions, - next_block_id_counter, - enable_use_keyed_state, - unconditional_set_state_functions, - )?; - if !inner_errors.is_empty() { - unconditional_set_state_functions.insert(instr.lvalue.identifier); - } - } - } - InstructionValue::StartMemoize { manual_memo_id, .. } => { - assert!( - active_manual_memo_id.is_none(), - "Unexpected nested StartMemoize instructions" - ); - active_manual_memo_id = Some(*manual_memo_id); - } - InstructionValue::FinishMemoize { manual_memo_id, .. } => { - assert!( - active_manual_memo_id == Some(*manual_memo_id), - "Expected FinishMemoize to align with previous StartMemoize instruction" - ); - active_manual_memo_id = None; - } - InstructionValue::CallExpression { callee, .. } => { - if is_set_state_id(callee.identifier, identifiers, types) - || unconditional_set_state_functions.contains(&callee.identifier) - { - if active_manual_memo_id.is_some() { - errors.push( - CompilerDiagnostic::new( - ErrorCategory::RenderSetState, - "Calling setState from useMemo may trigger an infinite loop", - Some( - "Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)".to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: callee.loc, - message: Some("Found setState() within useMemo()".to_string()), - identifier_name: None, - }), - ); - } else if unconditional_blocks.contains(&block.id) { - if enable_use_keyed_state { - errors.push( - CompilerDiagnostic::new( - ErrorCategory::RenderSetState, - "Cannot call setState during render", - Some( - "Calling setState during render may trigger an infinite loop.\n\ - * To reset state when other state/props change, use `const [state, setState] = useKeyedState(initialState, key)` to reset `state` when `key` changes.\n\ - * To derive data from other state/props, compute the derived data during render without using state".to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: callee.loc, - message: Some("Found setState() in render".to_string()), - identifier_name: None, - }), - ); - } else { - errors.push( - CompilerDiagnostic::new( - ErrorCategory::RenderSetState, - "Cannot call setState during render", - Some( - "Calling setState during render may trigger an infinite loop.\n\ - * To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders\n\ - * To derive data from other state/props, compute the derived data during render without using state".to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: callee.loc, - message: Some("Found setState() in render".to_string()), - identifier_name: None, - }), - ); - } - } - } - } - _ => {} - } - } - } - - Ok(errors) -} diff --git a/compiler/crates/react_compiler_validation/src/validate_preserved_manual_memoization.rs b/compiler/crates/react_compiler_validation/src/validate_preserved_manual_memoization.rs deleted file mode 100644 index f83263c1a103..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_preserved_manual_memoization.rs +++ /dev/null @@ -1,773 +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. - -//! Port of ValidatePreservedManualMemoization.ts -//! -//! Validates that all explicit manual memoization (useMemo/useCallback) was -//! accurately preserved, and that no originally memoized values became -//! unmemoized in the output. - -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, SourceLocation, -}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::{ - DeclarationId, DependencyPathEntry, Identifier, IdentifierId, IdentifierName, InstructionKind, - InstructionValue, ManualMemoDependency, ManualMemoDependencyRoot, Place, ReactiveBlock, - ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock, ReactiveStatement, ReactiveValue, - ScopeId, -}; - -/// State tracked during manual memo validation within a StartMemoize..FinishMemoize range. -struct ManualMemoBlockState { - /// Reassigned temporaries (declaration_id -> set of identifier ids that were reassigned to it). - reassignments: HashMap<DeclarationId, HashSet<IdentifierId>>, - /// Source location of the StartMemoize instruction. - loc: Option<SourceLocation>, - /// Declarations produced within this manual memo block. - decls: HashSet<DeclarationId>, - /// Normalized deps from source (useMemo/useCallback dep array). - deps_from_source: Option<Vec<ManualMemoDependency>>, - /// Manual memo id from StartMemoize. - manual_memo_id: u32, -} - -/// Top-level visitor state. -struct VisitorState<'a> { - env: &'a mut Environment, - manual_memo_state: Option<ManualMemoBlockState>, - /// Completed (non-pruned) scope IDs. - scopes: HashSet<ScopeId>, - /// Completed pruned scope IDs. - pruned_scopes: HashSet<ScopeId>, - /// Map from identifier ID to its normalized manual memo dependency. - temporaries: HashMap<IdentifierId, ManualMemoDependency>, -} - -/// Validate that manual memoization (useMemo/useCallback) is preserved. -/// -/// Walks the reactive function looking for StartMemoize/FinishMemoize instructions -/// and checks that: -/// 1. Dependencies' scopes have completed before the memo block starts -/// 2. Memoized values are actually within scopes (not unmemoized) -/// 3. Inferred scope dependencies match the source dependencies -pub fn validate_preserved_manual_memoization(func: &ReactiveFunction, env: &mut Environment) { - let mut state = VisitorState { - env, - manual_memo_state: None, - scopes: HashSet::new(), - pruned_scopes: HashSet::new(), - temporaries: HashMap::new(), - }; - visit_block(&func.body, &mut state); -} - -fn is_named(ident: &Identifier) -> bool { - matches!(ident.name, Some(IdentifierName::Named(_))) -} - -fn visit_block(block: &ReactiveBlock, state: &mut VisitorState) { - for stmt in block { - visit_statement(stmt, state); - } -} - -fn visit_statement(stmt: &ReactiveStatement, state: &mut VisitorState) { - match stmt { - ReactiveStatement::Instruction(instr) => { - visit_instruction(instr, state); - } - ReactiveStatement::Terminal(terminal) => { - visit_terminal(terminal, state); - } - ReactiveStatement::Scope(scope_block) => { - visit_scope(scope_block, state); - } - ReactiveStatement::PrunedScope(pruned) => { - visit_pruned_scope(pruned, state); - } - } -} - -fn visit_terminal( - terminal: &react_compiler_hir::ReactiveTerminalStatement, - state: &mut VisitorState, -) { - use react_compiler_hir::ReactiveTerminal; - match &terminal.terminal { - ReactiveTerminal::If { - consequent, - alternate, - .. - } => { - visit_block(consequent, state); - if let Some(alt) = alternate { - visit_block(alt, state); - } - } - ReactiveTerminal::Switch { cases, .. } => { - for case in cases { - if let Some(ref block) = case.block { - visit_block(block, state); - } - } - } - ReactiveTerminal::For { loop_block, .. } - | ReactiveTerminal::ForOf { loop_block, .. } - | ReactiveTerminal::ForIn { loop_block, .. } - | ReactiveTerminal::While { loop_block, .. } - | ReactiveTerminal::DoWhile { loop_block, .. } => { - visit_block(loop_block, state); - } - ReactiveTerminal::Label { block, .. } => { - visit_block(block, state); - } - ReactiveTerminal::Try { block, handler, .. } => { - visit_block(block, state); - visit_block(handler, state); - } - _ => {} - } -} - -fn visit_scope(scope_block: &ReactiveScopeBlock, state: &mut VisitorState) { - // Traverse the scope's instructions first - visit_block(&scope_block.instructions, state); - - // After traversing, validate scope dependencies against manual memo deps - if let Some(ref memo_state) = state.manual_memo_state { - if let Some(ref deps_from_source) = memo_state.deps_from_source { - let scope = &state.env.scopes[scope_block.scope.0 as usize]; - let deps = scope.dependencies.clone(); - let memo_loc = memo_state.loc; - let decls = memo_state.decls.clone(); - let deps_from_source = deps_from_source.clone(); - let temporaries = state.temporaries.clone(); - for dep in &deps { - validate_inferred_dep( - dep.identifier, - &dep.path, - &temporaries, - &decls, - &deps_from_source, - state.env, - memo_loc, - ); - } - } - } - - // Mark scope and merged scopes as completed - let scope = &state.env.scopes[scope_block.scope.0 as usize]; - let merged = scope.merged.clone(); - state.scopes.insert(scope_block.scope); - for merged_id in merged { - state.scopes.insert(merged_id); - } -} - -fn visit_pruned_scope( - pruned: &react_compiler_hir::PrunedReactiveScopeBlock, - state: &mut VisitorState, -) { - visit_block(&pruned.instructions, state); - state.pruned_scopes.insert(pruned.scope); -} - -fn visit_instruction(instr: &ReactiveInstruction, state: &mut VisitorState) { - // Record temporaries and deps in the instruction's value - record_temporaries(instr, state); - - match &instr.value { - ReactiveValue::Instruction(InstructionValue::StartMemoize { - manual_memo_id, - deps, - has_invalid_deps, - .. - }) => { - // TS: CompilerError.invariant(state.manualMemoState == null, ...) - if state.manual_memo_state.is_some() { - return; - } - - // TS: if (value.hasInvalidDeps === true) { return; } - if *has_invalid_deps { - return; - } - - let deps_from_source = deps.clone(); - - state.manual_memo_state = Some(ManualMemoBlockState { - loc: instr.loc, - decls: HashSet::new(), - deps_from_source, - manual_memo_id: *manual_memo_id, - reassignments: HashMap::new(), - }); - - // Check that each dependency's scope has completed before the memo - // TS: for (const {identifier, loc} of eachInstructionValueOperand(value)) - let operand_places = start_memoize_operands(deps); - for place in &operand_places { - let ident = &state.env.identifiers[place.identifier.0 as usize]; - if let Some(scope_id) = ident.scope { - if !state.scopes.contains(&scope_id) && !state.pruned_scopes.contains(&scope_id) - { - let diag = CompilerDiagnostic::new( - ErrorCategory::PreserveManualMemo, - "Existing memoization could not be preserved", - Some( - "React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. \ - This dependency may be mutated later, which could cause the value to change unexpectedly".to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: place.loc, - message: Some( - "This dependency may be modified later".to_string(), - ), - identifier_name: None, - }); - state.env.record_diagnostic(diag); - } - } - } - } - ReactiveValue::Instruction(InstructionValue::FinishMemoize { - decl, - pruned, - manual_memo_id, - .. - }) => { - if state.manual_memo_state.is_none() { - // StartMemoize had invalid deps, skip validation - return; - } - - // TS: CompilerError.invariant(state.manualMemoState.manualMemoId === value.manualMemoId, ...) - if state - .manual_memo_state - .as_ref() - .map_or(true, |s| s.manual_memo_id != *manual_memo_id) - { - state.manual_memo_state = None; - return; - } - - let memo_state = state.manual_memo_state.take().unwrap(); - - if !pruned { - // Check if the declared value is unmemoized - let decl_ident = &state.env.identifiers[decl.identifier.0 as usize]; - - if decl_ident.scope.is_none() { - // If the manual memo was inlined (useMemo -> IIFE), check reassignments - let decls_to_check = memo_state - .reassignments - .get(&decl_ident.declaration_id) - .map(|ids| ids.iter().copied().collect::<Vec<_>>()) - .unwrap_or_else(|| vec![decl.identifier]); - - for id in decls_to_check { - if is_unmemoized(id, &state.scopes, &state.env.identifiers) { - record_unmemoized_error(decl.loc, state.env); - } - } - } else { - // Single identifier with scope - if is_unmemoized(decl.identifier, &state.scopes, &state.env.identifiers) { - record_unmemoized_error(decl.loc, state.env); - } - } - } - } - ReactiveValue::Instruction(InstructionValue::StoreLocal { lvalue, value, .. }) => { - // Track reassignments from inlining of manual memo - if state.manual_memo_state.is_some() && lvalue.kind == InstructionKind::Reassign { - let decl_id = - state.env.identifiers[lvalue.place.identifier.0 as usize].declaration_id; - state - .manual_memo_state - .as_mut() - .unwrap() - .reassignments - .entry(decl_id) - .or_default() - .insert(value.identifier); - } - } - ReactiveValue::Instruction(InstructionValue::LoadLocal { place, .. }) => { - if state.manual_memo_state.is_some() { - let place_ident = &state.env.identifiers[place.identifier.0 as usize]; - if let Some(ref lvalue) = instr.lvalue { - let lvalue_ident = &state.env.identifiers[lvalue.identifier.0 as usize]; - if place_ident.scope.is_some() && lvalue_ident.scope.is_none() { - state - .manual_memo_state - .as_mut() - .unwrap() - .reassignments - .entry(lvalue_ident.declaration_id) - .or_default() - .insert(place.identifier); - } - } - } - } - _ => {} - } -} - -fn record_unmemoized_error(loc: Option<SourceLocation>, env: &mut Environment) { - let diag = CompilerDiagnostic::new( - ErrorCategory::PreserveManualMemo, - "Existing memoization could not be preserved", - Some( - "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".to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc, - message: Some("Could not preserve existing memoization".to_string()), - identifier_name: None, - }); - env.record_diagnostic(diag); -} - -/// Record temporaries from an instruction. -/// TS: `recordTemporaries` -fn record_temporaries(instr: &ReactiveInstruction, state: &mut VisitorState) { - let lvalue = &instr.lvalue; - let lv_id = lvalue.as_ref().map(|lv| lv.identifier); - if let Some(id) = lv_id { - if state.temporaries.contains_key(&id) { - return; - } - } - - if let Some(ref lvalue) = instr.lvalue { - let lv_ident = &state.env.identifiers[lvalue.identifier.0 as usize]; - if is_named(lv_ident) && state.manual_memo_state.is_some() { - state - .manual_memo_state - .as_mut() - .unwrap() - .decls - .insert(lv_ident.declaration_id); - } - } - - // Record deps from the instruction value first (before setting lvalue temporary) - record_deps_in_value(&instr.value, state); - - // Then set the lvalue temporary (TS always sets this, even for unnamed lvalues) - if let Some(ref lvalue) = instr.lvalue { - state.temporaries.insert( - lvalue.identifier, - ManualMemoDependency { - root: ManualMemoDependencyRoot::NamedLocal { - value: lvalue.clone(), - constant: false, - }, - path: Vec::new(), - loc: lvalue.loc, - }, - ); - } -} - -/// Record dependencies from a reactive value. -/// TS: `recordDepsInValue` -fn record_deps_in_value(value: &ReactiveValue, state: &mut VisitorState) { - match value { - ReactiveValue::SequenceExpression { - instructions, - value, - .. - } => { - for instr in instructions { - visit_instruction(instr, state); - } - record_deps_in_value(value, state); - } - ReactiveValue::OptionalExpression { value: inner, .. } => { - record_deps_in_value(inner, state); - } - ReactiveValue::ConditionalExpression { - test, - consequent, - alternate, - .. - } => { - record_deps_in_value(test, state); - record_deps_in_value(consequent, state); - record_deps_in_value(alternate, state); - } - ReactiveValue::LogicalExpression { left, right, .. } => { - record_deps_in_value(left, state); - record_deps_in_value(right, state); - } - ReactiveValue::Instruction(iv) => { - // TS: collectMaybeMemoDependencies(value, this.temporaries, false) - // Called for side-effect of building up the dependency chain through - // LoadGlobal -> PropertyLoad -> ... The return value is discarded here - // (only used in DropManualMemoization's caller), but we need to store - // the result in temporaries for the lvalue of the enclosing instruction. - // That storage is handled by record_temporaries after this function returns. - - // Track store targets within manual memo blocks - // TS: if (value.kind === 'StoreLocal' || value.kind === 'StoreContext' || value.kind === 'Destructure') - match iv { - InstructionValue::StoreLocal { lvalue, .. } - | InstructionValue::StoreContext { lvalue, .. } => { - if let Some(ref mut memo_state) = state.manual_memo_state { - let ident = &state.env.identifiers[lvalue.place.identifier.0 as usize]; - memo_state.decls.insert(ident.declaration_id); - if is_named(ident) { - state.temporaries.insert( - lvalue.place.identifier, - ManualMemoDependency { - root: ManualMemoDependencyRoot::NamedLocal { - value: lvalue.place.clone(), - constant: false, - }, - path: Vec::new(), - loc: lvalue.place.loc, - }, - ); - } - } - } - InstructionValue::Destructure { lvalue, .. } => { - if let Some(ref mut memo_state) = state.manual_memo_state { - for place in destructure_lvalue_places(&lvalue.pattern) { - let ident = &state.env.identifiers[place.identifier.0 as usize]; - memo_state.decls.insert(ident.declaration_id); - if is_named(ident) { - state.temporaries.insert( - place.identifier, - ManualMemoDependency { - root: ManualMemoDependencyRoot::NamedLocal { - value: place.clone(), - constant: false, - }, - path: Vec::new(), - loc: place.loc, - }, - ); - } - } - } - } - _ => {} - } - } - } -} - -/// Get operand places from a StartMemoize instruction's deps. -fn start_memoize_operands(deps: &Option<Vec<ManualMemoDependency>>) -> Vec<Place> { - let mut result = Vec::new(); - if let Some(deps) = deps { - for dep in deps { - if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &dep.root { - result.push(value.clone()); - } - } - } - result -} - -/// Get lvalue places from a Destructure pattern. -fn destructure_lvalue_places(pattern: &react_compiler_hir::Pattern) -> Vec<&Place> { - let mut result = Vec::new(); - match pattern { - react_compiler_hir::Pattern::Array(arr) => { - for item in &arr.items { - match item { - react_compiler_hir::ArrayPatternElement::Place(place) => { - result.push(place); - } - react_compiler_hir::ArrayPatternElement::Spread(spread) => { - result.push(&spread.place); - } - react_compiler_hir::ArrayPatternElement::Hole => {} - } - } - } - react_compiler_hir::Pattern::Object(obj) => { - for entry in &obj.properties { - match entry { - react_compiler_hir::ObjectPropertyOrSpread::Property(prop) => { - result.push(&prop.place); - } - react_compiler_hir::ObjectPropertyOrSpread::Spread(spread) => { - result.push(&spread.place); - } - } - } - } - } - result -} - -/// Check if an identifier is unmemoized (has a scope that hasn't completed). -fn is_unmemoized( - id: IdentifierId, - completed_scopes: &HashSet<ScopeId>, - identifiers: &[Identifier], -) -> bool { - let ident = &identifiers[id.0 as usize]; - if let Some(scope_id) = ident.scope { - !completed_scopes.contains(&scope_id) - } else { - false - } -} - -// ============================================================================= -// Dependency comparison (port of compareDeps / validateInferredDep) -// ============================================================================= - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -enum CompareDependencyResult { - Ok = 0, - RootDifference = 1, - PathDifference = 2, - Subpath = 3, - RefAccessDifference = 4, -} - -fn compare_deps( - inferred: &ManualMemoDependency, - source: &ManualMemoDependency, -) -> CompareDependencyResult { - let roots_equal = match (&inferred.root, &source.root) { - ( - ManualMemoDependencyRoot::Global { identifier_name: a }, - ManualMemoDependencyRoot::Global { identifier_name: b }, - ) => a == b, - ( - ManualMemoDependencyRoot::NamedLocal { value: a, .. }, - ManualMemoDependencyRoot::NamedLocal { value: b, .. }, - ) => a.identifier == b.identifier, - _ => false, - }; - if !roots_equal { - return CompareDependencyResult::RootDifference; - } - - let min_len = inferred.path.len().min(source.path.len()); - let mut is_subpath = true; - for i in 0..min_len { - if inferred.path[i].property != source.path[i].property { - is_subpath = false; - break; - } else if inferred.path[i].optional != source.path[i].optional { - return CompareDependencyResult::PathDifference; - } - } - - if is_subpath - && (source.path.len() == inferred.path.len() - || (inferred.path.len() >= source.path.len() - && !inferred.path.iter().any(|t| { - t.property == react_compiler_hir::PropertyLiteral::String("current".to_string()) - }))) - { - CompareDependencyResult::Ok - } else if is_subpath { - if source.path.iter().any(|t| { - t.property == react_compiler_hir::PropertyLiteral::String("current".to_string()) - }) || inferred.path.iter().any(|t| { - t.property == react_compiler_hir::PropertyLiteral::String("current".to_string()) - }) { - CompareDependencyResult::RefAccessDifference - } else { - CompareDependencyResult::Subpath - } - } else { - CompareDependencyResult::PathDifference - } -} - -/// Pretty-print a reactive scope dependency (e.g., `x.a.b?.c`) -fn pretty_print_scope_dependency( - dep_id: IdentifierId, - dep_path: &[DependencyPathEntry], - identifiers: &[react_compiler_hir::Identifier], -) -> String { - let ident = &identifiers[dep_id.0 as usize]; - let root_str = match &ident.name { - Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(), - Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(), - None => "[unnamed]".to_string(), - }; - let path_str: String = dep_path - .iter() - .map(|entry| { - let prop = match &entry.property { - react_compiler_hir::PropertyLiteral::String(s) => s.clone(), - react_compiler_hir::PropertyLiteral::Number(n) => format!("{}", n), - }; - if entry.optional { - format!("?.{}", prop) - } else { - format!(".{}", prop) - } - }) - .collect(); - format!("{}{}", root_str, path_str) -} - -/// Pretty-print a manual memo dependency for error messages. -fn print_manual_memo_dependency( - dep: &ManualMemoDependency, - identifiers: &[react_compiler_hir::Identifier], - with_optional: bool, -) -> String { - let root_str = match &dep.root { - ManualMemoDependencyRoot::NamedLocal { value, .. } => { - let ident = &identifiers[value.identifier.0 as usize]; - match &ident.name { - Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(), - Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(), - None => "[unnamed]".to_string(), - } - } - ManualMemoDependencyRoot::Global { identifier_name } => identifier_name.clone(), - }; - let path_str: String = dep - .path - .iter() - .map(|entry| { - let prop = match &entry.property { - react_compiler_hir::PropertyLiteral::String(s) => s.clone(), - react_compiler_hir::PropertyLiteral::Number(n) => format!("{}", n), - }; - if with_optional && entry.optional { - format!("?.{}", prop) - } else { - format!(".{}", prop) - } - }) - .collect(); - format!("{}{}", root_str, path_str) -} - -fn get_compare_dependency_result_description(result: CompareDependencyResult) -> &'static str { - match result { - CompareDependencyResult::Ok => "Dependencies equal", - CompareDependencyResult::RootDifference | CompareDependencyResult::PathDifference => { - "Inferred different dependency than source" - } - CompareDependencyResult::RefAccessDifference => "Differences in ref.current access", - CompareDependencyResult::Subpath => "Inferred less specific property than source", - } -} - -/// Validate that an inferred dependency matches a source dependency or was produced -/// within the manual memo block. -fn validate_inferred_dep( - dep_id: IdentifierId, - dep_path: &[DependencyPathEntry], - temporaries: &HashMap<IdentifierId, ManualMemoDependency>, - decls_within_memo_block: &HashSet<DeclarationId>, - valid_deps_in_memo_block: &[ManualMemoDependency], - env: &mut Environment, - memo_location: Option<SourceLocation>, -) { - // Normalize the dependency through temporaries - let normalized_dep = if let Some(temp) = temporaries.get(&dep_id) { - let mut path = temp.path.clone(); - path.extend_from_slice(dep_path); - ManualMemoDependency { - root: temp.root.clone(), - path, - loc: temp.loc, - } - } else { - let ident = &env.identifiers[dep_id.0 as usize]; - // TS: CompilerError.invariant(dep.identifier.name?.kind === 'named', ...) - if !is_named(ident) { - return; - } - ManualMemoDependency { - root: ManualMemoDependencyRoot::NamedLocal { - value: Place { - identifier: dep_id, - effect: react_compiler_hir::Effect::Read, - reactive: false, - loc: ident.loc, - }, - constant: false, - }, - path: dep_path.to_vec(), - loc: ident.loc, - } - }; - - // Check if the dep was declared within the memo block - if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &normalized_dep.root { - let ident = &env.identifiers[value.identifier.0 as usize]; - if decls_within_memo_block.contains(&ident.declaration_id) { - return; - } - } - - // Compare against each valid source dependency - let mut error_diagnostic: Option<CompareDependencyResult> = None; - for source_dep in valid_deps_in_memo_block { - let result = compare_deps(&normalized_dep, source_dep); - if result == CompareDependencyResult::Ok { - return; - } - error_diagnostic = Some(match error_diagnostic { - Some(prev) => prev.max(result), - None => result, - }); - } - - let ident = &env.identifiers[dep_id.0 as usize]; - - let extra = if is_named(ident) { - // Use the original dep_id/dep_path (matching TS prettyPrintScopeDependency(dep)) - let dep_str = pretty_print_scope_dependency(dep_id, dep_path, &env.identifiers); - let source_deps_str: String = valid_deps_in_memo_block - .iter() - .map(|d| print_manual_memo_dependency(d, &env.identifiers, true)) - .collect::<Vec<_>>() - .join(", "); - let result_desc = error_diagnostic - .map(|d| get_compare_dependency_result_description(d).to_string()) - .unwrap_or_else(|| "Inferred dependency not present in source".to_string()); - format!( - "The inferred dependency was `{}`, but the source dependencies were [{}]. {}", - dep_str, source_deps_str, result_desc - ) - } else { - String::new() - }; - - let description = format!( - "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. {}", - extra - ); - - let diag = CompilerDiagnostic::new( - ErrorCategory::PreserveManualMemo, - "Existing memoization could not be preserved", - Some(description.trim().to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: memo_location, - message: Some("Could not preserve existing manual memoization".to_string()), - identifier_name: None, - }); - env.record_diagnostic(diag); -} diff --git a/compiler/crates/react_compiler_validation/src/validate_static_components.rs b/compiler/crates/react_compiler_validation/src/validate_static_components.rs deleted file mode 100644 index deca1c9da2bd..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_static_components.rs +++ /dev/null @@ -1,100 +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. - -//! Validates against components that are created dynamically and whose identity -//! is not guaranteed to be stable (which would cause the component to reset on -//! each re-render). -//! -//! Port of ValidateStaticComponents.ts. - -use std::collections::HashMap; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory, SourceLocation, -}; -use react_compiler_hir::{HirFunction, IdentifierId, InstructionValue, JsxTag}; - -/// Validates that components used in JSX are not dynamically created during render. -/// -/// Returns a CompilerError containing all diagnostics found (may be empty). -/// Called via `env.logErrors()` pattern in Pipeline.ts. -pub fn validate_static_components(func: &HirFunction) -> CompilerError { - let mut error = CompilerError::new(); - let mut known_dynamic_components: HashMap<IdentifierId, Option<SourceLocation>> = - HashMap::new(); - - for (_block_id, block) in &func.body.blocks { - // Process phis: propagate dynamic component knowledge through phi nodes - 'phis: for phi in &block.phis { - for (_pred, operand) in &phi.operands { - if let Some(loc) = known_dynamic_components.get(&operand.identifier) { - known_dynamic_components.insert(phi.place.identifier, *loc); - continue 'phis; - } - } - } - - // Process instructions - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let lvalue_id = instr.lvalue.identifier; - let value = &instr.value; - - match value { - InstructionValue::FunctionExpression { loc, .. } - | InstructionValue::NewExpression { loc, .. } - | InstructionValue::MethodCall { loc, .. } - | InstructionValue::CallExpression { loc, .. } => { - known_dynamic_components.insert(lvalue_id, *loc); - } - InstructionValue::LoadLocal { place, .. } => { - if let Some(loc) = known_dynamic_components.get(&place.identifier) { - known_dynamic_components.insert(lvalue_id, *loc); - } - } - InstructionValue::StoreLocal { - lvalue, value: val, .. - } => { - if let Some(loc) = known_dynamic_components.get(&val.identifier) { - let loc = *loc; - known_dynamic_components.insert(lvalue_id, loc); - known_dynamic_components.insert(lvalue.place.identifier, loc); - } - } - InstructionValue::JsxExpression { tag, .. } => { - if let JsxTag::Place(tag_place) = tag { - if let Some(location) = known_dynamic_components.get(&tag_place.identifier) - { - let location = *location; - let diagnostic = CompilerDiagnostic::new( - ErrorCategory::StaticComponents, - "Cannot create components during render", - Some("Components created during render will reset their state each time they are created. Declare components outside of render".to_string()), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: tag_place.loc, - message: Some( - "This component is created during render".to_string(), - ), - identifier_name: None, - }) - .with_detail(CompilerDiagnosticDetail::Error { - loc: location, - message: Some( - "The component is created during render here".to_string(), - ), - identifier_name: None, - }); - error.push_diagnostic(diagnostic); - } - } - } - _ => {} - } - } - } - - error -} diff --git a/compiler/crates/react_compiler_validation/src/validate_use_memo.rs b/compiler/crates/react_compiler_validation/src/validate_use_memo.rs deleted file mode 100644 index 7691e73b7f81..000000000000 --- a/compiler/crates/react_compiler_validation/src/validate_use_memo.rs +++ /dev/null @@ -1,319 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use react_compiler_diagnostics::{ - CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory, SourceLocation, -}; -use react_compiler_hir::environment::Environment; -use react_compiler_hir::visitors::{ - each_instruction_value_operand_with_functions, each_terminal_operand, -}; -use react_compiler_hir::{ - FunctionId, HirFunction, IdentifierId, InstructionValue, ParamPattern, Place, PlaceOrSpread, - ReturnVariant, Terminal, -}; - -/// Validates useMemo() usage patterns. -/// -/// Port of ValidateUseMemo.ts. -/// Returns VoidUseMemo errors separately (for logging via logErrors, not as compile errors). -pub fn validate_use_memo(func: &HirFunction, env: &mut Environment) -> CompilerError { - validate_use_memo_impl( - func, - &env.functions, - &mut env.errors, - env.config.validate_no_void_use_memo, - ) -} - -/// Information about a FunctionExpression needed for validation. -struct FuncExprInfo { - func_id: FunctionId, - loc: Option<SourceLocation>, -} - -fn validate_use_memo_impl( - func: &HirFunction, - functions: &[HirFunction], - errors: &mut CompilerError, - validate_no_void_use_memo: bool, -) -> CompilerError { - let mut void_memo_errors = CompilerError::new(); - let mut use_memos: HashSet<IdentifierId> = HashSet::new(); - let mut react: HashSet<IdentifierId> = HashSet::new(); - let mut func_exprs: HashMap<IdentifierId, FuncExprInfo> = HashMap::new(); - let mut unused_use_memos: HashMap<IdentifierId, (SourceLocation, Option<String>)> = - HashMap::new(); - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - let lvalue = &instr.lvalue; - let value = &instr.value; - - // Remove used operands from unused_use_memos - if !unused_use_memos.is_empty() { - for operand_id in each_instruction_value_operand_ids(value, functions) { - unused_use_memos.remove(&operand_id); - } - } - - match value { - InstructionValue::LoadGlobal { binding, .. } => { - let name = binding.name(); - if name == "useMemo" { - use_memos.insert(lvalue.identifier); - } else if name == "React" { - react.insert(lvalue.identifier); - } - } - InstructionValue::PropertyLoad { - object, property, .. - } => { - if react.contains(&object.identifier) { - if let react_compiler_hir::PropertyLiteral::String(prop_name) = property { - if prop_name == "useMemo" { - use_memos.insert(lvalue.identifier); - } - } - } - } - InstructionValue::FunctionExpression { - lowered_func, loc, .. - } => { - func_exprs.insert( - lvalue.identifier, - FuncExprInfo { - func_id: lowered_func.func, - loc: *loc, - }, - ); - } - InstructionValue::CallExpression { callee, args, .. } => { - handle_possible_use_memo_call( - functions, - errors, - &mut void_memo_errors, - &use_memos, - &func_exprs, - &mut unused_use_memos, - callee, - args, - lvalue, - validate_no_void_use_memo, - ); - } - InstructionValue::MethodCall { property, args, .. } => { - handle_possible_use_memo_call( - functions, - errors, - &mut void_memo_errors, - &use_memos, - &func_exprs, - &mut unused_use_memos, - property, - args, - lvalue, - validate_no_void_use_memo, - ); - } - _ => {} - } - } - - // Check terminal operands for unused_use_memos - if !unused_use_memos.is_empty() { - for operand_id in each_terminal_operand_ids(&block.terminal) { - unused_use_memos.remove(&operand_id); - } - } - } - - // Report unused useMemo results - if !unused_use_memos.is_empty() { - for (loc, ident_name) in unused_use_memos.values() { - void_memo_errors.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::VoidUseMemo, - "useMemo() result is unused", - Some( - "This useMemo() value is unused. useMemo() is for computing and caching values, not for arbitrary side effects" - .to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: Some(*loc), - message: Some("useMemo() result is unused".to_string()), - identifier_name: ident_name.clone(), - }), - ); - } - } - - void_memo_errors -} - -#[allow(clippy::too_many_arguments)] -fn handle_possible_use_memo_call( - functions: &[HirFunction], - errors: &mut CompilerError, - void_memo_errors: &mut CompilerError, - use_memos: &HashSet<IdentifierId>, - func_exprs: &HashMap<IdentifierId, FuncExprInfo>, - unused_use_memos: &mut HashMap<IdentifierId, (SourceLocation, Option<String>)>, - callee: &Place, - args: &[PlaceOrSpread], - lvalue: &Place, - validate_no_void_use_memo: bool, -) { - let is_use_memo = use_memos.contains(&callee.identifier); - if !is_use_memo || args.is_empty() { - return; - } - - let first_arg = match &args[0] { - PlaceOrSpread::Place(place) => place, - PlaceOrSpread::Spread(_) => return, - }; - - let body_info = match func_exprs.get(&first_arg.identifier) { - Some(info) => info, - None => return, - }; - - let body_func = &functions[body_info.func_id.0 as usize]; - - // Validate no parameters - if !body_func.params.is_empty() { - let first_param = &body_func.params[0]; - let loc = match first_param { - ParamPattern::Place(place) => place.loc, - ParamPattern::Spread(spread) => spread.place.loc, - }; - errors.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::UseMemo, - "useMemo() callbacks may not accept parameters", - Some( - "useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation" - .to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc, - message: Some("Callbacks with parameters are not supported".to_string()), - identifier_name: None, - }), - ); - } - - // Validate not async or generator - if body_func.is_async || body_func.generator { - errors.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::UseMemo, - "useMemo() callbacks may not be async or generator functions", - Some( - "useMemo() callbacks are called once and must synchronously return a value" - .to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: body_info.loc, - message: Some("Async and generator functions are not supported".to_string()), - identifier_name: None, - }), - ); - } - - // Validate no context variable assignment - validate_no_context_variable_assignment(body_func, errors); - - if validate_no_void_use_memo && !has_non_void_return(body_func) { - void_memo_errors.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::VoidUseMemo, - "useMemo() callbacks must return a value", - Some( - "This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects" - .to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: body_info.loc, - message: Some("useMemo() callbacks must return a value".to_string()), - identifier_name: None, - }), - ); - } else if validate_no_void_use_memo { - if let Some(callee_loc) = callee.loc { - // The callee is always useMemo/React.useMemo since we checked is_use_memo above. - // The identifierName in Babel's AST SourceLocation is "useMemo". - unused_use_memos.insert(lvalue.identifier, (callee_loc, Some("useMemo".to_string()))); - } - } -} - -fn validate_no_context_variable_assignment(func: &HirFunction, errors: &mut CompilerError) { - let context: HashSet<IdentifierId> = - func.context.iter().map(|place| place.identifier).collect(); - - for (_block_id, block) in &func.body.blocks { - for &instr_id in &block.instructions { - let instr = &func.instructions[instr_id.0 as usize]; - if let InstructionValue::StoreContext { lvalue, .. } = &instr.value { - if context.contains(&lvalue.place.identifier) { - errors.push_diagnostic( - CompilerDiagnostic::new( - ErrorCategory::UseMemo, - "useMemo() callbacks may not reassign variables declared outside of the callback", - Some( - "useMemo() callbacks must be pure functions and cannot reassign variables defined outside of the callback function" - .to_string(), - ), - ) - .with_detail(CompilerDiagnosticDetail::Error { - loc: lvalue.place.loc, - message: Some("Cannot reassign variable".to_string()), - identifier_name: None, - }), - ); - } - } - } - } -} - -fn has_non_void_return(func: &HirFunction) -> bool { - for (_block_id, block) in &func.body.blocks { - if let Terminal::Return { return_variant, .. } = &block.terminal { - if matches!( - return_variant, - ReturnVariant::Explicit | ReturnVariant::Implicit - ) { - return true; - } - } - } - false -} - -/// Collect all operand IdentifierIds from an InstructionValue. -/// Thin wrapper around canonical `each_instruction_value_operand_with_functions` that maps to ids. -fn each_instruction_value_operand_ids( - value: &InstructionValue, - functions: &[HirFunction], -) -> Vec<IdentifierId> { - each_instruction_value_operand_with_functions(value, functions) - .into_iter() - .map(|p| p.identifier) - .collect() -} - -/// Collect all operand IdentifierIds from a Terminal. -/// Thin wrapper around canonical `each_terminal_operand` that maps to ids. -fn each_terminal_operand_ids(terminal: &Terminal) -> Vec<IdentifierId> { - each_terminal_operand(terminal) - .into_iter() - .map(|p| p.identifier) - .collect() -} diff --git a/compiler/docs/rust-port/rust-compiler-workflow-guide.md b/compiler/docs/rust-port/rust-compiler-workflow-guide.md deleted file mode 100644 index dba4fb5ecae6..000000000000 --- a/compiler/docs/rust-port/rust-compiler-workflow-guide.md +++ /dev/null @@ -1,364 +0,0 @@ -# React Compiler Rust Port — Workflow Guide - -Analysis of 141 Claude Code sessions from 2026-03-13 to 2026-04-10, totaling ~17,460 tool invocations across the Rust compiler port. - ---- - -## 1. Project Timeline & Phases - -| Phase | Dates | Focus | Sessions | -|-------|-------|-------|----------| -| **Research** | Mar 13–14 | Feasibility study, shared mutability analysis, pass-by-pass research | ~10 | -| **Planning** | Mar 14–16 | Architecture docs, numbered plans (0001–0005), scope types | ~15 | -| **Core Implementation** | Mar 16–25 | Babel AST crate, HIR lowering, pass porting, BuildHIR | ~50 | -| **Testing & Stabilization** | Mar 25–31 | Snap tests, test-rust-port 100%, OXC/SWC frontends | ~30 | -| **E2E & Diagnostics** | Apr 1–4 | Error formatting, diagnostic events, CI, e2e tests | ~20 | -| **Internal Validation** | Apr 6–10 | Testing against Meta internal code, minimize-rust-delta, hermes-parser | ~10 | - -### Key Milestones -- **Mar 25**: All 1717/1717 pass-level + code-level tests passing -- **Mar 30**: Snap tests 1717/1718 (99.9%) -- **Mar 31**: OptimizeForSSR ported, snap 1725/1725 -- **Apr 2**: E2E babel 1722/1724, diagnostic events aligned -- **Apr 4**: CI workflow configured, SWC/OXC e2e progress -- **Apr 6–10**: Internal codebase validation against Meta internal production code - ---- - -## 2. Custom Skills (Claude Code) - -Seven custom skills were created in `compiler/.claude/skills/`. These are critical to the workflow and should be loaded at the start of each session: - -| Skill | Usage Count | Purpose | -|-------|-------------|---------| -| `/compiler-verify` | 47 | Detects TS vs Rust changes, runs appropriate test suites | -| `/compiler-commit` | 30 | Runs verify + review, then commits with `[compiler]` or `[rust-compiler]` prefix, updates orchestrator log | -| `/compiler-review` | 21 | Launches a review subagent that compares Rust port against TS originals | -| `/compiler-orchestrator` | 5 | Autonomous loop: discover frontier → fix failures → port next pass → review → commit | -| `/compiler-port` | 5 | Port a single named pass end-to-end (looks up in Pipeline.ts, maps to Rust crate, implements, tests) | -| `/rust-port-status` | — | Reports current test pass rates | -| `/plan-update` | — | Updates numbered plan documents | - -### Typical Workflow Loop - -``` -1. /compiler-orchestrator (or manual work) - └── Discovers current test state - └── Fixes failures or ports next pass - └── Calls /compiler-verify internally - └── Calls /compiler-review internally - └── Calls /compiler-commit when ready - -2. Manual variant: - > "Fix X" - > /compiler-verify - > /compiler-review - > /compiler-commit -``` - -### Important Skill Loading Note - -The `/compiler-orchestrator` skill sometimes fails to auto-load. The workaround is: - -``` -> load the skill compiler/.claude/skills/compiler-orchestrator/SKILL.md -``` - -This happened multiple times in the trajectory history and your team should be aware of it. - ---- - -## 3. Test Commands & Infrastructure - -### Primary Test Scripts (by frequency of use) - -| Command | Invocations | Purpose | -|---------|-------------|---------| -| `bash compiler/scripts/test-rust-port.sh` | 1,345 | Compare Rust vs TS compiler output per-pass for all fixtures | -| `bash compiler/scripts/test-e2e.sh` | 269 | End-to-end: code output + diagnostic events across babel/swc/oxc | -| `cargo check` | 251 | Fast Rust compilation check | -| `npx tsx` | 207 | Run TypeScript scripts directly | -| `cargo build` | 179 | Full Rust build | -| `bash compiler/scripts/test-babel-ast.sh` | 174 | Babel AST round-trip serialization tests | -| `yarn snap --rust` | 121 | Snap fixture tests using Rust compiler backend | -| `yarn snap` | 63 | Snap fixture tests using TS compiler | -| `cargo test` | 46 | Rust unit tests | - -### Test Result Formats - -`test-rust-port.sh` supports `--json` for machine-readable output: -```bash -bash compiler/scripts/test-rust-port.sh --json 2>/dev/null -# Returns: { pass, autoDetected, total, passed, failed, frontier, perPass, failures } -``` - -`test-e2e.sh` reports in table format: -``` -| variant | code | events | total | -| babel | 1725/1725 (100%) | 1725/1725 (100%) | ... | -| swc | 1723/1725 (99.9%) | ... | ... | -``` - -### Test Against Internal Codebase - -Recent work (Apr 6–10) tested against Meta internal production code. Key patterns: - -```bash -# Run with specific compilation mode against an external directory -bash compiler/scripts/test-rust-port.sh --mode syntax <path-to-source-dir> - -# Use test-internal-files.sh with the production config and source root -bash compiler/scripts/test-internal-files.sh <config-path> <source-root> [flags] -``` - -- Files with `@flow` need hermes-parser (recently added to test-rust-port.ts) -- Must pass `compilationMode` to match production config -- Use `yarn snap minimize-rust-delta <path>` to find minimal repro for TS/Rust differences - ---- - -## 4. Cargo & Rust Workflow - -### Crate Structure - -| Crate | Purpose | -|-------|---------| -| `react_compiler` | Main entrypoint, pipeline orchestration | -| `react_compiler_ast` | Babel AST types + serde | -| `react_compiler_hir` | HIR types, environment, visitors | -| `react_compiler_lowering` | BuildHIR, HIRBuilder (AST → HIR) | -| `react_compiler_inference` | Mutation/aliasing/type inference | -| `react_compiler_optimization` | Optimization passes | -| `react_compiler_validation` | Validation passes | -| `react_compiler_reactive_scopes` | Reactive scope building + codegen | -| `react_compiler_diagnostics` | Error types, code frames | -| `react_compiler_e2e_cli` | E2E test binary | -| `react_compiler_swc` | SWC frontend | -| `react_compiler_oxc` | OXC frontend | - -### Common Cargo Patterns - -```bash -# Fast check (most common, 214 invocations) -cargo check --manifest-path compiler/crates/Cargo.toml - -# Build the NAPI binary for JS interop -cargo build --manifest-path compiler/crates/Cargo.toml - -# Run Rust unit tests -cargo test --manifest-path compiler/crates/Cargo.toml -``` - ---- - -## 5. Agent Patterns - -994 total agent invocations across sessions. - -| Agent Type | Count | Typical Use | -|------------|-------|-------------| -| `general` / `general-purpose` | 748 | Implementation, fixing, research | -| `meta_codesearch:code_search` | 204 | Codebase exploration | -| `Plan` | 20 | Architecture/implementation planning | -| `Explore` | 16 | Codebase navigation | -| `statusline-setup` | 4 | Terminal UI configuration | - -### Common Agent Patterns - -1. **Parallel pass research**: Launch one agent per compiler pass to analyze TS implementation for port feasibility -2. **Review agents**: Dedicated "Review Rust port changes" agents (14 invocations) -3. **Fix-specific agents**: "Fix AnalyseFunctions failures" (4x), "Fix PruneMaybeThrows validation failures" (2x) -4. **Worktree isolation**: 16 worktree entries for isolated implementation work - -### Worktree Usage - -Worktrees were used for larger changes that might need to be abandoned: -- `worktree-build-hir-impl` — BuildHIR implementation -- `worktree-structured-yawning-forest-rust` — test-rust-port enhancements -- `worktree-ts-to-rust-transpiler` — explored mechanical TS→Rust transpilation (abandoned) -- `worktree-module-type-provider` — module type provider work - ---- - -## 6. Key Files (by edit frequency) - -### Most Frequently Modified Files - -| File | Edits | Reads | Purpose | -|------|-------|-------|---------| -| `rust-port-orchestrator-log.md` | 135 | 104 | Running log of orchestrator progress | -| `build_hir.rs` | 112 | 162 | Core HIR lowering from AST | -| `program.rs` (entrypoint) | 103 | 173 | Main compilation entrypoint | -| `rust-port-research.md` | 85 | 47+21 | Research & analysis document | -| `test-rust-port.ts` | 77 | 90 | Primary test comparison script | -| `pipeline.rs` | 48 | 64 | Compiler pass pipeline | -| `test-e2e.ts` | 42 | 27 | End-to-end test script | -| `hir_builder.rs` | 37 | 53 | HIR builder utilities | -| `infer_mutation_aliasing_effects.rs` | 32 | 48 | Most complex inference pass | -| `runner.ts` (snap) | 32 | 35 | Snap test runner | - ---- - -## 7. Repeated Workflow Patterns - -### Pattern 1: "Analyze → Don't Fix → Report" -Used extensively in recent internal validation work: -``` -> Try running test-rust-port.sh with --mode 'syntax' against <path>. Analyze success/failure to categorize and report back. Do not proactively fix. -``` - -### Pattern 2: "Research → Plan → Implement → Verify → Review → Commit" -The standard development cycle: -``` -> Do additional research into <topic> -> Create a plan in compiler/docs/rust-port/... -> Implement the work in <plan> -> /compiler-verify -> /compiler-review -> /compiler-commit -``` - -### Pattern 3: "Team of Agents" -For large implementation tasks: -``` -> Use a team of agents (opus) to implement the remainder of the items in <plan>. Make sure to thoroughly test, verify the implementation against the plan. Use /compiler-verify and /compiler-commit. -``` - -### Pattern 4: "Orchestrator Loop" -For autonomous progress: -``` -> /compiler-orchestrator - (or: load the skill compiler/.claude/skills/compiler-orchestrator/SKILL.md) -``` - -### Pattern 5: "Debug CI" -``` -> debug the GitHub CI failure run at <github-actions-url> -> <paste error output> -> /compiler-commit -``` - -### Pattern 6: "Minimize Delta" -For finding minimal repros of TS/Rust differences: -``` -> yarn snap minimize-rust-delta <path> -``` - -### Pattern 7: "Interrupt and Redirect" -Joe frequently interrupts Claude mid-task to correct course: -- `[Request interrupted by user]` appears ~50+ times -- Common pattern: interrupt → provide specific guidance → continue -- Example: "you should be using `env.record_error(...)?`." - ---- - -## 8. Key Architectural Decisions (from trajectory) - -These came up repeatedly in prompts and are important context: - -1. **InstructionId over InstructionValue references**: Key insight that caches in InferMutationAliasingEffects can use `InstructionId` (interned) instead of holding references to `InstructionValue` objects, avoiding Rust borrow conflicts. - -2. **Environment is shared mutable**: Like HIR, the Environment object is mutably shared. Both needed careful Rust representation. - -3. **Error handling convention**: `env.record_error(...)?` — record_error returns `Err` only for Invariant category errors. Use `?` for short-circuit. Only use `let _ = ...` when both the category is non-Invariant AND you explicitly want to continue. - -4. **Keep logic in Rust core**: The babel/swc/oxc integrations should be thin wrappers. All interesting logic belongs in the Rust crates. - -5. **Commit prefix convention**: `[rust-compiler]` for changes to `compiler/crates/`, `[compiler]` for everything else. - -6. **No normalization in tests**: Minimize output normalization — run code through prettier only, compare directly. Added: reparse with babel → regenerate with `compact:true` → prettier. - ---- - -## 9. Current State & Remaining Work - -### Test Results (as of latest orchestrator log) -- **test-rust-port**: 1724/1724 (100%) -- **yarn snap --rust**: 1725/1725 (100%) -- **E2E babel**: 1722/1724 (2 inherent platform differences) -- **E2E swc**: Partial — event matching in progress -- **E2E oxc**: Partial — event matching in progress - -### Active Work Streams (as of Apr 10) -1. **Internal validation**: Testing against Meta internal production code with production configs -2. **Minimize rust deltas**: Using `yarn snap minimize-rust-delta` to find minimal repro cases for TS/Rust differences -3. **New test-internal-files script**: `compiler/scripts/test-internal-files.ts` — for testing against an external codebase with its production config -4. **compilationMode: 'syntax'** support was recently added and validated -5. **hermes-parser integration**: For `@flow` files in the internal codebase - -### Remaining Gaps -- SWC/OXC e2e event differences (structural, not code) -- Some internal codebase fixtures still show code differences between TS and Rust -- Internal skip list exists at `compiler/.test-internal-skip-list` -- Performance optimization: potential for returning per-function replacements instead of full program - ---- - -## 10. Recommendations for Team Picking Up - -### Getting Started -1. Read `compiler/docs/rust-port/rust-port-research.md` and `rust-port-notes.md` for architectural context -2. Read all numbered plans in `compiler/docs/rust-port/rust-port-0001-*` through `0005-*` -3. Review `compiler/docs/rust-port/rust-port-orchestrator-log.md` for chronological progress -4. Review `compiler/docs/rust-port/rust-port-gap-analysis.md` for known gaps - -### Essential Skill Loading -At the start of each Claude session: -``` -> load the skill compiler/.claude/skills/compiler-orchestrator/SKILL.md -``` -This ensures all six custom skills are available. If a skill isn't recognized, load it explicitly. - -### Daily Workflow -```bash -# Check current state -bash compiler/scripts/test-rust-port.sh -bash compiler/scripts/test-e2e.sh -yarn snap --rust - -# After making changes -/compiler-verify -/compiler-review -/compiler-commit <title> -``` - -### Key Commands to Know -```bash -# Fast iteration cycle -cargo check --manifest-path compiler/crates/Cargo.toml -bash compiler/scripts/test-rust-port.sh -yarn snap --rust - -# Debug a specific fixture -yarn snap --rust -p <fixture-name> -d - -# Compare TS vs Rust for a specific file -npx tsx compiler/scripts/test-rust-port.ts <pass-name> - -# Find minimal repro for TS/Rust difference -yarn snap minimize-rust-delta <fixture-path> - -# Test against an external codebase with its production config -bash compiler/scripts/test-internal-files.sh <config-path> <source-root> [flags] - -# E2E test across all frontends -bash compiler/scripts/test-e2e.sh -``` - -### Working Style Notes -- Joe used Opus 4.6 as the primary model throughout -- Frequent use of `/clear` between logical work units -- Heavy use of "analyze and report, don't fix" for investigation phases -- Corrections were provided inline with specific code patterns (e.g., "you should be using `env.record_error(...)?`") -- Plans were maintained in numbered markdown docs and updated as work progressed -- The orchestrator log (`rust-port-orchestrator-log.md`) serves as the canonical progress record - -### CI Configuration -GitHub CI workflow: `.github/workflows/compiler_rust.yml` -- Triggers on changes to `compiler/` directory -- Runs: `cargo check` → `cargo build` → `test-babel-ast.sh` → `test-rust-port.sh` → `yarn snap --rust` -- Job name appears as "React Compiler (Rust) Tests" in GitHub - ---- - -*Generated 2026-04-10 from analysis of 141 Claude Code conversation trajectories.* diff --git a/compiler/docs/rust-port/rust-port-0001-babel-ast.md b/compiler/docs/rust-port/rust-port-0001-babel-ast.md deleted file mode 100644 index e2d45539be40..000000000000 --- a/compiler/docs/rust-port/rust-port-0001-babel-ast.md +++ /dev/null @@ -1,420 +0,0 @@ -# Rust Port Step 1: Babel AST Crate - -## Goal - -Create a Rust crate (`compiler/crates/react_compiler_ast`) that precisely models the Babel AST structure, enabling JSON round-tripping: parse JS with Babel in Node.js, serialize to JSON, deserialize into Rust, re-serialize back to JSON, and get an identical result. - -This crate is the serialization boundary between the JS toolchain (Babel parser) and the Rust compiler. It must be a faithful 1:1 representation of Babel's AST output — not a simplified or custom IR. - -**Current status**: Complete (human reviewed). All 1714 compiler test fixtures round-trip successfully (0 failures). No `Unknown` catch-all variants remain. Scope types are defined separately in [rust-port-0002-scope-types.md](rust-port-0002-scope-types.md). - ---- - -## Crate Structure - -``` -compiler/crates/ - react_compiler_ast/ - Cargo.toml - src/ - lib.rs # Re-exports, top-level File/Program types - statements.rs # Statement enum and statement node structs - expressions.rs # Expression enum and expression node structs - literals.rs # Literal node structs (StringLiteral, NumericLiteral, etc.) - patterns.rs # PatternLike enum and pattern node structs - jsx.rs # JSX node structs and enums - declarations.rs # Import/export, TS declaration, and Flow declaration structs - common.rs # SourceLocation, Position, Comment, BaseNode, helpers - operators.rs # Operator enums (BinaryOperator, UnaryOperator, etc.) - tests/ - round_trip.rs # Round-trip test harness -``` - -TypeScript and Flow annotation types are co-located with the module that uses them — TS/Flow expressions live in `expressions.rs`, TS/Flow declarations live in `declarations.rs`. Class-related types are split between `expressions.rs` (ClassExpression, ClassBody) and `statements.rs` (ClassDeclaration). There is no single `Node` enum; the union types (`Statement`, `Expression`, `PatternLike`) serve as the dispatch enums directly. - -### Cargo.toml - -```toml -[package] -name = "react_compiler_ast" -version = "0.1.0" -edition = "2024" - -[dependencies] -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -[dev-dependencies] -walkdir = "2" -similar = "2" # for readable diffs in round-trip test -``` - -No other dependencies. The crate is pure data types + serde. - ---- - -## Core Design Decisions - -### 1. Internally tagged via `"type"` field - -Babel AST nodes use a `"type"` field as the discriminant (e.g., `"type": "FunctionDeclaration"`). Serde's default externally-tagged enum format doesn't match this. Use **internally tagged** enums with `#[serde(tag = "type")]`: - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum Statement { - BlockStatement(BlockStatement), - ReturnStatement(ReturnStatement), - IfStatement(IfStatement), - // ... -} -``` - -Each variant's struct contains the node-specific fields. The `"type"` field is handled by serde's internal tagging. - -### 2. BaseNode fields via flattening - -Every Babel node shares common fields (`start`, `end`, `loc`, `leadingComments`, etc.). A `BaseNode` struct is flattened into each node struct: - -```rust -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct BaseNode { - #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] - pub node_type: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub start: Option<u32>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub end: Option<u32>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub loc: Option<SourceLocation>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub range: Option<(u32, u32)>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extra: Option<serde_json::Value>, - #[serde(default, skip_serializing_if = "Option::is_none", rename = "leadingComments")] - pub leading_comments: Option<Vec<Comment>>, - #[serde(default, skip_serializing_if = "Option::is_none", rename = "innerComments")] - pub inner_comments: Option<Vec<Comment>>, - #[serde(default, skip_serializing_if = "Option::is_none", rename = "trailingComments")] - pub trailing_comments: Option<Vec<Comment>>, -} -``` - -The `node_type` field captures the `"type"` string when `BaseNode` is deserialized directly (not through a `#[serde(tag = "type")]` enum, which consumes the field). It defaults to `None` and is skipped when absent, so it doesn't interfere with round-tripping in either context. - -Each node struct flattens this: - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FunctionDeclaration { - #[serde(flatten)] - pub base: BaseNode, - pub id: Option<Identifier>, - pub params: Vec<PatternLike>, - pub body: BlockStatement, - #[serde(default)] - pub generator: bool, - #[serde(default, rename = "async")] - pub is_async: bool, - // ... -} -``` - -The `#[serde(flatten)]` + `#[serde(tag = "type")]` combination works correctly — the macro fallback described in the risk section was not needed. - -### 3. Naming conventions - -- Rust struct/enum names: PascalCase matching the Babel type name exactly (e.g., `FunctionDeclaration`, `JSXElement`) -- Rust field names: snake_case, with `#[serde(rename = "camelCase")]` for JSON mapping -- Reserved words: `#[serde(rename = "async")]` on field `is_async: bool`, `#[serde(rename = "type")]` handled by internal tagging -- Operator strings: mapped via `#[serde(rename = "+")]` etc. on enum variants - -### 4. Optional/nullable field patterns - -Babel's TypeScript definitions use several patterns. Map them consistently: - -| Babel TypeScript | JSON behavior | Rust type | -|---|---|---| -| `field: T` | Always present | `field: T` | -| `field?: T \| null` | Absent or `null` | `#[serde(default, skip_serializing_if = "Option::is_none")] field: Option<T>` | -| `field: Array<T \| null>` | Array with null holes | `field: Vec<Option<T>>` | -| `field: T \| null` (required but nullable) | Present, may be `null` | `field: Option<T>` (no `skip_serializing_if` — always serialize) | - -**Critical subtlety**: Some fields like `FunctionDeclaration.id` are typed `id?: Identifier | null` and appear as `"id": null` in JSON (present but null), not absent. The round-trip test catches any mismatches here. When Babel serializes `null` for a field, we must also serialize `null` — not omit it. The round-trip test is the source of truth for which fields use which pattern. - -A `nullable_value` custom deserializer in `common.rs` handles the case where a field needs to distinguish "absent" from "explicitly null" (deserializing the latter as `Some(Value::Null)`): - -```rust -pub fn nullable_value<'de, D>( - deserializer: D, -) -> Result<Option<Box<serde_json::Value>>, D::Error> -``` - -### 5. The `extra` field - -The `extra` field is an unstructured `Record<string, unknown>` in Babel. Use `serde_json::Value` to round-trip it exactly: - -```rust -#[serde(default, skip_serializing_if = "Option::is_none")] -pub extra: Option<serde_json::Value>, -``` - -### 6. `#[serde(deny_unknown_fields)]` — do NOT use - -Babel's AST may include fields we don't model (e.g., from plugins, or parser-specific metadata). To ensure forward compatibility and avoid brittle failures, do **not** use `deny_unknown_fields`. Instead, unknown fields are silently dropped during deserialization. The round-trip test detects any fields we're missing, since they'll be absent in the re-serialized output. - ---- - -## Node Type Coverage - -All node types that appear in the compiler's 1714 test fixtures are modeled and round-trip successfully. The types are organized as follows: - -### Statements (`statements.rs`, ~25 types) - -The `Statement` enum is the top-level dispatch for all statement and declaration nodes. It includes direct statement types and also pulls in declaration variants (import/export, TS, Flow) to avoid a separate `StatementOrDeclaration` wrapper. - -**Statement types**: `BlockStatement`, `ReturnStatement`, `IfStatement`, `ForStatement`, `WhileStatement`, `DoWhileStatement`, `ForInStatement`, `ForOfStatement`, `SwitchStatement` (+ `SwitchCase`), `ThrowStatement`, `TryStatement` (+ `CatchClause`), `BreakStatement`, `ContinueStatement`, `LabeledStatement`, `ExpressionStatement`, `EmptyStatement`, `DebuggerStatement`, `WithStatement`, `VariableDeclaration` (+ `VariableDeclarator`), `FunctionDeclaration`, `ClassDeclaration` - -**Helper enums**: `ForInit` (VariableDeclaration | Expression), `ForInOfLeft` (VariableDeclaration | PatternLike), `VariableDeclarationKind` - -### Declarations (`declarations.rs`, ~20 types) - -**Import/export**: `ImportDeclaration`, `ExportNamedDeclaration`, `ExportDefaultDeclaration`, `ExportAllDeclaration`, `ImportSpecifier` enum (ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier), `ExportSpecifier` enum (ExportSpecifier | ExportDefaultSpecifier | ExportNamespaceSpecifier), `ImportAttribute`, `ModuleExportName`, `Declaration` enum, `ExportDefaultDecl` enum - -**TypeScript declarations (pass-through)**: `TSTypeAliasDeclaration`, `TSInterfaceDeclaration`, `TSEnumDeclaration`, `TSModuleDeclaration`, `TSDeclareFunction` - -**Flow declarations (pass-through)**: `TypeAlias`, `OpaqueType`, `InterfaceDeclaration`, `DeclareVariable`, `DeclareFunction`, `DeclareClass`, `DeclareModule`, `DeclareModuleExports`, `DeclareExportDeclaration`, `DeclareExportAllDeclaration`, `DeclareInterface`, `DeclareTypeAlias`, `DeclareOpaqueType`, `EnumDeclaration` - -### Expressions (`expressions.rs`, ~35 types) - -**Core**: `Identifier`, `CallExpression`, `MemberExpression`, `OptionalCallExpression`, `OptionalMemberExpression`, `BinaryExpression`, `LogicalExpression`, `UnaryExpression`, `UpdateExpression`, `ConditionalExpression`, `AssignmentExpression`, `SequenceExpression`, `ArrowFunctionExpression` (+ `ArrowFunctionBody` enum), `FunctionExpression`, `ObjectExpression` (+ `ObjectExpressionProperty` enum, `ObjectProperty`, `ObjectMethod`), `ArrayExpression`, `NewExpression`, `TemplateLiteral`, `TaggedTemplateExpression`, `AwaitExpression`, `YieldExpression`, `SpreadElement`, `MetaProperty`, `ClassExpression` (+ `ClassBody`), `PrivateName`, `Super`, `Import`, `ThisExpression`, `ParenthesizedExpression`, `JSXElement`, `JSXFragment`, `AssignmentPattern` - -**TypeScript expressions**: `TSAsExpression`, `TSSatisfiesExpression`, `TSNonNullExpression`, `TSTypeAssertion`, `TSInstantiationExpression` - -**Flow expressions**: `TypeCastExpression` - -TypeScript and Flow type annotation bodies (e.g., `TSTypeAnnotation`, type parameters) use `serde_json::Value` for pass-through round-tripping rather than fully-typed structs. This is sufficient since the compiler doesn't inspect these deeply. - -### Literals (`literals.rs`, 7 types) - -`StringLiteral`, `NumericLiteral`, `BooleanLiteral`, `NullLiteral`, `BigIntLiteral`, `RegExpLiteral`, `TemplateElement` (+ `TemplateElementValue`) - -### Patterns (`patterns.rs`, ~5 types) - -`PatternLike` enum: `Identifier`, `ObjectPattern`, `ArrayPattern`, `AssignmentPattern`, `RestElement`, `MemberExpression` - -`ObjectPatternProperty` enum: `ObjectProperty` (as `ObjectPatternProp`), `RestElement` - -### JSX (`jsx.rs`, ~15 types) - -`JSXElement`, `JSXFragment`, `JSXOpeningElement`, `JSXClosingElement`, `JSXOpeningFragment`, `JSXClosingFragment`, `JSXAttribute`, `JSXSpreadAttribute`, `JSXExpressionContainer`, `JSXSpreadChild`, `JSXText`, `JSXEmptyExpression`, `JSXIdentifier`, `JSXMemberExpression`, `JSXNamespacedName` - -**Helper enums**: `JSXChild`, `JSXElementName`, `JSXAttributeItem`, `JSXAttributeName`, `JSXAttributeValue`, `JSXExpressionContainerExpr`, `JSXMemberExprObject` - -### Operators (`operators.rs`, 5 enums) - -`BinaryOperator`, `LogicalOperator`, `UnaryOperator`, `UpdateOperator`, `AssignmentOperator` — all variants mapped to their JS string representations via `#[serde(rename)]`. - -### Common types (`common.rs`) - -`Position` (line, column, optional index), `SourceLocation` (start, end, optional filename, optional identifierName), `Comment` enum (CommentBlock | CommentLine), `CommentData`, `BaseNode` - -### Top-level types (`lib.rs`) - -`File`, `Program`, `SourceType`, `InterpreterDirective` - -### Catch-all / Unknown variants: statements only - -Most enums do **not** have catch-all `Unknown(serde_json::Value)` variants: an unmodeled node type fails deserialization so the gap gets fixed rather than silently passing through an opaque blob. - -`Statement` is the one deliberate exception. Real TS module-interop syntax (`import x = require(...)`, `export = x`, `export as namespace X`) is legal Babel output that the model does not cover, and failing deserialization there failed entire files the TS reference compiles fine. `Statement::Unknown(UnknownStatement)` carries the complete raw node: top-level unknowns are preserved verbatim in output, function-body unknowns degrade to the standard `UnsupportedNode` bailout. Deserialization still dispatches modeled `type` tags through a typed helper, so a malformed modeled node errors with its precise message instead of degrading to `Unknown`; only genuinely unmodeled tags take the catch-all. The `known_statements!` macro in `statements.rs` is the single source for that dispatch. - -Expression/declaration/pattern enums keep the strict no-catch-all rule. - -This is distinct from unknown *fields*, which are silently dropped (see design decision #6 on `deny_unknown_fields`). An unknown field on a known node is harmless. - -### Union types as enums - -Fields typed as `Expression`, `Statement`, `LVal`, `Pattern`, etc. in Babel are Rust enums with `#[serde(tag = "type")]`. Where fields accept a union of specific types (e.g., `ObjectExpression.properties: Array<ObjectMethod | ObjectProperty | SpreadElement>`), purpose-specific enums are used. - ---- - -## Common Types - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Position { - pub line: u32, - pub column: u32, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub index: Option<u32>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SourceLocation { - pub start: Position, - pub end: Position, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub filename: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none", rename = "identifierName")] - pub identifier_name: Option<String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum Comment { - CommentBlock(CommentData), - CommentLine(CommentData), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CommentData { - pub value: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub start: Option<u32>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub end: Option<u32>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub loc: Option<SourceLocation>, -} -``` - -Note: `Position.index` and `SourceLocation.filename` are `Option` — Babel doesn't always emit these fields. - ---- - -## Top-Level Types - -```rust -/// The root type returned by @babel/parser -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct File { - #[serde(flatten)] - pub base: BaseNode, - pub program: Program, - #[serde(default)] - pub comments: Vec<Comment>, - #[serde(default)] - pub errors: Vec<serde_json::Value>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Program { - #[serde(flatten)] - pub base: BaseNode, - pub body: Vec<Statement>, - #[serde(default)] - pub directives: Vec<Directive>, - #[serde(rename = "sourceType")] - pub source_type: SourceType, - #[serde(default)] - pub interpreter: Option<InterpreterDirective>, - #[serde(rename = "sourceFile", default, skip_serializing_if = "Option::is_none")] - pub source_file: Option<String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum SourceType { - Module, - Script, -} -``` - -`Program.body` uses `Vec<Statement>` directly — declarations (import/export, TS, Flow) are variants of the `Statement` enum. - ---- - -## Round-Trip Test Infrastructure - -### Overview - -``` - Node.js Rust - ────── ──── -fixture.js ──> @babel/parser ──> JSON ──> serde::from_str ──> serde::to_string ──> JSON - │ │ - └──────────────── diff ────────────────────────────┘ -``` - -### Node.js script: `compiler/scripts/babel-ast-to-json.mjs` - -Parses each fixture file with Babel and writes the AST JSON to a temp directory. Takes two arguments: source directory and output directory. - -```javascript -import { parse } from '@babel/parser'; -// ... -const FIXTURE_DIR = process.argv[2]; // source dir with JS/TS files -const OUTPUT_DIR = process.argv[3]; // output dir for JSON files -``` - -**Key details**: -- Uses `@babel/parser` directly (not Hermes) with `errorRecovery: true` and `allowReturnOutsideFunction: true` -- Selects plugins based on content: `['flow', 'jsx']` for files containing `@flow`, otherwise `['typescript', 'jsx']` -- Always uses `sourceType: 'module'` -- Matches `**/*.{js,ts,tsx,jsx}` files -- Writes each fixture's AST as a separate `.json` file -- Writes `.parse-error` marker files for fixtures that fail to parse (skipped by the Rust test) - -### JSON normalization - -Before diffing, both the original and round-tripped JSON are normalized on the Rust side: - -1. **Key ordering**: Both JSONs are parsed as `serde_json::Value`, keys are recursively sorted, then compared. -2. **`undefined` vs absent**: `JSON.stringify` omits `undefined` values; serde's `skip_serializing_if = "Option::is_none"` does the same. -3. **Number precision**: Whole-number floats (e.g., `1.0`) are normalized to integers (e.g., `1`) for comparison. - -### Rust test: `compiler/crates/react_compiler_ast/tests/round_trip.rs` - -The test walks all `.json` files in the fixture directory, deserializes each into `File`, re-serializes, normalizes both sides, and diffs. It reports the first 5 failures with unified diffs (capped at 50 lines per fixture) using the `similar` crate. - -The fixture JSON directory is specified via the `FIXTURE_JSON_DIR` environment variable, with a fallback to `tests/fixtures/` alongside the test file. - -### Test runner: `compiler/scripts/test-babel-ast.sh` - -```bash -#!/bin/bash -set -e -# Usage: bash compiler/scripts/test-babel-ast.sh [fixture-source-dir] -# Defaults to the compiler's own test fixtures. -``` - -Generates fixture JSONs into a temp dir, runs the Rust round-trip test, and cleans up. Accepts an optional fixture source directory argument. - -**Running the test**: - -```bash -bash compiler/scripts/test-babel-ast.sh -``` - ---- - -## Remaining Work - -None — this plan is complete. All `Unknown` catch-all variants have been removed from every enum. During removal, three node types that were previously handled by the `Unknown` fallback were promoted to proper typed variants in the `Expression` enum: `JSXElement`, `JSXFragment`, and `AssignmentPattern`. - -Scope info types and scope resolution testing are tracked in [rust-port-0002-scope-types.md](rust-port-0002-scope-types.md). - ---- - -## Resolved Risks - -### `#[serde(flatten)]` + `#[serde(tag = "type")]` interaction - -This combination works correctly. No macro fallback was needed. The `BaseNode` is flattened into each node struct, and enums use `#[serde(tag = "type")]` for dispatch. The `BaseNode.node_type` field (renamed from `"type"`) handles the case where `BaseNode` is deserialized outside of a tagged enum context. - -### Floating point precision - -Resolved via the `normalize_json` function in the round-trip test. Whole-number f64 values are normalized to i64 before comparison (e.g., `1.0` → `1`). - -### Fixture parse failures - -3 of 1717 fixtures fail to parse with `@babel/parser` and are skipped (marked with `.parse-error` files). This is expected — some fixtures use intentionally invalid syntax. - -### Performance - -All 1714 fixtures round-trip in ~12 seconds (debug build). Not a concern. - -### Field presence ambiguity - -Resolved empirically via the round-trip test. Fields that Babel always emits (even as `null`) use `Option<T>` without `skip_serializing_if`. Fields that may be absent use `#[serde(default, skip_serializing_if = "Option::is_none")]`. The test is the source of truth. diff --git a/compiler/docs/rust-port/rust-port-0002-scope-types.md b/compiler/docs/rust-port/rust-port-0002-scope-types.md deleted file mode 100644 index dee8c0ab8438..000000000000 --- a/compiler/docs/rust-port/rust-port-0002-scope-types.md +++ /dev/null @@ -1,245 +0,0 @@ -# Rust Port Step 2: Scope Types - -## Goal - -Define a normalized, parser-agnostic scope information model (`ScopeInfo`) that captures binding resolution, scope chains, and import metadata needed by the compiler's HIR lowering phase. The scope data is stored separately from the AST and linked via position-based lookup maps. - -**Current status**: Complete (human reviewed). Scope types defined in `react_compiler_ast::scope`. Babel serialization in `babel-ast-to-json.mjs`. Scope resolution test passes for all 1714 fixtures. - ---- - -## Design Goals - -1. **Normalized/flat**: All data stored in flat `Vec`s indexed by `Copy`-able ID newtypes. No reference cycles, no `Rc`/`Arc`. Scope and binding records reference each other via IDs, not pointers. -2. **Parser-agnostic**: The scope types capture what the compiler needs, not the specifics of any parser's scope API. Any parser that can produce binding resolution and scope chain information can populate these types. -3. **AST types stay clean**: The AST crate's serde types have no scope-related fields. Scope-to-AST linkage is via position-based lookup maps in a separate `ScopeInfo` container. -4. **Sufficient for HIR lowering**: Must support all operations the compiler currently performs via Babel's scope API: `getBinding(name)`, `binding.kind`, `binding.scope`, `binding.path` (declaration node type), scope chain walking, `scope.bindings` iteration, and import source resolution. - ---- - -## Core ID Types - -```rust -/// Identifies a scope in the scope table. Copy-able, used as an index. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct ScopeId(pub u32); - -/// Identifies a binding (variable declaration) in the binding table. Copy-able, used as an index. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct BindingId(pub u32); -``` - -Both are newtype wrappers around `u32` and implement `Copy`. They serve as indices into flat `Vec`s in the `ScopeInfo` container. This pattern matches OXC's `ScopeId`/`SymbolId` and the compiler's own HIR `IdentifierId`. - ---- - -## Normalized Tables - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ScopeData { - pub id: ScopeId, - pub parent: Option<ScopeId>, - pub kind: ScopeKind, - /// Bindings declared directly in this scope, keyed by name. - /// Maps to BindingId for lookup in the binding table. - pub bindings: HashMap<String, BindingId>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ScopeKind { - Program, - Function, - Block, - #[serde(rename = "for")] - For, - Class, - Switch, - Catch, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BindingData { - pub id: BindingId, - pub name: String, - pub kind: BindingKind, - /// The scope this binding is declared in. - pub scope: ScopeId, - /// The type of the declaration AST node (e.g., "FunctionDeclaration", - /// "VariableDeclarator"). Used by the compiler to distinguish function - /// declarations from variable declarations during hoisting. - /// COMMENT: make this an enum similar to BindingKind - pub declaration_type: String, - /// For import bindings: the source module and import details. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub import: Option<ImportBindingData>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum BindingKind { - Var, - Let, - Const, - Param, - /// Import bindings (import declarations). - Module, - /// Function declarations (hoisted). - Hoisted, - /// Other local bindings (class declarations, etc.). - Local, - /// Binding kind not recognized by the serializer. - Unknown, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ImportBindingData { - /// The module specifier string (e.g., "react" in `import {useState} from 'react'`). - pub source: String, - pub kind: ImportBindingKind, - /// For named imports: the imported name (e.g., "bar" in `import {bar as baz} from 'foo'`). - /// None for default and namespace imports. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub imported: Option<String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ImportBindingKind { - Default, - Named, - Namespace, -} -``` - -Key differences from Babel's in-memory representation: -- **Bindings are stored in a flat table** indexed by `BindingId`, not nested inside scope objects. Each `ScopeData` stores `HashMap<String, BindingId>` mapping names to binding IDs rather than containing full binding data inline. -- **`declaration_type`** replaces Babel's `binding.path.isFunctionDeclaration()` / `binding.path.isVariableDeclarator()` checks. The compiler uses these to determine hoisting behavior — storing the declaration node type as a string avoids needing to cross-reference back into the AST. -- **`ImportBindingData`** captures import source, kind, and imported name, covering all the import resolution the compiler does via `binding.path.isImportSpecifier()` etc. - ---- - -## ScopeInfo Container - -```rust -/// Complete scope information for a program. Stored separately from the AST -/// and linked via position-based lookup maps. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ScopeInfo { - /// All scopes, indexed by ScopeId. scopes[id.0] gives the ScopeData for that scope. - pub scopes: Vec<ScopeData>, - /// All bindings, indexed by BindingId. bindings[id.0] gives the BindingData. - pub bindings: Vec<BindingData>, - - /// Maps an AST node's start offset to the scope it creates. - /// Populated for scope-creating nodes: Program, FunctionDeclaration, - /// FunctionExpression, ArrowFunctionExpression, BlockStatement, - /// ForStatement, ForInStatement, ForOfStatement, SwitchStatement, - /// CatchClause, ClassDeclaration, ClassExpression. - pub node_to_scope: HashMap<u32, ScopeId>, - - /// Maps an Identifier AST node's start offset to the binding it resolves to. - /// Only present for identifiers that resolve to a binding (not globals). - /// An identifier whose start offset is absent from this map is a global reference. - pub reference_to_binding: HashMap<u32, BindingId>, - - /// The program-level (module) scope. Always scopes[0]. - pub program_scope: ScopeId, -} -``` - -**AST-to-scope linkage**: The AST types themselves carry no scope information — they remain pure serde data types for JSON round-tripping. The `ScopeInfo` links to AST nodes via start offsets (`u32`), which are stable across serialization. Start offsets are unique per node in Babel's output, making them reliable keys. - -**Resolution algorithm** — equivalent to Babel's `scope.getBinding(name)`: - -```rust -impl ScopeInfo { - /// Look up a binding by name starting from the given scope, - /// walking up the parent chain. Returns None for globals. - pub fn get_binding(&self, scope_id: ScopeId, name: &str) -> Option<BindingId> { - let mut current = Some(scope_id); - while let Some(id) = current { - let scope = &self.scopes[id.0 as usize]; - if let Some(&binding_id) = scope.bindings.get(name) { - return Some(binding_id); - } - current = scope.parent; - } - None - } - - /// Look up the binding for an identifier reference by its AST node start offset. - /// Returns None for globals/unresolved references. - pub fn resolve_reference(&self, identifier_start: u32) -> Option<&BindingData> { - self.reference_to_binding - .get(&identifier_start) - .map(|id| &self.bindings[id.0 as usize]) - } - - /// Get all bindings declared in a scope (for hoisting iteration). - pub fn scope_bindings(&self, scope_id: ScopeId) -> impl Iterator<Item = &BindingData> { - self.scopes[scope_id.0 as usize] - .bindings - .values() - .map(|id| &self.bindings[id.0 as usize]) - } -} -``` - -**Identity comparison**: Babel uses object identity (`binding1.identifier === binding2.identifier`) to compare bindings. In the normalized form, `BindingId` equality serves this purpose — two references that resolve to the same `BindingId` refer to the same declaration. This is equivalent to OXC's `SymbolId` equality. - -**`generateUid`/`rename`**: These are mutating operations in Babel used during HIR lowering. In the Rust port, the scope info is read-only input. Unique name generation moves to the Rust side (the Environment already tracks a counter). Renaming is tracked in Rust's own data structures, same as the existing compiler does with its `HIRBuilder.#bindings` map. - ---- - -## Conversion from Other Parsers - -The `ScopeInfo` structure is parser-agnostic. Each parser integration produces an `(ast::File, ScopeInfo)` pair. The conversion patterns differ by parser: - -**From Babel** (current path): The Node.js side runs `@babel/traverse` on the parsed AST and serializes two JSON blobs: the AST (already implemented) and the `ScopeInfo`. The traversal assigns `ScopeId`s in preorder, assigns `BindingId`s in declaration order, and populates the lookup maps by recording each identifier reference's start offset and resolved binding. - -**From OXC**: OXC's `oxc_semantic` crate produces an arena-indexed `ScopeTree` + `SymbolTable` + `ReferenceTable` that maps closely to our structure: - -| OXC type | Our type | Conversion | -|----------|----------|------------| -| `oxc_semantic::ScopeId(u32)` | `ScopeId(u32)` | Direct ID remapping | -| `oxc_semantic::SymbolId(u32)` | `BindingId(u32)` | Direct ID remapping | -| `ScopeTree` (parent IDs, flags, bindings) | `Vec<ScopeData>` | Map flags to `ScopeKind`, copy parent chain, convert binding maps from `SymbolId` to `BindingId` | -| `SymbolTable` (name, scope, flags) | `Vec<BindingData>` | Map flags to `BindingKind`, copy name and scope ID | -| `ReferenceTable` (symbol ID per reference) | `reference_to_binding: HashMap<u32, BindingId>` | Map each reference's AST node span start to its resolved `BindingId` | -| AST node `scope_id` fields | `node_to_scope: HashMap<u32, ScopeId>` | Map each scope-creating node's span start to its `ScopeId` | - -OXC is the most natural fit — both use arena-indexed flat tables with `Copy`-able ID newtypes. The conversion is essentially remapping IDs, which is O(n) with no structural transformation. - -**From SWC**: SWC does not produce a separate scope tree. Instead, its resolver pass annotates each `Ident` node with a `SyntaxContext` (an interned ID encoding hygiene/scope context). Converting to our model requires: - -1. Run SWC's resolver pass to populate `SyntaxContext` on all identifiers -2. Traverse the resolved AST, building scope data by tracking `SyntaxContext` values and their nesting -3. For each unique `(name, SyntaxContext)` pair, create a `BindingData` entry -4. For each identifier reference, record its start offset → `BindingId` mapping -5. For each scope-creating node, record its start offset → `ScopeId` mapping - -This is more work than the OXC path but straightforward — SWC's `SyntaxContext` uniquely identifies each binding's scope context, which gives us the information we need to reconstruct a scope tree. - ---- - -## Completed Work - -All items below have been implemented and verified against all 1714 test fixtures. - -### Scope info types — Done - -Defined `ScopeInfo`, `ScopeData`, `BindingData`, and related types as Rust structs in `react_compiler_ast::scope`. Includes `ScopeId`, `BindingId` newtypes, `ScopeKind`, `BindingKind`, `ImportBindingData`, and the resolution methods on `ScopeInfo`. - -### Babel scope serialization — Done - -Extended `compiler/scripts/babel-ast-to-json.mjs` to produce `.scope.json` and `.renamed.json` files alongside the AST JSON. Uses `@babel/traverse` to collect scope/binding data with preorder ID assignment, then renames identifiers per the `name_s{scopeId}_b{bindingId}` scheme. - -### Scope resolution test — Done - -Implemented in `compiler/crates/react_compiler_ast/tests/scope_resolution.rs` with two tests: -1. **`scope_info_round_trip`**: Verifies ScopeInfo JSON deserializes, re-serializes correctly, and passes internal consistency checks. -2. **`scope_resolution_rename`**: Walks the AST JSON using ScopeInfo to rename identifiers, then compares against Babel's renamed output. Verifies that the ScopeInfo structure correctly reproduces Babel's binding resolution for all 1714 fixtures. - -Both tests run from `compiler/scripts/test-babel-ast.sh`. diff --git a/compiler/docs/rust-port/rust-port-0003-testing-infrastructure.md b/compiler/docs/rust-port/rust-port-0003-testing-infrastructure.md deleted file mode 100644 index 7632675849ae..000000000000 --- a/compiler/docs/rust-port/rust-port-0003-testing-infrastructure.md +++ /dev/null @@ -1,634 +0,0 @@ -# Rust Port Step 2: Testing Infrastructure - -## Goal - -Create a testing infrastructure that validates the Rust port produces identical results to the TypeScript compiler at every stage of the pipeline. The port proceeds incrementally — one pass at a time — so the test infrastructure must support running the pipeline up to any specified pass and comparing the intermediate state between TS and Rust. - -**Current status**: M1, M2, M3 implemented. All Rust tests expected to fail (todo!() stubs). Next step: port lower() (M4). - -**Known issues — resolved:** -- TS binary rewritten to call `compile()` directly (bypasses `transformFromAstSync` + `BabelPluginReactCompiler`). Individual pass functions aren't exported from dist, so logger-based capture is still used, but the Babel plugin orchestration layer is bypassed. (done) -- `debug_error` renamed to `format_errors` (done). `CompilerError` type name kept as-is since `CompilerDiagnostic` already exists as a different type in the diagnostics crate. -- Both TS and Rust now print `returnTypeAnnotation` in debug output. (done) -- `mark_predecessors` fallthrough handling: VERIFIED — matches TS `eachTerminalSuccessor` (does not include fallthroughs, correct). -- `GotoVariant::Break` usage in `remove_unnecessary_try_catch` and `remove_dead_do_while_statements`: VERIFIED — matches TS. -- All collection types migrated to `IndexMap`/`IndexSet` (done). - -**Known issues — remaining:** -- Debug output format: TS and Rust debug printers produce different output formats. Both need to converge on Rust `Debug`-style nested format. This will be addressed when the Rust lowering is implemented and output comparison becomes possible. -- TS debug printer collects identifiers/functions per-function; should print all from environment (matching Rust). Requires access to the Environment from TS, which is not currently exposed through the logger API. -- Rust binary config: `Environment::new()` needs matching config (`compilationMode: "all"`, `target: "19"`, etc.) — requires adding config support to the Rust Environment type. -- Error format output between TS and Rust has not been validated for byte-identical output. Will be validated when lowering produces real output. - ---- - -## Overview - -``` - fixture.js - │ - ┌──────────────────┴──────────────────┐ - ▼ ▼ - TS test binary @babel/parser ──> AST JSON - (parse with Babel, + Scope JSON - compile up to │ - target pass) ▼ - │ Rust test binary - │ (compile up to - │ target pass) - ▼ │ - TS debug output Rust debug output - │ │ - └──────────────── diff ───────────────────┘ -``` - -A single entrypoint script discovers fixtures, runs both the TS and Rust binaries on each fixture, and diffs their output. The inputs differ slightly: the TS binary takes the original fixture path (parsing with Babel internally, since the TS compiler expects a Babel `NodePath`), while the Rust binary takes pre-parsed AST JSON + Scope JSON. Both produce the same detailed debug representation of the compiler state after the target pass. - ---- - -## Entrypoint - -### `compiler/scripts/test-rust-port.sh <pass> [<dir>]` - -```bash -#!/bin/bash -set -e - -PASS="$1" # Required: name of the compiler pass to run up to -DIR="$2" # Optional: fixture root directory (default: compiler fixtures) - -# 1. Parse fixtures into AST JSON + Scope JSON (reuses existing scripts) -# 2. Build TS test binary (if needed) -# 3. Build Rust test binary (cargo build) -# 4. For each fixture: -# a. Run TS binary: node compiler/scripts/ts-compile-fixture.mjs <pass> <fixture.js> -# b. Run Rust binary: compiler/target/debug/test-rust-port <pass> <ast.json> <scope.json> -# c. Diff the outputs -# 5. Report results (pass/fail counts, first N diffs) -``` - -**Arguments:** -- `<pass>` — The name of the compiler pass to run up to. Uses the same names as the `log()` calls in Pipeline.ts (e.g., `HIR`, `SSA`, `InferTypes`, `InferMutationAliasingEffects`). See [Pass Names](#pass-names) below. -- `[<dir>]` — Optional root directory of fixtures. Scans for `**/*.{js,jsx,ts,tsx}` files. Defaults to `compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures`. - -**Output format:** Same style as `test-babel-ast.sh` — show the first 5 failures with colored unified diffs (using `diff` or the `similar` crate pattern), then a summary count. Example: - -``` -Testing 1714 fixtures up to pass: InferTypes - -FAIL compiler/simple.js ---- TypeScript -+++ Rust -@@ -3,7 +3,7 @@ - bb0 (block): - [1] $0:T = LoadGlobal global:console -- [2] $1:TFunction<BuiltInConsoleLog> = PropertyLoad $0.log -+ [2] $1:T = PropertyLoad $0.log - -... (first 50 lines of diff) - -Results: 1710 passed, 4 failed (1714 total) -``` - ---- - -## Pass Names - -These are the valid `<pass>` arguments, matching the `log()` name strings in Pipeline.ts. The test binaries run all passes up to and including the named pass. - -### HIR Phase - -| Pass Name | Pipeline.ts Function | -|-----------|---------------------| -| `HIR` | `lower()` | -| `PruneMaybeThrows` | `pruneMaybeThrows()` (first call) | -| `DropManualMemoization` | `dropManualMemoization()` | -| `InlineIIFEs` | `inlineImmediatelyInvokedFunctionExpressions()` | -| `MergeConsecutiveBlocks` | `mergeConsecutiveBlocks()` | -| `SSA` | `enterSSA()` | -| `EliminateRedundantPhi` | `eliminateRedundantPhi()` | -| `ConstantPropagation` | `constantPropagation()` | -| `InferTypes` | `inferTypes()` | -| `OptimizePropsMethodCalls` | `optimizePropsMethodCalls()` | -| `AnalyseFunctions` | `analyseFunctions()` | -| `InferMutationAliasingEffects` | `inferMutationAliasingEffects()` | -| `OptimizeForSSR` | `optimizeForSSR()` | -| `DeadCodeElimination` | `deadCodeElimination()` | -| `PruneMaybeThrows2` | `pruneMaybeThrows()` (second call) | -| `InferMutationAliasingRanges` | `inferMutationAliasingRanges()` | -| `InferReactivePlaces` | `inferReactivePlaces()` | -| `RewriteInstructionKinds` | `rewriteInstructionKindsBasedOnReassignment()` | -| `InferReactiveScopeVariables` | `inferReactiveScopeVariables()` | -| `MemoizeFbtOperands` | `memoizeFbtAndMacroOperandsInSameScope()` | -| `NameAnonymousFunctions` | `nameAnonymousFunctions()` | -| `OutlineFunctions` | `outlineFunctions()` | -| `AlignMethodCallScopes` | `alignMethodCallScopes()` | -| `AlignObjectMethodScopes` | `alignObjectMethodScopes()` | -| `PruneUnusedLabelsHIR` | `pruneUnusedLabelsHIR()` | -| `AlignReactiveScopesToBlockScopes` | `alignReactiveScopesToBlockScopesHIR()` | -| `MergeOverlappingReactiveScopes` | `mergeOverlappingReactiveScopesHIR()` | -| `BuildReactiveScopeTerminals` | `buildReactiveScopeTerminalsHIR()` | -| `FlattenReactiveLoops` | `flattenReactiveLoopsHIR()` | -| `FlattenScopesWithHooksOrUse` | `flattenScopesWithHooksOrUseHIR()` | -| `PropagateScopeDependencies` | `propagateScopeDependenciesHIR()` | - -### Reactive Phase - -| Pass Name | Pipeline.ts Function | -|-----------|---------------------| -| `BuildReactiveFunction` | `buildReactiveFunction()` | -| `PruneUnusedLabels` | `pruneUnusedLabels()` | -| `PruneNonEscapingScopes` | `pruneNonEscapingScopes()` | -| `PruneNonReactiveDependencies` | `pruneNonReactiveDependencies()` | -| `PruneUnusedScopes` | `pruneUnusedScopes()` | -| `MergeReactiveScopesThatInvalidateTogether` | `mergeReactiveScopesThatInvalidateTogether()` | -| `PruneAlwaysInvalidatingScopes` | `pruneAlwaysInvalidatingScopes()` | -| `PropagateEarlyReturns` | `propagateEarlyReturns()` | -| `PruneUnusedLValues` | `pruneUnusedLValues()` | -| `PromoteUsedTemporaries` | `promoteUsedTemporaries()` | -| `ExtractScopeDeclarationsFromDestructuring` | `extractScopeDeclarationsFromDestructuring()` | -| `StabilizeBlockIds` | `stabilizeBlockIds()` | -| `RenameVariables` | `renameVariables()` | -| `PruneHoistedContexts` | `pruneHoistedContexts()` | -| `Codegen` | `codegenFunction()` | - ---- - -## TS Test Binary - -### `compiler/scripts/ts-compile-fixture.mjs` - -A Node.js script that takes the original fixture path, parses it with Babel, and runs the compiler pipeline up to the target pass. It uses the real Babel `NodePath` and the existing `lower()` function directly — no JSON intermediary on the TS side. - -**Interface:** -``` -node compiler/scripts/ts-compile-fixture.mjs <pass> <fixture-path> -``` - -**Outputs to stdout:** -- On success: detailed debug representation of the HIR or ReactiveFunction, including outlined functions (see [Debug Output Format](#debug-output-format)) -- On error (thrown CompilerError): formatted error with full diagnostic details -- On accumulated errors (env has errors at the target pass): formatted accumulated errors — these take priority over the debug HIR output - -**Implementation approach:** - -```typescript -import { parse } from '@babel/parser'; -import traverse from '@babel/traverse'; -import { lower } from '../packages/babel-plugin-react-compiler/src/HIR/BuildHIR'; -// ... import all passes - -function main() { - const [pass, fixturePath] = process.argv.slice(2); - const source = fs.readFileSync(fixturePath, 'utf8'); - - // Parse with Babel to get a real NodePath (same as production compiler) - const ast = parse(source, { sourceType: 'module', plugins: [...], errorRecovery: true }); - let functionPath; - traverse(ast, { - 'FunctionDeclaration|ArrowFunctionExpression|FunctionExpression'(path) { - functionPath = path; - path.stop(); - } - }); - - const env = createEnvironment(/* default config, with pragma overrides from source */); - - try { - const hir = lower(functionPath, env); - if (pass === 'HIR') { - if (env.hasErrors()) { - return printFormattedErrors(env.errors()); - } - return printDebugHIR(hir, env); // includes outlined functions - } - - pruneMaybeThrows(hir); - if (pass === 'PruneMaybeThrows') { - if (env.hasErrors()) { - return printFormattedErrors(env.errors()); - } - return printDebugHIR(hir, env); - } - - // ... each pass in order, with the same pattern: - // somePass(hir); - // if (pass === 'PassName') { - // if (env.hasErrors()) { - // return printFormattedErrors(env.errors()); - // } - // return printDebugHIR(hir, env); - // } - - } catch (e) { - if (e instanceof CompilerError) { - return printFormattedError(e); - } - throw e; // re-throw non-compiler errors - } -} -``` - -**Key design decisions:** - -1. **Independent pipeline**: Does NOT call `runWithEnvironment()`. Implements the pass sequence independently, exactly mirroring the Rust binary. This ensures we're testing the pass behavior, not the pipeline orchestration. - -2. **Fixture path input, real Babel parse**: The TS binary takes the original fixture path and parses it with `@babel/parser` + `@babel/traverse` to get a real `NodePath` — reusing the existing `lower()` directly. This means the TS and Rust sides have slightly different inputs (fixture path vs. AST JSON + Scope JSON), but that's fine: the AST JSON is validated by the step 1 round-trip test, and the shared contract is the debug output format, not the input format. - -3. **Validation passes**: Validation passes that run between transform passes (e.g., `validateContextVariableLValues`, `validateHooksUsage`) are included in the pipeline. If a validation pass records errors or throws, that affects the output. The test compares the full behavior including validation. - -4. **Conditional passes**: Passes behind feature flags (e.g., `enableDropManualMemoization`, `enableJsxOutlining`) use the same default config in both TS and Rust. The config is fixed for testing — not configurable per-fixture (initially). If we later need per-fixture config, the fixture's pragma comment can be parsed. - -5. **Config pragmas**: Parse the first line of the original fixture source for config pragmas (e.g., `// @enableJsxOutlining`), same as the snap test runner does. Apply these to the environment config before running passes. This ensures feature-flag-gated passes are tested correctly. - ---- - -## Rust Test Binary - -### `compiler/crates/react_compiler/src/bin/test_rust_port.rs` - -A Rust binary in the main compiler crate that mirrors the TS test binary exactly. - -**Interface:** -``` -compiler/target/debug/test-rust-port <pass> <ast.json> <scope.json> -``` - -**Same output contract as the TS binary** — identical debug format on stdout. - -**Implementation:** - -```rust -fn main() -> Result<(), Box<dyn Error>> { - let args: Vec<String> = std::env::args().collect(); - let pass = &args[1]; - let ast_json = fs::read_to_string(&args[2])?; - let scope_json = fs::read_to_string(&args[3])?; - - let ast: react_compiler_ast::File = serde_json::from_str(&ast_json)?; - let scope: react_compiler_ast::ScopeInfo = serde_json::from_str(&scope_json)?; - - let mut env = Environment::new(/* config matching TS binary: compilationMode="all", target="19", etc. */); - - match run_pipeline(pass, &ast, &scope, &mut env) { - Ok(output) => { - print!("{}", output); - } - Err(error) => { - print!("{}", format_errors(&error)); - } - } - - Ok(()) -} - -fn run_pipeline( - target_pass: &str, - ast: &File, - scope: &ScopeInfo, - env: &mut Environment, -) -> Result<String, CompilerError> { - let mut hir = lower(ast, scope, env)?; - if target_pass == "HIR" { - if env.has_errors() { - return Ok(format_errors(env.errors())); - } - return Ok(debug_hir(&hir, env)); // includes outlined functions - } - - prune_maybe_throws(&mut hir); - if target_pass == "PruneMaybeThrows" { - if env.has_errors() { - return Ok(format_errors(env.errors())); - } - return Ok(debug_hir(&hir, env)); - } - - // ... each pass in order, with the same pattern: - // some_pass(&mut hir, env)?; - // if target_pass == "PassName" { - // if env.has_errors() { - // return Ok(format_errors(env.errors())); - // } - // return Ok(debug_hir(&hir, env)); - // } -} -``` - -**Crate structure**: The test binary lives in whatever crate contains the compiler pipeline (likely `react_compiler` or similar — to be created as passes are ported). It depends on `react_compiler_ast` for the input types. - ---- - -## Debug Output Format - -### Why Not PrintHIR - -The existing `PrintHIR.ts` omits important details: -- Mutable ranges hidden when `end <= start + 1` -- `DEBUG_MUTABLE_RANGES` flag defaults to `false` -- Type information omitted for unresolved types -- Source locations not printed -- UnaryExpression doesn't print operator -- Scope details minimal (just `_@scopeId` suffix) -- DeclarationId not printed -- Identifier's full type structure not shown - -For port validation, we need a representation that prints **everything** — similar to Rust's `#[derive(Debug)]` output. Every field of every identifier, every scope, every instruction must be visible so any divergence between TS and Rust is immediately caught. - -### Debug HIR Format - -A structured text format that prints every field of the HIR, **including outlined functions**. Both TS and Rust must produce byte-identical output for the same HIR state. The format uses **Rust `Debug` trait style** — nested struct/enum formatting with curly braces and named fields. - -**Design principles:** -- **Rust `Debug`-style format**: Output looks like Rust's `#[derive(Debug)]` output — `StructName { field: value, ... }` for structs, `EnumVariant { ... }` for enum variants -- Print every field, even defaults/empty values (no elision) -- Deterministic ordering (blocks in RPO, instructions in order, maps by sorted key) -- Stable identifiers (use numeric IDs, not memory addresses) -- Indent with 2 spaces for nesting -- Include all identifiers from the environment (not just those referenced in the function) -- Include all outlined functions from the environment (not just those referenced in the function), each printed with the same format, numbered sequentially (`Function #0`, `Function #1`, etc.) - -**Example output after `InferTypes`:** - -``` -Function #0: - HirFunction { - id: "example", - params: [ - Place { identifier: $3, effect: Read, reactive: false, loc: 1:20-1:21 }, - ], - returns: Place { identifier: $0, effect: Read, reactive: false, loc: 0:0-0:0 }, - returnTypeAnnotation: None, - context: [], - aliasing_effects: None, - } - - Identifiers: - $0: Identifier { id: 0, declaration_id: None, name: None, mutable_range: [0, 0], scope: None, type: Type, loc: 0:0-0:0 } - $1: Identifier { id: 1, declaration_id: 0, name: Some("x"), mutable_range: [1, 5], scope: None, type: TFunction(BuiltInArray), loc: 1:20-1:21 } - ... - - Blocks: - bb0 (block): - preds: [] - phis: [] - instructions: - Instruction { id: EvaluationOrder(1), lvalue: Place { identifier: $1, effect: Mutate, reactive: false, loc: 1:0-1:10 }, value: LoadGlobal { name: "console" }, effects: None, loc: 1:0-1:10 } - ... - terminal: Return { value: Place { identifier: $2, effect: Read, reactive: false, loc: 5:2-5:10 }, loc: 5:2-5:10 } -``` - -Note: This is Rust `Debug`-style formatting. Field names use `snake_case`. Optional values use `None`/`Some(...)`. Enum variants use `VariantName { ... }` or `VariantName(...)` syntax. - -### Debug Reactive Function Format - -Same approach for `ReactiveFunction` — print the full tree structure with all fields visible. - -### Debug Error Format - -When compilation produces errors (thrown or accumulated), output a structured error representation: - -``` -Error: - category: InvalidReact - severity: InvalidReact - reason: "Hooks must be called unconditionally" - description: "Cannot call a hook (useState) conditionally" - loc: 3:4-3:20 - suggestions: [] - details: - - severity: InvalidReact - reason: "This is a conditional" - loc: 2:2-5:3 -``` - -All fields of `CompilerDiagnostic` are included — reason, description, loc, severity, category, suggestions (with text + loc), and any nested detail diagnostics. - -### Implementation Strategy - -**TS side**: Create a `debugHIR(hir: HIRFunction, env: Environment): string` function in the test script that walks the HIR and prints everything using Rust `Debug`-style formatting (`StructName { field: value, ... }`). Prints all identifiers and outlined functions from the environment (not just those referenced by the function). This is NOT a modification to the existing `PrintHIR.ts` — it's a separate debug printer in the test infrastructure. Must also print `returnTypeAnnotation`. - -**Rust side**: Implement a custom `debug_hir()` function that produces Rust `Debug`-style output. While this is similar to `#[derive(Debug)]`, a custom implementation is needed for consistent field ordering and formatting. Prints all identifiers and functions from the environment. - -**Shared format specification**: The format is defined once (in this document) and both sides implement it. The round-trip test validates they produce identical output. Both sides must print `returnTypeAnnotation`. - ---- - -## Error Handling in Test Binaries - -Both test binaries handle errors uniformly: every pass checkpoint (each `if (pass === ...)` check) first inspects the environment for accumulated errors. If errors are present, the formatted errors are returned **instead of** the debug HIR. This ensures that error output is always comparable between TS and Rust. - -### Thrown Errors (try/catch in TS, Result::Err in Rust) - -- `CompilerError.invariant()` — truly unexpected state -- `CompilerError.throwTodo()` — unsupported but known pattern -- `CompilerError.throw*()` — other throwing methods - -In TS, the entire pipeline is wrapped in a `try/catch`. When a `CompilerError` is caught, the test binary prints the formatted error. Non-`CompilerError` exceptions re-throw (test binary crashes with non-zero exit code, treated as a test failure). - -In Rust, passes return `Result<_, CompilerDiagnostic>`. The `Err` case is handled at the top level by printing the formatted error. Panics (e.g., from `.unwrap()`) crash the binary with a non-zero exit code, treated as a test failure. - -### Accumulated Errors (env.hasErrors()) - -Errors recorded via `env.recordError()` / `env.logErrors()` accumulate on the environment. At every pass checkpoint, the test binary checks `env.hasErrors()` **before** printing the debug HIR. If errors are present, the formatted error list is printed instead of the HIR — the pipeline does not continue past the target pass when errors exist. - -This means each pass checkpoint follows the same pattern: - -``` -run_pass(hir); -if target_pass == "PassName": - if env.has_errors(): - return format_errors(env.errors()) // errors take priority - return debug_hir(hir, env) // no errors → print HIR -``` - -### Comparison Rules - -1. If TS throws and Rust returns Err: compare the formatted error output -2. If TS succeeds and Rust succeeds: compare the debug HIR/reactive output (including outlined functions) -3. If TS throws and Rust succeeds (or vice versa): test fails (mismatch) -4. If TS has accumulated errors and Rust doesn't (or vice versa): test fails -5. If both have accumulated errors at the same pass: compare the formatted error lists - ---- - -## Fixture Discovery - -The test script scans the fixture directory for `**/*.{js,jsx,ts,tsx}` files, matching the pattern used by `test-babel-ast.sh`. For each fixture: - -1. Parse with Babel to produce AST JSON + Scope JSON (reusing `babel-ast-to-json.mjs` and `babel-scope-to-json.mjs`) -2. Skip fixtures that fail to parse (`.parse-error` marker) -3. Run both TS and Rust binaries -4. Diff outputs - -**Fixture paths**: The test script passes the original fixture path to the TS binary (which handles its own parsing) and the pre-parsed AST/Scope JSON paths to the Rust binary. - ---- - -## Input Asymmetry: Fixture Path vs. AST JSON - -The TS and Rust test binaries take different inputs: - -- **TS binary**: Takes the original fixture path. Parses with `@babel/parser`, runs `@babel/traverse` to build scope info, and calls the existing `lower()` with a real Babel `NodePath`. This is the simplest approach — `lower()` is deeply entangled with Babel's `NodePath` API (`path.get()`, `path.scope.getBinding()`, etc.), so reusing it directly avoids reimplementing those dependencies. - -- **Rust binary**: Takes pre-parsed AST JSON + Scope JSON (produced by the step 1 infrastructure). Deserializes into `react_compiler_ast::File` and `ScopeInfo`, then calls a Rust `lower()` that works with these types directly — no Babel dependency. - -This asymmetry is intentional and acceptable: -1. The AST JSON round-trip is already validated by step 1 (1714/1714 fixtures pass), so the Rust side sees the same AST data that Babel produced. -2. The shared contract between the two sides is the **debug output format**, not the input format. -3. Keeping the TS side on real Babel `NodePath`s means we're comparing against the production compiler's actual behavior, not a reimplementation of its input handling. - ---- - -## Implementation Plan - -### M1: Debug Output Format + TS Test Binary - -**Goal**: Get the TS side working end-to-end so we have a reference output for every fixture at every pass. - -1. **Define the debug output format** — Write a precise specification for the text format. Create a `DebugPrintHIR.ts` module in `compiler/scripts/` (test infrastructure, not compiler source) that implements the format. - -2. **Define the debug error format** — Specify exact formatting for `CompilerDiagnostic` objects, including all fields. - -3. **Create `compiler/scripts/ts-compile-fixture.mjs`** — The TS test binary. Takes `<pass> <fixture-path>` and produces debug output. Parses the fixture source with Babel to get a real `NodePath`, runs passes up to the target, prints debug output. - -4. **Validate the TS binary** — Run it on all fixtures at several pass points (`HIR`, `SSA`, `InferTypes`, `InferMutationAliasingEffects`, `InferMutationAliasingRanges`) and verify the output is sensible and deterministic (running twice produces identical output). - -### M2: Shell Script + Diff Infrastructure - -**Goal**: The test script runs the TS binary on all fixtures and produces output files. Later, when Rust passes are implemented, it will also run the Rust binary and diff. - -1. **Create `compiler/scripts/test-rust-port.sh`** — The entrypoint script. Initially only runs the TS side (Rust passes don't exist yet). Supports `<pass>` and `[<dir>]` arguments. - -2. **Diff formatting** — Implement colored unified diff output, similar to `test-babel-ast.sh`. Show first 5 failures with diffs, then summary counts. - -3. **Exit codes** — Exit 0 on all pass, non-zero on any failure. Useful for CI integration. - -### M3: Rust Test Binary Scaffold - -**Goal**: Scaffold the Rust binary and a `todo!`-only stub for `lower()` so the end-to-end test loop works immediately — even though every test will fail. This validates the full test infrastructure (fixture discovery, Rust binary invocation, diff output) before any real porting begins. - -1. **Create the Rust compiler crate** — `compiler/crates/react_compiler/` with the binary target `test-rust-port`. Depends on `react_compiler_ast` for input types. - -2. **Stub `lower()`** — Create a `lower()` function with the correct signature that immediately calls `todo!("lower not yet implemented")`. This means the Rust binary will panic for every fixture, producing a non-zero exit code. The test script treats this as a test failure (expected at this stage). - -3. **Stub pipeline** — The `run_pipeline()` function calls the stubbed `lower()` and has placeholder match arms for all other pass names. Every pass beyond `lower()` also hits `todo!()`. - -4. **Implement `debug_hir()`** — Rust debug printer matching the TS format exactly. This won't be exercised until `lower()` is real, but having it in place means the first real pass port immediately produces diffable output. - -5. **Implement `debug_error()`** — Rust error printer matching the TS format. - -6. **Integrate into `test-rust-port.sh`** — Run both TS and Rust binaries, diff outputs. At this stage, **all tests are expected to fail** (Rust panics on `todo!()`). The test script should report the failure count and distinguish between "Rust panicked" vs "output mismatch" failures: - - ``` - Testing 1714 fixtures up to pass: HIR - - Results: 0 passed, 1714 failed (1714 total) - 1714 rust panicked (todo!), 0 output mismatch - ``` - - This confirms the infrastructure works end-to-end. As `lower()` and subsequent passes are implemented, the "rust panicked" count drops and "passed" / "output mismatch" counts rise. - -**Why stub with `todo!()` now**: The goal of this phase is to validate the test infrastructure itself, not the compiler port. By having a Rust binary that compiles and runs (but panics), we prove that fixture discovery, AST JSON passing, Rust binary invocation, and diff reporting all work correctly. When the real `lower()` port begins (step 4+), the developer can immediately see their progress reflected in the test results without any infrastructure work. - -### M4: Ongoing — Per-Pass Validation - -As each pass is ported to Rust, replace the `todo!()` stub with a real implementation: - -1. Replace the `todo!()` in the pass with a real implementation -2. Run `test-rust-port.sh <pass>` to compare TS and Rust output -3. Fix any differences until all (or nearly all) fixtures pass -4. Move to the next pass - -The first pass to port is `lower()`. Once it's real, fixtures at the `HIR` pass will transition from "rust panicked" to either "passed" or "output mismatch". The test infrastructure is complete after M3 — M4 is the ongoing usage pattern. - ---- - -## File Layout - -``` -compiler/ - scripts/ - test-rust-port.sh # Entrypoint script - ts-compile-fixture.mjs # TS test binary - debug-print-hir.mjs # Debug HIR printer (TS) - debug-print-reactive.mjs # Debug ReactiveFunction printer (TS) - debug-print-error.mjs # Debug error printer (TS) - crates/ - react_compiler/ - Cargo.toml - src/ - bin/ - test_rust_port.rs # Rust test binary - lib.rs - debug_print.rs # Debug HIR/Reactive/Error printer (Rust) - pipeline.rs # Pipeline runner (pass-by-pass) - react_compiler_hir/ - Cargo.toml - src/ - lib.rs # HIR types - environment.rs # Environment type - react_compiler_lowering/ - Cargo.toml - src/ - lib.rs # pub fn lower() entry point - build_hir.rs # Lowering functions - hir_builder.rs # HIRBuilder struct - react_compiler_diagnostics/ - Cargo.toml - src/ - lib.rs # CompilerError, CompilerDiagnostic, etc. - react_compiler_ast/ # Existing AST crate (from step 1) - ... -``` - ---- - -## TS Binary: Parsing Strategy - -The TS test binary parses the original fixture source with `@babel/parser` and `@babel/traverse`, then calls the existing `lower()` with the real `NodePath`. This ensures the TS reference output is 100% faithful to what the production compiler would produce. Any differences in the Rust side's HIR output reveal bugs in the Rust lowering — not artifacts of a reimplemented TS input layer. - ---- - -## Configuration - -Both test binaries use the **same configuration**. This includes `compilationMode: "all"`, `target: "19"`, and other settings that ensure both sides produce comparable output, plus any overrides from pragma comments in the fixture source. - -**Pragma parsing**: The first line of each fixture may contain config pragmas like `// @enableJsxOutlining @enableNameAnonymousFunctions:false`. Both test binaries parse this line and apply the overrides before running passes. - -**TS side**: Reuse the existing pragma parser from the snap test runner. - -**Rust side**: Implement a simple pragma parser that produces the same config. Initially, before the Rust pragma parser is built, use a fixed default config and skip fixtures with non-default pragmas (or have the TS binary output the resolved config as a JSON header that the Rust binary can consume). - ---- - -## Determinism Requirements - -For the diff to be meaningful, both test binaries must be fully deterministic: - -1. **Map/Set iteration order**: TS uses insertion-order Maps and Sets. Rust should use `IndexMap`/`IndexSet` (from the `indexmap` crate) for insertion-order maps and sets, matching TS's insertion-order `Map` and `Set`. The debug printer must sort by key (block IDs, identifier IDs, scope IDs) before printing. - -2. **ID assignment**: Both sides must assign the same IDs (IdentifierId, BlockId, ScopeId) in the same order. This is ensured by following the same pipeline logic. - -3. **Floating point**: Avoid floating point in debug output. All numeric values are integers (IDs, ranges, line/column numbers). - -4. **Source locations**: Print locations as `line:column-line:column`. Both sides read the same source locations from the AST JSON. - ---- - -## Scope and Non-Goals - -### In Scope -- Testing every pass from `lower` through `codegen` -- HIR debug output comparison -- ReactiveFunction debug output comparison -- Error output comparison (thrown and accumulated) -- Support for custom fixture directories -- Config pragma support - -### Not In Scope (Initially) -- Performance benchmarking (separate effort) -- Testing the Babel plugin integration (the Rust compiler is a standalone binary) -- Testing codegen output (the `Codegen` pass produces a Babel AST, which is tested by comparing its debug representation — not by running the generated code) -- Parallel test execution (run fixtures sequentially initially; parallelize later if needed) -- Watch mode diff --git a/compiler/docs/rust-port/rust-port-0004-build-hir.md b/compiler/docs/rust-port/rust-port-0004-build-hir.md deleted file mode 100644 index 74435f6cbfb0..000000000000 --- a/compiler/docs/rust-port/rust-port-0004-build-hir.md +++ /dev/null @@ -1,734 +0,0 @@ -# Rust Port Step 4: BuildHIR / HIR Lowering - -## Goal - -Port `BuildHIR.ts` (~4555 lines) and `HIRBuilder.ts` (~955 lines) into Rust equivalents in `compiler/crates/react_compiler_lowering/`. This is the first major compiler pass — it converts a Babel AST + scope info into the HIR control-flow graph representation. - -The Rust port should be structurally as close to the TypeScript as possible: viewing the TS and Rust side by side, the logic should look, read, and feel similar while working naturally in Rust. - -**Current status**: M1-M13 fully implemented. All statement types, expression types, destructuring, function expressions, JSX, switch/try-catch, for-of/in, optional chaining, and recursive lowering are complete. No `todo!()` stubs remain. `cargo check` passes. Remaining work: test against fixtures and fix divergences from TypeScript output. - -**Known issues to fix:** -- All collection types must use `IndexMap`/`IndexSet` (from the `indexmap` crate), not `BTreeMap`/`BTreeSet`/`HashMap`/`HashSet`. This is critical for `HIR.blocks` where `BTreeMap` destroys RPO insertion ordering. -- Functions `lower_function`, `lower_function_to_value`, `gather_captured_context`, `lower_object_property_key`, `lower_type` take `&Expression`. The AST crate uses `Expression` for keys and doesn't have standalone `Function`/`ObjectPropertyKey`/`TypeAnnotation` types, so `&Expression` is correct for the current AST structure. When these functions are implemented, they should pattern-match on the specific expression variants internally. -- `VariableBinding::Identifier.binding_kind` is `String` — must be a `BindingKind` enum. -- `HirBuilder` is missing `component_scope: ScopeId` field (needed for `gather_captured_context` in M9). -- `build_temporary_place` helper is missing (listed in M4). -- `mark_predecessors` fallthrough handling: VERIFIED — matches TS `eachTerminalSuccessor` (does not include fallthroughs, correct). -- `GotoVariant::Break` usage: VERIFIED — matches TS for both `remove_unnecessary_try_catch` and `remove_dead_do_while_statements`. - ---- - -## Crate Layout - -``` -compiler/crates/ - react_compiler_lowering/ - Cargo.toml - src/ - lib.rs # pub fn lower() entry point - build_hir.rs # lowerStatement, lowerExpression, lowerAssignment, etc. - hir_builder.rs # HIRBuilder struct - react_compiler_hir/ - Cargo.toml - src/ - lib.rs # HIR types: HirFunction, BasicBlock, Instruction, Terminal, Place, etc. - environment.rs # Environment struct (arenas, counters, config) - react_compiler_diagnostics/ - Cargo.toml - src/ - lib.rs # CompilerError, CompilerDiagnostic, ErrorCategory, etc. -``` - -### Dependencies - -```toml -# react_compiler_lowering/Cargo.toml -[dependencies] -react_compiler_ast = { path = "../react_compiler_ast" } -react_compiler_hir = { path = "../react_compiler_hir" } -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" } -``` - ---- - -## Key Design Decisions - -### 1. No NodePath — Work Directly with AST Structs + ScopeInfo - -The TypeScript `lower()` takes a `NodePath<t.Function>` and uses Babel's traversal API (`path.get()`, `path.scope.getBinding()`, etc.) extensively. The Rust port works with deserialized `react_compiler_ast` structs and the `ScopeInfo` from step 2. - -**TypeScript pattern:** -```typescript -function lowerStatement(builder: HIRBuilder, stmtPath: NodePath<t.Statement>) { - switch (stmtPath.type) { - case 'IfStatement': { - const stmt = stmtPath as NodePath<t.IfStatement>; - const test = lowerExpressionToTemporary(builder, stmt.get('test')); - ... - } - } -} -``` - -**Rust equivalent:** -```rust -fn lower_statement(builder: &mut HirBuilder, stmt: &ast::Statement) { - match stmt { - ast::Statement::IfStatement(stmt) => { - let test = lower_expression_to_temporary(builder, &stmt.test); - ... - } - } -} -``` - -The mapping is direct: `stmtPath.type` switch becomes `match stmt`, `stmt.get('test')` becomes `&stmt.test`, type narrowing via `as NodePath<T>` becomes Rust's `match` arm binding. - -### 2. Binding Resolution via ScopeInfo - -The TypeScript `resolveIdentifier()` and `resolveBinding()` methods use Babel's scope API (`path.scope.getBinding()`, `babelBinding.scope`, `babelBinding.path.isImportSpecifier()`, etc.). The Rust port replaces all of this with `ScopeInfo` lookups. - -**TypeScript** (`HIRBuilder.resolveIdentifier()`): -```typescript -const babelBinding = path.scope.getBinding(originalName); -if (babelBinding === outerBinding) { - if (path.isImportDefaultSpecifier()) { ... } -} -const resolvedBinding = this.resolveBinding(babelBinding.identifier); -``` - -**Rust equivalent:** -```rust -fn resolve_identifier(&mut self, name: &str, start_offset: u32) -> VariableBinding { - // Look up via ScopeInfo instead of Babel's scope API - let binding_id = self.scope_info.resolve_reference(start_offset); - match binding_id { - None => VariableBinding::Global { name: name.to_string() }, - Some(binding) => { - if binding.scope == self.scope_info.program_scope { - // Module-level binding — check import info - match &binding.import { - Some(import) => match import.kind { - ImportBindingKind::Default => VariableBinding::ImportDefault { ... }, - ImportBindingKind::Named => VariableBinding::ImportSpecifier { ... }, - ImportBindingKind::Namespace => VariableBinding::ImportNamespace { ... }, - }, - None => VariableBinding::ModuleLocal { name: name.to_string() }, - } - } else { - let identifier = self.resolve_binding(name, binding_id.unwrap()); - VariableBinding::Identifier { identifier, binding_kind: BindingKind::from(&binding.kind) } - } - } - } -} -``` - -Key differences: -- **`resolveBinding()` keying**: TypeScript uses Babel node reference identity (`mapping.node === node`) to distinguish same-named variables in different scopes. Rust uses `BindingId` from `ScopeInfo` — the map becomes `IndexMap<BindingId, IdentifierId>` instead of `Map<string, {node, identifier}>`. This is simpler and more correct. -- **`isContextIdentifier()`**: TypeScript checks `env.isContextIdentifier(binding.identifier)`. Rust checks whether the binding's scope is an ancestor of the current function's scope but not the program scope — this is a `ScopeInfo` query. -- **`gatherCapturedContext()`**: TypeScript traverses the function with Babel's traverser to find free variable references. Rust walks the AST directly using `ScopeInfo.reference_to_binding` to identify references that resolve to bindings in ancestor scopes. - -### 3. HIRBuilder Struct - -The `HIRBuilder` class maps to a Rust struct with `&mut self` methods. The closure-based APIs (`enter()`, `loop()`, `label()`, `switch()`) translate to methods that take `impl FnOnce(&mut Self) -> T`. - -```rust -pub struct HirBuilder<'a> { - completed: IndexMap<BlockId, BasicBlock>, - current: WipBlock, - entry: BlockId, - scopes: Vec<Scope>, - context: IndexMap<BindingId, Option<SourceLocation>>, - bindings: IndexMap<BindingId, IdentifierId>, - used_names: IndexMap<String, BindingId>, - instruction_table: Vec<Instruction>, - function_scope: ScopeId, - component_scope: ScopeId, // outermost component/hook scope, for gather_captured_context - env: &'a mut Environment, - scope_info: &'a ScopeInfo, - exception_handler_stack: Vec<BlockId>, - fbt_depth: u32, -} -``` - -**Closure patterns**: The TypeScript `enter()` method creates a new block, sets it as current, runs a closure, then restores the previous block. In Rust: - -```rust -impl<'a> HirBuilder<'a> { - fn enter(&mut self, kind: BlockKind, f: impl FnOnce(&mut Self, BlockId) -> Terminal) -> BlockId { - let wip = self.reserve(kind); - let wip_id = wip.id; - self.enter_reserved(wip, |this| f(this, wip_id)); - wip_id - } - - fn enter_reserved(&mut self, wip: WipBlock, f: impl FnOnce(&mut Self) -> Terminal) { - let prev = std::mem::replace(&mut self.current, wip); - let terminal = f(self); - let completed = std::mem::replace(&mut self.current, prev); - self.completed.insert(completed.id, BasicBlock { - kind: completed.kind, - id: completed.id, - instructions: completed.instructions, - terminal, - preds: IndexSet::new(), - phis: Vec::new(), - }); - } - - fn loop_scope<T>( - &mut self, - label: Option<String>, - continue_block: BlockId, - break_block: BlockId, - f: impl FnOnce(&mut Self) -> T, - ) -> T { - self.scopes.push(Scope::Loop { label, continue_block, break_block }); - let value = f(self); - self.scopes.pop(); - value - } -} -``` - -**Variable capture across closures**: TypeScript frequently assigns variables inside `enter()` closures that are read after: -```typescript -let callee: Place | null = null; -builder.enter('block', () => { - callee = lowerExpressionToTemporary(builder, ...); - return { kind: 'goto', ... }; -}); -// callee is used here -``` - -In Rust, this pattern is handled by returning values from the closure: -```rust -let (block_id, callee) = { - let block_id = builder.enter('block', |builder, _block_id| { - // We can't easily return extra values from enter() since it expects Terminal - // Instead, compute callee before/after enter(), or restructure - ... - }); - // Alternative: compute the value and store it on builder temporarily -}; -``` - -For cases where this is awkward, use a temporary field on the builder or restructure the code to compute the value outside the closure. The specific approach depends on the case — see the incremental implementation milestones for details. - -### 4. Source Locations - -TypeScript accesses `node.loc` directly. Rust accesses `node.base.loc` (through the `BaseNode` flattened into each AST struct). Helper: - -```rust -fn loc_from_node(base: &BaseNode) -> SourceLocation { - base.loc.as_ref().map(|l| hir::SourceLocation::from(l)).unwrap_or(GENERATED_SOURCE) -} -``` - -### 5. Error Handling - -Following the port notes: -- `CompilerError.invariant(cond, ...)` → `if !cond { panic!(...) }` or dedicated `compiler_invariant!` macro -- `CompilerError.throwTodo(...)` → `return Err(CompilerDiagnostic::todo(...))` -- `builder.recordError(...)` → `builder.record_error(...)` (accumulates on Environment) -- Non-null assertions (`!`) → `.unwrap()` or `.expect("...")` - -The `lower()` function returns `Result<HirFunction, CompilerError>` for invariant/thrown errors, while accumulated errors go to `env.errors`. - -### 6. `todo!()` Strategy for Incremental Implementation - -BuildHIR is too large (4555 lines) for a single implementation pass. Use Rust's `todo!()` macro to stub unimplemented branches: - -```rust -fn lower_statement(builder: &mut HirBuilder, stmt: &ast::Statement) { - match stmt { - ast::Statement::IfStatement(s) => lower_if_statement(builder, s), - ast::Statement::ReturnStatement(s) => lower_return_statement(builder, s), - ast::Statement::BlockStatement(s) => lower_block_statement(builder, s), - // Stubbed — will be filled in later milestones - ast::Statement::ForStatement(_) => todo!("lower ForStatement"), - ast::Statement::WhileStatement(_) => todo!("lower WhileStatement"), - ast::Statement::SwitchStatement(_) => todo!("lower SwitchStatement"), - ast::Statement::TryStatement(_) => todo!("lower TryStatement"), - // ... etc - } -} -``` - -This "fog of war" approach allows: -1. The code to compile at every step -2. Tests to run for fixtures that only use implemented features -3. Clear visibility into what remains -4. Agents to pick up individual `todo!()` arms and implement them - ---- - -## Structural Mapping: TypeScript → Rust - -### Top-Level Functions - -| TypeScript (BuildHIR.ts) | Rust (build_hir.rs) | Notes | -|---|---|---| -| `lower(func, env, bindings, capturedRefs)` | `pub fn lower(ast: &ast::File, scope_info: &ScopeInfo, env: &mut Environment) -> Result<HirFunction, CompilerError>` | Entry point. Takes the full File (extracts the function internally) | -| `lowerStatement(builder, stmtPath, label)` | `fn lower_statement(builder: &mut HirBuilder, stmt: &ast::Statement, label: Option<&str>)` | ~30 match arms | -| `lowerExpression(builder, exprPath)` | `fn lower_expression(builder: &mut HirBuilder, expr: &ast::Expression) -> InstructionValue` | ~40 match arms | -| `lowerExpressionToTemporary(builder, exprPath)` | `fn lower_expression_to_temporary(builder: &mut HirBuilder, expr: &ast::Expression) -> Place` | | -| `lowerValueToTemporary(builder, value)` | `fn lower_value_to_temporary(builder: &mut HirBuilder, value: InstructionValue) -> Place` | | -| `lowerAssignment(builder, loc, kind, target, value, assignmentStyle)` | `fn lower_assignment(builder: &mut HirBuilder, ...)` | Handles destructuring patterns | -| `lowerIdentifier(builder, exprPath)` | `fn lower_identifier(builder: &mut HirBuilder, name: &str, start: u32, loc: SourceLocation) -> Place` | | -| `lowerMemberExpression(builder, exprPath)` | `fn lower_member_expression(builder: &mut HirBuilder, expr: &ast::MemberExpression) -> InstructionValue` | | -| `lowerOptionalMemberExpression(builder, exprPath)` | `fn lower_optional_member_expression(builder: &mut HirBuilder, expr: &ast::OptionalMemberExpression) -> InstructionValue` | | -| `lowerOptionalCallExpression(builder, exprPath)` | `fn lower_optional_call_expression(builder: &mut HirBuilder, expr: &ast::OptionalCallExpression) -> InstructionValue` | | -| `lowerArguments(builder, args, isDev)` | `fn lower_arguments(builder: &mut HirBuilder, args: &[ast::Expression], is_dev: bool) -> Vec<PlaceOrSpread>` | | -| `lowerFunctionToValue(builder, expr)` | `fn lower_function_to_value(builder: &mut HirBuilder, expr: &ast::Function) -> InstructionValue` | | -| `lowerFunction(builder, expr)` | `fn lower_function(builder: &mut HirBuilder, expr: &ast::Function) -> LoweredFunction` | Recursive `lower()` call. Returns `LoweredFunction` (not `FunctionId`) | -| `lowerJsxElementName(builder, name)` | `fn lower_jsx_element_name(builder: &mut HirBuilder, name: &ast::JSXElementName) -> JsxTag` | | -| `lowerJsxElement(builder, child)` | `fn lower_jsx_element(builder: &mut HirBuilder, child: &ast::JSXChild) -> Option<Place>` | | -| `lowerObjectMethod(builder, property)` | `fn lower_object_method(builder: &mut HirBuilder, method: &ast::ObjectMethod) -> ObjectProperty` | | -| `lowerObjectPropertyKey(builder, key)` | `fn lower_object_property_key(builder: &mut HirBuilder, key: &ast::ObjectPropertyKey) -> ObjectPropertyKey` | | -| `lowerReorderableExpression(builder, expr)` | `fn lower_reorderable_expression(builder: &mut HirBuilder, expr: &ast::Expression) -> Place` | | -| `isReorderableExpression(builder, expr)` | `fn is_reorderable_expression(builder: &HirBuilder, expr: &ast::Expression) -> bool` | | -| `lowerType(node)` | `fn lower_type(node: &ast::TypeAnnotation) -> Type` | | -| `gatherCapturedContext(fn, componentScope)` | `fn gather_captured_context(func: &ast::Function, scope_info: &ScopeInfo, parent_scope: ScopeId) -> IndexMap<BindingId, Option<SourceLocation>>` | AST walk replaces Babel traverser | -| `captureScopes({from, to})` | `fn capture_scopes(scope_info: &ScopeInfo, from: ScopeId, to: ScopeId) -> IndexSet<ScopeId>` | | - -### HIRBuilder Methods - -| TypeScript (HIRBuilder.ts) | Rust (hir_builder.rs) | Notes | -|---|---|---| -| `constructor(env, options?)` | `HirBuilder::new(env, scope_info, function_scope, bindings, context, entry_block_kind)` | | -| `push(instruction)` | `builder.push(instruction)` | | -| `terminate(terminal, nextBlockKind)` | `builder.terminate(terminal, next_block_kind)` | | -| `terminateWithContinuation(terminal, continuation)` | `builder.terminate_with_continuation(terminal, continuation)` | | -| `reserve(kind)` | `builder.reserve(kind)` | Returns `WipBlock` | -| `complete(block, terminal)` | `builder.complete(block, terminal)` | | -| `enter(kind, fn)` | `builder.enter(kind, \|b, id\| { ... })` | Closure takes `&mut Self` | -| `enterReserved(wip, fn)` | `builder.enter_reserved(wip, \|b\| { ... })` | | -| `enterTryCatch(handler, fn)` | `builder.enter_try_catch(handler, \|b\| { ... })` | | -| `loop(label, continue, break, fn)` | `builder.loop_scope(label, continue_block, break_block, \|b\| { ... })` | | -| `label(label, break, fn)` | `builder.label_scope(label, break_block, \|b\| { ... })` | | -| `switch(label, break, fn)` | `builder.switch_scope(label, break_block, \|b\| { ... })` | | -| `lookupBreak(label)` | `builder.lookup_break(label)` | | -| `lookupContinue(label)` | `builder.lookup_continue(label)` | | -| `resolveIdentifier(path)` | `builder.resolve_identifier(name, start_offset)` | Uses ScopeInfo | -| `resolveBinding(node)` | `builder.resolve_binding(name, binding_id)` | Keyed by BindingId | -| `isContextIdentifier(path)` | `builder.is_context_identifier(name, start_offset)` | Uses ScopeInfo | -| `makeTemporary(loc)` | `builder.make_temporary(loc)` | | -| `build()` | `builder.build()` | Returns `(HIR, Vec<Instruction>)` — the HIR plus the flat instruction table | -| `recordError(error)` | `builder.record_error(error)` | | - -### Post-Build Helpers (HIRBuilder.ts) - -These helper functions in HIRBuilder.ts run after `build()` and clean up the CFG: - -| TypeScript | Rust | Notes | -|---|---|---| -| `getReversePostorderedBlocks(func)` | `get_reverse_postordered_blocks(hir)` | RPO sort + unreachable removal | -| `removeUnreachableForUpdates(fn)` | `remove_unreachable_for_updates(hir)` | | -| `removeDeadDoWhileStatements(func)` | `remove_dead_do_while_statements(hir)` | | -| `removeUnnecessaryTryCatch(fn)` | `remove_unnecessary_try_catch(hir)` | | -| `markInstructionIds(func)` | `mark_instruction_ids(hir)` | Assigns EvaluationOrder | -| `markPredecessors(func)` | `mark_predecessors(hir)` | Must include fallthrough blocks — verify `each_terminal_successor` matches TS `eachTerminalSuccessor` | -| `createTemporaryPlace(env, loc)` | `create_temporary_place(env, loc)` | | - -**Implementation notes for post-build helpers:** -- `remove_unnecessary_try_catch` and `remove_dead_do_while_statements`: Verify that the `GotoVariant` used when replacing terminals matches the TS equivalent. Currently uses `GotoVariant::Break` — confirm this is correct. -- `mark_predecessors`: The `each_terminal_successor` function must visit fallthrough blocks for terminals like `Try`, not just direct successors. Compare against TS `eachTerminalSuccessor` behavior. - ---- - -## Statement Lowering: Match Arm Inventory - -The `lowerStatement` function has ~30 match arms. Grouped by complexity: - -### Tier 1 — Trivial (1-10 lines each) -- `EmptyStatement` — no-op -- `DebuggerStatement` — single `Debugger` instruction -- `ExpressionStatement` — delegate to `lower_expression_to_temporary` -- `BreakStatement` — `builder.lookup_break()` + goto terminal -- `ContinueStatement` — `builder.lookup_continue()` + goto terminal -- `ThrowStatement` — lower expression + throw terminal - -### Tier 2 — Simple control flow (10-30 lines each) -- `ReturnStatement` — lower expression + return terminal -- `BlockStatement` — iterate body statements -- `IfStatement` — reserve blocks, enter consequent/alternate, branch terminal -- `WhileStatement` — test block + body block + loop scope -- `LabeledStatement` — delegate with label, or create label scope - -### Tier 3 — Complex control flow (30-100 lines each) -- `ForStatement` — init/test/update/body blocks, loop scope -- `ForOfStatement` — iterator protocol (GetIterator, IteratorNext, etc.) -- `ForInStatement` — similar to ForOf -- `DoWhileStatement` — body-first loop -- `SwitchStatement` — case discrimination with fall-through -- `TryStatement` — try/catch/finally blocks with exception handler stack - -### Tier 4 — Variable declarations and assignments (30-80 lines) -- `VariableDeclaration` — iterate declarators, handle destructuring -- `FunctionDeclaration` — hoist function, lower body - -### Tier 5 — Pass-through / error (1-10 lines each) -- TypeScript/Flow declarations — `todo!()` or skip -- Import/Export declarations — error (shouldn't appear in function body) -- `WithStatement` — error (unsupported) -- `ClassDeclaration` — lower class expression -- `EnumDeclaration` / `TSEnumDeclaration` — error - ---- - -## Expression Lowering: Match Arm Inventory - -The `lowerExpression` function has ~40 match arms. Grouped by complexity: - -### Tier 1 — Literals and simple values (1-10 lines each) -- `NullLiteral`, `BooleanLiteral`, `NumericLiteral`, `StringLiteral` — `Primitive` instruction -- `RegExpLiteral` — `RegExpLiteral` instruction -- `Identifier` — delegate to `lower_identifier` -- `MetaProperty` — `LoadGlobal` for `import.meta` -- `TSNonNullExpression`, `TSInstantiationExpression` — unwrap inner expression -- `TypeCastExpression`, `TSAsExpression`, `TSSatisfiesExpression` — unwrap inner expression - -### Tier 2 — Operators (10-30 lines each) -- `BinaryExpression` — lower operands + `BinaryExpression` instruction -- `UnaryExpression` — lower operand + `UnaryExpression` instruction -- `UpdateExpression` — read + increment + store (prefix vs postfix) -- `SequenceExpression` — lower all expressions, return last - -### Tier 3 — Object/Array construction (20-50 lines each) -- `ObjectExpression` — properties, spread, computed keys -- `ArrayExpression` — elements with holes and spreads -- `TemplateLiteral` — quasis + expressions -- `TaggedTemplateExpression` — tag + template - -### Tier 4 — Calls and member access (20-50 lines each) -- `CallExpression` — callee + arguments + `CallExpression`/`MethodCall` instruction -- `NewExpression` — similar to CallExpression -- `MemberExpression` — object + property + `PropertyLoad`/`ComputedLoad` -- `OptionalCallExpression` — optional chain with test blocks -- `OptionalMemberExpression` — optional chain with test blocks - -### Tier 5 — Control flow expressions (30-80 lines each) -- `ConditionalExpression` — if-like CFG with value blocks -- `LogicalExpression` — short-circuit evaluation with blocks -- `AssignmentExpression` — delegates to `lower_assignment` (destructuring) - -### Tier 6 — Complex (50-150 lines each) -- `JSXElement` — tag + props + children + fbt handling -- `JSXFragment` — children only -- `ArrowFunctionExpression` / `FunctionExpression` — recursive `lower_function` -- `AwaitExpression` — lower value + await instruction - ---- - -## Assignment Lowering - -`lowerAssignment` (~500 lines in BuildHIR.ts) handles destructuring and is the most complex single function after the statement/expression switches. It processes: - -### Match arms by target type: -- **`Identifier`** — `StoreLocal` instruction (with const/let/reassign distinction) -- **`MemberExpression`** — `PropertyStore` / `ComputedStore` instruction -- **`ArrayPattern`** — emit `Destructure` with `ArrayPattern` containing items, holes, rest elements, and default values -- **`ObjectPattern`** — emit `Destructure` with `ObjectPattern` containing properties, computed keys, rest elements, and default values -- **`AssignmentPattern`** — default value handling: lower the default, emit a conditional assignment - -### Rust approach: -The destructuring patterns map directly — the AST struct fields (`elements`, `properties`, `rest`) correspond to the Babel API calls. The main difference is accessing nested patterns through struct fields instead of `path.get()`. - ---- - -## Recursive Lowering for Nested Functions - -`lowerFunction()` calls `lower()` recursively for function expressions, arrow functions, and object methods. Key considerations for Rust: - -1. **Shared Environment**: Parent and child share `&mut Environment`. This works because the recursive call completes before the parent continues. - -2. **Shared Bindings**: The parent's `bindings` map is passed to the child so inner functions can resolve references to outer variables. In Rust, this is `&IndexMap<BindingId, IdentifierId>` — the parent's bindings are cloned or borrowed by the child. - -3. **Context gathering**: `gatherCapturedContext()` walks the function's AST to find free variable references. In Rust, this walks the AST structs using `ScopeInfo` to identify references that resolve to bindings in ancestor scopes (between the function's scope and the component scope). - -4. **Function arena storage**: The returned `HirFunction` is stored in `env.functions` (the function arena) and referenced by `FunctionId` in the `FunctionExpression` instruction value. - -```rust -fn lower_function(builder: &mut HirBuilder, func: &ast::Function) -> LoweredFunction { - let captured_context = gather_captured_context(func, builder.scope_info, builder.component_scope); - let lowered = lower(func, builder.scope_info, builder.env, Some(&builder.bindings), captured_context)?; - lowered -} -``` - ---- - -## Incremental Implementation Plan - -### M1: Scaffold + Infrastructure - -**Goal**: Crate structure compiles, `lower()` entry point exists, returns `todo!()`. - -1. Create `compiler/crates/react_compiler_diagnostics/` with `CompilerDiagnostic`, `CompilerError`, `ErrorCategory`, `CompilerErrorDetail`, `CompilerSuggestionOperation`. - -2. Create `compiler/crates/react_compiler_hir/` with core types: - - ID newtypes: `BlockId`, `IdentifierId`, `InstructionId` (index into the flat instruction table), `EvaluationOrder` (sequential numbering assigned during `markInstructionIds()` — this was previously called `InstructionId` in the TypeScript compiler), `DeclarationId`, `ScopeId`, `FunctionId`, `TypeId` - - `HirFunction`, `HIR`, `BasicBlock`, `WipBlock`, `BlockKind` - - `Instruction`, `InstructionValue` (enum with all ~40 variants, each stubbed as `todo!()` for fields) - - `Terminal` (enum with all variants) - - `Place`, `Identifier`, `MutableRange`, `SourceLocation` - - `Effect`, `InstructionKind`, `GotoVariant`, `BindingKind` (enum: `Var`, `Let`, `Const`, `Param`, `Using`, `AwaitUsing`, `CatchParam`, `ImplicitConst`) - - `Environment` (counters, arenas, config, errors) - - `FloatValue(u64)` — wrapper type for f64 values that need `Eq`/`Hash` (stores raw bits via `f64::to_bits()` for deterministic comparison) - -3. Create `compiler/crates/react_compiler_lowering/` with: - - `hir_builder.rs`: `HirBuilder` struct with all methods stubbed - - `build_hir.rs`: `lower_statement()` and `lower_expression()` with all arms as `todo!()` - - `lib.rs`: `pub fn lower()` that creates a builder and returns `todo!()` - -4. Verify: `cargo check` passes. - -### M2: HIRBuilder Core - -**Goal**: HIRBuilder methods work — can create blocks, terminate them, build the CFG. - -1. Implement `HirBuilder::new()`, `push()`, `terminate()`, `terminate_with_continuation()`, `reserve()`, `complete()`, `enter_reserved()`, `enter()`. - -2. Implement scope methods: `loop_scope()`, `label_scope()`, `switch_scope()`, `lookup_break()`, `lookup_continue()`. - -3. Implement `enter_try_catch()`, `resolve_throw_handler()`. - -4. Implement `make_temporary()`, `record_error()`. - -5. Implement `build()` including the post-build passes: - - `get_reverse_postordered_blocks()` - - `remove_unreachable_for_updates()` - - `remove_dead_do_while_statements()` - - `remove_unnecessary_try_catch()` - - `mark_instruction_ids()` - - `mark_predecessors()` - -### M3: Binding Resolution - -**Goal**: `resolve_identifier()` and `resolve_binding()` work with `ScopeInfo`. - -1. Implement `resolve_binding()` — maps `BindingId` to `IdentifierId`, creating new identifiers on first encounter. Uses `IndexMap<BindingId, IdentifierId>` instead of the TypeScript `Map<string, {node, identifier}>`. - -2. Implement `resolve_identifier()` — dispatches to Global, ImportDefault, ImportSpecifier, ImportNamespace, ModuleLocal, or Identifier based on `ScopeInfo` lookups. - -3. Implement `is_context_identifier()` — checks if a reference resolves to a binding in an ancestor scope. - -4. Implement `gather_captured_context()` — walks AST to find free variable references using `ScopeInfo`. - -### M4: `lower()` Entry Point + Basic Statements - -**Goal**: Can lower simple functions with `ReturnStatement`, `ExpressionStatement`, `BlockStatement`, `VariableDeclaration` (simple, non-destructuring). - -1. Implement the `lower()` function body: parameter processing, body lowering, final return terminal, `builder.build()`. - -2. Implement statement arms: - - `ReturnStatement` - - `ExpressionStatement` - - `BlockStatement` - - `EmptyStatement` - - `VariableDeclaration` (simple `let x = expr` only, destructuring as `todo!()`) - -3. Implement basic expression arms: - - `Identifier` (via `lower_identifier`) - - `NullLiteral`, `BooleanLiteral`, `NumericLiteral`, `StringLiteral` - - `BinaryExpression` - - `UnaryExpression` - -4. Implement helpers: `lower_expression_to_temporary()`, `lower_value_to_temporary()`, `build_temporary_place()`. - -5. **Test**: Run `test-rust-port.sh HIR` on simple fixtures. - -### M5: Control Flow - -**Goal**: Branches and loops work. - -1. `IfStatement` — consequent/alternate blocks, branch terminal -2. `WhileStatement` — test/body blocks, loop scope -3. `ForStatement` — init/test/update/body blocks -4. `DoWhileStatement` — body-first loop pattern -5. `BreakStatement`, `ContinueStatement` -6. `LabeledStatement` - -### M6: Expressions — Calls and Members - -**Goal**: Function calls and property access work. - -1. `CallExpression` — including method calls (callee is MemberExpression) -2. `NewExpression` -3. `MemberExpression` — PropertyLoad/ComputedLoad -4. `lower_arguments()` — spread handling -5. `SequenceExpression` - -### M7: Expressions — Short-circuit and Ternary - -**Goal**: Control-flow expressions produce correct CFG. - -1. `ConditionalExpression` — if-like structure with value blocks -2. `LogicalExpression` — short-circuit `&&`, `||`, `??` -3. `AssignmentExpression` — simple identifier/member assignment (destructuring deferred) - -### M8: Expressions — Remaining - -**Goal**: All expression types handled. - -1. `ObjectExpression` — properties, methods, computed, spread -2. `ArrayExpression` — elements, holes, spreads -3. `TemplateLiteral`, `TaggedTemplateExpression` -4. `UpdateExpression` — prefix/postfix increment/decrement -5. `RegExpLiteral` -6. `AwaitExpression` -7. `TypeCastExpression`, `TSAsExpression`, `TSSatisfiesExpression`, `TSNonNullExpression`, `TSInstantiationExpression` -8. `MetaProperty` - -### M9: Function Expressions + Recursive Lowering - -**Goal**: Nested functions work. - -1. `ArrowFunctionExpression`, `FunctionExpression` — call `lower_function()` -2. `lower_function()` — recursive `lower()` with captured context -3. `gather_captured_context()` — AST walk for free variables -4. Function arena storage via `FunctionId` -5. `FunctionDeclaration` statement — hoisted function lowering - -### M10: JSX - -**Goal**: JSX elements and fragments lower correctly. - -1. `JSXElement` — tag, props, children, fbt handling -2. `JSXFragment` — children -3. `lower_jsx_element_name()` — identifier, member expression, builtin tag dispatch -4. `lower_jsx_element()` — child lowering (text, expression, element, spread) -5. `lower_jsx_member_expression()` -6. `trimJsxText()` — whitespace normalization - -### M11: Destructuring + Complex Assignments - -**Goal**: Full destructuring support. - -1. `lower_assignment()` for `ArrayPattern` — items, holes, rest, defaults -2. `lower_assignment()` for `ObjectPattern` — properties, computed keys, rest, defaults -3. `lower_assignment()` for `AssignmentPattern` — default values -4. `VariableDeclaration` with destructuring patterns -5. Param destructuring in `lower()` entry point - -### M12: Switch + Try/Catch + Remaining - -**Goal**: All statement types handled, complete coverage. - -1. `SwitchStatement` — case discrimination, fall-through, break -2. `TryStatement` — try/catch/finally blocks, exception handler stack -3. `ForOfStatement` — iterator protocol -4. `ForInStatement` — for-in lowering -5. `WithStatement` — error -6. `ClassDeclaration` — class expression lowering -7. Type declarations — skip/pass-through -8. Import/Export declarations — error -9. `OptionalCallExpression`, `OptionalMemberExpression` — optional chaining -10. `lowerReorderableExpression()`, `isReorderableExpression()` - -### M13: Polish + Full Test Coverage - -**Goal**: All fixtures pass, no remaining `todo!()` in production paths. - -1. Remove all remaining `todo!()` stubs — replace with proper errors for truly unsupported syntax -2. Run `test-rust-port.sh HIR` on all 1714 fixtures -3. Debug and fix any divergences from TypeScript output -4. Handle edge cases: error recovery, Babel bug workarounds (where applicable), fbt depth tracking - ---- - -## Key Rust Patterns - -### Pattern 1: Switch/Case → Match - -Every `switch (stmtPath.type)` and `switch (exprPath.type)` becomes a `match` on the AST enum. Rust's exhaustive matching ensures no cases are missed (unlike TypeScript where the `default` arm might hide bugs). - -### Pattern 2: `path.get('field')` → Direct Field Access - -```typescript -// TypeScript -const test = stmt.get('test'); -const body = stmt.get('body'); -``` -```rust -// Rust -let test = &stmt.test; -let body = &stmt.body; -``` - -### Pattern 3: Type Guards → Match Arms - -```typescript -// TypeScript -if (param.isIdentifier()) { ... } -else if (param.isObjectPattern()) { ... } -``` -```rust -// Rust -match param { - ast::PatternLike::Identifier(id) => { ... } - ast::PatternLike::ObjectPattern(pat) => { ... } -} -``` - -### Pattern 4: `hasNode()` → `Option` Checks - -```typescript -// TypeScript -const alternate = stmt.get('alternate'); -if (hasNode(alternate)) { ... } -``` -```rust -// Rust -if let Some(alternate) = &stmt.alternate { ... } -``` - -### Pattern 5: Instruction Construction - -```typescript -// TypeScript -builder.push({ - id: makeInstructionId(0), - lvalue: { ...place }, - value: { kind: 'LoadGlobal', name, binding, loc }, - effects: null, - loc: exprLoc, -}); -``` -```rust -// Rust -builder.push(Instruction { - id: InstructionId(0), // renumbered by markInstructionIds - lvalue: place.clone(), - value: InstructionValue::LoadGlobal { name, binding, loc }, - effects: None, - loc: expr_loc, -}); -``` - ---- - -## Risks and Mitigations - -### Risk 1: `gatherCapturedContext()` Without Babel Traverser -**Impact**: Medium. The TypeScript version uses `fn.traverse()` to find free variable references. -**Mitigation**: Write a manual AST walker that visits all `Identifier` nodes in a function body and checks `ScopeInfo.reference_to_binding` for each one. This is simpler than Babel's traverser because we don't need the full visitor infrastructure — just recursive pattern matching over AST node types. - -### Risk 2: Variable Capture Across `enter()` Closures -**Impact**: Low-Medium. ~15-20 places in BuildHIR.ts assign variables inside `enter()` closures that are read outside. -**Mitigation**: Case-by-case restructuring. Options include: (a) returning the value from the closure via a tuple, (b) storing it on the builder temporarily, (c) restructuring to compute the value before/after the `enter()` call. Each instance is small and mechanical. - -### Risk 3: `isReorderableExpression()` Recursive Analysis -**Impact**: Low. This function deeply analyzes expressions to determine reorderability. -**Mitigation**: Direct recursive pattern matching on AST structs — actually simpler in Rust than TypeScript because there's no NodePath overhead. - -### Risk 4: Optional Chaining Lowering Complexity -**Impact**: Medium. `lowerOptionalCallExpression()` and `lowerOptionalMemberExpression()` (~250 lines combined) generate complex CFG structures with multiple blocks for null checks. -**Mitigation**: Port last (M12), after all simpler patterns are verified. The CFG generation logic maps directly — it's just verbose. - -### Risk 5: fbt/fbs Special Handling -**Impact**: Low. The fbt handling in JSXElement lowering uses Babel's `path.traverse()` for counting nested fbt tags. -**Mitigation**: Replace with a simple recursive AST walk that counts `JSXNamespacedName` nodes matching the fbt tag name. The fbtDepth counter on the builder is trivial. diff --git a/compiler/docs/rust-port/rust-port-0005-babel-plugin.md b/compiler/docs/rust-port/rust-port-0005-babel-plugin.md deleted file mode 100644 index 95432df6cb42..000000000000 --- a/compiler/docs/rust-port/rust-port-0005-babel-plugin.md +++ /dev/null @@ -1,704 +0,0 @@ -# Rust Port Step 5: Babel Plugin (`babel-plugin-react-compiler-rust`) - -## Goal - -Create a new, minimal Babel plugin package (`babel-plugin-react-compiler-rust`) that serves as a thin JavaScript shim over the Rust compiler. The JS side does only three things: - -1. **Pre-filter**: Quick name-based scan for potential React functions (capitalized or hook-like names) -2. **Invoke Rust**: Serialize the Babel AST, scope info, and resolved options to JSON; call the Rust compiler via NAPI -3. **Apply result**: Replace the program AST with the Rust-returned AST and forward logger events - -All complex logic — function detection, compilation mode decisions, directives, suppressions, gating rewrites, import insertion, outlined functions — lives in Rust. This ensures the logic is implemented once and reused across future OXC and SWC integrations. - -**Current status**: Implementation complete. All entrypoint logic ported to Rust: compile_program orchestration, shouldSkipCompilation, findFunctionsToCompile, getReactFunctionType/getComponentOrHookLike (with all name heuristics, callsHooksOrCreatesJsx, returnsNonNode, isValidComponentParams), directive parsing, suppression detection/filtering, ProgramContext (uid generation, import tracking), gating rewrites, import insertion. The actual per-function compilation (compileFn) returns a skip event pending full pipeline implementation. - -**Prerequisites**: [rust-port-0001-babel-ast.md](rust-port-0001-babel-ast.md) (complete), [rust-port-0002-scope-types.md](rust-port-0002-scope-types.md) (complete), core compilation pipeline in Rust (in progress). - ---- - -## Architecture Overview - -``` -┌─────────────────────────────────────────────────────────┐ -│ Babel │ -│ │ -│ 1. Parse source → Babel AST │ -│ 2. babel-plugin-react-compiler-rust │ -│ ┌─────────────────────────────────────────────┐ │ -│ │ JS Shim (~50 lines) │ │ -│ │ │ │ -│ │ a) Pre-filter: any capitalized/hook fns? │ │ -│ │ b) Pre-resolve: sources filter, reanimated,│ │ -│ │ isDev → serializable options │ │ -│ │ c) Extract scope tree (rust-port-0002) │ │ -│ │ d) JSON.stringify(ast, scope, options) │ │ -│ │ e) Call Rust via NAPI │ │ -│ │ f) Parse result, forward logger events │ │ -│ │ g) Replace program AST if changed │ │ -│ └──────────────┬──────────────────────────────┘ │ -│ │ JSON │ -│ ┌──────────────▼──────────────────────────────┐ │ -│ │ Rust Compiler (via napi-rs) │ │ -│ │ │ │ -│ │ - shouldSkipCompilation │ │ -│ │ - findFunctionsToCompile │ │ -│ │ (all compilation modes, directives, │ │ -│ │ forwardRef/memo, suppressions, etc.) │ │ -│ │ - compileFn (full pipeline) │ │ -│ │ - gating rewrites │ │ -│ │ - import insertion │ │ -│ │ - outlined function insertion │ │ -│ │ - panicThreshold handling │ │ -│ │ │ │ -│ │ Returns: modified AST | null + events │ │ -│ └─────────────────────────────────────────────┘ │ -│ │ -│ 3. Babel continues with modified (or original) AST │ -└─────────────────────────────────────────────────────────┘ -``` - -### Why This Split - -The guiding principle is **implement once in Rust, integrate thinly per tool**. The current TS plugin has ~1300 lines of complex entrypoint logic (`Program.ts`, `Imports.ts`, `Gating.ts`, `Suppression.ts`, `Reanimated.ts`, `Options.ts`). If this logic stayed in JS, it would need to be reimplemented for OXC and SWC integrations. By moving it all to Rust: - -- **Babel shim**: ~50 lines of JS -- **Future OXC integration**: ~50 lines of Rust (native `Traverse` trait, serialize to same JSON format) -- **Future SWC integration**: ~50 lines of Rust (native `VisitMut` trait, serialize to same JSON format) - -Each integration only needs to: (1) do a cheap pre-filter, (2) serialize AST + scope to the Babel JSON format, (3) call `compile()`, (4) apply the result. - ---- - -## Rust Public API - -The Rust compiler exposes a single entry point. This extends the existing planned API from `rust-port-notes.md` with structured results: - -```rust -/// Main entry point for the React Compiler. -/// -/// Receives a full program AST, scope information, and resolved options. -/// Returns a CompileResult containing either a modified AST or null, -/// along with structured logger events. -#[napi] -pub fn compile( - ast_json: String, - scope_json: String, - options_json: String, -) -> napi::Result<String> { - let ast: babel_ast::File = serde_json::from_str(&ast_json)?; - let scope: ScopeInfo = serde_json::from_str(&scope_json)?; - let opts: PluginOptions = serde_json::from_str(&options_json)?; - - let result = react_compiler::compile_program(ast, scope, opts); - - Ok(serde_json::to_string(&result)?) -} -``` - -### Result Type - -```rust -#[derive(Serialize)] -#[serde(tag = "kind")] -pub enum CompileResult { - /// Compilation succeeded (or no functions needed compilation). - /// `ast` is None if no changes were made to the program. - Success { - ast: Option<babel_ast::File>, - events: Vec<LoggerEvent>, - }, - /// A fatal error occurred and panicThreshold dictates it should throw. - /// The JS shim re-throws this as a CompilerError. - Error { - error: CompilerErrorInfo, - events: Vec<LoggerEvent>, - }, -} - -#[derive(Serialize)] -pub struct CompilerErrorInfo { - pub reason: String, - pub description: Option<String>, - pub details: Vec<CompilerErrorDetail>, -} -``` - -### Logger Events - -Rust returns the same structured events as the current TS compiler. The JS shim forwards them to the user-provided logger: - -```rust -#[derive(Serialize)] -#[serde(tag = "kind")] -pub enum LoggerEvent { - CompileSuccess { - fn_loc: Option<SourceLocation>, - fn_name: Option<String>, - memo_slots: u32, - memo_blocks: u32, - memo_values: u32, - pruned_memo_blocks: u32, - pruned_memo_values: u32, - }, - CompileError { - fn_loc: Option<SourceLocation>, - detail: CompilerErrorDetail, - }, - CompileSkip { - fn_loc: Option<SourceLocation>, - reason: String, - loc: Option<SourceLocation>, - }, - CompileUnexpectedThrow { - fn_loc: Option<SourceLocation>, - data: String, - }, - PipelineError { - fn_loc: Option<SourceLocation>, - data: String, - }, - // Note: Timing events are handled on the JS side (performance.mark/measure) -} -``` - ---- - -## Resolved Options - -Options that involve JS functions or runtime checks (like `sources` filter, Reanimated detection) cannot cross the NAPI boundary. The JS shim pre-resolves these before calling Rust: - -### JS-Side Resolution - -| Option | JS Resolves | Rust Receives | -|--------|------------|---------------| -| `sources` | Calls `sources(filename)` or checks string array | `should_compile: bool` | -| `enableReanimatedCheck` | Calls `pipelineUsesReanimatedPlugin()` | `enable_reanimated: bool` | -| `isDev` (for `enableResetCacheOnSourceFileChanges`) | Checks `__DEV__` / `NODE_ENV` | `is_dev: bool` | -| `logger` | Kept on JS side | Not sent (events returned instead) | - -### Serializable Options (Passed Directly to Rust) - -```typescript -// Options that serialize directly to Rust -interface RustPluginOptions { - // Pre-resolved by JS - shouldCompile: boolean; - enableReanimated: boolean; - isDev: boolean; - filename: string | null; - - // Passed through as-is - compilationMode: 'infer' | 'syntax' | 'annotation' | 'all'; - panicThreshold: 'all_errors' | 'critical_errors' | 'none'; - target: '17' | '18' | '19' | { kind: 'donotuse_meta_internal'; runtimeModule: string }; - gating: { source: string; importSpecifierName: string } | null; - dynamicGating: { source: string } | null; - noEmit: boolean; - outputMode: 'ssr' | 'client' | 'lint' | null; - eslintSuppressionRules: string[] | null; - flowSuppressions: boolean; - ignoreUseNoForget: boolean; - customOptOutDirectives: string[] | null; - environment: EnvironmentConfig; -} -``` - ---- - -## JS Shim: `babel-plugin-react-compiler-rust` - -### Package Structure - -``` -compiler/packages/babel-plugin-react-compiler-rust/ - package.json - tsconfig.json - src/ - index.ts # Babel plugin entry point (main export) - BabelPlugin.ts # Program visitor, pre-filter, bridge call - prefilter.ts # Name-based React function detection - bridge.ts # NAPI invocation, JSON serialization - scope.ts # Babel scope → ScopeInfo extraction (from rust-port-0002) - options.ts # Option resolution (pre-resolve JS-only options) -``` - -### `BabelPlugin.ts` — Babel Plugin Entry Point - -```typescript -import type * as BabelCore from '@babel/core'; -import {hasReactLikeFunctions} from './prefilter'; -import {compileWithRust} from './bridge'; -import {extractScopeInfo} from './scope'; -import {resolveOptions, type PluginOptions} from './options'; - -export default function BabelPluginReactCompilerRust( - _babel: typeof BabelCore, -): BabelCore.PluginObj { - return { - name: 'react-compiler-rust', - visitor: { - Program: { - enter(prog, pass): void { - const filename = pass.filename ?? null; - - // Step 1: Resolve options (pre-resolve JS-only values) - const opts = resolveOptions(pass.opts, pass.file, filename); - - // Step 2: Quick bail — should we compile this file at all? - if (!opts.shouldCompile) { - return; - } - - // Step 3: Pre-filter — any potential React functions? - if (!hasReactLikeFunctions(prog)) { - return; - } - - // Step 4: Extract scope info - const scopeInfo = extractScopeInfo(prog); - - // Step 5: Call Rust compiler - const result = compileWithRust( - prog.node, - scopeInfo, - opts, - pass.file.ast.comments ?? [], - ); - - // Step 6: Forward logger events - if (pass.opts.logger && result.events) { - for (const event of result.events) { - pass.opts.logger.logEvent(filename, event); - } - } - - // Step 7: Handle result - if (result.kind === 'error') { - // panicThreshold triggered — throw - const err = new Error(result.error.reason); - // Attach details for CompilerError compatibility - (err as any).details = result.error.details; - throw err; - } - - if (result.ast != null) { - // Replace the entire program body with Rust's output - prog.replaceWith(result.ast); - prog.skip(); // Don't re-traverse - } - }, - }, - }, - }; -} -``` - -### `prefilter.ts` — Name-Based Pre-Filter - -The pre-filter is intentionally loose. It checks only whether any function in the program has a name that *could* be a React component or hook. False positives (like `ParseURL` or `FormatDate`) are acceptable — Rust will quickly determine these aren't React functions and return `null`. - -```typescript -import {NodePath} from '@babel/core'; -import * as t from '@babel/types'; - -/** - * Quick check: does this program contain any functions with names that - * could be React components (capitalized) or hooks (useXxx)? - * - * This is intentionally loose — Rust handles the precise detection. - * We just want to avoid serializing files that definitely have no - * React functions (e.g., pure utility modules, CSS-in-JS, configs). - */ -export function hasReactLikeFunctions( - program: NodePath<t.Program>, -): boolean { - let found = false; - program.traverse({ - // Skip classes — their methods are not compiled - ClassDeclaration(path) { path.skip(); }, - ClassExpression(path) { path.skip(); }, - - FunctionDeclaration(path) { - if (found) return; - const name = path.node.id?.name; - if (name && isReactLikeName(name)) { - found = true; - path.stop(); - } - }, - FunctionExpression(path) { - if (found) return; - const name = inferFunctionName(path); - if (name && isReactLikeName(name)) { - found = true; - path.stop(); - } - }, - ArrowFunctionExpression(path) { - if (found) return; - const name = inferFunctionName(path); - if (name && isReactLikeName(name)) { - found = true; - path.stop(); - } - }, - }); - return found; -} - -function isReactLikeName(name: string): boolean { - return /^[A-Z]/.test(name) || /^use[A-Z0-9]/.test(name); -} - -/** - * Infer the name of an anonymous function expression from its parent - * (e.g., `const Foo = () => {}` → 'Foo'). - */ -function inferFunctionName( - path: NodePath<t.FunctionExpression | t.ArrowFunctionExpression>, -): string | null { - const parent = path.parentPath; - if ( - parent.isVariableDeclarator() && - parent.get('init').node === path.node && - parent.get('id').isIdentifier() - ) { - return (parent.get('id').node as t.Identifier).name; - } - if ( - parent.isAssignmentExpression() && - parent.get('right').node === path.node && - parent.get('left').isIdentifier() - ) { - return (parent.get('left').node as t.Identifier).name; - } - return null; -} -``` - -### `bridge.ts` — NAPI Bridge - -```typescript -// The napi-rs generated binding -import {compile as rustCompile} from '../native'; - -import type {ResolvedOptions} from './options'; -import type {ScopeInfo} from './scope'; -import type * as t from '@babel/types'; - -export interface CompileSuccess { - kind: 'success'; - ast: t.Program | null; - events: Array<LoggerEvent>; -} - -export interface CompileError { - kind: 'error'; - error: { - reason: string; - description?: string; - details: Array<unknown>; - }; - events: Array<LoggerEvent>; -} - -export type CompileResult = CompileSuccess | CompileError; - -export type LoggerEvent = { - kind: string; - [key: string]: unknown; -}; - -export function compileWithRust( - ast: t.Program, - scopeInfo: ScopeInfo, - options: ResolvedOptions, - comments: Array<t.Comment>, -): CompileResult { - // Attach comments to the AST for Rust (Babel stores them separately) - const astWithComments = {...ast, comments}; - - const resultJson = rustCompile( - JSON.stringify(astWithComments), - JSON.stringify(scopeInfo), - JSON.stringify(options), - ); - - return JSON.parse(resultJson) as CompileResult; -} -``` - -### `options.ts` — Option Resolution - -```typescript -import type * as BabelCore from '@babel/core'; -import { - pipelineUsesReanimatedPlugin, - injectReanimatedFlag, -} from './reanimated'; // Thin copy or import from existing - -export interface ResolvedOptions { - // Pre-resolved by JS - shouldCompile: boolean; - enableReanimated: boolean; - isDev: boolean; - filename: string | null; - - // Pass-through - compilationMode: string; - panicThreshold: string; - target: unknown; - gating: unknown; - dynamicGating: unknown; - noEmit: boolean; - outputMode: string | null; - eslintSuppressionRules: string[] | null; - flowSuppressions: boolean; - ignoreUseNoForget: boolean; - customOptOutDirectives: string[] | null; - environment: Record<string, unknown>; -} - -export type PluginOptions = Partial<ResolvedOptions> & Record<string, unknown>; - -export function resolveOptions( - rawOpts: PluginOptions, - file: BabelCore.BabelFile, - filename: string | null, -): ResolvedOptions { - // Resolve sources filter (may be a function) - let shouldCompile = true; - if (rawOpts.sources != null && filename != null) { - if (typeof rawOpts.sources === 'function') { - shouldCompile = rawOpts.sources(filename); - } else if (Array.isArray(rawOpts.sources)) { - shouldCompile = rawOpts.sources.some( - (prefix: string) => filename.indexOf(prefix) !== -1, - ); - } - } else if (rawOpts.sources != null && filename == null) { - shouldCompile = false; // sources specified but no filename - } - - // Resolve reanimated check - const enableReanimated = - (rawOpts.enableReanimatedCheck !== false) && - pipelineUsesReanimatedPlugin(file.opts.plugins); - - // Resolve isDev - const isDev = - (typeof __DEV__ !== 'undefined' && __DEV__ === true) || - process.env['NODE_ENV'] === 'development'; - - return { - shouldCompile, - enableReanimated, - isDev, - filename, - compilationMode: rawOpts.compilationMode ?? 'infer', - panicThreshold: rawOpts.panicThreshold ?? 'none', - target: rawOpts.target ?? '19', - gating: rawOpts.gating ?? null, - dynamicGating: rawOpts.dynamicGating ?? null, - noEmit: rawOpts.noEmit ?? false, - outputMode: rawOpts.outputMode ?? null, - eslintSuppressionRules: rawOpts.eslintSuppressionRules ?? null, - flowSuppressions: rawOpts.flowSuppressions ?? true, - ignoreUseNoForget: rawOpts.ignoreUseNoForget ?? false, - customOptOutDirectives: rawOpts.customOptOutDirectives ?? null, - environment: rawOpts.environment ?? {}, - }; -} -``` - ---- - -## What Rust Implements (from `Program.ts` and friends) - -The following logic moves entirely from the TS entrypoint into Rust. Rust operates on the deserialized Babel AST and scope info, and returns a modified AST. - -### From `Program.ts` - -| Function | What It Does | Rust Module | -|----------|-------------|-------------| -| `shouldSkipCompilation` | Check sources filter (pre-resolved), check for existing `c` import from runtime module | `entrypoint/program.rs` | -| `findFunctionsToCompile` | Traverse program, skip classes, apply compilation mode, call `getReactFunctionType` | `entrypoint/program.rs` | -| `getReactFunctionType` | Determine if a function is Component/Hook/Other based on compilation mode, names, directives | `entrypoint/program.rs` | -| `getComponentOrHookLike` | Name-based heuristics + `callsHooksOrCreatesJsx` + `isValidComponentParams` + `returnsNonNode` + `isForwardRefCallback` + `isMemoCallback` | `entrypoint/program.rs` | -| `processFn` | Per-function: check directives (opt-in/opt-out), compile, check output mode | `entrypoint/program.rs` | -| `tryCompileFunction` | Check suppressions, call `compileFn`, handle errors | `entrypoint/program.rs` | -| `applyCompiledFunctions` | Replace original functions with compiled versions, handle gating, insert outlined functions | `entrypoint/program.rs` | -| `createNewFunctionNode` | Build replacement AST node matching original function type | `entrypoint/program.rs` | -| `handleError` / `logError` | Apply panicThreshold, log to events | `entrypoint/program.rs` | - -### From `Imports.ts` - -| Function | What It Does | Rust Module | -|----------|-------------|-------------| -| `ProgramContext` | Track compiled functions, generate unique names, manage imports | `entrypoint/imports.rs` | -| `addImportsToProgram` | Insert import declarations (or require calls) into program body | `entrypoint/imports.rs` | -| `validateRestrictedImports` | Check for blocklisted import modules | `entrypoint/imports.rs` | - -### From `Gating.ts` - -| Function | What It Does | Rust Module | -|----------|-------------|-------------| -| `insertGatedFunctionDeclaration` | Rewrite function with gating conditional (optimized vs unoptimized) | `entrypoint/gating.rs` | -| `insertAdditionalFunctionDeclaration` | Handle hoisted function declarations referenced before declaration | `entrypoint/gating.rs` | - -### From `Suppression.ts` - -| Function | What It Does | Rust Module | -|----------|-------------|-------------| -| `findProgramSuppressions` | Parse eslint-disable/enable and Flow suppression comments | `entrypoint/suppression.rs` | -| `filterSuppressionsThatAffectFunction` | Check if suppression ranges overlap a function | `entrypoint/suppression.rs` | -| `suppressionsToCompilerError` | Convert suppressions to compiler errors | `entrypoint/suppression.rs` | - -### From `Reanimated.ts` - -| Function | What It Does | Rust Module | -|----------|-------------|-------------| -| `injectReanimatedFlag` | Set `enableCustomTypeDefinitionForReanimated` in environment config | Pre-resolved by JS; Rust receives `enableReanimated: bool` | -| `pipelineUsesReanimatedPlugin` | Check if reanimated babel plugin is present | Pre-resolved by JS | - -### From `Options.ts` - -| Function | What It Does | Rust Module | -|----------|-------------|-------------| -| `parsePluginOptions` | Validate and parse plugin options | JS resolves, Rust re-validates serializable subset | -| Option types and schemas | Zod schemas for options | Rust serde types with validation | -| `LoggerEvent` types | Event type definitions | Rust enum (serialized back to JS) | - ---- - -## NAPI Bridge Details - -### Technology: napi-rs - -The bridge uses [napi-rs](https://napi.rs/) to expose the Rust `compile` function to Node.js. This is the same approach used by SWC (`@swc/core`), Biome, and other Rust-based JS tools. - -### Serialization: JSON Strings - -The bridge passes JSON strings across the NAPI boundary. This is the simplest approach and provides several benefits: - -- **Debuggable**: JSON can be logged, inspected, and round-trip tested -- **Consistent with existing infrastructure**: The `react_compiler_ast` crate already handles JSON serde with all 1714 test fixtures passing -- **No schema coupling**: The JS side doesn't need generated bindings — just `JSON.stringify`/`JSON.parse` -- **Adequate performance**: For file-level granularity (one call per file), JSON serialization overhead is negligible compared to compilation time - -### Performance Considerations - -The JSON serialization adds overhead, but it is bounded: - -- **Serialization**: `JSON.stringify` of a typical program AST: ~1-5ms -- **Deserialization in Rust**: `serde_json::from_str`: ~1-5ms -- **Re-serialization in Rust**: `serde_json::to_string` of result: ~1-5ms -- **Parse in JS**: `JSON.parse` of result: ~1-5ms -- **Total overhead**: ~4-20ms per file -- **Compilation time**: Typically 50-500ms per file - -The serialization overhead is 2-10% of total time. If this becomes a bottleneck, a future optimization could use `Buffer` passing with a binary format, but JSON is the right starting point. - -### Native Module Structure - -``` -compiler/packages/babel-plugin-react-compiler-rust/ - native/ - Cargo.toml # napi-rs crate - src/ - lib.rs # #[napi] compile function - build.rs # napi-rs build script - npm/ # Platform-specific npm packages (generated by napi-rs) - darwin-arm64/ - darwin-x64/ - linux-x64-gnu/ - win32-x64-msvc/ - ... -``` - ---- - -## What Stays in JS vs What Moves to Rust - -### JS Side (Thin Shim) - -| Responsibility | Reason it stays in JS | -|---------------|----------------------| -| Pre-filter (name-based scan) | Avoids serialization for files with no React functions | -| Resolve `sources` filter | May be a JS function (not serializable) | -| Resolve Reanimated check | Requires `require.resolve` and Babel plugin list inspection | -| Resolve `isDev` | Requires `process.env` / `__DEV__` access | -| Extract scope info | Requires Babel scope API | -| Serialize AST/scope/options | Bridge responsibility | -| Forward logger events | Logger is a JS callback | -| Throw on fatal errors | JS exception mechanism | -| Replace program AST | Babel `path.replaceWith` API | -| Performance timing | `performance.mark/measure` API | - -### Rust Side (Everything Else) - -| Responsibility | Current TS Location | -|---------------|-------------------| -| `shouldSkipCompilation` (non-sources checks) | `Program.ts:782-816` | -| `findFunctionsToCompile` | `Program.ts:495-559` | -| `getReactFunctionType` | `Program.ts:818-864` | -| `getComponentOrHookLike` | `Program.ts:1049-1078` | -| All name/param/return heuristics | `Program.ts:897-1164` | -| `forwardRef`/`memo` detection | `Program.ts:951-970` | -| Directive parsing (`use memo`, `use no memo`, `use memo if(...)`) | `Program.ts:47-144` | -| Suppression detection and filtering | `Suppression.ts` (all) | -| Per-function compilation (`compileFn`) | `Pipeline.ts` | -| Gating rewrites | `Gating.ts` (all) | -| Import generation and insertion | `Imports.ts:225-306` | -| Outlined function insertion | `Program.ts:283-329` | -| `ProgramContext` (uid gen, import tracking) | `Imports.ts:64-209` | -| Error handling / panicThreshold | `Program.ts:146-222` | -| Option validation | `Options.ts:324-403` | - ---- - -## Cross-Tool Strategy (OXC, SWC) - -This architecture is designed to support future OXC and SWC integrations with minimal per-tool code. - -### Common Boundary: Babel JSON AST - -All integrations serialize to the same Babel JSON AST format that the `react_compiler_ast` crate expects. This means: - -- **OXC integration**: A Rust transform that converts OXC's native AST → Babel JSON AST → calls `compile()` → converts result back to OXC AST. Since both are Rust, this can use the struct types directly (no JSON step needed for the Rust→Rust path — just type conversion). -- **SWC integration**: A Rust transform (native or WASM plugin) that converts SWC's AST → Babel JSON AST → calls `compile()` → converts result back. - -### Scope Abstraction - -Each tool provides scope information differently: -- **Babel**: Scope tree object graph (extracted by JS, serialized to `ScopeInfo`) -- **OXC**: `ScopeTree` + `SymbolTable` from `oxc_semantic` (Rust-native, converted to `ScopeInfo`) -- **SWC**: Hygiene system (`SyntaxContext`/`Mark`) — requires building a scope tree equivalent - -The `ScopeInfo` type from `rust-port-0002` serves as the common abstraction. Each integration extracts its tool's scope model into this format. - -### Integration Size Comparison - -| Tool | Integration Code | Where Logic Lives | -|------|-----------------|-------------------| -| Babel (this doc) | ~50 lines JS + NAPI bridge | Rust | -| OXC (future) | ~100 lines Rust (AST conversion) | Rust | -| SWC (future) | ~100 lines Rust (AST conversion + scope extraction) | Rust | - ---- - -## Differences from Current TS Plugin - -### Behavioral Equivalence - -The Rust plugin must produce identical output to the TS plugin for all inputs. The existing test infrastructure (`yarn snap`) can be used to verify this by running both plugins on the same fixtures and comparing output. - -### Known Differences - -1. **Timing events**: Handled on the JS side using `performance.mark/measure` (not sent to Rust). The JS shim wraps the Rust call with timing markers. - -2. **`CompilerError` class**: Rust returns a plain JSON error object. The JS shim constructs a `CompilerError`-compatible exception for Babel's error reporting. - -3. **`debugLogIRs` logger callback**: This optional callback receives intermediate compiler pipeline values. Rust would need to serialize these if supported. **Decision**: Defer to a follow-up; not needed for initial parity. - -4. **Comments handling**: Babel stores comments separately on `file.ast.comments`, not attached to AST nodes. The JS shim attaches comments to the program AST before serializing. Rust uses them for suppression detection. diff --git a/compiler/docs/rust-port/rust-port-architecture.md b/compiler/docs/rust-port/rust-port-architecture.md deleted file mode 100644 index 2a1b64beaafc..000000000000 --- a/compiler/docs/rust-port/rust-port-architecture.md +++ /dev/null @@ -1,156 +0,0 @@ -# Rust Port: Architecture Guide - -Reference for key data structures, patterns, and constraints in the Rust compiler port. See `rust-port-research.md` for detailed per-pass analysis and `rust-port-notes.md` for the original design decisions. - -## Arenas and ID Types - -All shared mutable data is stored in arenas on `Environment`, referenced by copyable ID types. This replaces JavaScript's shared object references. - -| Arena | ID Type | Stored On | Replaces | -|-------|---------|-----------|----------| -| `identifiers: Vec<Identifier>` | `IdentifierId` | `Environment` | Shared `Identifier` object references across `Place` values | -| `scopes: Vec<ReactiveScope>` | `ScopeId` | `Environment` | Shared `ReactiveScope` references across identifiers | -| `functions: Vec<HIRFunction>` | `FunctionId` | `Environment` | Inline `HIRFunction` on `FunctionExpression`/`ObjectMethod` | -| `types: Vec<Type>` | `TypeId` | `Environment` | Inline `Type` on `Identifier` | - -All ID types are `Copy + Clone + Hash + Eq + PartialEq` newtypes wrapping `u32`. - -## Instructions and EvaluationOrder - -- `HirFunction.instructions: Vec<Instruction>` — flat instruction table -- `BasicBlock.instructions: Vec<InstructionId>` — indices into the table above -- The old TypeScript `InstructionId` is renamed to `EvaluationOrder` — it represents evaluation order and appears on both instructions and terminals -- The new `InstructionId` is an index into `HirFunction.instructions`, giving passes a single copyable ID to reference any instruction - -## Place is Clone, MutableRange is on Identifier/Scope - -`Place` stores an `IdentifierId` (not a shared reference), making it small and cheap to clone. Mutation of `mutable_range` goes through the identifier arena: - -```rust -env.identifiers[place.identifier].mutable_range.end = new_end; -``` - -After `InferReactiveScopeVariables`, an identifier's effective mutable range is its scope's range. Downstream passes access this through the scope arena: - -```rust -let range = match env.identifiers[id].scope { - Some(scope_id) => env.scopes[scope_id].range, - None => env.identifiers[id].mutable_range, -}; -``` - -## Function Arena and FunctionId - -`FunctionExpression` and `ObjectMethod` instruction values store a `FunctionId` instead of an inline `HIRFunction`. Inner functions are accessed via the arena: - -```rust -let inner = &env.functions[function_id]; // read -let inner = &mut env.functions[function_id]; // write -``` - -This makes `CreateFunction` aliasing effects store `FunctionId`, and function signature caches key by `FunctionId`. - -## AliasingEffect - -Effects own cloned `Place` values (cheap since `Place` contains `IdentifierId`). Key variants: - -- `Apply` — clones the args `Vec<PlaceOrSpreadOrHole>` from the instruction value -- `CreateFunction` — stores `FunctionId` (not the `FunctionExpression` itself), plus cloned `captures: Vec<Place>` - -Effect interning uses content hashing. The interned `EffectId` serves as both dedup key and allocation-site identity for abstract interpretation in `InferMutationAliasingEffects`. - -## Environment: Separate from HirFunction - -`HirFunction` does not store `env`. Passes receive `env: &mut Environment` as a separate parameter. Fields are flat (no sub-structs) to allow precise sliced borrows: - -```rust -// Simultaneous borrow of different fields is fine: -let id = &env.identifiers[some_id]; -let scope = &env.scopes[some_scope_id]; -``` - -## Ordered Maps - -Use `IndexMap`/`IndexSet` (from the `indexmap` crate) wherever the TypeScript uses `Map`/`Set` and iteration order matters. The primary case is `HIR.blocks: IndexMap<BlockId, BasicBlock>` which maintains reverse postorder. - -## Side Maps - -Side maps fall into four categories: - -1. **ID-only maps** — `HashMap<IdType, T>` / `HashSet<IdType>`. No borrow issues. Most passes use this. -2. **Reference-identity maps** — TypeScript `Map<Identifier, T>` becomes `HashMap<IdentifierId, T>`. Similarly `DisjointSet<Identifier>` becomes `DisjointSet<IdentifierId>`, `DisjointSet<ReactiveScope>` becomes `DisjointSet<ScopeId>`. -3. **Instruction/value reference maps** — Store `InstructionId` or `FunctionId` instead of references. Access the actual data through the instruction table or function arena when needed. -4. **Scope reference sets with mutation** — Store `ScopeId` in sets. Mutate through the arena: `env.scopes[scope_id].range.start = new_start`. - -When a pass needs to both iterate over data and mutate the HIR, use two-phase collect/apply: collect IDs or updates into a `Vec`, then apply mutations in a second loop. - -## Error Handling - -| TypeScript Pattern | Rust Approach | -|---|---| -| Non-null assertion (`!`) | `.unwrap()` (panic) | -| `CompilerError.invariant()`, `CompilerError.throwTodo()`, `throw ...` | Return `Err(CompilerDiagnostic)` via `Result` | -| `env.recordError()` or `pushDiagnostic()` with an invariant error | Return `Err(CompilerDiagnostic)` | -| `env.recordError()` or `pushDiagnostic()` with a NON invariant error | Keep as-is — accumulate on `Environment` | - -Preserve full error details: reason, description, location, suggestions, category. - -## JS→Rust Boundary - -The JS side serializes the Babel AST and Babel's scope information (scope tree, bindings, reference-to-binding map) to Rust. Keep this serialization thin: only send the core data structures that Babel already computed during parsing. Any derived analysis — identifier source locations, JSX classification, captured variables, etc. — should be computed on the Rust side by walking the AST. See `scope.ts`. - -## Pipeline and Pass Structure - -```rust -fn compile(ast: Ast, scope: Scope, env: &mut Environment) - -> Result<CompileResult, CompilerDiagnostic> -{ - let mut hir = lower(ast, scope, env)?; - some_pass(&mut hir, env)?; - // ... - let ast = codegen(...)?; - - if env.has_errors() { - Ok(CompileResult::Failure(env.take_errors())) - } else { - Ok(CompileResult::Success(ast)) - } -} -``` - -Pass signatures follow these patterns: - -```rust -// Most passes: mutable HIR + mutable environment -fn pass(func: &mut HirFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic>; - -// Passes that don't need env -fn pass(func: &mut HirFunction); - -// Validation passes: read-only HIR, env for error recording -fn validate(func: &HirFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic>; -``` - -Use `?` to propagate errors that would have thrown or short-circuited in TypeScript. Non-fatal errors are accumulated on `env` and checked at the end via `env.has_errors()`. - -## Structural Similarity to TypeScript - -Target ~85-95% structural correspondence. A developer should be able to view TypeScript and Rust side-by-side and trace the logic. The ported code should preserve: - -- **Same high-level data flow** through the code. Only deviate where strictly necessary due to data model differences (arenas, borrow checker workarounds, etc.). -- **Same grouping of types, functions, and "classes" (structs with methods) into files.** A TypeScript file maps to a Rust file with the same logical contents. -- **Similar filenames, type names, and identifier names**, adjusted for Rust naming conventions (`camelCase` -> `snake_case` for functions/variables, `PascalCase` preserved for types). -- **Crate structure**: The monolithic `babel-plugin-react-compiler` package is split into crates, roughly 1:1 by top-level folder (e.g., `src/HIR/` -> a crate, `src/Inference/` -> a crate, etc.). We split the lowering logic (BuildHIR and HIRBuilder) into react_compiler_lowering bc of its complexity. - -Key mechanical translations: - -| TypeScript | Rust | -|---|---| -| `switch (value.kind)` | `match &value` (exhaustive) | -| `Map<Identifier, T>` | `HashMap<IdentifierId, T>` | -| `for...of` with `Set.delete()` | `set.retain(\|x\| ...)` | -| `instr.value = { kind: 'X', ... }` | `std::mem::replace` + reconstruct | -| `{ ...place, effect: Effect.Read }` | `Place { effect: Effect::Read, ..place.clone() }` | -| `array.filter(x => ...)` | `vec.retain(\|x\| ...)` | -| `identifier.mutableRange.end = x` | `env.identifiers[id].mutable_range.end = x` | -| Builder closures setting outer variables | Return values from closures | \ No newline at end of file diff --git a/compiler/docs/rust-port/rust-port-gap-analysis.md b/compiler/docs/rust-port/rust-port-gap-analysis.md deleted file mode 100644 index 9e220b22434b..000000000000 --- a/compiler/docs/rust-port/rust-port-gap-analysis.md +++ /dev/null @@ -1,122 +0,0 @@ -# Rust Port Gap Analysis - -Comprehensive comparison of the TypeScript and Rust compiler implementations. -Generated 2026-03-30 from systematic review of all major subsystems. - -Current test status: Pass 1717/1717, Code 1716/1717, Snap 1717/1718. - ---- - -## Critical Gaps (incorrect compilation possible) - -### 3. Hardcoded `useMemoCache` identifier name -- **TS**: `ReactiveScopes/CodegenReactiveFunction.ts:166-178` - ```typescript - const useMemoCacheIdentifier = fn.env.programContext.addMemoCacheImport().name; - ``` -- **Rust**: `react_compiler_reactive_scopes/src/codegen_reactive_function.rs:179` - ```rust - callee: Box::new(Expression::Identifier(make_identifier("useMemoCache"))), - ``` -- The TS dynamically resolves the `useMemoCache` import to `_c` (from `react/compiler-runtime` import specifier `c`). The Rust hardcodes `"useMemoCache"`. The BabelPlugin.ts wrapper handles the rename to `_c` during AST application, so this works in practice, but it means the codegen output has an incorrect intermediate identifier. - - ---- - -## Moderate Gaps (feature gaps or edge cases) - -### 6. Missing `optimizeForSSR` pass -- **TS**: `Entrypoint/Pipeline.ts:223-226` -- **Rust**: MISSING entirely -- Inlines useState/useReducer, removes effects, strips event handlers from JSX, removes refs. Without it, SSR-mode compilation produces unoptimized output. - -### 7. Missing `enableForest` codegen path -- **TS**: `ReactiveScopes/CodegenReactiveFunction.ts:1527-1536` -- **Rust**: MISSING -- Skips hook guard wrapping and emits `typeArguments` on call expressions. Missing in Rust means forest mode has incorrect hook wrapping and dropped type arguments. - -### 8. Function name inference from AssignmentExpression and Property -- **TS**: `Entrypoint/Program.ts:1226-1268` — three cases: `parent.isAssignmentExpression()`, `parent.isProperty()`, `parent.isAssignmentPattern()` -- **Rust**: `program.rs:1483-1488` — only handles `VariableDeclarator` -- Functions in `Foo = () => {}`, `{useFoo: () => {}}`, or `{useFoo = () => {}} = {}` positions are nameless in Rust, preventing component/hook detection via name heuristics. - -### 9. Missing validations in outlined function pipeline -- **TS**: Outlined functions go through full `compileFn` → all validations -- **Rust**: `run_pipeline_passes` skips: `validateContextVariableLValues`, `validateUseMemo`, `validateNoDerivedComputationsInEffects/_exp`, `validateNoSetStateInEffects`, `validateNoJSXInTryStatement`, `validateNoCapitalizedCalls`, `validateStaticComponents` -- Also missing: `has_errors()` check at end, `memo_cache_import` registration - -### 10. Reanimated flag injection missing -- **TS**: `Babel/BabelPlugin.ts:48-53` — `injectReanimatedFlag(opts)` sets `enableCustomTypeDefinitionForReanimated = true` -- **Rust**: Detects reanimated but doesn't inject the flag into environment config -- Custom type definitions for reanimated shared values won't activate. - -### 11. Dev-mode `enableResetCacheOnSourceFileChanges` injection missing -- **TS**: `Babel/BabelPlugin.ts:54-65` — auto-enables in dev mode -- **Rust**: MISSING -- Fast refresh cache-reset code won't generate in dev mode unless explicitly configured. - -### 12. Outlined functions not re-queued for compilation -- **TS**: `Entrypoint/Program.ts:476-501` — outlined functions with a React function type are pushed back into the compilation queue -- **Rust**: `program.rs:2244-2262` — only does AST insertion -- Outlined functions don't receive full compilation treatment (memoization). - -### 13. Missing `addNewReference` in RenameVariables -- **TS**: `ReactiveScopes/RenameVariables.ts:163` — `this.#programContext.addNewReference(name)` -- **Rust**: MISSING -- Newly created variable names aren't registered with the program context, risking import binding conflicts. - -### 14. `known_incompatible` not checked for legacy signatures without aliasing config -- **TS**: `Inference/InferMutationAliasingEffects.ts:2351-2370` -- **Rust**: `infer_mutation_aliasing_effects.rs:2099-2100` — TODO comment, only checked in the `Apply` path with aliasing configs -- If any legacy signatures (without aliasing configs) have `known_incompatible` set, Rust silently continues. - ---- - -## Minor Gaps (cosmetic, defensive, or unlikely to trigger) - -### 15. Missing `assertValidMutableRanges` pass -- **TS**: `Pipeline.ts:246-249` — gated behind `config.assertValidMutableRanges` (defaults false) -- **Rust**: Config flag exists but pass never called -- Debugging-only validation, no production impact. - -### 16. Missing `ValidateNoDerivedComputationsInEffects_exp` experimental variant -- **TS**: 842-line experimental validation pass -- **Rust**: MISSING — only the non-experimental version is ported -- Only affects users who explicitly enable the experimental flag in lint mode. - -### 17. Missing `CompileUnexpectedThrow` event -- **TS**: `Program.ts:755-769` — logs when a pass incorrectly throws -- **Rust**: Event type defined but never emitted -- Development-time detection of misbehaving passes. - -### 18. Missing error for `sources`-specified-without-filename -- **TS**: Creates a Config error via `handleError` -- **Rust**: Silently sets `shouldCompile = false` - -### 19. Missing `codegen_block` temporary invariant check -- **TS**: `CodegenReactiveFunction.ts:474-492` — verifies no temporary was overwritten -- **Rust**: Restores snapshot without checking - -### 20. Extra `NullLiteralTypeAnnotation` rejection -- **Rust** rejects Flow `null` type annotation on first param (over-conservative vs TS) - -### 21. `UnsignedShiftRight` (`>>>`) not classified as primitive binary op -- **Rust**: `infer_types.rs:140-159` — missing from `is_primitive_binary_op` -- Operands won't be constrained to Primitive type, but result is still Primitive. - -### 22. Post-dominator frontier not cached in InferReactivePlaces -- **Rust**: Recomputes frontier on every call instead of caching per block ID -- Performance issue only, not correctness. - -### 23. `Math.random` missing `restParam` -- **TS**: `restParam: Effect.Read` -- **Rust**: `rest_param: None` (uses default `..Default::default()`) -- Affects extra-argument fallback effect only. - -### 24. `WeakSet.has` / `WeakMap.has` wrong signature shape -- **TS**: `positionalParams: [Effect.Read], restParam: null` -- **Rust**: Uses `pure_primitive_fn` → `positional_params: [], rest_param: Some(Effect::Read)` -- Difference in extra-argument fallback behavior only. - -### 25. Missing `throwUnknownException__testonly` in outlined function pipeline -- Test-only feature. diff --git a/compiler/docs/rust-port/rust-port-notes.md b/compiler/docs/rust-port/rust-port-notes.md deleted file mode 100644 index 6ca4626aa4cb..000000000000 --- a/compiler/docs/rust-port/rust-port-notes.md +++ /dev/null @@ -1,104 +0,0 @@ -## Input/Output Format: JSON AST and Scope Tree - -* Define a Rust representation of the Babel AST format using serde with custom serialization/deserialization in order to ensure that we always produce the "type" field, even outside of enum positions. Include full information from Babel, including source locations. -* Define a Scope type that encodes the tree of scope information, mapping to the information that babel represents in its own scope tree - -The main public API is roughly `compile(BabelAst, Scope) -> Option<BabelAst>` returning None if no changes, or Some with the updated ast. - -## Arenas - -Use arenas and Copy-able "id" values that index into the arenas in order to migrate "shared" mutable references. - -* `Identifier`: - * Table on Environment, stores actual Identifier values - * `Place.identifier` references indirectly via `IdentifierId` -* `ReactiveScope`: - * Table on Environment, stores actual ReactiveScope values - * `Identifier`, scope terminals, etc reference indirectly via `ScopeID` -* `Function`: - * Table on Environment, stores the inner HirFunction values - * `InstructionValue::FunctionExpression` and `::ObjectMethod` reference indirectly via `FunctionId` -* `Type`: - * Table on Environment, stores actual types - * `Identifier` types and other type values use `TypeId` to index into - -## Instructions Table - -Store instructions indirectly. This allows passes that need to cache or remember an instruction's location (to work around borrowing issues) to have a single id to use to reference that instruction. Do not use `(BlockId, usize)` or similar. - -* Rename `InstructionId` to `EvaluationOrder` - this type is actually about representing the evaluation order, and is not even instruction-specific: it is also present on terminals. -* `HirFunction` stores `instructions: Vec<InstructionId>` -* `BasicBlock.instructions` becomes `Vec<InstructionId>`, indexing into the `HirFunction.instructions` vec - -## AliasingEffect - -* `Place` values are cloned -* `Call` variant `args` array is cloned -* `CreateFunction` variant uses `FunctionId` referencing the function arena - -## Environment - -Pass a single mutable environment reference separately from the HIR. - -* Remove `HIRFunction.env`, pass the environment as `env: &mut Environment` instead -* Maintain the existing fields/types of `Environment` type (don't group them) -* Use direct field access of Environment properties, rather than via methods, to allow precise sliced borrows of portions of the environment - -## Error Handling - -In general there are two categories of errors: -- Anything that would have thrown, or would have short-circuited, should return an `Err(...)` with the single diagnosstic -- Otherwise, accumulate errors directly onto the environment. -- Error handling must preserve the full details of the errors: reason, description, location, details, suggestions, category, etc - -### Specific Error Patterns and Approaches - -* TypeScript non-null assertions: - * Example: `!` - * Approach: panic via `.unwrap()` or similar. -* Throwing expressions: - * Example: `throw ...` (latent bugs, should have been `invariant`) - * Example: `CompilerError.invariant()` - * Example: `CompilerError.throwTodo()` - * Example: `CompilerError.throw*` (other "throw-" methods) - * Approach: Make the function return a `Result<_, CompilerDiagnostic>`, and return `Err(...)` with the appropriate compiler error value. -* Non-throwing expressions (Invariant): - * Example: local `error` object and `error.pushDiagnostic()` (where the error *is* an invariant) - * Approach: Make the function return a `Result<_, CompilerDiagnostic>`, and change the `pushDiagnostic()` with `return Err(...)` to return with the invariant error. -* Non-throwing expressions (excluding Invariant): - * Example: local `error` object and `error.pushDiagnostic()` (where the error is *not* an invariant) - * Example: `env.recordError()` (where the error is *not* an invariant) - * Approach: keep as-is - -## Pass and Pipeline Structure - -Structure the pipeline and passes along these lines to align with the above error handling guidelines: - -``` -// pipeline.rs -fn compile( - ast: Ast, - scope: Scope, - env: &mut Environment, -) -> Result<CompileResult, CompilerDiagnostic>> { - // "?" to handle cases that would have thrown or produced an invariant - let mut hir = lower(ast, scope, env)?; - some_compiler_pass(&mut hir, env)?; - ... - let ast = codegen(...)?; - - if (env.has_errors()) { - // result with errors - Ok(CompileResult::Failure(env.take_errors())) - } else { - // result with - Ok(CompileResult::Success(ast)) - } -} - -// <compilerpasss>.rs -fn passname( - func: &mut HirFunction, - env: &mut Environment -) -> Result<_, CompilerDiagnostic>; -``` \ No newline at end of file diff --git a/compiler/docs/rust-port/rust-port-orchestrator-log.md b/compiler/docs/rust-port/rust-port-orchestrator-log.md deleted file mode 100644 index 06bfb2a33f00..000000000000 --- a/compiler/docs/rust-port/rust-port-orchestrator-log.md +++ /dev/null @@ -1,649 +0,0 @@ -# Status - -Overall: 1724/1724 passing, 0 failed. All passes ported through ValidatePreservedManualMemoization (#48). Codegen (#49) fully ported. Code comparison: 1724/1724. - -Snap (end-to-end): 1725/1725 passed, 0 failed - -## Transformation passes - -HIR: partial (1651/1653, 2 failures — block ID ordering) -PruneMaybeThrows: complete (1651/1651, includes 2nd call) -DropManualMemoization: complete -MergeConsecutiveBlocks: complete -SSA: complete (1650/1650) -EliminateRedundantPhi: complete -ConstantPropagation: complete -InferTypes: complete -OptimizePropsMethodCalls: complete -AnalyseFunctions: complete (1649/1649) -InferMutationAliasingEffects: complete (1643/1643) -OptimizeForSSR: complete (5/5, conditional, outputMode === 'ssr') -DeadCodeElimination: complete -InferMutationAliasingRanges: complete -InferReactivePlaces: complete -ValidateExhaustiveDependencies: complete -RewriteInstructionKindsBasedOnReassignment: complete -InferReactiveScopeVariables: complete -MemoizeFbtAndMacroOperandsInSameScope: complete -outlineJSX: complete (conditional on enableJsxOutlining) -NameAnonymousFunctions: complete (2/2, conditional) -OutlineFunctions: complete -AlignMethodCallScopes: complete -AlignObjectMethodScopes: complete -PruneUnusedLabelsHIR: complete -AlignReactiveScopesToBlockScopesHIR: complete -MergeOverlappingReactiveScopesHIR: complete -BuildReactiveScopeTerminalsHIR: complete -FlattenReactiveLoopsHIR: complete -FlattenScopesWithHooksOrUseHIR: complete -PropagateScopeDependenciesHIR: complete -BuildReactiveFunction: complete -AssertWellFormedBreakTargets: complete -PruneUnusedLabels: complete -AssertScopeInstructionsWithinScopes: complete -PruneNonEscapingScopes: complete -PruneNonReactiveDependencies: complete -PruneUnusedScopes: complete -MergeReactiveScopesThatInvalidateTogether: complete -PruneAlwaysInvalidatingScopes: complete -PropagateEarlyReturns: complete -PruneUnusedLValues: complete -PromoteUsedTemporaries: complete -ExtractScopeDeclarationsFromDestructuring: complete -StabilizeBlockIds: complete -RenameVariables: complete -PruneHoistedContexts: complete -ValidatePreservedManualMemoization: complete -Codegen: complete (1717/1717 code comparison) - -# Logs - -## 20260401-120000 Extend test-e2e with event comparison and fix bugs - -Extended test-e2e.sh to compare logEvent() calls across all frontends (babel, -swc, oxc) against the TS baseline. Added --json flag to e2e CLI binary to -expose logger events. Fixed two bugs found by the new comparison: (1) TS -Program.ts logged directive as [object Object] instead of its string value. -(2) Rust program.rs used inferred fn_name for CompileSuccess instead of -codegen_fn.id, causing arrow functions to report names the TS compiler doesn't. -Removed all code output normalization from test-e2e.ts — comparison now uses -prettier only. - -## 20260331-230000 Fix ValidateSourceLocations error count discrepancy - -Fixed 4 issues causing the Rust compiler to report 27 errors vs TS's 22 on the -error.todo-missing-source-locations fixture: (1) Don't record the root function node as -important (TS func.traverse visits descendants only). (2) Use make_var_declarator for -hoisted scope declarations to reconstruct VariableDeclarator source locations. (3) Pass -HIR pattern source locations through to generated ArrayPattern/ObjectPattern AST nodes. -(4) Sort validation errors by source position for deterministic output. yarn snap --rust -now 1725/1725 (was 1724/1725). - -## 20260331-220000 Port ValidateSourceLocations to Rust compiler - -Ported the test-only ValidateSourceLocations pass from TypeScript to Rust. This post-codegen -validation checks that important source locations (used by Istanbul coverage instrumentation) -are preserved in compiled output. Enabled via `@validateSourceLocations` pragma. The pass -traverses both the original Babel AST function and the generated CodegenFunction output, -comparing source locations for important node types. Code comparison now 1724/1724 (was -1723/1724) since both TS and Rust correctly error on error.todo-missing-source-locations. - -## 20260331-210000 Fix function name inference to match TS parent-checking behavior - -Fixed FunctionDiscoveryVisitor to only infer declarator names for direct function inits, -matching TS's path.parentPath.isVariableDeclarator() check. Previously current_declarator_name -leaked to all descendant functions (e.g., arrows nested inside object literals). Now the name -is explicitly scoped: set only for function/arrow/call inits, cleared in non-forwardRef/memo -call expressions, and cleared after forwardRef/memo calls finish processing arguments. -1723/1723 passing. - -## 20260331-200000 Fix CompilerDiagnostic::todo() to produce ErrorDetail variant - -Removed the flat-loc serialization hack from log_error, compiler_error_to_info, and -log_errors_as_events. Instead fixed the root cause: the From<CompilerDiagnostic> for -CompilerError impl now converts Todo-category diagnostics to CompilerErrorOrDiagnostic::ErrorDetail -(matching TS's CompilerError.throwTodo() → CompilerErrorDetail). Invariant-category -diagnostics remain as CompilerErrorOrDiagnostic::Diagnostic with sub-details (matching TS). -1723/1723 passing. - -## 20260331-190000 Fix inner function debug log flushing and todo error event format - -Fixed 2 pre-existing test failures. (1) In pipeline.rs, inner function debug logs were -lost when analyse_functions errored because `?` propagated before flushing logs. Fixed by -capturing the result, flushing logs, then propagating. (2) CompilerDiagnostic::todo() -produced nested error events while TS uses flat format with loc directly. Fixed by detecting -flat diagnostics (single Error detail matching reason) and converting to flat format in -log_error, compiler_error_to_info, and log_errors_as_events. 1722/1723 passing. - -## 20260331-180000 Add MutVisitor trait and refactor AST mutation to use shared walker - -Added MutVisitor trait with visit_statement/visit_expression/visit_identifier hooks and -walk_program_mut/walk_statement_mut/walk_expression_mut free functions to react_compiler_ast. -Refactored three groups of manual recursive AST walkers in program.rs (~780 lines) into three -visitor structs: ReplaceFnVisitor, ReplaceWithGatedVisitor, RenameIdentifierVisitor (~110 lines). -No test regressions (1721/1723). - -## 20260329-120000 Static base registries for ShapeRegistry and GlobalRegistry - -Replaced ShapeRegistry and GlobalRegistry type aliases (HashMap) with newtype structs -supporting a base+overlay pattern. Built-in shapes and globals are now initialized once -via LazyLock and shared across all Environment instances. Environment::with_config creates -lightweight overlay registries that point to the static base; custom hooks and lazily-resolved -module types go into the overlay's extras map. Cloning registries (e.g. for_outlined_fn) now -copies only the small extras map. ~18% overall Rust compiler speedup (1263ms → 1031ms across -1717 fixtures). No test regressions. - -## 20260328-180000 Consolidate duplicated helper logic across Rust crates - -Eliminated ~3,700 lines of duplicated helper code across 30 files. Created canonical -shared implementations for: visitor ID wrappers (visitors.rs), debug printer formatting -(new print.rs module), predicate helpers (MutableRange::contains, Effect::is_mutable, -Environment methods), post_dominator_frontier (dominator.rs), is_react_like_name -(environment.rs), and is_use_operator_type (lib.rs). Also created react_compiler_utils -crate with generic DisjointSet<K>. All 1717/1717 passing, no regressions. - -## 20260318-111828 Initial orchestrator status - -First run of orchestrator. 10 passes ported (HIR through OptimizePropsMethodCalls). -All passes have failures: HIR (1), PruneMaybeThrows (2), DropManualMemoization (17), -IIFE (153), MergeConsecutiveBlocks (153), SSA (198), EliminateRedundantPhi (198), -ConstantPropagation (199), InferTypes (727), OptimizePropsMethodCalls (745). - -## 20260318-134746 Fix HIR reserved-words error - -Fixed error.reserved-words.ts failure. The `BabelPlugin.ts` catch block was missing -the `details` array in the CompileError event for reserved word errors from scope serialization. -HIR now 1717/1717, frontier moved to PruneMaybeThrows. - -## 20260318-160000 Print inner functions in debug HIR output - -Changed debug HIR printer (TS + Rust) to print full inner function bodies inline -instead of `loweredFunc: <HIRFunction>` placeholder. Also removed `Function #N:` header. -HIR regressed to 775/1717 as inner function differences are now visible. - -## 20260318-210850 Fix inner function lowering bugs in HIR pass - -Fixed multiple bugs exposed by the new inner function debug printing: -- Removed extra `is_context_identifier` fallback in hir_builder.rs that incorrectly - emitted LoadContext instead of LoadLocal for non-context captured variables. -- Fixed source locations in gather_captured_context using IdentifierLocIndex lookup - instead of fabricated byte-offset-based locs. -- Changed ScopeInfo.reference_to_binding from HashMap to IndexMap for deterministic - insertion-order iteration matching Babel's traversal order. -- Added JSXOpeningElement loc tracking in identifier_loc_index for JSX context vars. -- Added node_type to UnsupportedNode for UpdateExpression and YieldExpression. -HIR now 1717/1717, frontier back to PruneMaybeThrows. - -## 20260318-220322 Fix PruneMaybeThrows and validation pass failures - -Fixed 15 failures at the PruneMaybeThrows frontier: -- Fixed unreachable block predecessor tracking in hir_builder.rs (preds were empty instead of cloned). -- Implemented validateContextVariableLValues — errors were written to temp_errors and discarded. -- Fixed validateUseMemo VoidUseMemo event logging to include diagnostic details array. -- Fixed place formatting in invariant error descriptions to match TS printPlace() output. -PruneMaybeThrows now 1653/1653, DropManualMemoization 1652/1652, frontier moved to MergeConsecutiveBlocks. - -## 20260318-223712 Fix MergeConsecutiveBlocks and SSA failures - -Fixed 39 failures (1 MergeConsecutiveBlocks + 38 SSA): -- Moved env.has_errors() bailout from before SSA to end of pipeline, matching TS behavior. -- Fixed SSA error event format (CompileUnexpectedThrow filtering, CompilerErrorDetail format). -- Fixed identifier formatting in SSA error descriptions to match TS printIdentifier() output. -- Added name$N normalization to test harness. -MergeConsecutiveBlocks 1652/1652, SSA 1651/1651, frontier moved to ConstantPropagation. - -## 20260318-224340 Fix ConstantPropagation source location - -Fixed PostfixUpdate constant propagation using the instruction loc instead of the -previous constant's loc. Now uses prev_loc from the matched constant. -ConstantPropagation 1651/1651, frontier moved to InferTypes (708 failures). - -## 20260318-235832 Fix InferTypes pass — 708 failures resolved - -Fixed all 708 InferTypes failures plus 1 OptimizePropsMethodCalls failure: -- Added `<generated_N>` shape ID normalization to test harness. -- Fixed built-in hook shape definitions (useState, useReducer, etc.) to use specific - indexed properties instead of wildcard-only shapes. -- Fixed React namespace to reuse built-in hook types instead of auto-generating new ones. -- Added console/global/globalThis typed properties to shape definitions. -- Implemented Reanimated module type provider. -- Fixed inner function global type pre-resolution and hook property name fallback. -- Implemented enableTreatSetIdentifiersAsStateSetters config support. -- Fixed validateHooksUsage error ordering for nested functions. -All 1717 tests passing, 0 failures. Next pass to port: #11 AnalyseFunctions. - -## 20260318-235832 Port AnalyseFunctions pass skeleton - -Ported AnalyseFunctions pass (#11) from TypeScript. Created react_compiler_inference crate. -Pass skeleton is correct but inner function analysis depends on sub-passes not yet ported. -1108/1651 passing (543 crash during inner function analysis). -Commit: 92cc807a9f - -## 20260319-014600 Fix InferMutationAliasingEffects effect inference bugs - -Fixed legacy signature effects, inner function aliasingEffects population (Phase 2/3), -context variable effect classification, and built-in method calleeEffects in globals.rs. -Added mutableOnlyIfOperandsAreMutable optimization for Array methods. -968 passed (+12), AnalyseFunctions 1104/1108, InferMutationAliasingEffects 902/1104. -Remaining failures need inferMutationAliasingRanges and aliasing config porting. - -## 20260319-023425 Add aliasing signature configs and fix Apply effects - -Added aliasing configs for Array.push, Array.map, Set.add, Object.entries/keys/values. -Fixed spread argument self-capture and NewExpression callee mutation check. -InferMutationAliasingEffects: 202→2 failures. 1168/1717 passing overall. -Remaining 549 failures mostly from inner function analysis needing sub-passes. - -## 20260319-025540 Port DeadCodeElimination pass - -Ported DeadCodeElimination (#14) from TypeScript into react_compiler_optimization crate. -Wired into pipeline and inner function analysis (lower_with_mutation_aliasing). -DCE 1102/1102, 0 failures. Overall 1168/1717. - -## 20260319-041553 Port PruneMaybeThrows (2nd) and InferMutationAliasingRanges - -Added second PruneMaybeThrows call (#15) to pipeline. -Ported InferMutationAliasingRanges (#16) — computes mutable ranges, Place effects, -and function-level effects. Wired into pipeline and inner function analysis. -InferMutationAliasingRanges 1181/1218 (37 failures from unported inferReactiveScopeVariables). -Overall 1247/1717 (+79). - -## 20260319-092045 Port InferReactivePlaces, RewriteInstructionKinds, InferReactiveScopeVariables - -Ported three passes in parallel: -- InferReactivePlaces (#17): 951/1169 (81.3%) — post-dominator frontier differences -- RewriteInstructionKindsBasedOnReassignment (#18): 943/951 (98.7%) -- InferReactiveScopeVariables (#19): 112/943 (11.9%) — major issues with scope assignment -Overall 179/1717. InferReactiveScopeVariables needs significant fixing. - -## 20260319-093515 Fix InferReactiveScopeVariables scope output - -Added missing ReactiveScope fields (dependencies, declarations, reassignments, etc.). -Fixed debug printer to output all scope fields matching TS format. -Fixed DisjointSet ordering (HashMap→IndexMap) and scope loc computation. -InferReactiveScopeVariables: 1033/1033 (100%). Overall 1099/1717. -Remaining 618 failures in upstream passes, mainly InferReactivePlaces (397). - -## 20260319-103726 Fix InferReactivePlaces — 397→173 failures - -Fixed three bugs in InferReactivePlaces: -- Added FunctionExpression/ObjectMethod context variables as operands for reactivity propagation. -- Fixed useRef stable type detection (Object type, not just Function). -- Separated value operand vs lvalue flag setting to avoid over-marking. -InferReactivePlaces 1270/1443 (173 failures). Overall 1316/1717 (+217). - -## 20260319-111719 Fix InferMutationAliasingEffects function expression Apply effects - -Added function expression value tracking for Apply effects — when a callee is a -locally-declared function expression with known aliasing effects, use its signature -instead of falling through to the default "no signature" path. -InferMutationAliasingEffects: 110→21 failures. Overall 1401/1717 (+84). - -## 20260319-141741 Fix InferMutationAliasingEffects and InferMutationAliasingRanges bugs - -Fixed MutationReason formatting (AssignCurrentProperty), PropertyStore type check -(Type::Poly→Type::TypeVar), context/params effect ordering, and Switch/Try terminal -operand effects. Overall 1518→1566 passing (+48). - -## 20260319-160000 Fix top 10 correctness bug risks from ANALYSIS.md - -Fixed 6 of the top 10 correctness bugs identified in the port fidelity review -(bugs #1, #2, #9 were already fixed; #8 skipped per architecture doc guidance): -- globals.rs: Array callback methods (filter, find, findIndex, forEach, every, some, - flatMap, reduce) changed from positionalParams to restParam, added noAlias: true. -- constant_propagation.rs: is_valid_identifier now rejects JS reserved words. -- constant_propagation.rs: js_abstract_equal uses proper JS ToNumber semantics. -- merge_consecutive_blocks.rs: phi replacement instructions include Alias effect. -- merge_consecutive_blocks.rs: recursive merge into inner FunctionExpression/ObjectMethod. -- infer_types.rs: context variable places on inner functions now type-resolved. -Overall 1566→1566 passing (+1 net after recount with updated baseline). - -## 20260319-164422 Fix InferMutationAliasingRanges FunctionExpression/ObjectMethod operand handling - -Added FunctionExpression and ObjectMethod arms to apply_operand_effects in -infer_mutation_aliasing_ranges.rs. Context variables of inner functions now get -their mutableRange.start fixup applied, preventing invalid [0:N] ranges. -Overall 1566→1568 passing (+2). - -## 20260319-183501 Fix AnalyseFunctions — all 1717 tests passing - -Fixed three categories of bugs to clear AnalyseFunctions frontier: -- globals.rs: BuiltInEffectEventFunction signature — rest_param and callee_effect - changed from Effect::Read to Effect::ConditionallyMutate, matching TS definition. -- infer_mutation_aliasing_effects.rs: Added transitive freeze of function expression - captures, uninitialized identifier access detection with correct source locations. -- infer_mutation_aliasing_ranges.rs: Context var effect defaulting — FunctionExpression - operands not in operandEffects now default to Effect::Read. -- analyse_functions.rs: Early return on invariant errors from inner function processing. -- pipeline.rs: Invariant error propagation after analyse_functions. -AnalyseFunctions: 1717/1717 (0 failures). Overall 1568→1577 passing (+9). - -## 20260319-201728 Fix While terminal successors and spread argument Todo check - -Fixed `terminal_successors` for While terminals — was returning `loop_block` instead of -`test`, causing phi node identifiers in subsequent blocks to never be initialized. -Added spread argument Freeze effect Todo check matching TS `computeEffectsForSignature`. -Added error check after outer `infer_mutation_aliasing_effects` in pipeline.rs. -AnalyseFunctions: 6→1 failures, InferMutationAliasingEffects: 16→5 failures. Overall +5. - -## 20260319-211815 Fix remaining test failures — all passes clean through InferMutationAliasingRanges - -Fixed 8 remaining failures across AnalyseFunctions (1), InferMutationAliasingEffects (5), -InferMutationAliasingRanges (2): -- Fixed CreateFrom reason selection (HashSet non-deterministic order → primary_reason helper). -- Added aliasing_config_temp_cache to prevent duplicate identifier allocation in fixpoint. -- Added mutable spread tracking to compute_effects_for_aliasing_signature_config. -- Fixed each_instruction_value_operands to yield FunctionExpression context variables. -All 1717 fixtures passing through InferMutationAliasingRanges. Frontier: null (all clean). -Next: port passes #20+ (MemoizeFbtAndMacroOperandsInSameScope onwards). - -## 20260320-042126 Port all remaining HIR passes (#20-#31) - -Ported 12 passes in a single session, completing all 31 HIR passes: -- #20 MemoizeFbtAndMacroOperandsInSameScope (662 lines) -- #21 NameAnonymousFunctions + outlineJSX stub (380 lines) -- #22 OutlineFunctions (162 lines) -- #23 AlignMethodCallScopes (183 lines) -- #24 AlignObjectMethodScopes (205 lines) -- #25 PruneUnusedLabelsHIR (108 lines) -- #26 AlignReactiveScopesToBlockScopesHIR (782 lines) — biggest jump: 73→1243 passed -- #27 MergeOverlappingReactiveScopesHIR (789 lines) -- #28 BuildReactiveScopeTerminalsHIR (736 lines) — 1243→1392 passed -- #29 FlattenReactiveLoopsHIR (70 lines) -- #30 FlattenScopesWithHooksOrUseHIR (156 lines) -- #31 PropagateScopeDependenciesHIR (2382 lines) — the final HIR pass -Overall: 1342/1717 passing (78%). 375 failures from pre-existing upstream diffs. -Next pass is #32 BuildReactiveFunction — BLOCKED, needs test infra extension. - -## 20260320-133636 Fix remaining failures: 375→80 - -Fixed 295 of 375 failures across multiple passes: -- VED pipeline guard: always run VED (TS 'off' is truthy). Fixed 58 failures. -- OutlineFunctions: debug printer includes outlined function bodies, UID naming - convention matches Babel, depth-first name allocation ordering. Fixed ~125. -- Validation passes ported: ValidateNoSetStateInRender, ValidateExhaustiveDependencies, - ValidateNoJSXInTryStatement, ValidateNoSetStateInEffects. Fixed ~40. -- PropagateScopeDependenciesHIR: BTreeSet determinism, inner function hoistable - property loads, propagation result fix, deferred dependency check. Fixed ~30. -- ANALYSIS.md issues: globals.rs callee effects, infer_types fresh names map, - RewriteInstructionKinds Phase 2 ordering + invariant restoration. Fixed ~10. -- Test harness: normalizeIds reset at function boundaries. Fixed ~15. -Remaining 80 failures: RIKBR (23, VED false positive cascade), PSDH (20), -ValidateNoSetStateInRender (13), OutlineFunctions (9), InferReactivePlaces (7), -MergeOverlapping (3), others (5). -Overall: 1637/1717 passing (95.3%). - -## 20260320-141021 Port validateNoDerivedComputationsInEffects_exp - -Ported the experimental validateNoDerivedComputationsInEffects_exp validation pass -from TypeScript to Rust. The 13 "ValidateNoSetStateInRender" failures were actually -caused by this unported pass — the test harness misattributed them to the preceding pass. -Created validate_no_derived_computations_in_effects.rs (1269 lines) in react_compiler_validation. -Overall: 1650/1717 passing (96.1%), 67 failures remaining. - -## 20260320-161141 Fix ValidateNoSetStateInEffects — port createControlDominators - -Ported createControlDominators / isRefControlledBlock logic from ControlDominators.ts -into validate_no_set_state_in_effects.rs. Added post-dominator frontier computation -and phi-node predecessor block fallback. Fixes 1 failure (valid-setState-in-useEffect-controlled-by-ref-value.js). -Overall: 1651/1717 passing (96.2%), 66 failures remaining. - -## 20260320-171654 Fix upstream validation passes — 7 InferReactivePlaces failures resolved - -Fixed 3 validation passes causing 7 failures misattributed to InferReactivePlaces: -- ValidateNoRefAccessInRender: hook kind detection via env lookup instead of shape_id matching, - added missing else branch for useState/useReducer, fixed joinRefAccessRefTypes semantics. -- ValidateLocalsNotReassignedAfterRender: added LoadContext propagation, noAlias check for - Array callback methods to eliminate false positives. -- Ported non-experimental ValidateNoDerivedComputationsInEffects (replacing TODO stub). -Overall: 1658/1717 passing (96.6%), 59 failures remaining. - -## 20260320-201055 Fix multiple passes — 1658→1673 (+15 tests) - -Three categories of fixes: -- ObjectExpression computed key operand ordering: fixed in 4 files (infer_reactive_places, - infer_mutation_aliasing_effects, merge_overlapping_reactive_scopes, propagate_scope_deps). - TS yields computed key before value; Rust had them reversed. Fixed 10 PSDH + 5 RIKBR. -- Port ValidateStaticComponents: new validation pass detecting dynamically-created components. - Fixed 5 static-components/invalid-* fixtures. -- Port reduceMaybeOptionalChains in PropagateScopeDependenciesHIR: reduces optional chains - when base is known non-null. Fixed 3 fixtures. -- RIKBR error format: fixed Some(Reassign) → Reassign, added place detail string. -Overall: 1673/1717 passing (97.4%), 44 failures remaining. - -## 20260320-213855 Fix VED, PSDH, AlignObjectMethod — 1673→1695 (+22) - -Removed VED error stripping (was hiding 18 legitimate errors) after fixing VED false -positives via correct StartMemoize/FinishMemoize scoping of dependency collection. -Fixed PSDH inner function traversal for nested FunctionExpressions. Fixed -AlignObjectMethodScopes scope range accumulation (HashMap for min/max). -Overall: 1695/1717 passing (98.7%), 22 failures remaining. - -## 20260321-000048 Fix PSDH assumed-invoked functions and outline_jsx — 1695→1700 (+5) - -Fixed PSDH get_assumed_invoked_functions to share temporaries map across inner function -recursion. Fixed outline_jsx: aliasingEffects Some(vec![]) instead of None, IndexMap for -prop ordering, skip all JSX instructions in outlined groups. -Overall: 1700/1717 passing (99.0%), 17 failures remaining. - -## 20260321-000048 Fix OutlineFunctions and MergeOverlappingReactiveScopesHIR — 1700→1709 (+9) - -Fixed outline_jsx block rewrite to place replacement at LAST JSX position (matching TS -reverse iteration). Fixed MergeOverlappingReactiveScopesHIR scope deduplication to preserve -insertion order instead of sorting by ScopeId. All OutlineFunctions and MergeOverlapping -passes now clean. Remaining 8 failures: PSDH scope declarations (5), error reporting from -unported reactive passes (3). -Overall: 1709/1717 passing (99.5%), 8 failures remaining. - -## 20260321-010000 Fix PropagateScopeDependenciesHIR — 1709→1713 (+4) - -Fixed two bugs in PSDH: -- ProcessedInstr key collision: used IdentifierId instead of EvaluationOrder (not unique - across functions), fixing 3 scope declaration failures + 2 ASIWS cascades. -- Iterative non-null propagation fails on loops: replaced with recursive DFS using - active/done state tracking (matching TS recursivelyPropagateNonNull). -All 4 remaining failures are blocked on unported reactive passes or error handling. -Overall: 1713/1717 passing (99.8%), 4 failures remaining. - -## 20260320-213806 Port all reactive passes after BuildReactiveFunction - -Ported 15 reactive passes + visitor infrastructure from TypeScript to Rust: -- Visitor/transform traits (visitors.rs) with closure-based traversal -- assertWellFormedBreakTargets, pruneUnusedLabels, assertScopeInstructionsWithinScopes -- pruneNonEscapingScopes (1123 lines), pruneNonReactiveDependencies, pruneUnusedScopes -- mergeReactiveScopesThatInvalidateTogether, pruneAlwaysInvalidatingScopes, propagateEarlyReturns -- pruneUnusedLValues, promoteUsedTemporaries, extractScopeDeclarationsFromDestructuring -- stabilizeBlockIds, renameVariables, pruneHoistedContexts -Fixed RenameVariables value-level lvalue visiting and inner function traversal (154 failures fixed). -Fixed PruneNonReactiveDependencies inner function context visiting (23 failures fixed). - -## 20260323-130614 Fix RenameVariables, ExtractScopeDeclarations, PruneNonEscapingScopes — 36→13 failures - -Fixed 23 test failures across three passes: -- RenameVariables: PrunedScope scoping fix (visit_block_inner for pruned scopes, matching TS - traverseBlock vs visitBlock), plus addNewReference registration in pipeline.rs. 16→2 failures. -- ExtractScopeDeclarationsFromDestructuring: Fixed temporary place metadata — copy type from - original identifier, preserve source location on identifier, use GeneratedSource for Place loc. 8→0 failures. -- PruneNonEscapingScopes: Added FunctionExpression/ObjectMethod context operands from - env.functions for captured variable tracking. 1→0 failures. -Overall: 1704/1717 passing (99.2%), 13 failures remaining. - -## 20260323-160933 Fix 11 failures, add Result support to ReactiveFunctionTransform - -Fixed 11 test failures (13→2 remaining): -- MergeReactiveScopesThatInvalidateTogether: propagate parent_deps through terminals, - add lvalue tracking in FindLastUsage. 6→0 failures. -- Error message formatting: formatLoc treats null as (generated), invariant error details - in RIKBR, BuildReactiveFunction error format fix. 5→0 failures. -- PruneHoistedContexts: return Err() for Todo errors instead of state workaround. - -Refactored ReactiveFunctionTransform trait to return Result<..., CompilerError> on all -methods, enabling proper error propagation. Removed all .unwrap() calls on -transform_reactive_function — callers propagate with ?. -Overall: 1715/1717 passing (99.9%), 2 failures remaining (block ID ordering). - -## 20260323-201154 Implement apply_compiled_functions — codegen application - -Implemented the full codegen application pipeline so the Rust compiler now produces -actual compiled JavaScript output instead of returning the original source: -- compile_result.rs: Added id, params, body, generator, is_async fields to CodegenFunction -- pipeline.rs: Pass through AST fields from codegen result -- program.rs: Full apply_compiled_functions implementation — finds functions by BaseNode.start, - replaces params/body, inserts outlined functions, renames useMemoCache, adds imports -- codegen_reactive_function.rs: All BaseNode::default() → BaseNode::typed("...") for proper - JSON serialization of AST node types -- common.rs: Added BaseNode::typed() constructor -- BabelPlugin.ts: Replaced prog.replaceWith() with pass.file.ast.program assignment, - added comment deduplication for JSON round-trip reference sharing -- imports.rs: BaseNode::typed() for import-related AST nodes -Pass tests: 1715/1717 (2 flaky, pass individually). Code tests: 1586/1717 (92.4%). -Remaining 131 code failures: error handling differences (67), codegen output (23), -gating features (21), outlined ordering (12), other (8). - -## 20260324-210207 Fix outlined ordering, type annotations, script source type — 130→110 code failures - -Fixed three categories of code comparison failures: -- Outlined function ordering: changed from reverse to forward iteration in apply_compiled_functions, - matching Babel's insertAfter behavior. Fixed 12 failures. -- Type annotation preservation: added type_annotation field to TypeCastExpression in HIR, - populated during lowering for TSAsExpression/TSSatisfiesExpression/TSTypeAssertion/FlowTypeCast, - emitted in codegen as proper AST wrapper nodes. Fixed 6 failures. -- Script source type: implemented require() syntax for CJS modules in imports.rs using - VariableDeclaration with ObjectPattern destructuring + require() CallExpression. Fixed 1 failure. -Code comparison: 1586→1607 passing (93.6%). 110 remaining. - -## 20260324-214542 Implement gating codegen — 110→96 code failures - -Implemented function gating for the Rust compiler port: -- Standard gating: wraps compiled functions in `gating() ? compiled : original` conditional -- Hoisted gating: creates dispatcher function for functions referenced before declaration -- Dynamic gating: supports `'use memo if(identifier)'` directive with @dynamicGating config -- Export handling: export default/named function gating patterns -- Import sorting: case-insensitive to match JS localeCompare behavior -17 gating fixtures fixed (21/29 gating tests passing). 8 remaining are function discovery, -error handling paths, and unimplemented instrumentation features. -Code comparison: 1607→1621 passing (94.4%). 96 remaining. - -## 20260324-233646 Port ValidatePreservedManualMemoization — 96→38 code failures - -Ported ValidatePreservedManualMemoization from TypeScript to Rust (~440 lines). -Validates that compiled output preserves manual useMemo/useCallback memoization: -- StartMemoize operand scope checks (dependency scope must complete before memo block) -- FinishMemoize unmemoized value detection (values must be within reactive scopes) -- Scope dependency matching (inferred deps must match manually specified deps) -Replaced TODO stub in pipeline.rs with real validation pass call. -Fixed 58 code comparison failures. Code: 1621→1679 (97.8%). 38 remaining. - -## 20260325-011107 Fix error handling, enum passthrough, codegen invariants — 38→30 code failures - -Fixed 8 code comparison failures: -- Enum declarations: preserve original AST node through codegen instead of __unsupported_* placeholder -- throwUnknownException__testonly: pipeline support for test-only exception pragma -- MethodCall invariant: codegen checks property resolves to MemberExpression -- Unnamed temporary invariant: convert_identifier returns Result, errors on unnamed temps -- Const/Let declaration invariant: cannot have outer lvalue (expression reference) -- useMemo-switch-return: fixed as side effect (was flaky, now passes consistently) -Code: 1679→1687 (98.3%). 30 remaining. - -## 20260325-123533 Fix JSX outlining, function discovery, gating — 32→14 code failures - -Two parallel fixes: -1. JSX outlining: re-compile outlined functions through full pipeline (create fresh Environment, - build synthetic AST, lower to HIR, run all passes). All 9 jsx-outlining-* fixtures pass. -2. Function discovery: add ExpressionStatement + deep expression recursion to AST replacement/ - gating/rename traversals. Fix infer mode for React.memo/forwardRef, nested arrows in - exports, gating edge cases. -Commits: 526eced507 (function discovery), plus outstanding environment.rs changes. -Code: 1687→1703 (99.2%). 14 remaining. - -## 20260325-145443 Fix all remaining failures — 1717/1717 pass + code (100%) - -Fixed final 14 code failures + 1 pass-level failure: -- Instrumentation: enableEmitInstrumentForget codegen (3 fixtures), enableEmitHookGuards - with per-hook-call try/finally wrapping (1 fixture) -- Dynamic gating: fixed error handling to use handle_error (3 fixtures) -- StabilizeBlockIds: IndexSet for deterministic iteration, fixing dominator.js + useMemo-inverted-if -- Fast refresh: enableResetCacheOnSourceFileChanges with HMAC-SHA256 hash codegen (1 fixture) -- Reserved words: Babel plugin throws on scope extraction failure with panicThreshold (1 fixture) -- Source locations: run full pipeline before recording Todo error (1 fixture) -- Variable renaming: surface BindingRename from HIR to BabelPlugin for scope.rename() (2 fixtures) -- Use-no-forget: add memo cache import before error check in pipeline (1 fixture) -ALL TESTS PASSING: Pass 1717/1717, Code 1717/1717. - -## 20260328-235900 Remove local visitor copies — use canonical react_compiler_hir::visitors - -Replaced ~1,800 lines of duplicated visitor/iterator match logic across 21 files with -calls to canonical `react_compiler_hir::visitors` functions. Remaining local functions are -thin wrappers (e.g., calling canonical and mapping `Place` → `IdentifierId`). -Added `each_instruction_value_operand_with_functions` to canonical visitors for split-borrow cases. -All 1717 tests still passing. Pass 1717/1717, Code 1717/1717. - -## 20260330-134202 Fix 30 snap test failures — validation, codegen, prefilter - -Fixed 30 snap test failures across multiple categories: -- ValidatePreservedManualMemoization: added has_invalid_deps flag to suppress spurious errors (7 fixed) -- Type provider validation: fixed error messages, added namespace import validation (3 fixed) -- knownIncompatible: implemented IncompatibleLibrary error check with early return (3 fixed) -- JSON log ordering: added CompileErrorWithLoc variant, fixed severity with logged_severity() (2 fixed) -- Code-frame abbreviation: ported CODEFRAME_MAX_LINES logic to Rust BabelPlugin.ts (2 fixed) -- Codegen error formatting: for-init messages, MethodCall span narrowing, for-in/of locs (4 fixed) -- Error message text: "this is Const" format matching TS (1 fixed) -- Prefilter: React.memo/forwardRef detection in TS and SWC prefilters (3 fixed) -- globals.rs: toString() on BuiltInObject/MixedReadonly, is_ref_like_name fix (3 fixed) -- scope.rs/hir_builder.rs: name-based binding fallback for component-syntax ref params (1 fixed) -- Snap runner: auto-enable sync mode when --rust is set (1 infra fix) -Pass 1717/1717, Code 1717/1717, Snap 1702/1718. - -## 20260330-145244 Fix remaining snap failures — 1717/1718 (99.9%) - -Fixed 10 more snap test failures: -- FBT loc propagation (8 fixed): Added loc to convert_identifier, codegen_place, make_var_declarator, - codegen_jsx_attribute, and instruction value expressions in codegen_reactive_function.rs. -- identifierName in diagnostics (1 fixed): Enhanced get_identifier_name_with_loc in - validate_no_derived_computations_in_effects.rs with fallback to declaration_id and source extraction. -- Component/hook declaration syntax (2 fixed): Added __componentDeclaration and __hookDeclaration - boolean fields to FunctionDeclaration AST, updated program.rs to detect these in function discovery. -- BuiltInMixedReadonly methods (2 fixed): Added 13 missing methods (indexOf, includes, at, map, - flatMap, filter, concat, slice, every, some, find, findIndex, join) to globals.rs. -- idx-no-outlining (1 fixed): Normalize unused _refN declarations in snap reporter. -- ValidateSourceLocations: silently skip in Rust (pipeline.rs). -Pass 1717/1717, Code 1716/1717, Snap 1717/1718. Only remaining: error.todo-missing-source-locations (intentional). - -## 20260331-220427 Port OptimizeForSSR pass - -Ported OptimizeForSSR (#13) from TypeScript to Rust. The pass optimizes components for -SSR by inlining useState/useReducer, removing effects and event handlers, and stripping -known event handler/ref props from builtin JSX. Gated on outputMode === 'ssr'. -Created optimize_for_ssr.rs in react_compiler_optimization crate. Added is_plain_object_type -and is_start_transition_type helpers to react_compiler_hir. -test-rust-port: 1724/1724, Snap --rust: 1725/1725. - -## 20260402-103329 Fix e2e diagnostic event mismatches — 123→2 failures - -Fixed 121 of 123 babel e2e test failures (diagnostic events only, no code changes): -- Description field: removed skip_serializing_if on `description` and `message` fields - in CompilerErrorDetailInfo, ensuring `null` is always serialized (70 fixtures). -- Diagnostic suggestions: added LoggerSuggestionInfo struct with LoggerSuggestionOp enum - (InsertBefore=0, InsertAfter=1, Remove=2, Replace=3), ported suggestion generation from - TS. Implemented exhaustive deps suggestion generation in validate_exhaustive_dependencies (23 fixtures). -- record_error Result type: Environment::record_error() now returns Result<(), CompilerError>, - returning Err for Invariant category. All callers use `?` for short-circuit propagation. -- CompileUnexpectedThrow events: Added emission in process_fn when CompilerError has is_thrown - flag, matching TS tryCompileFunction behavior (5 fixtures). -- Invariant error format: Changed invariant errors to use CompilerDiagnostic with details array - (matching TS CompilerError.invariant() format) in codegen_reactive_function (5 fixtures). -- JSX outlining events: Emit CompileSuccess for outlined functions with fn_type.is_some() after - main compilation loop, matching TS queue ordering (9 fixtures). -- Empty suggestions: Fixed `Some(vec![])` vs `None` for empty suggestion arrays (2 fixtures). -Remaining 2: handle-unexpected-exception (PipelineError stack trace), todo-kitchensink (index field). -test-rust-port: 1724/1724, e2e babel: 1722/1724, swc: 1584/1724, oxc: 688/1724. - -## 20260401-105521 Move error formatting to Rust, fix JSXAttribute loc in codegen - -Moved error formatting from JS to Rust: added code_frame.rs to react_compiler_diagnostics -with code frame rendering and format_compiler_error(). Rust now returns pre-formatted error -messages via formatted_message field on CompilerErrorInfo, eliminating ~160 lines of JS -formatting code (formatCompilerError, categoryToHeading, printCodeFrame) and the @babel/code-frame -dependency from babel-plugin-react-compiler-rust. Also fixed JSXExpressionContainer nodes in -codegen to propagate source locations from place.loc, eliminating the ensureNodeLocs JS post-pass. -test-rust-port: 1724/1724, Snap: 1725/1725, Snap --rust: 1725/1725. diff --git a/compiler/docs/rust-port/rust-port-oxc.md b/compiler/docs/rust-port/rust-port-oxc.md deleted file mode 100644 index 0d3bce1337ae..000000000000 --- a/compiler/docs/rust-port/rust-port-oxc.md +++ /dev/null @@ -1,272 +0,0 @@ -# Plan: `react_compiler_oxc` — OXC Frontend for React Compiler - -## Context - -The Rust React Compiler (`compiler/crates/`) currently accepts Babel-format AST (`react_compiler_ast::File`) + scope info (`ScopeInfo`) and compiles via `compile_program()`. The only frontend is a Babel NAPI bridge (`compiler/packages/babel-plugin-react-compiler-rust/`). This plan adds an OXC frontend that enables both **build-time code transformation** and **linting** via the OXC ecosystem, all in pure Rust (no JS/NAPI boundary). - -## Crate Structure - -``` -compiler/crates/react_compiler_oxc/ - Cargo.toml - src/ - lib.rs — Public API: transform(), lint(), ReactCompilerRule - prefilter.rs — Quick check for React-like function names in OXC AST - convert_ast.rs — OXC AST → react_compiler_ast::File - convert_ast_reverse.rs — react_compiler_ast → OXC AST (for applying results) - convert_scope.rs — OXC Semantic → ScopeInfo - diagnostics.rs — CompileResult → OxcDiagnostic conversion -``` - -### Dependencies (Cargo.toml) - -```toml -[dependencies] -react_compiler_ast = { path = "../react_compiler_ast" } -react_compiler = { path = "../react_compiler" } -react_compiler_diagnostics = { path = "../react_compiler_diagnostics" } -oxc_parser = "..." -oxc_ast = "..." -oxc_semantic = "..." -oxc_allocator = "..." -oxc_span = "..." -oxc_diagnostics = "..." -oxc_linter = "..." # for Rule trait -indexmap = "..." -``` - -## Module Details - -### 1. `prefilter.rs` — Quick React Function Check - -Port of `babel-plugin-react-compiler-rust/src/prefilter.ts`. - -```rust -pub fn has_react_like_functions(program: &oxc_ast::ast::Program) -> bool -``` - -- Use `oxc_ast::Visit` trait to walk the AST -- Check `FunctionDeclaration` names, `VariableDeclarator` inits that are arrow/function expressions -- Skip class bodies -- Name check: `starts_with(uppercase)` or matches `use[A-Z0-9]` -- Return `true` on first match (early exit) - -### 2. `convert_scope.rs` — OXC Semantic → ScopeInfo - -```rust -pub fn convert_scope_info(semantic: &oxc_semantic::Semantic) -> ScopeInfo -``` - -This is the most natural conversion — both use arena-indexed flat tables with copyable u32 IDs. - -**Scopes:** Iterate `semantic.scopes()`. For each scope: -- `ScopeId` — direct u32 remapping -- `parent` — from `scope_tree.get_parent_id()` -- `kind` — map `ScopeFlags` → `ScopeKind` (Top→Program, Function→Function, CatchClause→Catch, etc.; use parent AST node to distinguish For vs Block) -- `bindings` — from `scope_tree.get_bindings()`, map name→SymbolId to name→BindingId - -**Bindings:** Iterate `semantic.symbols()`. For each symbol: -- `BindingId` — direct u32 remapping from SymbolId -- `name`, `scope` — direct from SymbolTable -- `kind` — inspect declaration AST node type: VariableDeclaration(var/let/const), FunctionDeclaration→Hoisted, param→Param, ImportDeclaration→Module -- `declaration_type` — string name of the declaring AST node type -- `declaration_start` — span.start of the binding's declaring identifier -- `import` — for Module bindings, extract source/kind/imported from the ImportDeclaration - -**`node_to_scope`:** Walk AST nodes that create scopes; map `node.span().start → ScopeId`. - -**`reference_to_binding`:** Iterate all references from SymbolTable. For each resolved reference: map `reference.span().start → BindingId`. Also add each symbol's declaration identifier span. - -**`program_scope`:** `ScopeId(0)`. - -Key files: -- Target types: `compiler/crates/react_compiler_ast/src/scope.rs` -- Reference impl: `compiler/packages/babel-plugin-react-compiler-rust/src/scope.ts` - -### 3. `convert_ast.rs` — OXC AST → react_compiler_ast::File - -```rust -pub fn convert_program( - program: &oxc_ast::ast::Program, - source_text: &str, - comments: &[oxc_ast::Comment], -) -> react_compiler_ast::File -``` - -**Approach:** Recursive conversion, one function per AST category (statement, expression, pattern, JSX, etc.). Data is copied out of OXC's arena into owned `react_compiler_ast` types. - -**ConvertCtx:** Holds a line-offset table (built from source_text at init) for computing `Position { line, column, index }` from byte offsets. - -**BaseNode construction:** -- `start = Some(span.start)`, `end = Some(span.end)` — critical for scope lookups -- `loc` — computed via line-offset table binary search - -**Key mappings:** -| OXC | react_compiler_ast | -|-----|-------------------| -| `Statement` enum variants | `statements::Statement` variants | -| `Expression` enum variants | `expressions::Expression` variants | -| `Declaration` (separate in OXC) | Folded into `Statement` (Babel style) | -| `BindingPattern` | `patterns::PatternLike` | -| `JSXElement/Fragment/etc` | `jsx::*` types | -| TS type annotations | `Option<Box<serde_json::Value>>` (opaque passthrough) | - -**Comments:** Map OXC `Comment { kind, span }` → `react_compiler_ast::common::Comment` (CommentBlock/CommentLine with start/end/value). - -Key files: -- Target types: `compiler/crates/react_compiler_ast/src/` (all modules) - -### 4. `convert_ast_reverse.rs` — react_compiler_ast → OXC AST - -Mirror of `convert_ast.rs`. Converts the compiled Babel-format AST back into OXC AST nodes. - -```rust -pub fn convert_program_to_oxc<'a>( - file: &react_compiler_ast::File, - allocator: &'a oxc_allocator::Allocator, -) -> oxc_ast::ast::Program<'a> -``` - -- Allocates new OXC AST nodes into the provided arena -- Maps each `react_compiler_ast` type back to its OXC equivalent -- The `CompileResult::Success { ast, .. }` returns `ast: Option<serde_json::Value>` — first deserialize to `react_compiler_ast::File`, then convert to OXC - -This is the most labor-intensive module but avoids the perf cost of re-parsing. - -### 5. `diagnostics.rs` — Compiler Results → OXC Diagnostics - -```rust -pub fn compile_result_to_diagnostics( - result: &CompileResult, - source_text: &str, -) -> Vec<oxc_diagnostics::OxcDiagnostic> -``` - -Map compiler events/errors to OXC diagnostics: -- `LoggerEvent::CompileError { fn_loc, detail }` → `OxcDiagnostic::warn/error` with label at fn_loc span -- `CompileResult::Error { error, .. }` → `OxcDiagnostic::error` -- Preserve error messages and source locations - -### 6. `lib.rs` — Public API - -#### Transform API (build pipeline) - -```rust -/// Result of compiling a program -pub struct TransformResult<'a> { - /// The compiled program (None if no changes needed) - pub program: Option<oxc_ast::ast::Program<'a>>, - pub diagnostics: Vec<oxc_diagnostics::OxcDiagnostic>, - pub events: Vec<LoggerEvent>, -} - -/// Primary API — accepts pre-parsed AST + semantic -pub fn transform<'a>( - program: &oxc_ast::ast::Program, - semantic: &oxc_semantic::Semantic, - source_text: &str, - comments: &[oxc_ast::Comment], - options: PluginOptions, - output_allocator: &'a oxc_allocator::Allocator, -) -> TransformResult<'a> - -/// Convenience wrapper — parses from source text -pub fn transform_source<'a>( - source_text: &str, - source_type: oxc_span::SourceType, - options: PluginOptions, - output_allocator: &'a oxc_allocator::Allocator, -) -> TransformResult<'a> -``` - -Flow: -1. Prefilter (`has_react_like_functions`). Skip if `compilationMode == "all"`. -2. Convert AST (`convert_program`) -3. Convert scope (`convert_scope_info`) -4. Call `compile_program(file, scope_info, options)` -5. On success with modified AST: deserialize JSON → `File`, reverse-convert to OXC AST -6. Convert diagnostics - -#### Lint API - -```rust -pub struct LintResult { - pub diagnostics: Vec<oxc_diagnostics::OxcDiagnostic>, -} - -/// Lint — accepts pre-parsed AST + semantic -pub fn lint( - program: &oxc_ast::ast::Program, - semantic: &oxc_semantic::Semantic, - source_text: &str, - comments: &[oxc_ast::Comment], - options: PluginOptions, -) -> LintResult - -/// Convenience wrapper -pub fn lint_source( - source_text: &str, - source_type: oxc_span::SourceType, - options: PluginOptions, -) -> LintResult -``` - -Same as transform but with `no_emit = true` / lint output mode. Only collects diagnostics, no AST output. - -#### oxc_linter::Rule Implementation - -```rust -pub struct ReactCompilerRule { - options: PluginOptions, -} - -impl oxc_linter::Rule for ReactCompilerRule { - fn run_once(&self, ctx: &LintContext) { - // ctx already has parsed AST + semantic - let result = lint( - ctx.program(), - ctx.semantic(), - ctx.source_text(), - ctx.comments(), - self.options.clone(), - ); - for diagnostic in result.diagnostics { - ctx.diagnostic(diagnostic); - } - } -} -``` - -This avoids double-parsing since oxc_linter provides pre-parsed AST and semantic analysis. - -## Implementation Phases - -### Phase 1: Foundation (convert_scope + convert_ast + prefilter) -- `convert_scope.rs` with unit tests comparing against Babel scope extraction -- `convert_ast.rs` with unit tests comparing against Babel parser JSON output -- `prefilter.rs` with simple true/false tests -- These are independently testable without the full pipeline - -### Phase 2: Lint path (diagnostics + lint API + Rule) -- `diagnostics.rs` -- `lint()` function in `lib.rs` -- `ReactCompilerRule` impl -- Test against existing compiler fixtures — verify diagnostics match - -### Phase 3: Transform path (reverse converter + transform API) -- `convert_ast_reverse.rs` -- `transform()` function in `lib.rs` -- Integration tests: compile fixtures through OXC pipeline, compare output with Babel pipeline - -### Phase 4: Differential testing -- Cross-validate AST conversion: parse same source with both Babel and OXC, convert both to `react_compiler_ast::File`, diff -- Cross-validate scope conversion: compare `ScopeInfo` from both paths -- Run full fixture suite through both pipelines, compare compiled output - -## Verification - -1. **Unit tests:** Each module has tests for its conversion logic -2. **Fixture tests:** Use existing fixtures at `compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/` -3. **Differential tests:** Compare OXC path output against Babel path output for same inputs -4. **`cargo test -p react_compiler_oxc`** — run all crate tests -5. **Scope correctness:** Most critical — incorrect scope info causes wrong compilation. Snapshot `ScopeInfo` JSON and compare against Babel extraction golden files diff --git a/compiler/docs/rust-port/rust-port-reactive-function.md b/compiler/docs/rust-port/rust-port-reactive-function.md deleted file mode 100644 index 2332a1476afb..000000000000 --- a/compiler/docs/rust-port/rust-port-reactive-function.md +++ /dev/null @@ -1,567 +0,0 @@ -# Rust Port: ReactiveFunction and Reactive Passes - -Current status: **Phase 2 In Progress** — Reactive types, crate skeleton, TS/Rust debug printers, and BuildReactiveFunction are implemented. 1458/1717 fixtures pass (85%). Remaining failures are mostly earlier-pass error propagation differences and a few loop scheduling edge cases. - -## Overview - -This document covers porting the reactive function representation and all passes from `BuildReactiveFunction` through `CodegenReactiveFunction` from TypeScript to Rust. - -The reactive function is a tree-structured IR derived from the HIR CFG. `BuildReactiveFunction` converts the flat CFG into a nested tree where control flow constructs (if/switch/loops/try) and reactive scopes are represented as nested blocks rather than block references. Subsequent passes prune, merge, and transform scopes, then codegen converts the tree to output AST. - -## 1. Rust Type Representation - -**Location**: New file `compiler/crates/react_compiler_hir/src/reactive.rs`, re-exported from `lib.rs` - -All types derive `Debug, Clone`. - -### ReactiveFunction - -```rust -/// Tree representation of a compiled function, converted from the CFG-based HIR. -/// TS: ReactiveFunction in HIR.ts -pub struct ReactiveFunction { - pub loc: Option<SourceLocation>, - pub id: Option<String>, - pub name_hint: Option<String>, - pub params: Vec<ParamPattern>, - pub generator: bool, - pub is_async: bool, - pub body: ReactiveBlock, - pub directives: Vec<String>, - // No env field — passed separately per established Rust convention -} -``` - -### ReactiveBlock and ReactiveStatement - -```rust -/// TS: ReactiveBlock = Array<ReactiveStatement> -pub type ReactiveBlock = Vec<ReactiveStatement>; - -/// TS: ReactiveStatement (discriminated union with 'kind' field) -pub enum ReactiveStatement { - Instruction(ReactiveInstruction), - Terminal(ReactiveTerminalStatement), - Scope(ReactiveScopeBlock), - PrunedScope(PrunedReactiveScopeBlock), -} -``` - -### ReactiveInstruction and ReactiveValue - -```rust -/// TS: ReactiveInstruction -pub struct ReactiveInstruction { - pub id: EvaluationOrder, // TS InstructionId = Rust EvaluationOrder - pub lvalue: Option<Place>, - pub value: ReactiveValue, - pub effects: Option<Vec<AliasingEffect>>, - pub loc: Option<SourceLocation>, -} - -/// Extends InstructionValue with compound expression types that were -/// separate blocks+terminals in HIR but become nested expressions here. -/// TS: ReactiveValue = InstructionValue | ReactiveLogicalValue | ... -pub enum ReactiveValue { - /// All ~35 base instruction value kinds - Instruction(InstructionValue), - - /// TS: ReactiveLogicalValue - LogicalExpression { - operator: LogicalOperator, - left: Box<ReactiveValue>, - right: Box<ReactiveValue>, - loc: Option<SourceLocation>, - }, - - /// TS: ReactiveTernaryValue - ConditionalExpression { - test: Box<ReactiveValue>, - consequent: Box<ReactiveValue>, - alternate: Box<ReactiveValue>, - loc: Option<SourceLocation>, - }, - - /// TS: ReactiveSequenceValue - SequenceExpression { - instructions: Vec<ReactiveInstruction>, - id: EvaluationOrder, - value: Box<ReactiveValue>, - loc: Option<SourceLocation>, - }, - - /// TS: ReactiveOptionalCallValue - OptionalExpression { - id: EvaluationOrder, - value: Box<ReactiveValue>, - optional: bool, - loc: Option<SourceLocation>, - }, -} -``` - -### Terminals - -```rust -pub struct ReactiveTerminalStatement { - pub terminal: ReactiveTerminal, - pub label: Option<ReactiveLabel>, -} - -pub struct ReactiveLabel { - pub id: BlockId, - pub implicit: bool, -} - -pub enum ReactiveTerminalTargetKind { - Implicit, - Labeled, - Unlabeled, -} - -pub enum ReactiveTerminal { - Break { - target: BlockId, - id: EvaluationOrder, - target_kind: ReactiveTerminalTargetKind, - loc: Option<SourceLocation>, - }, - Continue { - target: BlockId, - id: EvaluationOrder, - target_kind: ReactiveTerminalTargetKind, - loc: Option<SourceLocation>, - }, - Return { - value: Place, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Throw { - value: Place, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Switch { - test: Place, - cases: Vec<ReactiveSwitchCase>, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - DoWhile { - loop_block: ReactiveBlock, // "loop" is a Rust keyword - test: ReactiveValue, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - While { - test: ReactiveValue, - loop_block: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - For { - init: ReactiveValue, - test: ReactiveValue, - update: Option<ReactiveValue>, - loop_block: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - ForOf { - init: ReactiveValue, - test: ReactiveValue, - loop_block: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - ForIn { - init: ReactiveValue, - loop_block: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - If { - test: Place, - consequent: ReactiveBlock, - alternate: Option<ReactiveBlock>, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Label { - block: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, - Try { - block: ReactiveBlock, - handler_binding: Option<Place>, - handler: ReactiveBlock, - id: EvaluationOrder, - loc: Option<SourceLocation>, - }, -} - -pub struct ReactiveSwitchCase { - pub test: Option<Place>, - pub block: Option<ReactiveBlock>, // TS: ReactiveBlock | void -} -``` - -### Scope Blocks - -```rust -pub struct ReactiveScopeBlock { - pub scope: ScopeId, // Arena pattern: scope data in Environment - pub instructions: ReactiveBlock, -} - -pub struct PrunedReactiveScopeBlock { - pub scope: ScopeId, - pub instructions: ReactiveBlock, -} -``` - -### Reused Existing Types - -All of these are already defined in `react_compiler_hir`: -- `Place`, `InstructionValue`, `AliasingEffect`, `LogicalOperator`, `ParamPattern` -- `BlockId`, `ScopeId`, `IdentifierId`, `EvaluationOrder`, `TypeId`, `FunctionId` -- `SourceLocation` (from `react_compiler_diagnostics`) -- `ReactiveScope`, `ReactiveScopeDependency`, `ReactiveScopeDeclaration`, `ReactiveScopeEarlyReturn` - -### Key Design Decisions - -1. **ReactiveValue wraps InstructionValue**: `ReactiveValue::Instruction(InstructionValue)` wraps the existing ~35-variant enum. Passes that match specific kinds use `ReactiveValue::Instruction(InstructionValue::FunctionExpression { .. })`. - -2. **Box for recursive types**: `ReactiveValue` fields use `Box<ReactiveValue>` for recursion. `ReactiveBlock` (Vec) naturally heap-allocates, breaking the size cycle for terminals. - -3. **ScopeId, not cloned scope**: `ReactiveScopeBlock` stores `ScopeId`. Scope data lives in `env.scopes[scope_id]`. Passes that read/write scope data access it through the environment. - -4. **No Environment on ReactiveFunction**: Passes take `env: &Environment` or `env: &mut Environment` as a separate parameter, following the established Rust pattern. - -5. **EvaluationOrder, not InstructionId**: The TS `InstructionId` (evaluation order counter) maps to Rust `EvaluationOrder`. Rust's `InstructionId` is the flat instruction table index (not used in reactive types). - -## 2. New Crate: `react_compiler_reactive_scopes` - -``` -compiler/crates/react_compiler_reactive_scopes/ - Cargo.toml - src/ - lib.rs - build_reactive_function.rs - print_reactive_function.rs - visitors.rs - assert_well_formed_break_targets.rs - assert_scope_instructions_within_scopes.rs - prune_unused_labels.rs - prune_non_escaping_scopes.rs - prune_non_reactive_dependencies.rs - prune_unused_scopes.rs - merge_reactive_scopes_that_invalidate_together.rs - prune_always_invalidating_scopes.rs - propagate_early_returns.rs - prune_unused_lvalues.rs - promote_used_temporaries.rs - extract_scope_declarations_from_destructuring.rs - stabilize_block_ids.rs - rename_variables.rs - prune_hoisted_contexts.rs - validate_preserved_manual_memoization.rs -``` - -**Cargo.toml dependencies**: `react_compiler_hir`, `react_compiler_diagnostics`, `indexmap` - -Add to workspace `Cargo.toml` members and as dependency of `react_compiler`. - -Maps to TS directory: `src/ReactiveScopes/` - -## 3. Debug Printing - -### Approach: New Verbose Format (like DebugPrintHIR) - -Create a new verbose `DebugPrintReactiveFunction` format that prints every field of every type recursively, analogous to `DebugPrintHIR`. Both TS and Rust need new implementations. - -### TS Side - -Create `compiler/packages/babel-plugin-react-compiler/src/HIR/DebugPrintReactiveFunction.ts`: - -- Entry point: `export function printDebugReactiveFunction(fn: ReactiveFunction): string` -- Uses the same `DebugPrinter` class from `DebugPrintHIR.ts` -- Prints function metadata: id, name_hint, generator, async, loc, params (full Place detail), directives -- Recursively prints `fn.body` (ReactiveBlock): - - `ReactiveInstruction`: id, lvalue (full Place with identifier declaration), value, effects, loc - - `ReactiveScopeBlock`/`PrunedReactiveScopeBlock`: full scope detail (id, range, dependencies with paths and locs, declarations with identifier info, reassignments, earlyReturnValue, merged, loc), then nested instructions - - `ReactiveTerminalStatement`: label info, terminal kind, all fields including nested blocks - - `ReactiveValue` compound types: kind, all fields recursively; `Instruction` variant delegates to `formatInstructionValue` -- Appends outlined functions and Environment errors (same pattern as DebugPrintHIR) -- Reuses shared formatters: `formatPlace`, `formatIdentifier`, `formatType`, `formatLoc`, `formatAliasingEffect`, `formatInstructionValue` -- Export from `compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts` - -### Rust Side - -`compiler/crates/react_compiler_reactive_scopes/src/print_reactive_function.rs`: - -- Entry point: `pub fn debug_reactive_function(func: &ReactiveFunction, env: &Environment) -> String` -- Uses the `DebugPrinter` struct pattern from `compiler/crates/react_compiler/src/debug_print.rs` -- Must produce output identical to the TS `printDebugReactiveFunction` - -### Shared Print Helpers - -Extract these as `pub` from `compiler/crates/react_compiler/src/debug_print.rs` (currently private): -- `format_place(place, env) -> String` -- `format_identifier(id, env) -> String` -- `format_type(type_id, env) -> String` -- `format_loc(loc) -> String` -- `format_aliasing_effect(effect) -> String` -- `format_instruction_value(value, env, indent) -> Vec<String>` -- The `DebugPrinter` struct itself (or extract to a shared module) - -## 4. Test Infrastructure Changes - -### `compiler/scripts/test-rust-port.ts` - -1. **Import** `printDebugReactiveFunction` from the new TS file - -2. **Handle `kind: 'reactive'`** — replace the `throw new Error(...)` at lines 297-305: - ```typescript - } else if (entry.kind === 'reactive') { - log.push({ - kind: 'entry', - name: entry.name, - value: printDebugReactiveFunction(entry.value), - }); - } - ``` - -3. **Handle `kind: 'ast'`** — keep the TODO error for now (codegen is deferred) - -4. **ID normalization** — the existing `normalizeIds` function handles `bb\d+`, `@\d+`, `Identifier(\d+)`, `Type(\d+)`, `\w+\$\d+`, `mutableRange` patterns. Should work for reactive output. Verify after BuildReactiveFunction is ported; may need additional patterns for scope-specific fields in the verbose format. - -### Rust Pipeline (`pipeline.rs`) - -After `PropagateScopeDependenciesHIR`, transition from HIR to ReactiveFunction: - -```rust -let mut reactive_fn = react_compiler_reactive_scopes::build_reactive_function(&hir, &env); -let debug = react_compiler_reactive_scopes::debug_reactive_function(&reactive_fn, &env); -context.log_debug(DebugLogEntry::new("BuildReactiveFunction", debug)); - -react_compiler_reactive_scopes::assert_well_formed_break_targets(&reactive_fn)?; -context.log_debug(DebugLogEntry::new("AssertWellFormedBreakTargets", "ok".to_string())); - -react_compiler_reactive_scopes::prune_unused_labels(&mut reactive_fn); -let debug = react_compiler_reactive_scopes::debug_reactive_function(&reactive_fn, &env); -context.log_debug(DebugLogEntry::new("PruneUnusedLabels", debug)); - -// ... etc for each pass -``` - -## 5. Phased Porting Plan - -### Phase 1 — Foundation - -1. Create `reactive.rs` in `react_compiler_hir` with all types from Section 1 -2. Create `react_compiler_reactive_scopes` crate skeleton with `Cargo.toml` and empty `lib.rs` -3. Create TS `DebugPrintReactiveFunction.ts` with verbose format -4. Extract shared print helpers from `debug_print.rs` as public -5. Port verbose format to Rust `print_reactive_function.rs` -6. Update `test-rust-port.ts` to handle `kind: 'reactive'` - -### Phase 2 — BuildReactiveFunction - -The critical pass (~700 lines). Converts HIR CFG to ReactiveFunction tree. - -- **Source**: `compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveFunction.ts` -- **Target**: `compiler/crates/react_compiler_reactive_scopes/src/build_reactive_function.rs` -- **Key structures to port**: - - `Context` class: tracks `emitted: Set<BlockId>`, `scopeFallthroughs: Set<BlockId>`, `#scheduled: Set<BlockId>`, `#catchHandlers: Set<BlockId>`, `#controlFlowStack: Array<ControlFlowTarget>` - - `Driver` class: `traverseBlock`, `visitBlock`, `visitValueBlock`, `visitValueBlockTerminal`, `visitTestBlock`, `extractValueBlockResult`, `wrapWithSequence`, `visitBreak`, `visitContinue` -- **Signature**: `pub fn build_reactive_function(hir: &HirFunction, env: &Environment) -> ReactiveFunction` -- **Wire into pipeline.rs** -- **Test**: `bash compiler/scripts/test-rust-port.sh BuildReactiveFunction` - -### Phase 3 — Validation Passes - -- `assert_well_formed_break_targets` (~30 lines) — checks break/continue targets exist -- `assert_scope_instructions_within_scopes` (~80 lines) — validates scope ranges contain instructions - -### Phase 4 — Simple Transforms (pipeline order) - -1. `prune_unused_labels` (~50 lines) — removes unnecessary labels emitted by BuildReactiveFunction -2. `prune_non_reactive_dependencies` (~40 lines) — removes non-reactive deps from scopes -3. `prune_unused_scopes` (~60 lines) — converts scopes without outputs to pruned-scopes -4. `prune_always_invalidating_scopes` (~80 lines) — removes always-invalidating scopes -5. `prune_unused_lvalues` (~70 lines) — nulls out unused lvalues -6. `stabilize_block_ids` (~60 lines) — renumbers block IDs for stable output - -### Phase 5 — Complex Transforms (pipeline order) - -1. `prune_non_escaping_scopes` (~500 lines) — most complex reactive pass, removes scopes for non-escaping values -2. `merge_reactive_scopes_that_invalidate_together` (~400 lines) — merges adjacent scopes with same deps -3. `propagate_early_returns` (~200 lines) — handles early returns inside reactive scopes -4. `promote_used_temporaries` (~400 lines) — promotes temporaries to named variables -5. `extract_scope_declarations_from_destructuring` (~150 lines) — handles destructuring in scope declarations -6. `rename_variables` (~200 lines) — renames variables for output, returns `HashSet<String>` -7. `prune_hoisted_contexts` (~100 lines) — removes hoisted context declarations - -### Phase 6 — Codegen (deferred, separate plan) - -- `codegen_function` (~2000+ lines) — converts ReactiveFunction to CodegenFunction (Babel AST) -- Depends on Babel AST output types being available in Rust -- Will be planned separately - -## 6. Pass Signatures - -```rust -// Construction: -pub fn build_reactive_function(hir: &HirFunction, env: &Environment) -> ReactiveFunction; - -// Debug printing: -pub fn debug_reactive_function(func: &ReactiveFunction, env: &Environment) -> String; - -// Validation (read-only): -pub fn assert_well_formed_break_targets(func: &ReactiveFunction) -> Result<(), CompilerDiagnostic>; -pub fn assert_scope_instructions_within_scopes(func: &ReactiveFunction, env: &Environment) -> Result<(), CompilerDiagnostic>; - -// Transforms (no env needed): -pub fn prune_unused_labels(func: &mut ReactiveFunction); -pub fn stabilize_block_ids(func: &mut ReactiveFunction); - -// Transforms (read env for scope/identifier data): -pub fn prune_non_escaping_scopes(func: &mut ReactiveFunction, env: &Environment); -pub fn prune_non_reactive_dependencies(func: &mut ReactiveFunction, env: &Environment); -pub fn prune_unused_scopes(func: &mut ReactiveFunction, env: &Environment); -pub fn prune_always_invalidating_scopes(func: &mut ReactiveFunction, env: &Environment); -pub fn prune_unused_lvalues(func: &mut ReactiveFunction, env: &Environment); -pub fn promote_used_temporaries(func: &mut ReactiveFunction, env: &Environment); -pub fn prune_hoisted_contexts(func: &mut ReactiveFunction, env: &Environment); - -// Transforms (mutate env — create temporaries, modify scope data): -pub fn merge_reactive_scopes_that_invalidate_together(func: &mut ReactiveFunction, env: &mut Environment); -pub fn propagate_early_returns(func: &mut ReactiveFunction, env: &mut Environment); -pub fn rename_variables(func: &mut ReactiveFunction, env: &mut Environment) -> HashSet<String>; -pub fn extract_scope_declarations_from_destructuring(func: &mut ReactiveFunction, env: &mut Environment); - -// Validation (optional, gated on config): -pub fn validate_preserved_manual_memoization(func: &ReactiveFunction, env: &Environment) -> Result<(), CompilerDiagnostic>; -``` - -## 7. Visitor/Transform Framework - -Use closure-based traversal helpers and direct recursion, matching the existing Rust codebase style (standalone functions, not trait hierarchies). - -```rust -/// Read-only traversal of all statements in a block (recursive into nested blocks) -pub fn visit_reactive_block(block: &ReactiveBlock, visitor: &mut impl FnMut(&ReactiveStatement)); - -/// Mutating traversal with drain-and-rebuild pattern -pub fn transform_reactive_block( - block: &mut ReactiveBlock, - transform: &mut impl FnMut(ReactiveStatement) -> TransformResult, -); - -pub enum TransformResult { - Keep(ReactiveStatement), - Remove, - Replace(ReactiveStatement), - ReplaceMany(Vec<ReactiveStatement>), -} - -/// Iterate over all Place operands in a ReactiveValue -pub fn each_reactive_value_operand(value: &ReactiveValue) -> impl Iterator<Item = &Place>; - -/// Map over all blocks contained in a ReactiveTerminal -pub fn map_terminal_blocks(terminal: &mut ReactiveTerminal, f: impl FnMut(&mut ReactiveBlock)); -``` - -The drain-and-rebuild pattern for transforms: -1. `let stmts: Vec<_> = block.drain(..).collect();` -2. For each statement, call the transform closure -3. Collect results into a new Vec -4. Assign back to `*block` - -This avoids borrow checker issues with in-place mutation while iterating. - -## 8. Skill Updates - -### `compiler/.claude/skills/compiler-orchestrator/SKILL.md` - -Expand pass table rows #32-#49: - -| # | Log Name | Kind | Notes | -|---|----------|------|-------| -| 32 | BuildReactiveFunction | reactive | | -| 33 | AssertWellFormedBreakTargets | debug | Validation | -| 34 | PruneUnusedLabels | reactive | | -| 35 | AssertScopeInstructionsWithinScopes | debug | Validation | -| 36 | PruneNonEscapingScopes | reactive | | -| 37 | PruneNonReactiveDependencies | reactive | | -| 38 | PruneUnusedScopes | reactive | | -| 39 | MergeReactiveScopesThatInvalidateTogether | reactive | | -| 40 | PruneAlwaysInvalidatingScopes | reactive | | -| 41 | PropagateEarlyReturns | reactive | | -| 42 | PruneUnusedLValues | reactive | | -| 43 | PromoteUsedTemporaries | reactive | | -| 44 | ExtractScopeDeclarationsFromDestructuring | reactive | | -| 45 | StabilizeBlockIds | reactive | | -| 46 | RenameVariables | reactive | | -| 47 | PruneHoistedContexts | reactive | | -| 48 | ValidatePreservedManualMemoization | debug | Conditional | -| 49 | Codegen | ast | | - -Remove "BLOCKED" status from #32. Add crate mapping: `src/ReactiveScopes/` -> `react_compiler_reactive_scopes`. - -### `compiler/.claude/skills/compiler-port/SKILL.md` - -- **Step 0**: Remove the block on `kind: 'reactive'` passes (currently says "report that test-rust-port only supports `hir` kind passes currently and stop") -- **Step 1**: Add `src/ReactiveScopes/` -> `react_compiler_reactive_scopes` to the TS-to-Rust crate mapping table -- **Step 2**: Add reactive types file to context gathering list - -### `compiler/.claude/agents/port-pass.md` - -- Add note that reactive passes take `&mut ReactiveFunction` + `&Environment`/`&mut Environment` (not `&mut HirFunction`) -- Test command remains: `bash compiler/scripts/test-rust-port.sh <PassName>` - -## 9. Key Files - -| File | Action | -|------|--------| -| `compiler/crates/react_compiler_hir/src/reactive.rs` | Create: all reactive types | -| `compiler/crates/react_compiler_hir/src/lib.rs` | Edit: `pub mod reactive; pub use reactive::*;` | -| `compiler/crates/react_compiler_reactive_scopes/` | Create: new crate | -| `compiler/crates/Cargo.toml` (workspace) | Edit: add member | -| `compiler/crates/react_compiler/Cargo.toml` | Edit: add dependency | -| `compiler/crates/react_compiler/src/debug_print.rs` | Edit: extract shared helpers as `pub` | -| `compiler/crates/react_compiler/src/entrypoint/pipeline.rs` | Edit: wire reactive passes | -| `compiler/packages/.../src/HIR/DebugPrintReactiveFunction.ts` | Create: verbose debug printer | -| `compiler/packages/.../src/HIR/index.ts` | Edit: export | -| `compiler/scripts/test-rust-port.ts` | Edit: handle `kind: 'reactive'` | -| `compiler/.claude/skills/compiler-orchestrator/SKILL.md` | Edit: expand pass table | -| `compiler/.claude/skills/compiler-port/SKILL.md` | Edit: remove reactive block, add crate mapping | -| `compiler/.claude/agents/port-pass.md` | Edit: add reactive pass patterns | - -## 10. TS Source Files Reference - -| Pass | TS Source | -|------|-----------| -| BuildReactiveFunction | `src/ReactiveScopes/BuildReactiveFunction.ts` | -| AssertWellFormedBreakTargets | `src/ReactiveScopes/AssertWellFormedBreakTargets.ts` | -| PruneUnusedLabels | `src/ReactiveScopes/PruneUnusedLabels.ts` | -| AssertScopeInstructionsWithinScopes | `src/ReactiveScopes/AssertScopeInstructionsWithinScopes.ts` | -| PruneNonEscapingScopes | `src/ReactiveScopes/PruneNonEscapingScopes.ts` | -| PruneNonReactiveDependencies | `src/ReactiveScopes/PruneNonReactiveDependencies.ts` | -| PruneUnusedScopes | `src/ReactiveScopes/PruneUnusedScopes.ts` | -| MergeReactiveScopesThatInvalidateTogether | `src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts` | -| PruneAlwaysInvalidatingScopes | `src/ReactiveScopes/PruneAlwaysInvalidatingScopes.ts` | -| PropagateEarlyReturns | `src/ReactiveScopes/PropagateEarlyReturns.ts` | -| PruneUnusedLValues | `src/ReactiveScopes/PruneTemporaryLValues.ts` | -| PromoteUsedTemporaries | `src/ReactiveScopes/PromoteUsedTemporaries.ts` | -| ExtractScopeDeclarationsFromDestructuring | `src/ReactiveScopes/ExtractScopeDeclarationsFromDestructuring.ts` | -| StabilizeBlockIds | `src/ReactiveScopes/StabilizeBlockIds.ts` | -| RenameVariables | `src/ReactiveScopes/RenameVariables.ts` | -| PruneHoistedContexts | `src/ReactiveScopes/PruneHoistedContexts.ts` | -| ValidatePreservedManualMemoization | `src/Validation/ValidatePreservedManualMemoization.ts` | -| Visitors/Transform | `src/ReactiveScopes/visitors.ts` | -| PrintReactiveFunction | `src/ReactiveScopes/PrintReactiveFunction.ts` | -| CodegenReactiveFunction | `src/ReactiveScopes/CodegenReactiveFunction.ts` | diff --git a/compiler/docs/rust-port/rust-port-research.md b/compiler/docs/rust-port/rust-port-research.md deleted file mode 100644 index 2b58e7a337e9..000000000000 --- a/compiler/docs/rust-port/rust-port-research.md +++ /dev/null @@ -1,1460 +0,0 @@ -# React Compiler: Rust Port Feasibility Research - -## Table of Contents - -1. [Executive Summary](#executive-summary) -2. [Key Data Structures](#key-data-structures) -3. [The Shared Mutable Reference Problem](#the-shared-mutable-reference-problem) -4. [Environment as Shared Mutable State](#environment-as-shared-mutable-state) -5. [Side Maps: Passes Storing HIR References](#side-maps-passes-storing-hir-references) -6. [AliasingEffect: Shared References and Rust Ownership](#aliasingeffect-shared-references-and-rust-ownership) -7. [Recommended Rust Architecture](#recommended-rust-architecture) -8. [Input/Output Format](#inputoutput-format) -9. [Error Handling](#error-handling) -10. [Structural Similarity: TypeScript ↔ Rust Alignment](#structural-similarity-typescript--rust-alignment) -11. [Pipeline Overview](#pipeline-overview) -12. [Pass-by-Pass Analysis](#pass-by-pass-analysis) - - [Phase 1: Lowering (AST to HIR)](#phase-1-lowering) - - [Phase 2: Normalization](#phase-2-normalization) - - [Phase 3: SSA Construction](#phase-3-ssa-construction) - - [Phase 4: Optimization (Pre-Inference)](#phase-4-optimization-pre-inference) - - [Phase 5: Type and Effect Inference](#phase-5-type-and-effect-inference) - - [Phase 6: Mutation/Aliasing Analysis](#phase-6-mutationaliasing-analysis) - - [Phase 7: Optimization (Post-Inference)](#phase-7-optimization-post-inference) - - [Phase 8: Reactivity Inference](#phase-8-reactivity-inference) - - [Phase 9: Scope Construction](#phase-9-scope-construction) - - [Phase 10: Scope Alignment and Merging](#phase-10-scope-alignment-and-merging) - - [Phase 11: Scope Terminal Construction](#phase-11-scope-terminal-construction) - - [Phase 12: Scope Dependency Propagation](#phase-12-scope-dependency-propagation) - - [Phase 13: Reactive Function Construction](#phase-13-reactive-function-construction) - - [Phase 14: Reactive Function Transforms](#phase-14-reactive-function-transforms) - - [Phase 15: Codegen](#phase-15-codegen) - - [Validation Passes](#validation-passes) -13. [External Dependencies](#external-dependencies) -14. [Risk Assessment](#risk-assessment) -15. [Recommended Migration Strategy](#recommended-migration-strategy) - ---- - -## Executive Summary - -Porting the React Compiler from TypeScript to Rust is **feasible and the Rust code can remain structurally very close to the TypeScript**. The compiler's algorithms are well-suited to Rust. The TypeScript implementation relies on patterns that conflict with Rust's ownership model, but all have clean, well-understood solutions using arenas and indirect references: - -1. **Shared Identifier references**: Multiple `Place` objects reference the same `Identifier` object. **Solution**: Arena-allocated identifiers on `Environment`, referenced by copyable `IdentifierId` index. - -2. **Shared ReactiveScope references**: Multiple identifiers share the same `ReactiveScope` object (including its mutable range). **Solution**: Arena-allocated scopes on `Environment`, referenced by `ScopeId`. - -3. **Inner function storage**: `FunctionExpression`/`ObjectMethod` instructions store inner `HIRFunction` values inline. **Solution**: Arena-allocated functions on `Environment`, referenced by `FunctionId`. - -4. **Type storage**: Types stored inline on identifiers. **Solution**: Arena-allocated types on `Environment`, referenced by `TypeId`. - -5. **Instructions stored inline in blocks**: `BasicBlock.instructions` stores `Instruction` objects directly. **Solution**: Flat instruction table on `HIRFunction`, referenced by `InstructionId`. The existing `InstructionId` (evaluation order counter) is renamed to `EvaluationOrder` since it applies to both instructions and terminals. - -6. **Environment as shared mutable singleton**: The `Environment` object is threaded through the entire compilation via `fn.env` and mutated by many passes. **Solution**: Remove `HIRFunction.env` and pass `env: &mut Environment` separately. Maintain existing fields (no sub-struct grouping) to allow precise sliced borrows via direct field access. - -**Key finding on structural similarity**: After deep analysis of every pass, the vast majority of compiler passes can be ported to Rust with **~85-95% structural correspondence** — meaning you could view the TypeScript and Rust side-by-side and easily trace the logic. The main mechanical differences are: -- `match` instead of `switch` (exhaustive by default in Rust) -- `HashMap<IdentifierId, T>` instead of `Map<Identifier, T>` (reference identity → value identity) -- `Vec::retain()` instead of delete-during-Set-iteration -- `std::mem::replace` / `std::mem::take` for in-place enum variant swaps -- Two-phase collect/apply instead of mutate-through-stored-references - -**Complexity breakdown** (revised after deep per-pass analysis): -- ~25 passes are straightforward to port (simple traversal, local mutation, ID-only side maps) -- ~13 passes require moderate refactoring (stored references → IDs, iteration order changes) -- ~4 passes require significant redesign (InferMutationAliasingRanges, BuildHIR, CodegenReactiveFunction, AnalyseFunctions) -- Input/output boundaries use JSON AST interchange via serde, with a Rust Babel AST type - -**Input/output format**: Define a Rust representation of the Babel AST format using serde with custom serialization/deserialization (ensuring the `"type"` field is always produced, even outside of enum positions). Include full information from Babel, including source locations. A `Scope` type encodes the tree of scope information mapping to Babel's scope tree. The main public API is `compile(BabelAst, Scope) -> Option<BabelAst>`, returning `None` if no changes. - -**Error handling**: Two categories — errors that would have thrown in TypeScript (invariants, todo errors, short-circuiting) return `Err(CompilerDiagnostic)` via `Result`, while non-throwing accumulated diagnostics are recorded directly on `Environment`. TypeScript non-null assertions become `.unwrap()` panics. - -**Note on InferMutationAliasingEffects**: Previously categorized as "significant redesign" due to maps using JS reference identity with `InstructionValue` keys. An upstream refactor ([PR #33650](https://github.com/facebook/react/pull/33650)) replaces `InstructionValue` with interned `AliasingEffect` as allocation-site keys, eliminating synthetic InstructionValues and the `effectInstructionValueCache`. Since effects are already interned by content hash, they map directly to a copyable `EffectId` index in Rust. Additionally, `AliasingEffect` variants share `Place` references with `InstructionValue` fields — in Rust, Places are cloned cheaply (with arena-based `IdentifierId`). The `CreateFunction` variant's `FunctionExpression` reference is replaced with a `FunctionId` referencing the function arena on `Environment`. See [§AliasingEffect section](#aliasingeffect-shared-references-and-rust-ownership) for the full analysis. This is "moderate refactoring" — no algorithmic redesign needed. - ---- - -## Key Data Structures - -### HIRFunction -``` -HIRFunction { - body: HIR { - entry: BlockId, - blocks: Map<BlockId, BasicBlock> // ordered map, reverse postorder - }, - instructions: Vec<Instruction>, // flat instruction table, indexed by InstructionId - params: Array<Place | SpreadPattern>, - returns: Place, - context: Array<Place>, // captured variables from outer scope - aliasingEffects: Array<AliasingEffect> | null, -} -``` - -**Note**: `env` is removed from `HIRFunction` and passed separately as `env: &mut Environment`. Inner functions are stored in the function arena on `Environment` (see [§Recommended Rust Architecture](#recommended-rust-architecture)). - -### BasicBlock -``` -BasicBlock { - id: BlockId, - kind: 'block' | 'value' | 'loop' | 'sequence' | 'catch', - instructions: Vec<InstructionId>, // indices into HIRFunction.instructions - terminal: Terminal, // control flow (goto, if, for, return, etc.) - preds: Set<BlockId>, - phis: Set<Phi>, // SSA join points -} -``` - -### Instruction -``` -Instruction { - order: EvaluationOrder, // evaluation order (renamed from InstructionId) - lvalue: Place, // destination - value: InstructionValue, // discriminated union (~40 variants) - effects: Array<AliasingEffect> | null, // populated by InferMutationAliasingEffects - loc: SourceLocation, -} -``` - -**Note**: The previous `InstructionId` type is renamed to `EvaluationOrder` because it represents evaluation order and is not instruction-specific (terminals also carry it). A new `InstructionId` type is introduced as an index into the `HIRFunction.instructions` table, allowing passes to reference instructions by a single copyable ID rather than `(BlockId, usize)`. - -### Place (CRITICAL for Rust port) -``` -Place { - kind: 'Identifier', - identifier: IdentifierId, // ← index into Identifier arena on Environment (shared reference in TS) - effect: Effect, // Read, Mutate, Capture, Freeze, etc. - reactive: boolean, // set by InferReactivePlaces - loc: SourceLocation, -} -``` - -### Identifier (CRITICAL for Rust port) -``` -Identifier { - id: IdentifierId, // unique after SSA (opaque number) - declarationId: DeclarationId, - name: IdentifierName | null, // null for temporaries, mutated by RenameVariables - mutableRange: MutableRange, // { start, end } — mutated by InferMutationAliasingRanges - scope: ScopeId | null, // index into scope arena — mutated by InferReactiveScopeVariables - type: TypeId, // index into type arena — mutated by InferTypes - loc: SourceLocation, -} -``` - -### FunctionExpression / ObjectMethod -``` -FunctionExpression { - loweredFunc: FunctionId, // index into function arena on Environment - ... // other fields remain inline -} -``` - -**Note**: Inner `HIRFunction` values are stored in a function arena on `Environment`, referenced by `FunctionId`. This replaces inline storage and provides a stable, copyable reference for passes that need to cache or access inner functions. - -### ReactiveScope -``` -ReactiveScope { - id: ScopeId, - range: MutableRange, // mutated by alignment passes - dependencies: Set<ReactiveScopeDependency>, // populated by PropagateScopeDependencies - declarations: Map<IdentifierId, ReactiveScopeDeclaration>, - reassignments: Set<IdentifierId>, - earlyReturnValue: { value: IdentifierId, loc, label } | null, - merged: Set<ScopeId>, -} -``` - -### MutableRange -``` -MutableRange { - start: EvaluationOrder, // inclusive (renamed from InstructionId) - end: EvaluationOrder, // exclusive -} -``` - ---- - -## The Shared Mutable Reference Problem - -This is the **central challenge** for a Rust port. In TypeScript, the compiler relies on JavaScript's reference semantics in three pervasive patterns: - -### Pattern 1: Shared Identifier Mutation -```typescript -// Multiple Place objects share the SAME Identifier object -const place1: Place = { identifier: someIdentifier, ... }; -const place2: Place = { identifier: someIdentifier, ... }; // same object! - -// A pass mutates the identifier through one place... -place1.identifier.mutableRange.end = 42; - -// ...and the change is visible through the other -console.log(place2.identifier.mutableRange.end); // 42 -``` - -Used by: InferMutationAliasingRanges, InferReactiveScopeVariables, InferTypes, InferReactivePlaces, RenameVariables, PromoteUsedTemporaries, EnterSSA, EliminateRedundantPhi, AnalyseFunctions, and many more. - -### Pattern 2: Shared ReactiveScope References -```typescript -// Multiple Identifiers share the same ReactiveScope AND MutableRange -identifier.mutableRange = scope.range; // line 132 of InferReactiveScopeVariables - -// Now identifier.mutableRange IS scope.range (same JS object) -// A pass expands the scope range... -scope.range.end = 100; - -// ...visible through the identifier -console.log(identifier.mutableRange.end); // 100 -``` - -This is explicitly noted in AnalyseFunctions.ts (line 30-34): "NOTE: inferReactiveScopeVariables makes identifiers in the scope point to the *same* mutableRange instance." - -Used by: AlignMethodCallScopes, AlignObjectMethodScopes, AlignReactiveScopesToBlockScopesHIR, MergeOverlappingReactiveScopesHIR, MemoizeFbtAndMacroOperandsInSameScope. - -### Pattern 3: Iterate-and-Mutate / Side Map References -```typescript -// Store a reference to an HIR object in a side map -const nodes: Map<Identifier, Node> = new Map(); -nodes.set(identifier, { id: identifier, ... }); - -// Later, mutate the object through the stored reference -node.id.mutableRange.end = 42; // mutates HIR through map reference -``` - -Used by: InferMutationAliasingRanges (AliasingState.nodes), EnterSSA (SSABuilder.#states.defs), InferMutationAliasingEffects (Context caches — see note below about upstream simplification), DropManualMemoization (sidemap.manualMemos), InlineIIFEs (functions map), AlignReactiveScopesToBlockScopesHIR (activeScopes), and others. - ---- - -## Environment as Shared Mutable State - -### Complete Environment Analysis - -Environment is created once per top-level function compilation and stored on `HIRFunction.env`. It is shared via reference across the entire compilation, including nested functions. - -#### Mutable State (mutated by passes) -| Field | Mutated by | Pattern | -|-------|-----------|---------| -| `#nextIdentifer: number` | BuildHIR, EnterSSA, OutlineJSX, InferMutationAliasingEffects (via `createTemporaryPlace`) | Auto-increment counter | -| `#nextBlock: number` | BuildHIR, InlineIIFEs | Auto-increment counter | -| `#nextScope: number` | InferReactiveScopeVariables | Auto-increment counter | -| `#errors: CompilerError` | All validation passes, DropManualMemoization, InferMutationAliasingRanges, CodegenReactiveFunction | Append-only accumulator | -| `#outlinedFunctions: Array` | OutlineJSX, OutlineFunctions | Append-only list | -| `#moduleTypes: Map` | `getGlobalDeclaration` (lazy cache fill) | One-time lazy initialization | - -#### Read-Only State (accessed but never mutated) -| Field | Accessed by | -|-------|------------| -| `config: EnvironmentConfig` | Pipeline.ts (feature flags), InferMutationAliasingEffects, DropManualMemoization, MemoizeFbtAndMacroOperandsInSameScope, InferReactiveScopeVariables | -| `fnType: ReactFunctionType` | Pipeline.ts | -| `outputMode: CompilerOutputMode` | Pipeline.ts, DeadCodeElimination | -| `#globals: GlobalRegistry` | InferTypes (via `getGlobalDeclaration`), DropManualMemoization | -| `#shapes: ShapeRegistry` | InferTypes (via `getPropertyType`, `getFunctionSignature`), InferMutationAliasingEffects, InferReactivePlaces, FlattenScopesWithHooksOrUseHIR, NameAnonymousFunctions | -| `logger` | Pipeline.ts, AnalyseFunctions | -| `programContext` | BuildHIR, CodegenReactiveFunction, OutlineJSX | - -#### How Environment is Shared with Nested Functions - -Parent and nested functions share the **exact same Environment instance**. When `lower()` is called for a nested function expression, it receives the same `env`. This means: -- ID counters are globally unique across the entire function tree -- Errors from inner function compilation are visible to the parent -- Outlined functions from inner compilations accumulate on the shared list -- Configuration is shared (same feature flags everywhere) - -This sharing is sequential, not concurrent: `AnalyseFunctions` processes each child function synchronously before returning to the parent. - -### Recommended Rust Representation - -Remove `HIRFunction.env` and pass `env: &mut Environment` as a separate parameter to passes. Maintain the existing fields and types of the `Environment` struct — do not group them into sub-structs. Use direct field access (rather than methods) to allow precise sliced borrows of portions of the environment. - -```rust -struct Environment { - // Configuration (read-only after construction) - config: EnvironmentConfig, - fn_type: ReactFunctionType, - output_mode: CompilerOutputMode, - - // Type registries (read-only after lazy init) - globals: GlobalRegistry, - shapes: ShapeRegistry, - module_types: HashMap<String, Option<Global>>, - - // Mutable counters - next_identifier: IdentifierId, - next_block: BlockId, - next_scope: ScopeId, - - // Arenas - identifiers: Vec<Identifier>, // indexed by IdentifierId - scopes: Vec<ReactiveScope>, // indexed by ScopeId - functions: Vec<HIRFunction>, // indexed by FunctionId - types: Vec<Type>, // indexed by TypeId - - // Accumulated state - errors: Vec<CompilerDiagnostic>, - outlined_functions: Vec<OutlinedFunction>, - - // Other - logger: Option<Logger>, - program_context: ProgramContext, -} -``` - -**Why no sub-structs**: Keeping all fields flat on `Environment` allows Rust's borrow checker to reason about independent field borrows. For example, a pass can simultaneously borrow `env.identifiers` and `env.config` without conflict, because the borrow checker can see they are distinct fields. Grouping fields into sub-structs would require borrowing the entire sub-struct even when only one field is needed. - -**Pass signatures** return `Result` for errors that would have thrown in TypeScript: - -```rust -// Most passes: need mutable HIR + mutable environment -fn enter_ssa(func: &mut HIRFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> { ... } - -// Validation passes: -fn validate_hooks_usage(func: &HIRFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> { ... } - -// Passes that don't use env at all (many!): -fn merge_consecutive_blocks(func: &mut HIRFunction) { ... } -fn constant_propagation(func: &mut HIRFunction) { ... } -``` - -**Key insight from per-pass analysis**: The majority of passes (PruneMaybeThrows, MergeConsecutiveBlocks, ConstantPropagation, EliminateRedundantPhi, OptimizePropsMethodCalls, DeadCodeElimination, RewriteInstructionKinds, PruneUnusedLabelsHIR, FlattenReactiveLoopsHIR, and all reactive function transforms) do NOT use Environment at all. Only ~12 passes need `env`, and most only read config flags or call `getHookKind()`. - -For the `AnalyseFunctions` recursive pattern (where parent and child share the same Environment), `&mut Environment` works naturally because the recursive call completes before the parent continues — there is only one `&mut` active at a time. - ---- - -## Side Maps: Passes Storing HIR References - -### The Core Problem - -Many passes store references to HIR values (Places, Identifiers, Instructions, InstructionValues, ReactiveScopes) in "side maps" (HashMaps, Sets, arrays) while simultaneously mutating the HIR. In Rust, this creates borrow conflicts because you cannot hold an immutable reference (in the map) while mutating through a different path. - -### Classification of Side Map Patterns - -After analyzing every pass, side map patterns fall into four categories: - -#### Category 1: ID-Only Maps (No Borrow Issues) -Maps keyed and valued by opaque IDs (`IdentifierId`, `BlockId`, `ScopeId`, `InstructionId`, `DeclarationId`). These are `Copy` types with no aliasing concerns. - -**Passes**: PruneMaybeThrows, MergeConsecutiveBlocks, ConstantPropagation, DeadCodeElimination, RewriteInstructionKinds, InferReactivePlaces (reactive set), PruneUnusedLabelsHIR, FlattenReactiveLoopsHIR, FlattenScopesWithHooksOrUseHIR, StabilizeBlockIds, and most reactive function transforms. - -**Rust approach**: Direct `HashMap<IdType, T>` / `HashSet<IdType>`. No changes needed. - -#### Category 2: Reference-Identity Maps (Replace Keys with IDs) -Maps using JavaScript object identity (`===`) as the key, typically `Map<Identifier, T>` or `Map<BasicBlock, T>` or `DisjointSet<Identifier>` / `DisjointSet<ReactiveScope>`. - -**Passes**: EnterSSA (`Map<BasicBlock, State>`, `Map<Identifier, Identifier>`), EliminateRedundantPhi (`Map<Identifier, Identifier>`), InferMutationAliasingRanges (`Map<Identifier, Node>`), InferReactiveScopeVariables (`DisjointSet<Identifier>`), InferReactivePlaces (`DisjointSet<Identifier>`), AlignMethodCallScopes (`DisjointSet<ReactiveScope>`), AlignObjectMethodScopes (`Set<Identifier>`, `DisjointSet<ReactiveScope>`), MergeOverlappingReactiveScopes (`DisjointSet<ReactiveScope>`). - -**Rust approach**: Replace with `HashMap<IdentifierId, T>`, `HashMap<BlockId, T>`, `DisjointSet<IdentifierId>`, `DisjointSet<ScopeId>`. This is **always simpler and more correct** than the TypeScript — it eliminates an entire class of bugs where cloned objects silently fail identity checks. - -#### Category 3: Instruction/Value Reference Maps (Store Indices Instead) -Maps that store references to actual `Instruction`, `FunctionExpression`, or `InstructionValue` objects, then later access fields on those objects or mutate them. - -**Passes**: InferMutationAliasingEffects (`Map<Instruction, InstructionSignature>`, `Map<FunctionExpression, AliasingSignature>`), DropManualMemoization (`Map<IdentifierId, TInstruction<FunctionExpression>>`, `ManualMemoCallee.loadInstr`), InlineIIFEs (`Map<IdentifierId, FunctionExpression>`), NameAnonymousFunctions (`Node.fn: FunctionExpression`). - -**Note**: InferMutationAliasingEffects currently uses `Map<InstructionValue, AbstractValue>` and `Map<IdentifierId, Set<InstructionValue>>` with `InstructionValue` objects as allocation-site identity tokens (JS reference identity), including both real InstructionValues from the HIR (for `CreateFunction`) and synthetic objects fabricated as allocation-site markers. An upstream refactor ([PR #33650](https://github.com/facebook/react/pull/33650)) replaces all `InstructionValue` keys with interned `AliasingEffect` objects, eliminating the synthetic InstructionValues and `effectInstructionValueCache` entirely. Since effects are already interned by content hash, reference identity equals content identity — exactly what's needed for Rust. In Rust, the `EffectId` (index into the interning table) serves as the allocation-site key directly. See [§AliasingEffect section](#aliasingeffect-shared-references-and-rust-ownership) for the full analysis. - -**Rust approach**: Store only what is actually needed: -- If the map is for existence checking: use `HashSet<IdentifierId>` -- If specific fields are needed later: extract and store those fields (e.g., store `InstructionId` to reference the instruction table) -- Instructions are stored in a flat table on `HIRFunction`, referenced by `InstructionId` — passes can reference any instruction by a single copyable ID -- `FunctionExpression`/`ObjectMethod` inner functions are accessed via `FunctionId` referencing the function arena on `Environment` -- For InferMutationAliasingEffects: use `InstructionId` for instruction signature cache, `EffectId` (interning table index) for value-identity maps, `FunctionId` for function signature caches - -#### Category 4: Scope Reference Sets with In-Place Mutation (Arena Access) -Sets or maps of `ReactiveScope` references where the scope's `range` fields are mutated while the scope is in the collection. - -**Passes**: AlignReactiveScopesToBlockScopesHIR (`Set<ReactiveScope>` iterated while mutating `scope.range`), AlignMethodCallScopes (DisjointSet forEach with range mutation), AlignObjectMethodScopes (same pattern), MergeOverlappingReactiveScopesHIR (DisjointSet with range mutation), MemoizeFbtAndMacroOperandsInSameScope (scope range mutation). - -**Rust approach**: Store `ScopeId` in sets/DisjointSets. Mutate through arena: `env.scopes[scope_id].range.start = ...`. The set holds copyable IDs, and the mutation goes through the arena — completely disjoint borrows. - -### Critical Insight: The Shared MutableRange Aliasing - -The most architecturally significant side map pattern is in `InferReactiveScopeVariables` (line 132): -```typescript -identifier.mutableRange = scope.range; -``` - -This makes ALL identifiers in a scope share the SAME `MutableRange` object as the scope. Every subsequent scope-alignment pass relies on this: mutating `scope.range.start` automatically updates all identifiers' `mutableRange`. - -**Recommended Rust approach**: Identifiers store `scope: Option<ScopeId>`. The "effective mutable range" is always accessed through the scope arena: -```rust -fn effective_mutable_range(id: &Identifier, scopes: &[ReactiveScope]) -> MutableRange { - match id.scope { - Some(scope_id) => scopes[scope_id.index()].range, - None => id.mutable_range, // pre-scope original range - } -} -``` - -All downstream passes that read `identifier.mutableRange` (like `isMutable()`, `inRange()`) would need access to `env.scopes`. This is a mechanical refactor — every call site accesses the scope arena via `Environment`. - ---- - -## AliasingEffect: Shared References and Rust Ownership - -### Overview - -`AliasingEffect` is a discriminated union (17 variants) that describes data flow, mutation, and other side effects of instructions and terminals. Effects are **created** by `InferMutationAliasingEffects`, stored on `Instruction.effects` and `Terminal.effects`, and **consumed** by `InferMutationAliasingRanges`, `AnalyseFunctions`, validation passes, and `PrintHIR`. This section analyzes the shared references between `AliasingEffect` variants, `Instruction`, and `InstructionValue`, and how they map to Rust ownership. - -### Shared Reference Inventory - -Every `AliasingEffect` variant contains `Place` objects. In the TypeScript implementation, these are the **same JS object references** as the Places in the `InstructionValue` and `Instruction.lvalue` — not copies. This creates a web of shared references: - -#### Category A: Place Sharing (Instruction/InstructionValue → Effect) - -Nearly every instruction kind in `computeSignatureForInstruction` creates effects that directly reference Places from the instruction: - -| InstructionValue Kind | Effect Created | Shared Place Fields | -|---|---|---| -| `ArrayExpression` | `Create into:lvalue`, `Capture from:element into:lvalue` | `lvalue`, each `element` from `value.elements` | -| `ObjectExpression` | `Create into:lvalue`, `Capture from:property.place into:lvalue` | `lvalue`, each `property.place` from `value.properties` | -| `PropertyStore/ComputedStore` | `Mutate value:object`, `Capture from:value into:object` | `value.object`, `value.value`, `lvalue` | -| `PropertyLoad/ComputedLoad` | `CreateFrom from:object into:lvalue` | `value.object`, `lvalue` | -| `PropertyDelete/ComputedDelete` | `Mutate value:object` | `value.object`, `lvalue` | -| `Destructure` | `CreateFrom from:value.value into:place` per pattern item | `value.value`, each pattern item place | -| `JsxExpression` | `Freeze value:operand`, `Capture`, `Render place:tag/child` | `lvalue`, `value.tag`, each child, each prop place | -| `GetIterator` | `Alias/Capture from:collection into:lvalue` | `value.collection`, `lvalue` | -| `IteratorNext` | `MutateConditionally value:iterator`, `CreateFrom from:collection` | `value.iterator`, `value.collection`, `lvalue` | -| `StoreLocal` | `Assign from:value.value into:value.lvalue.place` | `value.value`, `value.lvalue.place`, `lvalue` | -| `LoadLocal` | `Assign from:value.place into:lvalue` | `value.place`, `lvalue` | -| `Await` | `MutateTransitiveConditionally value:value.value`, `Capture` | `value.value`, `lvalue` | - -#### Category B: Call Instructions — Deep Sharing via Apply - -For `CallExpression`, `MethodCall`, and `NewExpression`, a single `Apply` effect is created that shares **multiple fields** including the args array itself: - -```typescript -// From computeSignatureForInstruction (line 1832-1841) -effects.push({ - kind: 'Apply', - receiver, // same Place as value.receiver or value.callee - function: callee, // same Place as value.callee or value.property - mutatesFunction: ..., - args: value.args, // THE SAME ARRAY REFERENCE from InstructionValue - into: lvalue, // same Place as instruction.lvalue - signature, // shared FunctionSignature from type registry - loc: value.loc, -}); -``` - -The `args` field is the **exact same array object** as the InstructionValue's `args`. In Rust, this must be either cloned or accessed via the instruction. - -#### Category C: FunctionExpression — The Deepest Sharing - -The `CreateFunction` variant holds a direct reference to the `FunctionExpression` or `ObjectMethod` InstructionValue: - -```typescript -// From computeSignatureForInstruction (line 1946-1953) -effects.push({ - kind: 'CreateFunction', - into: lvalue, - function: value, // THE SAME FunctionExpression/ObjectMethod InstructionValue - captures: value.loweredFunc.func.context.filter( - operand => operand.effect === Effect.Capture, - ), -}); -``` - -This is the most architecturally significant sharing because `effect.function` is used in three distinct ways: - -1. **As an allocation-site token** in abstract interpretation (reference identity): - - `state.initialize(effect.function, {...})` → `#values.set(value, kind)` — FunctionExpression as map key - - `state.define(effect.into, effect.function)` → `#variables.set(id, new Set([value]))` — FunctionExpression as set value - -2. **For deep structural access**: - - `effect.function.loweredFunc.func.aliasingEffects` — reads the nested function's inferred effects - - `effect.function.loweredFunc.func.context` — iterates captured variables - -3. **For mutation** of the nested function's context: - - `operand.effect = Effect.Read` (line 838) — mutates `Place.effect` on the nested function's context variables - -**Rust approach**: `CreateFunction` stores a `FunctionId` referencing the function arena on `Environment`. Allocation-site identity uses `EffectId` (from effect interning), deep structural access uses `env.functions[function_id]`, and context mutation uses `&mut env.functions[function_id].context`. - -### Allocation-Site Identity: InstructionValue → AliasingEffect (PR #33650) - -The abstract interpretation in `InferenceState` tracks the abstract kind (Mutable, Frozen, Primitive, etc.) of each "allocation site" and which allocation sites each identifier points to. Currently this uses `InstructionValue` objects as allocation-site identity tokens via JS reference identity: - -``` -#values: Map<InstructionValue, AbstractValue> // InstructionValue as KEY (reference identity) -#variables: Map<IdentifierId, Set<InstructionValue>> // InstructionValue as SET VALUE -``` - -Allocation sites are created from: -- **Params/context variables**: Synthetic `{kind: 'Primitive'}` or `{kind: 'ObjectExpression'}` objects -- **`Create`/`CreateFrom` effects**: Synthetic InstructionValues via `effectInstructionValueCache` (maps interned effect → synthetic InstructionValue) -- **`CreateFunction` effects**: The actual `FunctionExpression` InstructionValue from the HIR - -**Upstream simplification** ([facebook/react#33650](https://github.com/facebook/react/pull/33650)): This PR replaces `InstructionValue` with the interned `AliasingEffect` itself as the allocation-site key: - -``` -#values: Map<AliasingEffect, AbstractValue> // interned AliasingEffect as KEY -#variables: Map<IdentifierId, Set<AliasingEffect>> -``` - -The changes: -1. **Params/context**: Synthetic `InstructionValue` objects are replaced with `AliasingEffect` objects (e.g., `{kind: 'Create', into: place, value: ValueKind.Context, reason: ValueReason.Other}`) -2. **`Create`/`CreateFrom` effects**: `effectInstructionValueCache` is eliminated entirely. `state.initialize(effect, ...)` and `state.define(place, effect)` use the interned effect directly as the key/value -3. **`CreateFunction` effects**: `state.initialize(effect.function, ...)` → `state.initialize(effect, ...)` — the CreateFunction effect itself is the key, not the FunctionExpression -4. **`state.values()` return type**: Changes from `Array<InstructionValue>` to `Array<AliasingEffect>`. Code that checks function values now uses `values[0].kind === 'CreateFunction'` and accesses `values[0].function` for the FunctionExpression -5. **`freezeValue` method**: Checks `value.kind === 'CreateFunction'` and accesses `value.function.loweredFunc.func.context` instead of `value.kind === 'FunctionExpression'` - -Since effects are already interned by content hash (via `context.internEffect()`), reference identity equals content identity. This means the interned `AliasingEffect` maps directly to a copyable `EffectId` index in Rust — no separate `AllocationSiteId` type is needed. - -**Key insight for CreateFunction**: After PR #33650, the `CreateFunction` effect's `function` field (the FunctionExpression/ObjectMethod reference) is **no longer used as a map key** for allocation-site tracking. It is only used for: -1. **Deep structural access**: `effect.function.loweredFunc.func.context` and `.aliasingEffects` -2. **As a key in `functionSignatureCache`**: `Map<FunctionExpression, AliasingSignature>` (the one remaining reference-identity map using FunctionExpression) -3. **Mutation**: `operand.effect = Effect.Read` on context variables - -In Rust, `CreateFunction` stores a `FunctionId` referencing the function arena on `Environment`. The function's context and aliasing effects are accessed via `env.functions[function_id]`. The allocation-site identity is the `EffectId` of the interned CreateFunction effect. The `functionSignatureCache` keys by `FunctionId` instead of FunctionExpression reference. - -### Effect Interning - -Effects are interned by content hash in `Context.internEffect()`: - -```typescript -internEffect(effect: AliasingEffect): AliasingEffect { - const hash = hashEffect(effect); // hash based on identifier IDs, not Place references - let interned = this.internedEffects.get(hash); - if (interned == null) { - this.internedEffects.set(hash, effect); - interned = effect; - } - return interned; -} -``` - -The hash uses `place.identifier.id` (a number) rather than Place reference identity. The interned effect retains the Place references from whichever instruction first created that hash. In the fixpoint loop, re-processing an instruction may produce an effect with the same hash but different Place objects; interning returns the **original** effect with its original Place references. This is safe in TypeScript (both Places point to the same shared Identifier), but in Rust it means the interned effect's Places may not be the "current" instruction's Places — they are equivalent by ID but different allocations. - -With PR #33650, the interned effect is also the allocation-site key. Since interning guarantees that the same `EffectId` is returned for structurally identical effects, the fixpoint loop correctly converges — the same allocation site is used across iterations. - -### Consumers: How Effects Are Read - -#### InferMutationAliasingRanges (primary consumer) - -Iterates `instr.effects` for every instruction and reads Place fields: -- `effect.into.identifier` → used as key in `AliasingState.nodes` and to call `state.create()` -- `effect.from.identifier` → used in `state.assign()`, `state.capture()`, `state.maybeAlias()` -- `effect.value.identifier` → stored in `mutations` array, passed to `state.mutate()` -- `effect.function.loweredFunc.func` → used in `state.create()` for Function nodes -- `effect.place.identifier` → stored in `renders` array for Render effects -- `effect.error` → for MutateFrozen/MutateGlobal/Impure, recorded on Environment - -Also reads terminal effects: `block.terminal.effects` for Alias and Freeze effects on maybe-throw/return terminals. - -Also reads effects a second time (Part 2, lines 359-421) to compute legacy per-operand `Effect` enum values. This pass accesses `effect.*.identifier.id` and `effect.*.identifier.mutableRange.end` through effect Places. - -**Key observation**: InferMutationAliasingRanges reads `identifier.id`, `identifier` (for the reference-identity map key), and `identifier.mutableRange` from effect Places. It never mutates them through the effect's Places (mutations go through the graph nodes). With arena-based identifiers, `place.identifier` is an `IdentifierId` (`Copy`), and `mutableRange` is accessed via the identifier arena. No Place reference comparison is done — all passes access identifiers through their IDs, never by comparing Place object references. - -#### AnalyseFunctions - -Reads `fn.aliasingEffects` (the function-level effects from `InferMutationAliasingRanges`) to populate context variable effect annotations: -- `effect.from.identifier.id` — for Assign/Alias/Capture/CreateFrom/MaybeAlias variants -- `effect.value.identifier.id` — for Mutate/MutateConditionally/MutateTransitive/MutateTransitiveConditionally - -Only reads identifier IDs. Does not access Places beyond `.identifier.id`. - -#### ValidateNoFreezingKnownMutableFunctions - -Reads `fn.aliasingEffects` on nested `FunctionExpression` values: -- Stores `Mutate`/`MutateTransitive` effects in `Map<IdentifierId, AliasingEffect>` -- Reads `effect.value.identifier.id`, `effect.value.identifier.name`, `effect.value.loc` - -Accesses Identifier fields (name, loc) beyond just the ID, but these are read-only. - -#### Other Passes (do NOT read AliasingEffects) - -`ValidateLocalsNotReassignedAfterRender`, `ValidateNoImpureFunctionsInRender`, and `PruneNonEscapingScopes` import from AliasingEffects.ts or InferMutationAliasingEffects.ts but only use `getFunctionCallSignature` or the legacy `Effect` enum on Places — they do not read `instr.effects` or `fn.aliasingEffects`. - -#### PrintHIR - -Reads all effect fields for debug output. Read-only. - -### Recommended Rust Representation - -#### AliasingEffect Enum - -With arena-based identifiers, `Place` becomes a small `Copy`/`Clone` struct. Effects can own cloned Places: - -```rust -#[derive(Clone)] -enum AliasingEffect { - Freeze { value: Place, reason: ValueReason }, - Mutate { value: Place, reason: Option<MutationReason> }, - MutateConditionally { value: Place }, - MutateTransitive { value: Place }, - MutateTransitiveConditionally { value: Place }, - Capture { from: Place, into: Place }, - Alias { from: Place, into: Place }, - MaybeAlias { from: Place, into: Place }, - Assign { from: Place, into: Place }, - Create { into: Place, value: ValueKind, reason: ValueReason }, - CreateFrom { from: Place, into: Place }, - ImmutableCapture { from: Place, into: Place }, - Render { place: Place }, - - Apply { - receiver: Place, - function: Place, - mutates_function: bool, - args: Vec<PlaceOrSpreadOrHole>, // cloned from InstructionValue - into: Place, - signature: Option<FunctionSignature>, - loc: SourceLocation, - }, - CreateFunction { - into: Place, - /// Index into function arena on Environment. - /// Used to access context variables, aliasing effects, etc. - function: FunctionId, - captures: Vec<Place>, // cloned from context, filtered - }, - - MutateFrozen { place: Place, error: CompilerDiagnostic }, - MutateGlobal { place: Place, error: CompilerDiagnostic }, - Impure { place: Place, error: CompilerDiagnostic }, -} -``` - -Key design decisions: -- **Place is cloned, not shared**: Since `Place` stores `IdentifierId` (a `Copy` type) + `Effect` + `bool` + `SourceLocation`, it is small enough to clone cheaply. No shared references needed. -- **`CreateFunction.function`** stores a `FunctionId` referencing the function arena on `Environment`. Code that needs `func.context` or `func.aliasingEffects` accesses `env.functions[function_id]` directly (see [Accessing Functions from CreateFunction](#accessing-functions-from-createfunction) below). -- **`Apply.args`** is a cloned `Vec`, not a shared reference to the InstructionValue's args. This is a shallow clone of `Place`/`SpreadPattern`/`Hole` values (all small, copyable types with arena IDs). - -#### EffectId as Allocation-Site Identity - -With PR #33650, the interned `AliasingEffect` replaces `InstructionValue` as the allocation-site key. In Rust, the `EffectId` (index into the interning table) serves directly as the allocation-site identity — no separate `AllocationSiteId` is needed: - -```rust -struct InferenceState { - /// The kind of each value, keyed by the EffectId of its creation effect - values: HashMap<EffectId, AbstractValue>, - /// The set of allocation sites pointed to by each identifier - variables: HashMap<IdentifierId, SmallVec<[EffectId; 2]>>, -} - -impl InferenceState { - /// Initialize a value at the given allocation site - fn initialize(&mut self, effect_id: EffectId, kind: AbstractValue) { - self.values.insert(effect_id, kind); - } - - /// Define a variable to point at an allocation site - fn define(&mut self, place: &Place, effect_id: EffectId) { - self.variables.insert(place.identifier, smallvec![effect_id]); - } - - /// Look up which allocation sites a place points to - fn values(&self, place: &Place) -> &[EffectId] { - self.variables.get(&place.identifier).expect("uninitialized").as_slice() - } -} -``` - -Each call to `state.initialize(effect, kind)` / `state.define(place, effect)` in TypeScript becomes `state.initialize(effect_id, kind)` / `state.define(place, effect_id)` in Rust, where `effect_id` is the `EffectId` returned by the effect interner. This applies uniformly to all creation effects: -- **`Create`/`CreateFrom`**: The interned effect's `EffectId` is both the interning key and the allocation-site key -- **`CreateFunction`**: Same — the interned CreateFunction effect's `EffectId` is the allocation-site key (the `FunctionExpression` reference is no longer used as a key) -- **Params/context**: Synthetic `AliasingEffect::Create` values are interned and their `EffectId` serves as the allocation site - -The `effectInstructionValueCache` is eliminated entirely (PR #33650 removes it). The `functionSignatureCache: Map<FunctionExpression, AliasingSignature>` becomes `HashMap<FunctionId, AliasingSignature>` — keyed by the `FunctionId` rather than the FunctionExpression reference. - -#### Effect Interning - -```rust -struct EffectInterner { - effects: Vec<AliasingEffect>, // indexed by EffectId - by_hash: HashMap<String, EffectId>, // dedup by content hash -} - -#[derive(Copy, Clone, Hash, Eq, PartialEq)] -struct EffectId(u32); - -impl EffectInterner { - fn intern(&mut self, effect: AliasingEffect) -> EffectId { - let hash = hash_effect(&effect); - *self.by_hash.entry(hash).or_insert_with(|| { - let id = EffectId(self.effects.len() as u32); - self.effects.push(effect); - id - }) - } -} -``` - -Since the interned effect IS the allocation-site key, there is no additional cache or mapping needed. The `EffectId` serves as interning dedup key, allocation-site identity, and cache key for `applySignatureCache`. The `functionSignatureCache` is keyed by `FunctionId`. - -#### Accessing Functions from CreateFunction - -In Rust, `CreateFunction` stores `function: FunctionId`, so the inner function is accessed directly from the function arena on `Environment`: - -```rust -// Read access: -let inner_func = &env.functions[effect.function]; - -// Mutable access: -let inner_func = &mut env.functions[effect.function]; -``` - -No instruction lookup or index is needed — the `FunctionId` provides direct O(1) access to the inner function's context variables, aliasing effects, and other data. - -#### Context Variable Mutation - -The mutation `operand.effect = Effect.Read` (in `applyEffect` for `CreateFunction`) modifies Places on the nested function's context. In Rust: - -```rust -// During CreateFunction processing, after determining abstract kinds: -let inner_func = &mut env.functions[effect.function]; -for operand in &mut inner_func.context { - if operand.effect == Effect::Capture { - let kind = state.kind(operand).kind; - if matches!(kind, ValueKind::Primitive | ValueKind::Frozen | ValueKind::Global) { - operand.effect = Effect::Read; - } - } -} -``` - -Since inner functions live in the function arena on `Environment` (not inline in the instruction), the borrow to `env.functions[function_id]` is completely disjoint from the outer `HIRFunction` being processed. No collect-then-apply workaround is needed. - -### Summary of Rust Approach for AliasingEffect - -| TypeScript Pattern | Rust Equivalent | Complexity | -|---|---|---| -| Effect Places share InstructionValue Places | Clone Places (cheap with `IdentifierId`) | Trivial | -| `Apply.args` shares InstructionValue's args array | Clone the `Vec<PlaceOrSpreadOrHole>` | Trivial | -| `CreateFunction.function` = the FunctionExpression | Store `FunctionId`, direct arena access | Trivial | -| `InstructionValue` as allocation-site key (→ `AliasingEffect` after #33650) | `EffectId` from interning table | Trivial | -| `effectInstructionValueCache` (eliminated by #33650) | Not needed — `EffectId` is the allocation site directly | N/A | -| `functionSignatureCache` (FunctionExpr → Signature) | `HashMap<FunctionId, AliasingSignature>` | Trivial | -| Effect interning by content hash | `EffectInterner` with `Vec` + `HashMap` | Low | -| `operand.effect = Effect.Read` mutation | `&mut env.functions[function_id].context` — disjoint borrow | Trivial | -| `applySignatureCache` (Signature × Apply → Effects) | `HashMap<(EffectId, EffectId), Vec<AliasingEffect>>` | Low | -| `state.values(place)` returning `AliasingEffect[]` | Returns `&[EffectId]` | Trivial | - -**Overall assessment**: AliasingEffect translates cleanly to Rust. With PR #33650, the interned `EffectId` serves as both the dedup key and allocation-site identity, eliminating the need for a separate `AllocationSiteId`. Place sharing is resolved by cloning (cheap with arena-based identifiers), and inner function access uses `FunctionId` into the function arena on `Environment`. No fundamental algorithmic redesign is needed. The fixpoint loop, effect interning, and abstract interpretation structure remain structurally identical. - ---- - -## Recommended Rust Architecture - -### Arena-Based Identifier Storage - -Stored as `identifiers: Vec<Identifier>` directly on `Environment`. - -```rust -#[derive(Copy, Clone, Hash, Eq, PartialEq)] -struct IdentifierId(u32); - -#[derive(Clone)] -struct Place { - identifier: IdentifierId, // index into Environment.identifiers - effect: Effect, - reactive: bool, - loc: SourceLocation, -} - -struct Identifier { - id: IdentifierId, - declaration_id: DeclarationId, - name: Option<IdentifierName>, - mutable_range: MutableRange, - scope: Option<ScopeId>, - ty: TypeId, // index into Environment.types - loc: SourceLocation, -} -``` - -### Arena-Based Scope Storage - -Stored as `scopes: Vec<ReactiveScope>` directly on `Environment`. - -```rust -#[derive(Copy, Clone, Hash, Eq, PartialEq)] -struct ScopeId(u32); -``` - -### Arena-Based Function Storage - -Stored as `functions: Vec<HIRFunction>` directly on `Environment`. `FunctionExpression` and `ObjectMethod` instruction values store a `FunctionId` instead of inline function data. - -```rust -#[derive(Copy, Clone, Hash, Eq, PartialEq)] -struct FunctionId(u32); -``` - -### Arena-Based Type Storage - -Stored as `types: Vec<Type>` directly on `Environment`. `Identifier.ty` stores a `TypeId` instead of an inline `Type` value. - -```rust -#[derive(Copy, Clone, Hash, Eq, PartialEq)] -struct TypeId(u32); -``` - -### Instructions Table - -Instructions are stored in a flat table on `HIRFunction` (`instructions: Vec<Instruction>`), indexed by `InstructionId`. `BasicBlock.instructions` becomes `Vec<InstructionId>`, referencing into this table. The existing `InstructionId` type is renamed to `EvaluationOrder` since it represents evaluation order and is present on both instructions and terminals. - -```rust -#[derive(Copy, Clone, Hash, Eq, PartialEq)] -struct InstructionId(u32); - -#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -struct EvaluationOrder(u32); -``` - -This allows passes to cache or reference an instruction's location via a single copyable ID, avoiding `(BlockId, usize)` tuples. - -### CFG Representation - -```rust -/// Use IndexMap for insertion-order iteration (matching JS Map semantics) -struct HIR { - entry: BlockId, - blocks: IndexMap<BlockId, BasicBlock>, -} -``` - -### Pass Signature Patterns - -Passes return `Result` for errors that would have thrown in TypeScript. - -```rust -/// Most passes: mutable HIR + mutable environment -fn enter_ssa(func: &mut HIRFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> { ... } - -/// Validation passes -fn validate_hooks_usage(func: &HIRFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> { ... } - -/// Passes that don't need env at all (many!) -fn merge_consecutive_blocks(func: &mut HIRFunction) { ... } -fn constant_propagation(func: &mut HIRFunction) { ... } -``` - -### Key Rust Patterns for Common TypeScript Idioms - -#### Pattern A: InstructionValue Variant Swap (`std::mem::replace`) -```rust -// TypeScript: instr.value = { kind: 'CallExpression', callee: instr.value.property, ... } -// Rust: take ownership, destructure, construct new variant -let old = std::mem::replace(&mut instr.value, InstructionValue::Tombstone); -if let InstructionValue::MethodCall { property, args, loc, .. } = old { - instr.value = InstructionValue::CallExpression { callee: property, args, loc }; -} else { - instr.value = old; -} -``` - -#### Pattern B: Place Cloning via Spread (`{...place}`) -```rust -// TypeScript: const newPlace = { ...place, effect: Effect.Read } -// Rust: Place is Clone (or Copy if small enough) -let new_place = Place { effect: Effect::Read, ..place.clone() }; -``` - -#### Pattern C: Delete-During-Set-Iteration (`retain`) -```rust -// TypeScript: for (const phi of block.phis) { if (dead) block.phis.delete(phi); } -// Rust: retain is the idiomatic equivalent -block.phis.retain(|phi| !is_dead(phi)); -``` - -#### Pattern D: Map Iteration with Block Deletion -```rust -// TypeScript: for (const [, block] of fn.body.blocks) { fn.body.blocks.delete(id); } -// Rust: collect keys first, then remove + get_mut -let block_ids: Vec<BlockId> = blocks.keys().copied().collect(); -for block_id in block_ids { - if should_merge(block_id) { - let removed = blocks.remove(&block_id).unwrap(); - let pred = blocks.get_mut(&pred_id).unwrap(); - pred.instructions.extend(removed.instructions); - } -} -``` - -#### Pattern E: Closure Variables Set Inside Builder Callbacks -```rust -// TypeScript: let callee = null; builder.enter(() => { callee = ...; return terminal; }); -// Rust: closure returns the value, or use Option<T> initialized before -let (block_id, callee) = builder.enter(|b| { - let callee = /* compute */; - let terminal = /* build */; - (terminal, callee) // return both -}); -``` - ---- - -## Input/Output Format - -Define a Rust representation of the Babel AST format using serde with custom serialization/deserialization in order to ensure that the `"type"` field is always produced, even outside of enum positions. Include full information from Babel, including source locations. Define a `Scope` type that encodes the tree of scope information, mapping to the information that Babel represents in its own scope tree. - -The main public API is roughly: - -```rust -/// Returns None if the function doesn't need changes, Some with the compiled output otherwise. -fn compile(ast: BabelAst, scope: Scope) -> Option<BabelAst> -``` - -This replaces the current Babel-plugin integration pattern where the compiler receives NodePath objects. The JSON AST interchange decouples the Rust compiler from any specific JS parser or AST format at the implementation level while maintaining Babel compatibility at the serialization boundary. - ---- - -## Error Handling - -In general there are two categories of errors: -- Anything that would have thrown, or would have short-circuited, should return an `Err(...)` with the single diagnostic -- Otherwise, accumulate errors directly onto the environment -- Error handling must preserve the full details of the errors: reason, description, location, details, suggestions, category, etc - -### Specific Error Patterns and Approaches - -| TypeScript Pattern | Example | Rust Approach | -|---|---|---| -| Non-null assertions (`!`) | `value!.field` | Panic via `.unwrap()` or similar | -| Throwing expressions | `throw ...`, `CompilerError.invariant()`, `CompilerError.throwTodo()`, `CompilerError.throw*()` | Make the function return `Result<_, CompilerDiagnostic>`, return `Err(...)` | -| Non-throwing (invariant) | Local `error` + `error.pushDiagnostic()` where the error IS an invariant | Make the function return `Result<_, CompilerDiagnostic>`, change `pushDiagnostic()` to `return Err(...)` | -| Non-throwing (non-invariant) | Local `error` + `error.pushDiagnostic()`, `env.recordError()` | Keep as-is — accumulate on environment | - -### Pass and Pipeline Structure - -```rust -// pipeline.rs -fn compile( - ast: Ast, - scope: Scope, - env: &mut Environment, -) -> Result<CompileResult, CompilerDiagnostic> { - // "?" to handle cases that would have thrown or produced an invariant - let mut hir = lower(ast, scope, env)?; - some_compiler_pass(&mut hir, env)?; - // ... - let ast = codegen(...)?; - - if env.has_errors() { - Ok(CompileResult::Failure(env.take_errors())) - } else { - Ok(CompileResult::Success(ast)) - } -} - -// <compiler_pass>.rs -fn pass_name( - func: &mut HirFunction, - env: &mut Environment, -) -> Result<(), CompilerDiagnostic>; -``` - ---- - -## Structural Similarity: TypeScript ↔ Rust Alignment - -### Design Goal - -The Rust code should be visually and structurally aligned with the original TypeScript. A developer should be able to have the TypeScript on the left side of the screen and the Rust on the right, scroll them together, and easily see how the logic corresponds. - -### What Looks Nearly Identical (~95% match) - -Most passes consist of these patterns that translate almost line-for-line: - -| TypeScript Pattern | Rust Equivalent | -|---|---| -| `switch (value.kind) { case 'X': ... }` | `match &value { InstructionValue::X { .. } => ... }` | -| `for (const [, block] of fn.body.blocks)` | `for block in func.body.blocks.values()` | -| `for (const instr of block.instructions)` | `for instr in &block.instructions` | -| `const map = new Map<K, V>()` | `let mut map: HashMap<K, V> = HashMap::new()` | -| `map.get(key) ?? defaultValue` | `map.get(&key).copied().unwrap_or(default)` | -| `if (x === null) { ... }` | `if x.is_none() { ... }` or `let Some(x) = x else { ... }` | -| `CompilerError.invariant(cond, ...)` | `assert!(cond, "...")` or `panic!("...")` | -| `do { ... } while (changed)` | `loop { ... if !changed { break; } }` | -| `array.push(item)` | `vec.push(item)` | -| `set.has(item)` | `set.contains(&item)` | - -### What Looks Slightly Different (~80% match) - -| TypeScript Pattern | Rust Equivalent | Reason | -|---|---|---| -| `Map<Identifier, T>` (reference keys) | `HashMap<IdentifierId, T>` | Reference identity → value identity | -| `DisjointSet<ReactiveScope>` | `DisjointSet<ScopeId>` | Same reason | -| `place.identifier.mutableRange.end = x` | `env.identifiers[place.identifier].mutable_range.end = x` | Arena indirection | -| `identifier.scope = sharedScope` | `identifier.scope = Some(scope_id)` | Reference → ID | -| `for...of` with `Set.delete()` | `set.retain(|x| ...)` | Different idiom, same semantics | -| `instr.value = { kind: 'X', ... }` | `instr.value = InstructionValue::X { ... }` (with `mem::replace`) | Ownership swap | - -### What Looks Substantially Different (~60% match) - -| TypeScript Pattern | Rust Equivalent | Reason | -|---|---|---| -| Storing `&Instruction` in side map | Store `InstructionId`, access via instruction table | Cannot hold references during mutation | -| Builder closures capturing outer `&mut` | Return values from closures, or split borrows | Borrow checker | -| `node.id.mutableRange.end = x` (graph node → HIR mutation) | Collect updates, apply to `env.identifiers` after traversal | Cannot mutate HIR through graph references | -| `identifier.mutableRange = scope.range` (shared object aliasing) | `identifier.scope = Some(scope_id)` + lookup via arena | Fundamental ownership model difference | - -### Passes Ranked by Structural Similarity to Rust - -**Nearly identical (95%+)**: PruneMaybeThrows, OptimizePropsMethodCalls, FlattenReactiveLoopsHIR, FlattenScopesWithHooksOrUseHIR, MergeConsecutiveBlocks, DeadCodeElimination, PruneUnusedLabelsHIR, RewriteInstructionKindsBasedOnReassignment, EliminateRedundantPhi, all validation passes, PruneUnusedLabels, PruneUnusedScopes, PruneNonReactiveDependencies, PruneAlwaysInvalidatingScopes, StabilizeBlockIds, PruneHoistedContexts - -**Very similar (85-95%)**: ConstantPropagation, EnterSSA, InferTypes, InferReactivePlaces, DropManualMemoization, InlineIIFEs, MemoizeFbtAndMacroOperandsInSameScope, AlignMethodCallScopes, AlignObjectMethodScopes, OutlineFunctions, NameAnonymousFunctions, BuildReactiveScopeTerminalsHIR, PropagateScopeDependenciesHIR, PropagateEarlyReturns, MergeReactiveScopesThatInvalidateTogether, PromoteUsedTemporaries, RenameVariables, ExtractScopeDeclarationsFromDestructuring - -**Moderately similar (70-85%)**: AnalyseFunctions, InferReactiveScopeVariables, AlignReactiveScopesToBlockScopesHIR, MergeOverlappingReactiveScopesHIR, OutlineJSX, BuildReactiveFunction, PruneNonEscapingScopes, OptimizeForSSR, PruneUnusedLValues - -**Moderately similar (70-85%)** *(additional)*: InferMutationAliasingEffects (after [PR #33650](https://github.com/facebook/react/pull/33650): allocation-site keys → `EffectId` via interning, Place sharing → Clone, CreateFunction → FunctionId arena access — see [§AliasingEffect section](#aliasingeffect-shared-references-and-rust-ownership)) - -**Requires redesign (50-70%)**: InferMutationAliasingRanges (graph-through-HIR mutation), BuildHIR (Babel AST coupling), CodegenReactiveFunction (Babel AST output) - ---- - -## Pipeline Overview - -``` -Babel AST - │ - ▼ -┌─────────────────────────────────────────────┐ -│ Phase 1: Lowering │ -│ BuildHIR (lower) │ -└─────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ Phase 2-3: Normalization + SSA │ -│ PruneMaybeThrows │ -│ DropManualMemoization │ -│ InlineIIFEs │ -│ MergeConsecutiveBlocks │ -│ EnterSSA │ -│ EliminateRedundantPhi │ -└─────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ Phase 4-5: Optimization + Type Inference │ -│ ConstantPropagation │ -│ InferTypes │ -│ OptimizePropsMethodCalls │ -└─────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ Phase 6: Mutation/Aliasing Analysis │ -│ AnalyseFunctions │ -│ InferMutationAliasingEffects │ -│ DeadCodeElimination │ -│ InferMutationAliasingRanges │ -└─────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ Phase 7-8: Post-Inference + Reactivity │ -│ InferReactivePlaces │ -│ RewriteInstructionKindsBasedOnReassignment│ -└─────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ Phase 9-12: Scope Construction + Alignment │ -│ InferReactiveScopeVariables │ -│ MemoizeFbtAndMacroOperandsInSameScope │ -│ OutlineJSX / OutlineFunctions │ -│ AlignMethodCallScopes │ -│ AlignObjectMethodScopes │ -│ AlignReactiveScopesToBlockScopesHIR │ -│ MergeOverlappingReactiveScopesHIR │ -│ BuildReactiveScopeTerminalsHIR │ -│ FlattenReactiveLoopsHIR │ -│ FlattenScopesWithHooksOrUseHIR │ -│ PropagateScopeDependenciesHIR │ -└─────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ Phase 13-14: Reactive Function │ -│ BuildReactiveFunction (CFG → tree) │ -│ PruneUnusedLabels │ -│ PruneNonEscapingScopes │ -│ PruneNonReactiveDependencies │ -│ PruneUnusedScopes │ -│ MergeReactiveScopesThatInvalidateTogether │ -│ PruneAlwaysInvalidatingScopes │ -│ PropagateEarlyReturns │ -│ PruneUnusedLValues │ -│ PromoteUsedTemporaries │ -│ ExtractScopeDeclarationsFromDestructuring │ -│ StabilizeBlockIds │ -│ RenameVariables │ -│ PruneHoistedContexts │ -└─────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────┐ -│ Phase 15: Codegen │ -│ CodegenReactiveFunction (tree → Babel AST)│ -└─────────────────────────────────────────────┘ - │ - ▼ -Babel AST (with memoization) -``` - ---- - -## Pass-by-Pass Analysis - -### Phase 1: Lowering - -#### BuildHIR (`lower`) -**What it does**: Converts Babel AST to HIR by traversing the AST and building a control-flow graph with BasicBlocks, Instructions, and Terminals. - -**Environment usage**: Heavy. Uses `env.nextIdentifierId`, `env.nextBlockId` for all ID allocation. Uses `env.recordError()` for fault-tolerant error handling. Uses `env.parentFunction.scope` for Babel scope analysis. Uses `env.isContextIdentifier()` and `env.programContext`. Environment is shared with nested function lowering via recursive `lower()` calls. - -**Side maps**: -- `#bindings: Map<string, {node, identifier}>` — caches Identifier objects by name, using Babel node reference equality to distinguish same-named variables in different scopes -- `#context: Map<t.Identifier, SourceLocation>` — Babel node keys (reference identity) -- `#completed: Map<BlockId, BasicBlock>` — ID-keyed (safe) -- `followups: Array<{place, path}>` — temporary Place storage during destructuring - -**Structural similarity**: ~65%. The HIRBuilder class maps to a Rust struct with `&mut self` methods. The `enter()/loop()/label()` closure patterns translate to methods taking `impl FnOnce(&mut Self) -> Terminal`. However, several patterns require restructuring: -- Variables assigned inside closures and read outside (e.g., `let callee = null; builder.enter(() => { callee = ...; })`) must return values from the closure instead -- `resolveBinding()` uses Babel node reference equality (`mapping.node === node`) — needs parser-specific node IDs -- Recursive `lower()` for nested functions needs `std::mem::take` to extract child function data -- The Babel AST input arrives as JSON (deserialized via serde), replacing direct Babel NodePath traversal - -**Unexpected issues**: Babel bug workarounds (lines 413-418, 4488-4498) would not be needed with a different parser. The `promoteTemporary()` pattern is straightforward in Rust. The `fbtDepth` counter is trivial. - ---- - -### Phase 2: Normalization - -#### PruneMaybeThrows -**Env usage**: None. **Side maps**: `Map<BlockId, BlockId>` (IDs only). **Similarity**: ~95%. -Simple terminal mutation (`handler = null`), phi rewiring, and CFG cleanup. The phi operand mutation-during-iteration needs `drain().collect()` in Rust. Block iteration order must be RPO for chain resolution. - -#### DropManualMemoization -**Env usage**: `getGlobalDeclaration`, `getHookKindForType`, `recordError`, `createTemporaryPlace`, config flags. **Side maps**: `IdentifierSidemap` with 6 collections — `functions` stores `TInstruction` references (use `HashSet<IdentifierId>` instead), `manualMemos.loadInstr` stores instruction reference (store `InstructionId` instead), others are ID-keyed. **Similarity**: ~85%. -Two-phase collect+rewrite. In Rust, the `functions` map needs only existence checking (not the actual instruction reference). `manualMemos.loadInstr` only needs `.id` — store the ID directly. - -#### InlineImmediatelyInvokedFunctionExpressions -**Env usage**: `env.nextBlockId`, `env.nextIdentifierId` (via `createTemporaryPlace`). **Side maps**: `functions: Map<IdentifierId, FunctionExpression>` stores instruction value references. **Similarity**: ~80%. -The `functions` map stores `FunctionExpression` references — in Rust, store `FunctionId` for the inner function. The queue-while-iterating pattern needs index-based loop (`while i < queue.len()`). Block ownership transfer uses `blocks.remove()` + `blocks.insert()`. - -#### MergeConsecutiveBlocks -**Env usage**: None. **Side maps**: `MergedBlocks` (ID-only map), `fallthroughBlocks` (ID-only set). **Similarity**: ~90%. -Main Rust challenge: iteration + deletion. Collect block IDs first, then `remove()` + `get_mut()`. Phi operand rewriting needs collect-then-apply. - ---- - -### Phase 3: SSA Construction - -#### EnterSSA -**Env usage**: `env.nextIdentifierId` for fresh SSA identifiers. **Side maps**: `#states: Map<BasicBlock, State>` with `defs: Map<Identifier, Identifier>` (both reference-identity keyed), `unsealedPreds: Map<BasicBlock, number>`, `#unknown/#context: Set<Identifier>`. **Similarity**: ~85%. -All reference-identity maps become ID-keyed: `Vec<State>` indexed by BlockId, `HashMap<IdentifierId, IdentifierId>` for defs. The recursive `getIdAt()` works cleanly because `IdentifierId` is `Copy` — no borrows held across recursive calls. The `enter()` closure for nested functions is just save/restore of `self.current`. `makeType()` global counter must become per-compilation. - -#### EliminateRedundantPhi -**Env usage**: None. **Side maps**: `rewrites: Map<Identifier, Identifier>` (reference keys). **Similarity**: ~95%. -Becomes `HashMap<IdentifierId, IdentifierId>`. `rewritePlace` becomes `place.identifier_id = new_id`. Phi deletion during iteration becomes `block.phis.retain(|phi| ...)`. The fixpoint loop and labeled `continue` translate directly. - ---- - -### Phase 4: Optimization (Pre-Inference) - -#### ConstantPropagation -**Env usage**: None. **Side maps**: `constants: Map<IdentifierId, Constant>` (ID-keyed, safe). **Similarity**: ~90%. -The fixpoint loop, `evaluateInstruction()` switch, and terminal rewriting all map directly. Constants map stores cloned `Primitive`/`LoadGlobal` values (small, cheap to clone). The CFG cleanup cascade after branch elimination needs shared infrastructure. The `block.kind === 'sequence'` guard translates to an enum check. - -#### OptimizePropsMethodCalls -**Env usage**: None. **Side maps**: None. **Similarity**: ~98%. -The simplest pass in the compiler. A single linear scan with one `match` arm and `std::mem::replace` for the value swap. ~20 lines of Rust. - ---- - -### Phase 5: Type and Effect Inference - -#### InferTypes -**Env usage**: `getGlobalDeclaration`, `getPropertyType`, `getFallthroughPropertyType`, config flags. **Side maps**: `Unifier.substitutions: Map<TypeId, Type>` (ID-keyed), `names: Map<IdentifierId, string>` (ID-keyed). **Similarity**: ~90%. -Unification-based type inference is very natural in Rust. The `Type` enum needs `Box<Type>` for recursive variants (`Function.return`, `Property.objectType`). The TypeScript generator pattern for constraint generation can be replaced with direct `unifier.unify()` calls during the walk. The `apply()` phase is straightforward mutable traversal. `makeType()` global counter needs per-compilation scope. - ---- - -### Phase 6: Mutation/Aliasing Analysis - -#### AnalyseFunctions -**Env usage**: Shares Environment between parent and child via `fn.env`. Uses logger. **Side maps**: None (operates entirely through in-place HIR mutation). **Similarity**: ~85%. -The recursive `lowerWithMutationAliasing` pattern works with `&mut` because it is sequential. Inner functions are stored in the function arena on `Environment` and accessed via `FunctionId`, so no extraction/replacement is needed. The mutableRange reset (`identifier.mutableRange = {start: 0, end: 0}`) is a simple value write in Rust (no aliasing to break because Rust uses values, not shared objects). - -#### InferMutationAliasingEffects -**Env usage**: `env.config` (3 reads), `env.getFunctionSignature`, `env.enableValidations`, `createTemporaryPlace`. InferenceState stores `env` as read-only reference. **Side maps**: `statesByBlock/queuedStates` (BlockId-keyed), Context class with caches (`Map<Instruction, InstructionSignature>`, `Map<FunctionExpression, AliasingSignature>`, `Map<AliasingSignature, Map<AliasingEffect, ...>>`), InferenceState with `#values: Map<InstructionValue, AbstractValue>` and `#variables: Map<IdentifierId, Set<InstructionValue>>`. **Similarity**: ~80%. - -**Shared references in AliasingEffect** (see [§AliasingEffect: Shared References and Rust Ownership](#aliasingeffect-shared-references-and-rust-ownership) for full analysis): `computeSignatureForInstruction` creates effects that share Place objects with the Instruction's `lvalue` and `InstructionValue` fields. The `Apply` effect shares the args array reference. The `CreateFunction` effect stores the actual `FunctionExpression`/`ObjectMethod` InstructionValue. In Rust, Places are cloned (cheap with `IdentifierId`) and `CreateFunction` stores a `FunctionId` for function arena access. - -**Allocation-site identity**: Currently uses `InstructionValue` as reference-identity keys. PR [#33650](https://github.com/facebook/react/pull/33650) replaces this with interned `AliasingEffect` objects — since effects are already interned by content hash, the interned effect IS the allocation-site key. In Rust, this maps to `EffectId` (index into the interning table). No separate `AllocationSiteId` is needed. - -**Reference-identity maps and their Rust equivalents** (after PR #33650): -- `instructionSignatureCache: Map<Instruction, ...>` → `HashMap<InstructionId, InstructionSignature>` -- `#values: Map<AliasingEffect, AbstractValue>` → `HashMap<EffectId, AbstractValue>` (EffectId = interning index = allocation-site ID) -- `#variables: Map<IdentifierId, Set<AliasingEffect>>` → `HashMap<IdentifierId, SmallVec<[EffectId; 2]>>` -- `effectInstructionValueCache` → eliminated by PR #33650 -- `functionSignatureCache: Map<FunctionExpression, ...>` → `HashMap<FunctionId, AliasingSignature>` (key by FunctionId from arena) -- `applySignatureCache: Map<AliasingSignature, Map<AliasingEffect, ...>>` → `HashMap<EffectId, HashMap<EffectId, ...>>` -- `internedEffects: Map<string, AliasingEffect>` → `EffectInterner { effects: Vec<AliasingEffect>, by_hash: HashMap<String, EffectId> }` - -All keys become `Copy` types (`InstructionId`, `EffectId`, `IdentifierId`), trivially `Hash + Eq`, with no reference identity needed. - -The overall structure (fixpoint loop, InferenceState clone/merge, applyEffect recursion, Context caching) can remain nearly identical. The `applyEffect` recursive method works with `&mut InferenceState` + `&mut Context` parameters — Rust's reborrowing handles the recursion naturally. - -**Context variable mutation**: During `CreateFunction` processing, `operand.effect = Effect.Read` mutates Places on the nested function's context. In Rust, the inner function is accessed via `&mut env.functions[function_id]`, which is completely disjoint from the outer `HIRFunction` being processed. - -#### DeadCodeElimination -**Env usage**: `env.outputMode` (one read for SSR hook pruning). **Side maps**: `State.identifiers: Set<IdentifierId>`, `State.named: Set<string>` (both value-keyed, safe). **Similarity**: ~95%. -Two-phase mark-and-sweep is perfectly natural in Rust. `Vec::retain` replaces `retainWhere`. Destructuring pattern rewrites use `iter_mut()` + `truncate()`. - -#### InferMutationAliasingRanges (HIGH COMPLEXITY) -**Env usage**: `env.enableValidations` (one read), `env.recordError` (error recording). **Side maps**: `AliasingState.nodes: Map<Identifier, Node>` (reference-identity keys), each Node containing `createdFrom/captures/aliases/maybeAliases: Map<Identifier, number>` and `edges: Array<{node: Identifier, ...}>`. Also `mutations/renders` arrays storing Place references. **Similarity**: ~75%. - -**Effect consumption**: Iterates `instr.effects` for every instruction, reading Place fields (`effect.into`, `effect.from`, `effect.value`, `effect.place`). For `CreateFunction` effects, accesses `effect.function.loweredFunc.func` to create Function graph nodes. In Rust, `CreateFunction` stores `FunctionId`; the function is accessed via `env.functions[function_id]` (see [§AliasingEffect section](#aliasingeffect-shared-references-and-rust-ownership)). All other effect Place accesses only need `place.identifier` (an `IdentifierId` in Rust), with no shared reference concerns. - -**All Identifier-keyed maps become `HashMap<IdentifierId, T>`**. The critical `node.id.mutableRange.end = ...` pattern (mutating HIR through graph node references) needs restructuring: either store computed range updates on the Node and apply after traversal (recommended), or use arena-based identifiers. The BFS in `mutate()` collects edge targets into temporary `Vec<IdentifierId>` before pushing to queue, resolving borrow conflicts. The two-part structure (build graph → apply ranges) maps well to Rust's two-phase pattern. The temporal `index` counter and edge ordering translate directly. - -**Potential latent issue**: The `edges` array uses `break` (line 763) assuming monotonic insertion order, but pending phi edges from back-edges could break this ordering. The Rust port should consider using `continue` instead of `break` for safety. - ---- - -### Phase 7: Optimization (Post-Inference) - -#### OptimizeForSSR -**Env usage**: None directly (conditional on pipeline `outputMode` check). **Side maps**: `inlinedState: Map<IdentifierId, InstructionValue>` (ID-keyed). **Similarity**: ~90%. -Stores cloned InstructionValue objects. The two-pass pattern translates directly. - ---- - -### Phase 8: Reactivity Inference - -#### InferReactivePlaces -**Env usage**: `getHookKind(fn.env, ...)` for hook detection. **Side maps**: `ReactivityMap.reactive: Set<IdentifierId>` (safe), `ReactivityMap.aliasedIdentifiers: DisjointSet<Identifier>` (reference-identity), `StableSidemap.map: Map<IdentifierId, {isStable}>` (ID-keyed). **Similarity**: ~85%. -DisjointSet becomes `DisjointSet<IdentifierId>`. The `isReactive()` side-effect pattern (sets `place.reactive = true` during reads) works in Rust as `fn is_reactive(&self, place: &mut Place) -> bool` — the ReactivityMap holds only IDs while `place` is mutably borrowed from the HIR, so borrows are disjoint. The fixpoint loop translates directly. - -#### RewriteInstructionKindsBasedOnReassignment -**Env usage**: None. **Side maps**: `declarations: Map<DeclarationId, LValue | LValuePattern>` stores references to lvalue objects for retroactive `.kind` mutation. **Similarity**: ~85%. -The aliased-mutation-through-map pattern is best handled with a two-pass approach: Pass 1 collects `HashSet<DeclarationId>` of reassigned variables, Pass 2 assigns `InstructionKind` values. Or use `HashMap<DeclarationId, InstructionKind>` and apply in a final pass. - ---- - -### Phase 9: Scope Construction - -#### InferReactiveScopeVariables -**Env usage**: `env.nextScopeId`, `env.config.enableForest`, `env.logger`. **Side maps**: `scopeIdentifiers: DisjointSet<Identifier>` (reference-identity), `declarations: Map<DeclarationId, Identifier>` (stores Identifier references), `scopes: Map<Identifier, ReactiveScope>` (reference keys). **Similarity**: ~75%. - -**THE CRITICAL ALIASING PASS**: Line 132 `identifier.mutableRange = scope.range` creates the shared-MutableRange aliasing that all downstream scope passes depend on. In Rust with arenas: identifiers store `scope: Option<ScopeId>`. The "effective mutable range" is accessed via scope lookup. All downstream passes that read `mutableRange` access the scope arena via `env.scopes`. DisjointSet becomes `DisjointSet<IdentifierId>`, scopes map becomes `HashMap<IdentifierId, ScopeId>`. - -#### MemoizeFbtAndMacroOperandsInSameScope -**Env usage**: `fn.env.config.customMacros` (one read). **Side maps**: `macroKinds: Map<string, MacroDefinition>` (string keys), `macroTags: Map<IdentifierId, MacroDefinition>` (ID keys), `macroValues: Set<IdentifierId>` (IDs). **Similarity**: ~90%. -All ID-keyed. The scope mutation (`operand.identifier.scope = scope`, `expandFbtScopeRange`) becomes `identifier.scope = Some(scope_id)` + `env.scopes[scope_id].range.start = min(...)`. The cyclic `MacroDefinition` structure can use arena indices or hardcoded match logic. - ---- - -### Phase 10: Scope Alignment and Merging - -#### AlignMethodCallScopes -**Env usage**: None. **Side maps**: `scopeMapping: Map<IdentifierId, ReactiveScope | null>` (ID keys), `mergedScopes: DisjointSet<ReactiveScope>` (reference-identity). **Similarity**: ~90%. -DisjointSet becomes `DisjointSet<ScopeId>`. Range merging through arena: `env.scopes[root_id].range.start = min(...)`. Scope rewriting: `identifier.scope = Some(root_id)`. - -#### AlignObjectMethodScopes -**Env usage**: None. **Side maps**: `objectMethodDecls: Set<Identifier>` (reference-identity), `DisjointSet<ReactiveScope>`. **Similarity**: ~88%. -Same patterns as AlignMethodCallScopes. `Set<Identifier>` becomes `HashSet<IdentifierId>`. **Porting hazard**: The lvalue-only scope repointing (Phase 2b) relies on shared Identifier references. With arena-based identifiers where each Place has its own copy, repointing must cover ALL occurrences, not just lvalues. If using a central identifier arena (recommended), lvalue-only repointing is fine. - -#### AlignReactiveScopesToBlockScopesHIR -**Env usage**: None. **Side maps**: `activeScopes: Set<ReactiveScope>` (reference-identity, iterated while mutating `scope.range`), `seen: Set<ReactiveScope>`, `placeScopes: Map<Place, ReactiveScope>` (**dead code — never read**), `valueBlockNodes: Map<BlockId, ValueBlockNode>`. **Similarity**: ~85%. -`activeScopes` becomes `HashSet<ScopeId>`. Scope mutation through arena: `for &scope_id in &active_scopes { env.scopes[scope_id].range.start = min(...); }` — perfectly clean borrows (HashSet is immutable, arena is mutable). The `placeScopes` map can be omitted entirely. - -#### MergeOverlappingReactiveScopesHIR -**Env usage**: None. **Side maps**: `joinedScopes: DisjointSet<ReactiveScope>` (reference-identity), `placeScopes: Map<Place, ReactiveScope>` (Place reference keys). **Similarity**: ~85%. -DisjointSet becomes `DisjointSet<ScopeId>`. Same arena-based range merging pattern. Place-keyed maps become unnecessary with identifier-arena approach. - ---- - -### Phase 11: Scope Terminal Construction - -#### BuildReactiveScopeTerminalsHIR -**Env usage**: None. **Side maps**: `rewrittenFinalBlocks: Map<BlockId, BlockId>` (IDs), `nextBlocks: Map<BlockId, BasicBlock>` (block storage), `queuedRewrites`. **Similarity**: ~85%. -Complete blocks map replacement (`fn.body.blocks = nextBlocks`). Block splitting creates new blocks from instruction slices. Phi rewriting across old/new blocks. All structurally translatable. - -#### FlattenReactiveLoopsHIR -**Env usage**: None. **Side maps**: `activeLoops: Array<BlockId>` (IDs only). **Similarity**: ~98%. -Simple terminal variant replacement (`scope` → `pruned-scope`). Uses `Vec::retain` for the active loops stack. ~40 lines of Rust logic. The terminal swap uses `std::mem::replace` or shared inner data struct. - -#### FlattenScopesWithHooksOrUseHIR -**Env usage**: `getHookKind(fn.env, ...)` (one hook resolution call). **Side maps**: `activeScopes: Array<{block, fallthrough}>`, `prune: Array<BlockId>` (both ID-only). **Similarity**: ~95%. -Two-phase detect/rewrite. Stack-based scope tracking with `Vec::retain`. Terminal variant conversion. Very clean Rust translation. - ---- - -### Phase 12: Scope Dependency Propagation - -#### PropagateScopeDependenciesHIR -**Env usage**: None directly. **Side maps**: `temporaries: Map<IdentifierId, ReactiveScopeDependency>` (ID-keyed, but `ReactiveScopeDependency` contains `identifier: Identifier` reference), `DependencyCollectionContext` with `#declarations: Map<DeclarationId, Decl>`, `#reassignments: Map<Identifier, Decl>` (reference keys), `deps: Map<ReactiveScope, Array<...>>` (reference keys). **Similarity**: ~80%. -Reference-keyed maps become ID-keyed. `deps` becomes `HashMap<ScopeId, Vec<ReactiveScopeDependency>>`. The PropertyPathRegistry tree with parent pointers needs arena allocation. Scope mutation (`scope.declarations.set(...)`, `scope.dependencies.add(...)`) through arena. - ---- - -### Phase 13: Reactive Function Construction - -#### BuildReactiveFunction -**Env usage**: Copies `fn.env` to reactive function. **Side maps**: Scheduling/traversal state during CFG-to-tree conversion. **Similarity**: ~80%. -Major structural transformation (CFG → tree). The builder pattern works with `&mut` state. Deep recursion for value blocks is bounded by CFG depth. Shared Places/scopes/identifiers use arena indices in the new tree structure. - ---- - -### Phase 14: Reactive Function Transforms - -All reactive function transforms use the `ReactiveFunctionVisitor` / `ReactiveFunctionTransform` pattern. - -**ReactiveFunctionVisitor/Transform pattern → Rust traits**: -```rust -trait ReactiveFunctionTransform { - type State; - fn transform_terminal(&mut self, stmt: &mut ReactiveTerminalStatement, state: &mut Self::State) - -> Transformed<ReactiveStatement> { Transformed::Keep } - fn transform_instruction(&mut self, stmt: &mut ReactiveInstructionStatement, state: &mut Self::State) - -> Transformed<ReactiveStatement> { Transformed::Keep } - // ... default implementations for traversal ... -} - -enum Transformed<T> { - Keep, - Remove, - Replace(T), - ReplaceMany(Vec<T>), -} -``` - -The `traverseBlock` method handles `ReplaceMany` by lazily building a new `Vec` (only allocating on first mutation). This maps to Rust's `Option<Vec<T>>` pattern. - -Individual passes: - -| Pass | Env | Side Maps | Similarity | -|------|-----|-----------|------------| -| PruneUnusedLabels | None | `Set<BlockId>` | ~95% | -| PruneNonEscapingScopes | None | Dependency graph with cycle detection | ~85% | -| PruneNonReactiveDependencies | None | None significant | ~95% | -| PruneUnusedScopes | None | None significant | ~95% | -| MergeReactiveScopesThatInvalidateTogether | None | Scope metadata comparison | ~85% | -| PruneAlwaysInvalidatingScopes | None | None significant | ~95% | -| PropagateEarlyReturns | None | Early return tracking state | ~85% | -| PruneUnusedLValues | None | Lvalue usage tracking | ~90% | -| PromoteUsedTemporaries | None | Identifier name mutation | ~90% | -| ExtractScopeDeclarationsFromDestructuring | None | None significant | ~90% | -| StabilizeBlockIds | None | `Map<BlockId, BlockId>` remapping | ~95% | -| RenameVariables | None | Name collision tracking | ~90% | -| PruneHoistedContexts | None | Context declaration tracking | ~95% | - ---- - -### Phase 15: Codegen - -#### CodegenReactiveFunction -**Env usage**: `env.programContext` (imports, bindings), `env.getOutlinedFunctions()`, `env.recordErrors()`, `env.config`. **Side maps**: Context class with cache slot management, scope metadata tracking. **Similarity**: ~60%. - -**The most significantly different pass** due to AST output generation. 1000+ lines of `t.*()` Babel API calls are replaced with constructing Rust Babel AST types that serialize to JSON via serde. Core scope logic (cache slot allocation, dependency checking, memoization code structure) can look structurally similar. - -The `uniqueIdentifiers` and `fbtOperands` parameters translate directly. - ---- - -### Validation Passes - -~15 validation passes share a common pattern: read-only HIR/ReactiveFunction traversal + error reporting via `env.recordError()`. They are the **easiest passes to port**. Common structure: - -```rust -fn validate_hooks_usage(func: &HIRFunction, env: &mut Environment) -> Result<(), ()> { - for block in func.body.blocks.values() { - for instr in &block.instructions { - match &instr.value { - // check for violations, record errors - } - } - } - Ok(()) -} -``` - -All use `HashMap<IdentifierId, T>` for state tracking (ID-keyed, safe). Some return `CompilerError` directly instead of recording. The `tryRecord()` wrapping pattern maps to `Result` in Rust. - ---- - -## External Dependencies - -### Input/Output: JSON AST Interchange - -The Rust compiler defines its own representation of the Babel AST format using serde with custom serialization/deserialization, ensuring the `"type"` field is always produced (even outside of enum positions). Input ASTs are deserialized from JSON, and output ASTs are serialized back to JSON for consumption by the Babel plugin. A `Scope` type encodes the scope tree information that Babel provides. The main public API is `compile(BabelAst, Scope) -> Option<BabelAst>`, returning `None` if no changes are needed. - -This approach decouples the Rust compiler from any specific JS parser — the JSON boundary handles the translation. The `resolveBinding()` pattern in BuildHIR (which uses Babel node reference equality in TypeScript) maps to scope-tree lookups via the `Scope` type. - ---- - -## Risk Assessment - -### Low Risk (straightforward port) -- All validation passes -- Simple transformation passes (PruneMaybeThrows, PruneUnusedLabelsHIR, FlattenReactiveLoopsHIR, FlattenScopesWithHooksOrUseHIR, StabilizeBlockIds, RewriteInstructionKindsBasedOnReassignment, OptimizePropsMethodCalls, MergeConsecutiveBlocks) -- Reactive pruning passes (PruneUnusedLabels, PruneUnusedScopes, PruneAlwaysInvalidatingScopes, PruneNonReactiveDependencies) - -### Medium Risk (requires systematic refactoring) -- SSA passes (EnterSSA, EliminateRedundantPhi) — reference-identity maps → ID maps -- Scope construction passes — centralized scope arena with ID-based references -- Type inference (InferTypes) — arena-based Type storage, TypeId generation -- Constant propagation — separated constants map, CFG cleanup infrastructure -- Dead code elimination — two-phase collect/apply -- Scope alignment passes — DisjointSet<ScopeId>, arena-based range mutation -- Reactive function transforms — Visitor/MutVisitor trait design with Transformed enum - -### Medium Risk *(additional)* -- **InferMutationAliasingEffects**: After [PR #33650](https://github.com/facebook/react/pull/33650), allocation-site identity uses interned `AliasingEffect` (→ `EffectId`), eliminating `InstructionValue` keys and `effectInstructionValueCache`. Remaining reference-identity maps use Instructions (→ `InstructionId`) and FunctionExpressions (→ `FunctionId`). All become copyable ID-keyed maps. Place sharing between effects and instructions is resolved by cloning (cheap with arena-based identifiers). `CreateFunction`'s FunctionExpression reference becomes a `FunctionId` referencing the function arena. Fixpoint loop and abstract interpretation structure port directly. See [§AliasingEffect section](#aliasingeffect-shared-references-and-rust-ownership) for full analysis. - -### High Risk (significant redesign) -- **BuildHIR**: JSON AST deserialization, scope tree integration, closure-heavy builder patterns -- **InferMutationAliasingRanges**: Graph-through-HIR mutation, temporal reasoning, deferred range updates -- **CodegenReactiveFunction**: JSON AST output construction via serde, 1000+ lines of AST building -- **AnalyseFunctions**: Recursive nested function processing via function arena, shared mutableRange semantics - -### Critical Architectural Decisions (must be designed upfront) -1. **Arena-based storage on Environment**: Identifiers, scopes, functions, and types are stored as flat `Vec` fields on `Environment`, referenced by copyable ID types (`IdentifierId`, `ScopeId`, `FunctionId`, `TypeId`). Affects every pass. -2. **Instructions table**: Instructions stored in flat `Vec<Instruction>` on `HIRFunction`, referenced by `InstructionId`. Old `InstructionId` renamed to `EvaluationOrder`. -3. **Scope-based mutableRange access**: After InferReactiveScopeVariables, effective mutable range = scope's range. All downstream `isMutable()`/`inRange()` calls access the scope arena via `env.scopes`. -4. **JSON AST interchange**: Input/output via serde-serialized Babel AST types and a `Scope` type for scope tree information. -5. **Environment as single `&mut`**: No sub-struct grouping — flat fields allow precise sliced borrows. Passed separately from `HIRFunction`. -6. **Error handling**: `Result<_, CompilerDiagnostic>` for thrown errors, accumulated errors on `Environment`. - ---- - -## Recommended Migration Strategy - -### Phase 1: Foundation -1. Define Rust data model (flat `Environment` with arena fields for Identifiers/Scopes/Functions/Types, all ID newtypes) -2. Define HIR types as Rust enums/structs (InstructionValue ~40 variants, Terminal ~20 variants) -3. Define flat `Environment` struct with arena fields, counters, config, and accumulated state -4. Implement shared infrastructure: `DisjointSet<T: Copy>`, `IndexMap` wrappers, visitor utilities -5. Define Babel AST types with serde serialization/deserialization for JSON AST interchange -6. Build JSON serialization for HIR (enables testing against TypeScript implementation) - -### Phase 2: Core Pipeline -1. Port BuildHIR (highest effort, most value — requires JSON AST deserialization and Scope type integration) -2. Port normalization passes (PruneMaybeThrows, MergeConsecutiveBlocks — simple, builds confidence) -3. Port SSA (EnterSSA, EliminateRedundantPhi — establishes arena patterns) -4. Port ConstantPropagation, InferTypes -5. Validate output matches TypeScript via JSON comparison at each stage - -### Phase 3: Analysis Engine -1. Port AnalyseFunctions (establishes recursive compilation pattern) -2. Port InferMutationAliasingEffects (establish EffectId interning table — EffectId serves as allocation-site identity, FunctionId-based function arena access for CreateFunction) -3. Port DeadCodeElimination -4. Port InferMutationAliasingRanges (establish deferred-range-update pattern) -5. Port InferReactivePlaces - -### Phase 4: Scope System -1. Port InferReactiveScopeVariables (establishes ScopeId → mutableRange indirection) -2. Port scope alignment passes (Align*, Merge* — establish DisjointSet<ScopeId> pattern) -3. Port BuildReactiveScopeTerminalsHIR -4. Port PropagateScopeDependenciesHIR - -### Phase 5: Output -1. Port BuildReactiveFunction (establishes reactive tree representation) -2. Port reactive function transforms (Prune*, Promote*, Rename* — use trait-based visitor) -3. Port CodegenReactiveFunction with JSON AST output -4. Port validation passes (easiest, can be done in parallel) -5. End-to-end integration testing diff --git a/compiler/fixtures/align-method-call-scope-range-sync.js b/compiler/fixtures/align-method-call-scope-range-sync.js deleted file mode 100644 index 42cfa15c55b9..000000000000 --- a/compiler/fixtures/align-method-call-scope-range-sync.js +++ /dev/null @@ -1,23 +0,0 @@ -// Repro for method-call scope alignment range sync: when -// AlignMethodCallScopes merges scopes for a method call and its -// computed property, the updated scope range must be propagated -// to identifier mutable_ranges so later passes see correct ranges. - -function Component({items}) { - const filtered = items.filter(x => x.active); - const mapped = filtered.map(x => x.name); - const sorted = mapped.sort(); - return <List items={sorted} />; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [ - { - items: [ - {active: true, name: 'a'}, - {active: false, name: 'b'}, - ], - }, - ], -}; diff --git a/compiler/fixtures/fbt-scope-range-sync-with-jsx.js b/compiler/fixtures/fbt-scope-range-sync-with-jsx.js deleted file mode 100644 index c7d2b431c437..000000000000 --- a/compiler/fixtures/fbt-scope-range-sync-with-jsx.js +++ /dev/null @@ -1,20 +0,0 @@ -// Repro for fbt scope range sync: when expandFbtScopeRange modifies -// scope.range.start, identifier mutable_ranges must be synced so -// MergeOverlappingReactiveScopesHIR can detect the overlap. -import fbt from 'fbt'; - -function Component({size, icon}) { - return ( - <Badge icon={icon}> - {fbt( - `Available in ${fbt.param('size', size)} only`, - 'Badge text with dynamic param' - )} - </Badge> - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{size: 'Large', icon: 'rx'}], -}; diff --git a/compiler/package.json b/compiler/package.json index 1e65cbf700d4..4492b7021080 100644 --- a/compiler/package.json +++ b/compiler/package.json @@ -7,7 +7,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/react/react.git" + "url": "git+https://github.com/facebook/react.git" }, "scripts": { "copyright": "node scripts/copyright.js", diff --git a/compiler/packages/babel-plugin-react-compiler-rust/native/.gitignore b/compiler/packages/babel-plugin-react-compiler-rust/native/.gitignore deleted file mode 100644 index 0eb56da7f48b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/native/.gitignore +++ /dev/null @@ -1 +0,0 @@ -index.node diff --git a/compiler/packages/babel-plugin-react-compiler-rust/native/Cargo.toml b/compiler/packages/babel-plugin-react-compiler-rust/native/Cargo.toml deleted file mode 100644 index 8af091ca97ba..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/native/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "react_compiler_napi" -version = "0.1.0" -edition = "2024" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -napi = { version = "2", features = ["napi4"] } -napi-derive = "2" -react_compiler = { path = "../../../crates/react_compiler" } -react_compiler_ast = { path = "../../../crates/react_compiler_ast" } -serde = "1" -serde_json = { version = "1", features = ["unbounded_depth"] } - -[build-dependencies] -napi-build = "2" diff --git a/compiler/packages/babel-plugin-react-compiler-rust/native/build.rs b/compiler/packages/babel-plugin-react-compiler-rust/native/build.rs deleted file mode 100644 index 9fc236788932..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/native/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -extern crate napi_build; - -fn main() { - napi_build::setup(); -} diff --git a/compiler/packages/babel-plugin-react-compiler-rust/native/src/lib.rs b/compiler/packages/babel-plugin-react-compiler-rust/native/src/lib.rs deleted file mode 100644 index ac58e0ca90db..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/native/src/lib.rs +++ /dev/null @@ -1,137 +0,0 @@ -use std::time::Instant; - -use napi_derive::napi; -use react_compiler::entrypoint::PluginOptions; -use react_compiler::entrypoint::compile_program; -use react_compiler::timing::TimingEntry; -use react_compiler_ast::File; -use react_compiler_ast::scope::ScopeInfo; -use serde::Deserialize; - -/// Deserialize JSON with no recursion limit (for deeply nested ASTs). -fn from_json_str<'de, T: Deserialize<'de>>(s: &'de str) -> serde_json::Result<T> { - let mut deserializer = serde_json::Deserializer::from_str(s); - deserializer.disable_recursion_limit(); - T::deserialize(&mut deserializer) -} - -/// Main entry point for the React Compiler. -/// -/// Receives a full program AST, scope information, and resolved options -/// as JSON strings. Returns a JSON string containing the CompileResult. -/// -/// This function is called by the JS shim (bridge.ts) via napi-rs. -/// Spawns a dedicated thread with 64MB stack to handle deeply nested ASTs -/// that would overflow the default Node.js thread stack. -#[napi] -pub fn compile(ast_json: String, scope_json: String, options_json: String) -> napi::Result<String> { - let handle = std::thread::Builder::new() - .stack_size(64 * 1024 * 1024) // 64MB stack - .spawn(move || compile_inner(ast_json, scope_json, options_json)) - .map_err(|e| napi::Error::from_reason(format!("Failed to spawn compiler thread: {}", e)))?; - - match handle.join() { - Ok(result) => result, - Err(panic_payload) => { - let msg = if let Some(s) = panic_payload.downcast_ref::<&str>() { - format!("Rust compiler panicked: {}", s) - } else if let Some(s) = panic_payload.downcast_ref::<String>() { - format!("Rust compiler panicked: {}", s) - } else { - "Rust compiler panicked (unknown payload)".to_string() - }; - Err(napi::Error::from_reason(msg)) - } - } -} - -fn compile_inner( - ast_json: String, - scope_json: String, - options_json: String, -) -> napi::Result<String> { - // Check if profiling is enabled by peeking at the options JSON - let profiling = options_json.contains("\"__profiling\":true"); - - let deser_start = Instant::now(); - - let ast: File = from_json_str(&ast_json) - .map_err(|e| napi::Error::from_reason(format!("Failed to parse AST JSON: {}", e)))?; - - let scope: ScopeInfo = from_json_str(&scope_json) - .map_err(|e| napi::Error::from_reason(format!("Failed to parse scope JSON: {}", e)))?; - - let opts: PluginOptions = from_json_str(&options_json) - .map_err(|e| napi::Error::from_reason(format!("Failed to parse options JSON: {}", e)))?; - - let deser_duration = deser_start.elapsed(); - - let compile_start = Instant::now(); - let mut result = compile_program(ast, scope, opts); - let compile_duration = compile_start.elapsed(); - - // If profiling is enabled, prepend NAPI deserialization timing and append serialization timing - if profiling { - let napi_deser_entry = TimingEntry { - name: "napi_deserialize".to_string(), - duration_us: deser_duration.as_micros() as u64, - }; - - // Insert NAPI timing entries - match &mut result { - react_compiler::entrypoint::CompileResult::Success { timing, .. } => { - timing.insert(0, napi_deser_entry); - } - react_compiler::entrypoint::CompileResult::Error { timing, .. } => { - timing.insert(0, napi_deser_entry); - } - } - - // Add compile_program duration (the total Rust compilation time including pass timing) - let compile_entry = TimingEntry { - name: "napi_compile_program".to_string(), - duration_us: compile_duration.as_micros() as u64, - }; - match &mut result { - react_compiler::entrypoint::CompileResult::Success { timing, .. } => { - timing.push(compile_entry); - } - react_compiler::entrypoint::CompileResult::Error { timing, .. } => { - timing.push(compile_entry); - } - } - } - - let ser_start = Instant::now(); - let result_json = serde_json::to_string(&result) - .map_err(|e| napi::Error::from_reason(format!("Failed to serialize result: {}", e)))?; - - if profiling { - // We need to inject the serialization timing into the already-serialized JSON. - // Since timing is a JSON array at the end of the result, we can append to it. - let ser_duration = ser_start.elapsed(); - let ser_entry = format!( - r#"{{"name":"napi_serialize","duration_us":{}}}"#, - ser_duration.as_micros() - ); - - // Find the timing array in the JSON and append our entry - if let Some(pos) = result_json.rfind("\"timing\":[") { - // Find the closing ] of the timing array - let timing_start = pos + "\"timing\":[".len(); - if let Some(close_bracket) = result_json[timing_start..].rfind(']') { - let abs_close = timing_start + close_bracket; - let mut patched = result_json[..abs_close].to_string(); - if abs_close > timing_start { - // Array is non-empty, add comma - patched.push(','); - } - patched.push_str(&ser_entry); - patched.push_str(&result_json[abs_close..]); - return Ok(patched); - } - } - } - - Ok(result_json) -} diff --git a/compiler/packages/babel-plugin-react-compiler-rust/package.json b/compiler/packages/babel-plugin-react-compiler-rust/package.json deleted file mode 100644 index ada9134e0e82..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "babel-plugin-react-compiler-rust", - "version": "0.0.0-experimental", - "description": "Babel plugin for React Compiler (Rust backend).", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "license": "MIT", - "files": [ - "dist", - "!*.tsbuildinfo" - ], - "scripts": { - "build": "tsc", - "watch": "tsc --watch", - "snap": "yarn workspace snap run snap", - "snap:build": "yarn workspace snap run build", - "snap:ci": "yarn snap:build && yarn snap --rust", - "test": "yarn snap:ci" - }, - "dependencies": { - "@babel/types": "^7.26.0" - }, - "devDependencies": { - "@babel/core": "^7.2.0", - "typescript": "^5.0.0" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/react/react.git", - "directory": "compiler/packages/babel-plugin-react-compiler-rust" - } -} diff --git a/compiler/packages/babel-plugin-react-compiler-rust/src/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler-rust/src/BabelPlugin.ts deleted file mode 100644 index 0e9c25bea552..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/src/BabelPlugin.ts +++ /dev/null @@ -1,285 +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 type * as BabelCore from '@babel/core'; -import {hasReactLikeFunctions} from './prefilter'; -import {compileWithRust, type BindingRenameInfo} from './bridge'; -import {extractScopeInfo} from './scope'; -import {resolveOptions, type PluginOptions} from './options'; - -export default function BabelPluginReactCompilerRust( - _babel: typeof BabelCore, -): BabelCore.PluginObj { - let compiledProgram = false; - return { - name: 'react-compiler-rust', - visitor: { - Program: { - enter(prog, pass): void { - // Guard against re-entry: replaceWith() below causes Babel - // to re-traverse the new Program, which would re-trigger this - // handler. Skip if we've already compiled. - if (compiledProgram) { - return; - } - compiledProgram = true; - const filename = pass.filename ?? null; - - // Step 1: Resolve options (pre-resolve JS-only values) - const opts = resolveOptions( - pass.opts as PluginOptions, - pass.file, - filename, - pass.file.ast, - ); - - // Step 2: Quick bail — should we compile this file at all? - if (!opts.shouldCompile) { - return; - } - - // Step 3: Pre-filter — any potential React functions? - // Skip prefilter when compilationMode is 'all' (compiles all functions) - if (opts.compilationMode !== 'all' && !hasReactLikeFunctions(prog)) { - return; - } - - // Step 4: Extract scope info - const logger = (pass.opts as PluginOptions).logger; - let scopeInfo; - try { - scopeInfo = extractScopeInfo(prog); - } catch (e) { - // Scope extraction can fail on unsupported syntax (e.g., reserved - // word as binding name). Report as CompileUnexpectedThrow + - // CompileError, matching TS compiler behavior. - const errMsg = e instanceof Error ? e.message : String(e); - const dotIdx = errMsg.indexOf('. '); - const reason = dotIdx >= 0 ? errMsg.substring(0, dotIdx) : errMsg; - let description: string | undefined = - dotIdx >= 0 ? errMsg.substring(dotIdx + 2) : undefined; - if (description?.endsWith('.')) { - description = description.slice(0, -1); - } - if (logger) { - logger.logEvent(filename, { - kind: 'CompileUnexpectedThrow', - data: `Error: ${errMsg}`, - }); - logger.logEvent(filename, { - kind: 'CompileError', - detail: { - reason, - severity: 'Error', - category: 'Syntax', - description: description ?? null, - suggestions: null, - details: [ - { - kind: 'error', - loc: null, - message: 'reserved word', - }, - ], - }, - }); - } - const panicThreshold = (pass.opts as PluginOptions).panicThreshold; - if ( - panicThreshold === 'all_errors' || - panicThreshold === 'critical_errors' - ) { - const heading = 'Error'; - const parts = [`${heading}: ${reason}`]; - if (description != null) { - parts.push(`\n\n${description}.`); - } - const formatted = `Found 1 error:\n\n${parts.join('')}`; - const err = new Error(formatted); - (err as any).details = []; - throw err; - } - return; - } - - // Step 5: Call Rust compiler - const optsForRust = - (logger as any)?.debugLogIRs != null - ? {...opts, __debug: true} - : opts; - const result = compileWithRust( - pass.file.ast, - scopeInfo, - optsForRust, - pass.file.code ?? null, - ); - - // Step 6: Forward logger events and debug logs via orderedLog - if (logger && result.orderedLog && result.orderedLog.length > 0) { - for (const item of result.orderedLog) { - if (item.type === 'event') { - logger.logEvent(filename, item.event); - } else if (item.type === 'debug' && logger.debugLogIRs) { - logger.debugLogIRs(item.entry); - } - } - } else if (logger && result.events) { - for (const event of result.events) { - logger.logEvent(filename, event); - } - } - - // Step 7: Handle result - if (result.kind === 'error') { - const message = - (result.error as any).rawMessage ?? - (result.error as any).formattedMessage ?? - 'Unexpected compiler error'; - const err = new Error(message); - (err as any).details = result.error.details; - throw err; - } - - if (result.ast != null) { - // Replace the program with Rust's compiled output. - const newFile = result.ast as any; - const newProgram = newFile.program ?? newFile; - - // After JSON round-tripping through Rust, comment objects that were - // shared by reference in Babel's AST (e.g., a comment between two - // statements appears as trailingComments on stmt A and leadingComments - // on stmt B, sharing the same JS object) become separate objects. - // Babel's generator uses reference identity to avoid printing the - // same comment twice. We restore sharing by deduplicating: for each - // unique comment position, we keep one canonical object and replace - // all duplicates with references to it. - deduplicateComments(newProgram); - - // Use Babel's replaceWith() API so that subsequent plugins - // (babel-plugin-fbt, babel-plugin-fbt-runtime, babel-plugin-idx) - // properly traverse the new AST. Direct assignment to - // pass.file.ast.program bypasses Babel's traversal tracking, - // and prog.skip() would prevent all merged plugin visitors from - // running on the new children. - pass.file.ast.comments = []; - prog.replaceWith(newProgram); - } - - // Apply variable renames from lowering to the Babel AST. - // Must run AFTER the AST replacement so that scope.rename() - // operates on the compiled output, not the original (discarded) AST. - if (result.renames != null && result.renames.length > 0) { - applyRenames(prog, result.renames); - } - }, - }, - }, - }; -} - -/** - * Deduplicate comments across AST nodes after JSON round-tripping. - * - * Babel's parser attaches the same comment object to multiple nodes - * (e.g., as trailingComments on node A and leadingComments on node B). - * The code generator uses reference identity (`===`) to avoid printing - * a comment twice. After JSON serialization/deserialization through Rust, - * these shared references become separate objects with identical content. - * - * This function walks the AST, finds comments with the same (start, end) - * position, and replaces duplicates with references to a single canonical - * object, restoring the sharing that Babel expects. - */ -/** - * Apply variable renames from the Rust compiler's lowering phase to the Babel AST. - * - * During lowering, the Rust compiler renames variables that shadow outer bindings - * (e.g., an inner function parameter `ref` that shadows an outer `ref` becomes `ref_0`). - * In the TS compiler, this is done via Babel's `scope.rename()` during HIRBuilder. - * Since the Rust compiler doesn't have access to Babel's scope API, it records the - * renames and returns them here for the Babel plugin to apply. - */ -function applyRenames( - prog: BabelCore.NodePath<BabelCore.types.Program>, - renames: Array<BindingRenameInfo>, -): void { - // Build a map from declaration start position to rename info - const renamesByPos = new Map<number, BindingRenameInfo>(); - for (const rename of renames) { - renamesByPos.set(rename.declarationStart, rename); - } - - // Traverse all scopes to find bindings that match by position - prog.traverse({ - Scope(path: BabelCore.NodePath) { - const scope = path.scope; - for (const [name, binding] of Object.entries( - scope.bindings as Record<string, any>, - )) { - const start = binding.identifier.start; - if (start != null) { - const rename = renamesByPos.get(start); - if (rename != null && name === rename.original) { - scope.rename(rename.original, rename.renamed); - renamesByPos.delete(start); - } - } - } - }, - } as BabelCore.Visitor); -} - -function deduplicateComments(node: any): void { - // Map from "start:end" to canonical comment object - const canonical = new Map<string, any>(); - - function dedup(comments: any[]): any[] { - return comments.map(c => { - const key = `${c.start}:${c.end}`; - const existing = canonical.get(key); - if (existing != null) { - return existing; - } - canonical.set(key, c); - return c; - }); - } - - function visit(n: any): void { - if (n == null || typeof n !== 'object') return; - if (Array.isArray(n)) { - for (const item of n) { - visit(item); - } - return; - } - if (n.leadingComments) { - n.leadingComments = dedup(n.leadingComments); - } - if (n.trailingComments) { - n.trailingComments = dedup(n.trailingComments); - } - if (n.innerComments) { - n.innerComments = dedup(n.innerComments); - } - for (const key of Object.keys(n)) { - if ( - key === 'leadingComments' || - key === 'trailingComments' || - key === 'innerComments' || - key === 'start' || - key === 'end' || - key === 'loc' - ) { - continue; - } - visit(n[key]); - } - } - - visit(node); -} diff --git a/compiler/packages/babel-plugin-react-compiler-rust/src/bridge.ts b/compiler/packages/babel-plugin-react-compiler-rust/src/bridge.ts deleted file mode 100644 index 9a3bba4b3d5e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/src/bridge.ts +++ /dev/null @@ -1,197 +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 type {ResolvedOptions} from './options'; -import type {ScopeInfo} from './scope'; -import type * as t from '@babel/types'; - -export interface DebugLogEntry { - kind: 'debug'; - name: string; - value: string; -} - -export interface BindingRenameInfo { - original: string; - renamed: string; - declarationStart: number; -} - -export interface OrderedLogItem { - type: 'event' | 'debug'; - event?: LoggerEvent; - entry?: DebugLogEntry; -} - -export interface CompileSuccess { - kind: 'success'; - ast: t.File | null; - events: Array<LoggerEvent>; - orderedLog?: Array<OrderedLogItem>; - renames?: Array<BindingRenameInfo>; -} - -export interface CompileError { - kind: 'error'; - error: { - reason: string; - description?: string; - details: Array<unknown>; - }; - events: Array<LoggerEvent>; - orderedLog?: Array<OrderedLogItem>; -} - -export type CompileResult = CompileSuccess | CompileError; - -export type LoggerEvent = { - kind: string; - [key: string]: unknown; -}; - -// The napi-rs generated binding. -// This will be available once the native module is built. -// For now, we use a dynamic require that will be resolved at runtime. -let rustCompile: - | ((ast: string, scope: string, options: string) => string) - | null = null; - -function getRustCompile(): ( - ast: string, - scope: string, - options: string, -) => string { - if (rustCompile == null) { - try { - // Try to load the native module - const native = require('../native'); - rustCompile = native.compile; - } catch (e) { - throw new Error( - 'babel-plugin-react-compiler-rust: Failed to load native module. ' + - 'Make sure the native addon is built. Error: ' + - (e as Error).message, - ); - } - } - return rustCompile!; -} - -/** - * Encode lone surrogate escapes so they survive the Rust serde_json round-trip. - * JS JSON.stringify can produce \uD800-\uDFFF lone surrogates which are invalid - * in Rust's serde_json (expects valid UTF-8/Unicode). We encode them as recoverable - * markers (__SURROGATE_XXXX__) and restore them via restoreJsonSurrogates on output. - * - * Important: we must NOT replace escaped surrogate sequences like \\uD83D\\uDE80 - * that appear in extra.raw fields (literal source text). Those have a double - * backslash in the JSON (the first \ escapes the second), so we use a negative - * lookbehind to skip them. - */ -function sanitizeJsonSurrogates(json: string): string { - // Encode lone surrogates as recoverable markers instead of replacing with - // \uFFFD. This preserves the original surrogate values through the Rust - // round-trip. restoreJsonSurrogates reverses this on the output side. - return json - .replace( - /(?<!\\)\\u([dD][89aAbB][0-9a-fA-F]{2})(?!\\u[dD][c-fC-F][0-9a-fA-F]{2})/g, - (_, hex) => `__SURROGATE_${hex.toUpperCase()}__`, - ) - .replace( - /(?<!\\u[dD][89aAbB][0-9a-fA-F]{2})(?<!\\)\\u([dD][c-fC-F][0-9a-fA-F]{2})/g, - (_, hex) => `__SURROGATE_${hex.toUpperCase()}__`, - ); -} - -function restoreJsonSurrogates(json: string): string { - return json.replace(/__SURROGATE_([0-9A-F]{4})__/g, (_, hex) => `\\u${hex}`); -} - -export function compileWithRust( - ast: t.File, - scopeInfo: ScopeInfo, - options: ResolvedOptions, - code?: string | null, -): CompileResult { - const compile = getRustCompile(); - - const optionsWithCode = - code != null ? {...options, __sourceCode: code} : options; - const resultJson = compile( - sanitizeJsonSurrogates(JSON.stringify(ast)), - JSON.stringify(scopeInfo), - JSON.stringify(optionsWithCode), - ); - - return JSON.parse(restoreJsonSurrogates(resultJson)) as CompileResult; -} - -export interface TimingEntry { - name: string; - duration_us: number; -} - -export interface BridgeTiming { - jsStringifyAst_us: number; - jsStringifyScope_us: number; - jsStringifyOptions_us: number; - napiCall_us: number; - jsParseResult_us: number; -} - -export interface ProfiledCompileResult { - result: CompileResult; - bridgeTiming: BridgeTiming; - rustTiming: Array<TimingEntry>; -} - -export function compileWithRustProfiled( - ast: t.File, - scopeInfo: ScopeInfo, - options: ResolvedOptions, - code?: string | null, -): ProfiledCompileResult { - const compile = getRustCompile(); - - const optionsWithCode = - code != null - ? {...options, __sourceCode: code, __profiling: true} - : {...options, __profiling: true}; - - const t0 = performance.now(); - const astJson = sanitizeJsonSurrogates(JSON.stringify(ast)); - const t1 = performance.now(); - const scopeJson = JSON.stringify(scopeInfo); - const t2 = performance.now(); - const optionsJson = JSON.stringify(optionsWithCode); - const t3 = performance.now(); - - const resultJson = compile(astJson, scopeJson, optionsJson); - const t4 = performance.now(); - - const result = JSON.parse( - restoreJsonSurrogates(resultJson), - ) as CompileResult & { - timing?: Array<TimingEntry>; - }; - const t5 = performance.now(); - - const rustTiming = result.timing ?? []; - delete result.timing; - - return { - result, - bridgeTiming: { - jsStringifyAst_us: Math.round((t1 - t0) * 1000), - jsStringifyScope_us: Math.round((t2 - t1) * 1000), - jsStringifyOptions_us: Math.round((t3 - t2) * 1000), - napiCall_us: Math.round((t4 - t3) * 1000), - jsParseResult_us: Math.round((t5 - t4) * 1000), - }, - rustTiming, - }; -} diff --git a/compiler/packages/babel-plugin-react-compiler-rust/src/index.ts b/compiler/packages/babel-plugin-react-compiler-rust/src/index.ts deleted file mode 100644 index 0df1f8d271a2..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/src/index.ts +++ /dev/null @@ -1,15 +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. - */ - -export {default} from './BabelPlugin'; -export type {PluginOptions} from './options'; -export type { - CompileResult, - CompileSuccess, - CompileError, - LoggerEvent, -} from './bridge'; diff --git a/compiler/packages/babel-plugin-react-compiler-rust/src/options.ts b/compiler/packages/babel-plugin-react-compiler-rust/src/options.ts deleted file mode 100644 index 5984caa83f99..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/src/options.ts +++ /dev/null @@ -1,180 +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 type * as BabelCore from '@babel/core'; -import type * as t from '@babel/types'; - -export interface ResolvedOptions { - // Pre-resolved by JS - shouldCompile: boolean; - enableReanimated: boolean; - isDev: boolean; - filename: string | null; - - // Pass-through - compilationMode: string; - panicThreshold: string; - target: unknown; - gating: unknown; - dynamicGating: unknown; - noEmit: boolean; - outputMode: string | null; - eslintSuppressionRules: string[] | null; - flowSuppressions: boolean; - ignoreUseNoForget: boolean; - customOptOutDirectives: string[] | null; - environment: Record<string, unknown>; -} - -export interface Logger { - logEvent(filename: string | null, event: unknown): void; - debugLogIRs?(value: unknown): void; -} - -export type PluginOptions = Partial<ResolvedOptions> & { - sources?: ((filename: string) => boolean) | string[]; - enableReanimatedCheck?: boolean; - logger?: Logger | null; -} & Record<string, unknown>; - -/** - * Check if the Babel pipeline uses the Reanimated plugin. - */ -function pipelineUsesReanimatedPlugin( - plugins: Array<BabelCore.PluginItem> | null | undefined, -): boolean { - if (Array.isArray(plugins)) { - for (const plugin of plugins) { - if (plugin != null && typeof plugin === 'object' && 'key' in plugin) { - const key = (plugin as any).key; - if ( - typeof key === 'string' && - key.indexOf('react-native-reanimated') !== -1 - ) { - return true; - } - } - } - } - // Check if reanimated module is available - if (typeof require !== 'undefined') { - try { - return !!require.resolve('react-native-reanimated'); - } catch { - return false; - } - } - return false; -} - -/** - * Prepare the environment config for JSON serialization to Rust. - * Converts Map instances to plain objects, pre-resolves moduleTypeProvider, - * and strips non-serializable fields. - */ -function serializeEnvironment( - rawEnv: Record<string, unknown>, - ast: t.File, -): Record<string, unknown> { - const environment: Record<string, unknown> = {...rawEnv}; - - // Convert customHooks Map to plain object for JSON serialization - if (rawEnv.customHooks instanceof Map) { - const hooks: Record<string, unknown> = {}; - for (const [key, value] of rawEnv.customHooks) { - hooks[key] = value; - } - environment.customHooks = hooks; - } - - // Pre-resolve moduleTypeProvider: collect all import sources from AST, - // call the provider for each, and serialize results as a map - const moduleTypeProvider = rawEnv.moduleTypeProvider as - | ((name: string) => unknown) - | null - | undefined; - delete environment.moduleTypeProvider; - - if (typeof moduleTypeProvider === 'function') { - const moduleTypes: Record<string, unknown> = {}; - for (const node of ast.program.body) { - if ( - node.type === 'ImportDeclaration' && - typeof node.source.value === 'string' - ) { - const moduleName = node.source.value; - if (!(moduleName in moduleTypes)) { - const result = moduleTypeProvider(moduleName); - if (result != null) { - moduleTypes[moduleName] = result; - } - } - } - } - if (Object.keys(moduleTypes).length > 0) { - environment.moduleTypeProvider = moduleTypes; - } - } - - delete environment.flowTypeProvider; - - return environment; -} - -export function resolveOptions( - rawOpts: PluginOptions, - file: BabelCore.BabelFile, - filename: string | null, - ast: t.File, -): ResolvedOptions { - // Resolve sources filter (may be a function) - let shouldCompile = true; - if (rawOpts.sources != null && filename != null) { - if (typeof rawOpts.sources === 'function') { - shouldCompile = rawOpts.sources(filename); - } else if (Array.isArray(rawOpts.sources)) { - shouldCompile = rawOpts.sources.some( - (prefix: string) => filename.indexOf(prefix) !== -1, - ); - } - } else if (rawOpts.sources != null && filename == null) { - shouldCompile = false; // sources specified but no filename - } - - // Resolve reanimated check - const enableReanimated = - rawOpts.enableReanimatedCheck !== false && - pipelineUsesReanimatedPlugin(file.opts.plugins); - - // Resolve isDev - const isDev = - (typeof globalThis !== 'undefined' && - (globalThis as any).__DEV__ === true) || - process.env['NODE_ENV'] === 'development'; - - return { - shouldCompile, - enableReanimated, - isDev, - filename, - compilationMode: (rawOpts.compilationMode as string) ?? 'infer', - panicThreshold: (rawOpts.panicThreshold as string) ?? 'none', - target: rawOpts.target ?? '19', - gating: rawOpts.gating ?? null, - dynamicGating: rawOpts.dynamicGating ?? null, - noEmit: rawOpts.noEmit ?? false, - outputMode: (rawOpts.outputMode as string) ?? null, - eslintSuppressionRules: rawOpts.eslintSuppressionRules ?? null, - flowSuppressions: rawOpts.flowSuppressions ?? true, - ignoreUseNoForget: rawOpts.ignoreUseNoForget ?? false, - customOptOutDirectives: rawOpts.customOptOutDirectives ?? null, - environment: serializeEnvironment( - (rawOpts.environment as Record<string, unknown>) ?? {}, - ast, - ), - }; -} diff --git a/compiler/packages/babel-plugin-react-compiler-rust/src/prefilter.ts b/compiler/packages/babel-plugin-react-compiler-rust/src/prefilter.ts deleted file mode 100644 index 56a415ca5859..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/src/prefilter.ts +++ /dev/null @@ -1,156 +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 type {NodePath} from '@babel/core'; -import type * as t from '@babel/types'; - -/** - * Quick check: does this program contain any functions with names that - * could be React components (capitalized) or hooks (useXxx)? - * - * This is intentionally loose — Rust handles the precise detection. - * We just want to avoid serializing files that definitely have no - * React functions (e.g., pure utility modules, CSS-in-JS, configs). - */ -export function hasReactLikeFunctions(program: NodePath<t.Program>): boolean { - let found = false; - program.traverse({ - // Skip classes — their methods are not compiled - ClassDeclaration(path) { - path.skip(); - }, - ClassExpression(path) { - path.skip(); - }, - - FunctionDeclaration(path) { - if (found) return; - const name = path.node.id?.name; - if ( - (name && isReactLikeName(name)) || - hasOptInDirective(path.node) || - isComponentOrHookDeclaration(path.node) - ) { - found = true; - path.stop(); - } - }, - FunctionExpression(path) { - if (found) return; - const idName = path.node.id?.name; - const inferredName = inferFunctionName(path); - if ( - (idName && isReactLikeName(idName)) || - (inferredName && isReactLikeName(inferredName)) || - isInsideMemoOrForwardRef(path) || - hasOptInDirective(path.node) - ) { - found = true; - path.stop(); - } - }, - ArrowFunctionExpression(path) { - if (found) return; - const name = inferFunctionName(path); - if ( - (name && isReactLikeName(name)) || - isInsideMemoOrForwardRef(path) || - hasOptInDirective(path.node) - ) { - found = true; - path.stop(); - } - }, - }); - return found; -} - -/** - * Check if a function expression/arrow is the first argument of - * React.memo(), React.forwardRef(), memo(), or forwardRef(). - */ -function isInsideMemoOrForwardRef( - path: NodePath<t.FunctionExpression | t.ArrowFunctionExpression>, -): boolean { - const parent = path.parentPath; - if (parent == null || !parent.isCallExpression()) return false; - const callExpr = parent.node as t.CallExpression; - // Must be the first argument - if (callExpr.arguments[0] !== path.node) return false; - const callee = callExpr.callee; - // Direct calls: memo(...) or forwardRef(...) - if ( - callee.type === 'Identifier' && - (callee.name === 'memo' || callee.name === 'forwardRef') - ) { - return true; - } - // Member expression calls: React.memo(...) or React.forwardRef(...) - if ( - callee.type === 'MemberExpression' && - callee.object.type === 'Identifier' && - callee.object.name === 'React' && - callee.property.type === 'Identifier' && - (callee.property.name === 'memo' || callee.property.name === 'forwardRef') - ) { - return true; - } - return false; -} - -/** - * Check if a function has an opt-in directive ('use memo' or 'use forget') - * in its body, indicating it should be compiled in annotation mode. - */ -function hasOptInDirective( - node: - | t.FunctionDeclaration - | t.FunctionExpression - | t.ArrowFunctionExpression, -): boolean { - if (node.body.type !== 'BlockStatement') return false; - return node.body.directives.some( - d => d.value.value === 'use memo' || d.value.value === 'use forget', - ); -} - -function isReactLikeName(name: string): boolean { - return /^[A-Z]/.test(name) || /^use[A-Z0-9]/.test(name); -} - -function isComponentOrHookDeclaration(node: t.FunctionDeclaration): boolean { - return ( - Object.prototype.hasOwnProperty.call(node, '__componentDeclaration') || - Object.prototype.hasOwnProperty.call(node, '__hookDeclaration') - ); -} - -/** - * Infer the name of an anonymous function expression from its parent - * (e.g., `const Foo = () => {}` → 'Foo'). - */ -function inferFunctionName( - path: NodePath<t.FunctionExpression | t.ArrowFunctionExpression>, -): string | null { - const parent = path.parentPath; - if (parent == null) return null; - if ( - parent.isVariableDeclarator() && - parent.get('init').node === path.node && - parent.get('id').isIdentifier() - ) { - return (parent.get('id').node as t.Identifier).name; - } - if ( - parent.isAssignmentExpression() && - parent.get('right').node === path.node && - parent.get('left').isIdentifier() - ) { - return (parent.get('left').node as t.Identifier).name; - } - return null; -} diff --git a/compiler/packages/babel-plugin-react-compiler-rust/src/scope.ts b/compiler/packages/babel-plugin-react-compiler-rust/src/scope.ts deleted file mode 100644 index 54aeef2c8ec6..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/src/scope.ts +++ /dev/null @@ -1,467 +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 type {NodePath} from '@babel/core'; -import type * as t from '@babel/types'; - -export interface ScopeData { - id: number; - parent: number | null; - kind: string; - bindings: Record<string, number>; -} - -export interface BindingData { - id: number; - name: string; - kind: string; - scope: number; - declarationType: string; - declarationStart?: number; - declarationNodeId?: number; - import?: ImportBindingData; -} - -export interface ImportBindingData { - source: string; - kind: string; - imported?: string; -} - -export interface ScopeInfo { - scopes: Array<ScopeData>; - bindings: Array<BindingData>; - nodeToScope: Record<number, number>; - nodeToScopeEnd: Record<number, number>; - referenceToBinding: Record<number, number>; - refNodeIdToBinding: Record<number, number>; - nodeIdToScope: Record<number, number>; - programScope: number; -} - -/** - * Recursively map identifier references inside a pattern (including destructuring) - * to a binding. Only maps identifiers that match the binding name. - */ -function mapPatternIdentifiers( - path: NodePath, - bindingId: number, - bindingName: string, - mapRef: (start: number, bindingId: number, node: t.Node) => void, -): void { - if (path.isIdentifier()) { - if (path.node.name === bindingName) { - const start = path.node.start; - if (start != null) { - mapRef(start, bindingId, path.node); - } - } - } else if (path.isArrayPattern()) { - for (const element of path.get('elements')) { - if (element.node != null) { - mapPatternIdentifiers( - element as NodePath, - bindingId, - bindingName, - mapRef, - ); - } - } - } else if (path.isObjectPattern()) { - for (const prop of path.get('properties')) { - if (prop.isRestElement()) { - mapPatternIdentifiers( - prop.get('argument'), - bindingId, - bindingName, - mapRef, - ); - } else if (prop.isObjectProperty()) { - mapPatternIdentifiers( - prop.get('value') as NodePath, - bindingId, - bindingName, - mapRef, - ); - } - } - } else if (path.isAssignmentPattern()) { - mapPatternIdentifiers( - path.get('left') as NodePath, - bindingId, - bindingName, - mapRef, - ); - } else if (path.isRestElement()) { - mapPatternIdentifiers(path.get('argument'), bindingId, bindingName, mapRef); - } else if (path.isMemberExpression()) { - // MemberExpression in LVal position (e.g., a.b = ...) - const obj = path.get('object'); - if (obj.isIdentifier() && obj.node.name === bindingName) { - const start = obj.node.start; - if (start != null) { - mapRef(start, bindingId, obj.node); - } - } - } -} - -/** - * Extract scope information from a Babel Program path. - * - * The goal here is to serialize only the core scope data structure — scopes, - * bindings, and the mappings that link AST positions to them — and leave all - * interesting analysis to the Rust side. Babel already computes scope/binding - * resolution during parsing, so we extract that work rather than re-implement - * it. But any *derived* information (source locations of identifiers, whether - * a reference is a JSXIdentifier, which variables are captured across function - * boundaries, etc.) is intentionally omitted: the Rust compiler can recover it - * by walking the parsed AST it already has. - * - * Keeping this serialization layer thin makes the JS/Rust boundary easier to - * reason about and avoids shipping redundant data across FFI. - */ -export function extractScopeInfo(program: NodePath<t.Program>): ScopeInfo { - const scopes: Array<ScopeData> = []; - const bindings: Array<BindingData> = []; - const nodeToScope: Record<number, number> = {}; - const nodeToScopeEnd: Record<number, number> = {}; - const referenceToBinding: Record<number, number> = {}; - const refNodeIdToBinding: Record<number, number> = {}; - const nodeIdToScope: Record<number, number> = {}; - - let nextNodeId = 1; - function getOrAssignNodeId(node: t.Node): number { - const n = node as any; - if (n._nodeId == null) { - n._nodeId = nextNodeId++; - } - return n._nodeId; - } - - function mapRef(start: number, bindingId: number, node: t.Node): void { - referenceToBinding[start] = bindingId; - const nodeId = getOrAssignNodeId(node); - refNodeIdToBinding[nodeId] = bindingId; - } - - // Map from Babel scope uid to our scope id - const scopeUidToId = new Map<string, number>(); - - // Helper to register a scope and its bindings - function registerScope( - babelScope: { - uid: number; - parent: {uid: number} | null; - bindings: Record<string, any>; - }, - path: NodePath | null, - ): void { - const uid = String(babelScope.uid); - if (scopeUidToId.has(uid)) return; - - const scopeId = scopes.length; - scopeUidToId.set(uid, scopeId); - - // Determine parent scope id - let parentId: number | null = null; - if (babelScope.parent) { - const parentUid = String(babelScope.parent.uid); - if (scopeUidToId.has(parentUid)) { - parentId = scopeUidToId.get(parentUid)!; - } - } - - // Determine scope kind - const kind = path != null ? getScopeKind(path) : 'program'; - - // Collect bindings declared in this scope - const scopeBindings: Record<string, number> = {}; - const ownBindings = babelScope.bindings; - for (const name of Object.keys(ownBindings)) { - const babelBinding = ownBindings[name]; - if (!babelBinding) continue; - - const bindingId = bindings.length; - scopeBindings[name] = bindingId; - - const bindingData: BindingData = { - id: bindingId, - name, - kind: getBindingKind(babelBinding), - scope: scopeId, - declarationType: babelBinding.path.node.type, - declarationStart: babelBinding.identifier.start ?? undefined, - declarationNodeId: getOrAssignNodeId(babelBinding.identifier), - }; - - // Check for import bindings - if (babelBinding.kind === 'module') { - const importData = getImportData(babelBinding); - if (importData) { - bindingData.import = importData; - } - } - - bindings.push(bindingData); - - // Map identifier references to bindings. - // Position-0 entries are handled by mapRef's collision detection — - // see the comment above pos0BindingId for details. - for (const ref of babelBinding.referencePaths) { - const start = ref.node.start; - if (start != null) { - mapRef(start, bindingId, ref.node); - } - } - - // Map constant violations (LHS of assignments like `a = b`, `a++`, `for (a of ...)`) - for (const violation of babelBinding.constantViolations) { - if (violation.isAssignmentExpression()) { - const left = violation.get('left'); - mapPatternIdentifiers( - left, - bindingId, - babelBinding.identifier.name, - mapRef, - ); - } else if (violation.isUpdateExpression()) { - const arg = violation.get('argument'); - if (arg.isIdentifier()) { - const start = arg.node.start; - if (start != null) { - mapRef(start, bindingId, arg.node); - } - } - } else if ( - violation.isForOfStatement() || - violation.isForInStatement() - ) { - const left = violation.get('left'); - mapPatternIdentifiers( - left, - bindingId, - babelBinding.identifier.name, - mapRef, - ); - } else if (violation.isFunctionDeclaration()) { - // Function redeclarations: `function x() {} function x() {}` - // Map the function name identifier to the binding - const funcId = (violation.node as any).id; - if (funcId?.start != null) { - mapRef(funcId.start, bindingId, funcId); - } - } - } - - // Map the binding identifier itself - const bindingStart = babelBinding.identifier.start; - if (bindingStart != null) { - mapRef(bindingStart, bindingId, babelBinding.identifier); - } - } - - // Map AST node to scope. - // Skip zero-width nodes (e.g., synthetic IIFEs from Hermes match desugar - // where start === end === 0) — they would collide with real scopes at position 0. - // The Rust compiler handles missing entries via parent-based scope lookup. - if (path != null) { - const nodeStart = path.node.start; - const nodeEnd = path.node.end; - if (nodeStart != null && nodeEnd != null && nodeEnd > nodeStart) { - nodeToScope[nodeStart] = scopeId; - nodeToScopeEnd[nodeStart] = nodeEnd; - } - const scopeNodeId = getOrAssignNodeId(path.node); - nodeIdToScope[scopeNodeId] = scopeId; - } - - scopes.push({ - id: scopeId, - parent: parentId, - kind, - bindings: scopeBindings, - }); - } - - // Register the program scope first (program.traverse doesn't visit the Program node itself) - registerScope(program.scope as any, program); - - // Collect all child scopes by traversing the program - program.traverse({ - enter(path) { - registerScope(path.scope as any, path); - }, - }); - - // Add JSX intrinsic element names to referenceToBinding when they match a - // local binding. The TS compiler's gatherCapturedContext explicitly traverses - // JSX elements and looks up bindings for their tag names, but Babel does NOT - // include JSX intrinsic tag names in binding.referencePaths. We replicate the - // TS behavior by adding these references here so the Rust compiler's context - // capture correctly detects them. - program.traverse({ - JSXOpeningElement(path) { - const name = path.get('name'); - if (!name.isJSXIdentifier()) { - return; - } - const tagName = name.node.name; - const binding = path.scope.getBinding(tagName); - if (binding != null) { - const bindingScopeUid = String((binding.scope as any).uid); - const bindingScopeId = scopeUidToId.get(bindingScopeUid); - if (bindingScopeId != null) { - const scopeData = scopes.find(s => s.id === bindingScopeId); - if (scopeData != null && tagName in scopeData.bindings) { - const start = name.node.start; - if (start != null) { - // mapRef also populates refNodeIdToBinding, the only map the - // Rust side consumes (referenceToBinding is deprecated there). - mapRef(start, scopeData.bindings[tagName], name.node); - } - } - } - } - }, - }); - - // Babel's scope crawl does not collect every identifier that - // isReferencedIdentifier() classifies as a reference (observed: Flow - // FunctionTypeParam names resolving to value bindings are missing from - // binding.referencePaths under @babel/core's traversal). The TS compiler's - // FindContextIdentifiers and BuildHIR hoisting don't use referencePaths — - // they re-traverse Identifier nodes and call isReferencedIdentifier() - // directly, so they DO see these references. Replicate that view by mapping - // any referenced identifier the crawl missed. - program.traverse({ - Identifier(path: NodePath<t.Identifier>) { - if (!path.isReferencedIdentifier()) { - return; - } - const node = path.node as any; - const start = node.start; - if (start == null) { - return; - } - if (node._nodeId != null && refNodeIdToBinding[node._nodeId] != null) { - return; - } - const binding = path.scope.getBinding(path.node.name); - if (binding == null) { - return; - } - const bindingScopeUid = String((binding.scope as any).uid); - const bindingScopeId = scopeUidToId.get(bindingScopeUid); - if (bindingScopeId == null) { - return; - } - const scopeData = scopes.find(s => s.id === bindingScopeId); - if (scopeData != null && path.node.name in scopeData.bindings) { - mapRef(start, scopeData.bindings[path.node.name], node); - } - }, - }); - - // Assign _nodeId to ALL Identifier and JSXIdentifier nodes in the AST, - // not just those that resolve to bindings. This ensures global references - // (Array, Error, etc.) also have _nodeId set, letting the Rust compiler - // distinguish "no binding found via node-ID = global" from "no node-ID at all". - program.traverse({ - Identifier(path: NodePath<t.Identifier>) { - getOrAssignNodeId(path.node); - }, - JSXIdentifier(path: NodePath<t.JSXIdentifier>) { - getOrAssignNodeId(path.node); - }, - }); - - // Program scope should always be id 0 - const programScopeUid = String((program.scope as any).uid); - const programScopeId = scopeUidToId.get(programScopeUid) ?? 0; - - return { - scopes, - bindings, - nodeToScope, - nodeToScopeEnd, - referenceToBinding, - refNodeIdToBinding, - nodeIdToScope, - programScope: programScopeId, - }; -} - -function getScopeKind(path: NodePath): string { - if (path.isProgram()) return 'program'; - if (path.isFunction()) return 'function'; - if ( - path.isForStatement() || - path.isForInStatement() || - path.isForOfStatement() - ) - return 'for'; - if (path.isClassDeclaration() || path.isClassExpression()) return 'class'; - if (path.isSwitchStatement()) return 'switch'; - if (path.isCatchClause()) return 'catch'; - return 'block'; -} - -function getBindingKind(binding: {kind: string; path: NodePath}): string { - switch (binding.kind) { - case 'var': - return 'var'; - case 'let': - return 'let'; - case 'const': - return 'const'; - case 'param': - return 'param'; - case 'module': - return 'module'; - case 'hoisted': - return 'hoisted'; - case 'local': - return 'local'; - default: - return 'unknown'; - } -} - -function getImportData(binding: { - path: NodePath; -}): ImportBindingData | undefined { - const decl = binding.path; - if ( - !decl.isImportSpecifier() && - !decl.isImportDefaultSpecifier() && - !decl.isImportNamespaceSpecifier() - ) { - return undefined; - } - - const importDecl = decl.parentPath; - if (!importDecl?.isImportDeclaration()) { - return undefined; - } - - const source = importDecl.node.source.value; - - if (decl.isImportDefaultSpecifier()) { - return {source, kind: 'default'}; - } - if (decl.isImportNamespaceSpecifier()) { - return {source, kind: 'namespace'}; - } - if (decl.isImportSpecifier()) { - const imported = decl.node.imported; - const importedName = - imported.type === 'Identifier' ? imported.name : imported.value; - return {source, kind: 'named', imported: importedName}; - } - return undefined; -} diff --git a/compiler/packages/babel-plugin-react-compiler-rust/tsconfig.json b/compiler/packages/babel-plugin-react-compiler-rust/tsconfig.json deleted file mode 100644 index c8a1f1b4e463..000000000000 --- a/compiler/packages/babel-plugin-react-compiler-rust/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "lib": ["ES2020"], - "outDir": "dist", - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "resolveJsonModule": true, - "moduleResolution": "node" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/compiler/packages/babel-plugin-react-compiler/package.json b/compiler/packages/babel-plugin-react-compiler/package.json index 05406af6b1a9..7647bcab1ca4 100644 --- a/compiler/packages/babel-plugin-react-compiler/package.json +++ b/compiler/packages/babel-plugin-react-compiler/package.json @@ -65,7 +65,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/react/react.git", + "url": "git+https://github.com/facebook/react.git", "directory": "compiler/packages/babel-plugin-react-compiler" } } 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 ce565cd05cd2..c0576c7521f1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -7,7 +7,12 @@ import * as t from '@babel/types'; import {z} from 'zod/v4'; -import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError'; +import { + CompilerDiagnostic, + CompilerError, + CompilerErrorDetail, + CompilerErrorDetailOptions, +} from '../CompilerError'; import { EnvironmentConfig, ExternalFunction, @@ -251,23 +256,10 @@ export type LoggerEvent = | PipelineErrorEvent | TimingEvent; -export type CompileErrorDetail = { - category: string; - reason: string; - description: string | null; - severity: string; - suggestions: Array<unknown> | null; - details?: Array<{ - kind: string; - loc: t.SourceLocation | null; - message: string | null; - }>; - loc?: t.SourceLocation | null; -}; export type CompileErrorEvent = { kind: 'CompileError'; fnLoc: t.SourceLocation | null; - detail: CompileErrorDetail; + detail: CompilerErrorDetail | CompilerDiagnostic; }; export type CompileDiagnosticEvent = { kind: 'CompileDiagnostic'; 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 3b88e22c2b03..a0cd02817828 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -161,9 +161,7 @@ function runWithEnvironment( log({kind: 'hir', name: 'PruneMaybeThrows', value: hir}); validateContextVariableLValues(hir); - log({kind: 'debug', name: 'ValidateContextVariableLValues', value: 'ok'}); validateUseMemo(hir); - log({kind: 'debug', name: 'ValidateUseMemo', value: 'ok'}); if (env.enableDropManualMemoization) { dropManualMemoization(hir); @@ -181,9 +179,7 @@ function runWithEnvironment( log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir}); assertConsistentIdentifiers(hir); - log({kind: 'debug', name: 'AssertConsistentIdentifiers', value: 'ok'}); assertTerminalSuccessorsExist(hir); - log({kind: 'debug', name: 'AssertTerminalSuccessorsExist', value: 'ok'}); enterSSA(hir); log({kind: 'hir', name: 'SSA', value: hir}); @@ -192,7 +188,6 @@ function runWithEnvironment( log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir}); assertConsistentIdentifiers(hir); - log({kind: 'debug', name: 'AssertConsistentIdentifiers', value: 'ok'}); constantPropagation(hir); log({kind: 'hir', name: 'ConstantPropagation', value: hir}); @@ -203,11 +198,9 @@ function runWithEnvironment( if (env.enableValidations) { if (env.config.validateHooksUsage) { validateHooksUsage(hir); - log({kind: 'debug', name: 'ValidateHooksUsage', value: 'ok'}); } if (env.config.validateNoCapitalizedCalls) { validateNoCapitalizedCalls(hir); - log({kind: 'debug', name: 'ValidateNoCapitalizedCalls', value: 'ok'}); } } @@ -237,25 +230,17 @@ function runWithEnvironment( log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir}); if (env.enableValidations) { validateLocalsNotReassignedAfterRender(hir); - log({ - kind: 'debug', - name: 'ValidateLocalsNotReassignedAfterRender', - value: 'ok', - }); if (env.config.assertValidMutableRanges) { assertValidMutableRanges(hir); - log({kind: 'debug', name: 'AssertValidMutableRanges', value: 'ok'}); } if (env.config.validateRefAccessDuringRender) { validateNoRefAccessInRender(hir); - log({kind: 'debug', name: 'ValidateNoRefAccessInRender', value: 'ok'}); } if (env.config.validateNoSetStateInRender) { validateNoSetStateInRender(hir); - log({kind: 'debug', name: 'ValidateNoSetStateInRender', value: 'ok'}); } if ( @@ -263,36 +248,19 @@ function runWithEnvironment( env.outputMode === 'lint' ) { env.logErrors(validateNoDerivedComputationsInEffects_exp(hir)); - log({ - kind: 'debug', - name: 'ValidateNoDerivedComputationsInEffects', - value: 'ok', - }); } else if (env.config.validateNoDerivedComputationsInEffects) { validateNoDerivedComputationsInEffects(hir); - log({ - kind: 'debug', - name: 'ValidateNoDerivedComputationsInEffects', - value: 'ok', - }); } if (env.config.validateNoSetStateInEffects && env.outputMode === 'lint') { env.logErrors(validateNoSetStateInEffects(hir, env)); - log({kind: 'debug', name: 'ValidateNoSetStateInEffects', value: 'ok'}); } if (env.config.validateNoJSXInTryStatements && env.outputMode === 'lint') { env.logErrors(validateNoJSXInTryStatement(hir)); - log({kind: 'debug', name: 'ValidateNoJSXInTryStatement', value: 'ok'}); } validateNoFreezingKnownMutableFunctions(hir); - log({ - kind: 'debug', - name: 'ValidateNoFreezingKnownMutableFunctions', - value: 'ok', - }); } inferReactivePlaces(hir); @@ -305,7 +273,6 @@ function runWithEnvironment( ) { // NOTE: this relies on reactivity inference running first validateExhaustiveDependencies(hir); - log({kind: 'debug', name: 'ValidateExhaustiveDependencies', value: 'ok'}); } } @@ -322,7 +289,6 @@ function runWithEnvironment( env.outputMode === 'lint' ) { env.logErrors(validateStaticComponents(hir)); - log({kind: 'debug', name: 'ValidateStaticComponents', value: 'ok'}); } if (env.enableMemoization) { @@ -395,7 +361,6 @@ function runWithEnvironment( value: hir, }); assertValidBlockNesting(hir); - log({kind: 'debug', name: 'AssertValidBlockNesting', value: 'ok'}); buildReactiveScopeTerminalsHIR(hir); log({ @@ -405,7 +370,6 @@ function runWithEnvironment( }); assertValidBlockNesting(hir); - log({kind: 'debug', name: 'AssertValidBlockNesting', value: 'ok'}); flattenReactiveLoopsHIR(hir); log({ @@ -421,9 +385,7 @@ function runWithEnvironment( value: hir, }); assertTerminalSuccessorsExist(hir); - log({kind: 'debug', name: 'AssertTerminalSuccessorsExist', value: 'ok'}); assertTerminalPredsExist(hir); - log({kind: 'debug', name: 'AssertTerminalPredsExist', value: 'ok'}); propagateScopeDependenciesHIR(hir); log({ @@ -440,7 +402,6 @@ function runWithEnvironment( }); assertWellFormedBreakTargets(reactiveFunction); - log({kind: 'debug', name: 'AssertWellFormedBreakTargets', value: 'ok'}); pruneUnusedLabels(reactiveFunction); log({ @@ -449,11 +410,6 @@ function runWithEnvironment( value: reactiveFunction, }); assertScopeInstructionsWithinScopes(reactiveFunction); - log({ - kind: 'debug', - name: 'AssertScopeInstructionsWithinScopes', - value: 'ok', - }); pruneNonEscapingScopes(reactiveFunction); log({ @@ -544,11 +500,6 @@ function runWithEnvironment( env.config.validatePreserveExistingMemoizationGuarantees ) { validatePreservedManualMemoization(reactiveFunction); - log({ - kind: 'debug', - name: 'ValidatePreservedManualMemoization', - value: 'ok', - }); } const ast = codegenFunction(reactiveFunction, { 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 30c005a8cc2f..e7aa76a3e043 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -8,12 +8,10 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; import { - CompilerDiagnostic, CompilerError, CompilerErrorDetail, ErrorCategory, } from '../CompilerError'; -import {CompileErrorDetail} from './Options'; import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; @@ -172,44 +170,6 @@ export type CompileResult = { compiledFn: CodegenFunction; }; -/** - * Format a CompilerDiagnostic or CompilerErrorDetail class instance - * into a plain object for logEvent(). This ensures the logged value - * has all fields as direct properties (no getters, no nested `options`). - */ -export function formatDetailForLogging( - detail: CompilerDiagnostic | CompilerErrorDetail, -): CompileErrorDetail { - if (detail instanceof CompilerDiagnostic) { - return { - category: detail.category, - reason: detail.reason, - description: detail.description ?? null, - severity: detail.severity, - suggestions: detail.suggestions ?? null, - details: detail.options.details.map(d => { - if (d.kind === 'error') { - const loc = d.loc != null && typeof d.loc !== 'symbol' ? d.loc : null; - return {kind: d.kind, loc, message: d.message}; - } else { - return {kind: d.kind, loc: null, message: d.message}; - } - }), - }; - } else { - const loc = - detail.loc != null && typeof detail.loc !== 'symbol' ? detail.loc : null; - return { - category: detail.category, - reason: detail.reason, - description: detail.description ?? null, - severity: detail.severity, - suggestions: detail.suggestions ?? null, - loc, - }; - } -} - function logError( err: unknown, context: { @@ -224,7 +184,7 @@ function logError( context.opts.logger.logEvent(context.filename, { kind: 'CompileError', fnLoc, - detail: formatDetailForLogging(detail), + detail, }); } } else { @@ -678,7 +638,7 @@ function processFn( programContext.logEvent({ kind: 'CompileSkip', fnLoc: fn.node.body.loc ?? null, - reason: `Skipped due to '${directives.optOut.value.value}' directive.`, + reason: `Skipped due to '${directives.optOut.value}' directive.`, loc: directives.optOut.loc ?? null, }); return null; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/DebugPrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/DebugPrintHIR.ts deleted file mode 100644 index eb6ce325dd7d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/DebugPrintHIR.ts +++ /dev/null @@ -1,1404 +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 {assertExhaustive} from '../Utils/utils'; -import type { - BasicBlock, - HIRFunction, - Identifier, - Instruction, - InstructionValue, - LValue, - NonLocalBinding, - ObjectPropertyKey, - Pattern, - Phi, - Place, - ReactiveScope, - SourceLocation, - SpreadPattern, - Terminal, -} from './HIR'; -import type {Type} from './Types'; -import type {AliasingEffect} from '../Inference/AliasingEffects'; -import type {CompilerDiagnostic, CompilerErrorDetail} from '../CompilerError'; -import type {IdentifierId, ScopeId} from './HIR'; - -export function printDebugHIR(fn: HIRFunction): string { - const printer = new DebugPrinter(); - printer.formatFunction(fn); - - const outlined = fn.env.getOutlinedFunctions(); - for (let i = 0; i < outlined.length; i++) { - printer.line(''); - printer.formatFunction(outlined[i].fn); - } - - printer.line(''); - printer.line('Environment:'); - printer.indent(); - const errors = fn.env.aggregateErrors(); - printer.formatErrors(errors); - printer.dedent(); - - return printer.toString(); -} - -export class DebugPrinter { - seenIdentifiers: Set<IdentifierId> = new Set(); - seenScopes: Set<ScopeId> = new Set(); - output: Array<string> = []; - indentLevel: number = 0; - - line(text: string): void { - this.output.push(' '.repeat(this.indentLevel) + text); - } - - indent(): void { - this.indentLevel++; - } - - dedent(): void { - this.indentLevel--; - } - - toString(): string { - return this.output.join('\n'); - } - - formatFunction(fn: HIRFunction): void { - this.indent(); - this.line(`id: ${fn.id !== null ? `"${fn.id}"` : 'null'}`); - this.line( - `name_hint: ${fn.nameHint !== null ? `"${fn.nameHint}"` : 'null'}`, - ); - this.line(`fn_type: ${fn.fnType}`); - this.line(`generator: ${fn.generator}`); - this.line(`is_async: ${fn.async}`); - this.line(`loc: ${this.formatLoc(fn.loc)}`); - - this.line('params:'); - this.indent(); - fn.params.forEach((param, i) => { - if (param.kind === 'Identifier') { - this.formatPlaceField(`[${i}]`, param); - } else { - this.line(`[${i}] Spread:`); - this.indent(); - this.formatPlaceField('place', param.place); - this.dedent(); - } - }); - this.dedent(); - - this.line('returns:'); - this.indent(); - this.formatPlaceField('value', fn.returns); - this.dedent(); - - this.line('context:'); - this.indent(); - fn.context.forEach((ctx, i) => { - this.formatPlaceField(`[${i}]`, ctx); - }); - this.dedent(); - - if (fn.aliasingEffects !== null) { - this.line('aliasingEffects:'); - this.indent(); - fn.aliasingEffects.forEach((effect, i) => { - this.line(`[${i}] ${this.formatAliasingEffect(effect)}`); - }); - this.dedent(); - } else { - this.line('aliasingEffects: null'); - } - - this.line('directives:'); - this.indent(); - fn.directives.forEach((d, i) => { - this.line(`[${i}] "${d}"`); - }); - this.dedent(); - - this.line( - `returnTypeAnnotation: ${fn.returnTypeAnnotation !== null ? fn.returnTypeAnnotation.type : 'null'}`, - ); - - this.line(''); - this.line('Blocks:'); - this.indent(); - for (const [blockId, block] of fn.body.blocks) { - this.formatBlock(blockId, block); - } - this.dedent(); - this.dedent(); - } - - formatBlock(blockId: number, block: BasicBlock): void { - this.line(`bb${blockId} (${block.kind}):`); - this.indent(); - - const preds = [...block.preds]; - this.line(`preds: [${preds.map(p => `bb${p}`).join(', ')}]`); - - this.line('phis:'); - this.indent(); - for (const phi of block.phis) { - this.formatPhi(phi); - } - this.dedent(); - - this.line('instructions:'); - this.indent(); - block.instructions.forEach((instr, i) => { - this.formatInstruction(instr, i); - }); - this.dedent(); - - this.line('terminal:'); - this.indent(); - this.formatTerminal(block.terminal); - this.dedent(); - - this.dedent(); - } - - formatPhi(phi: Phi): void { - this.line('Phi {'); - this.indent(); - this.formatPlaceField('place', phi.place); - this.line('operands:'); - this.indent(); - for (const [blockId, place] of phi.operands) { - this.line(`bb${blockId}:`); - this.indent(); - this.formatPlaceField('value', place); - this.dedent(); - } - this.dedent(); - this.dedent(); - this.line('}'); - } - - formatInstruction(instr: Instruction, index: number): void { - this.line(`[${index}] Instruction {`); - this.indent(); - this.line(`id: ${instr.id}`); - this.formatPlaceField('lvalue', instr.lvalue); - this.line('value:'); - this.indent(); - this.formatInstructionValue(instr.value); - this.dedent(); - if (instr.effects !== null) { - this.line('effects:'); - this.indent(); - instr.effects.forEach((effect, i) => { - this.line(`[${i}] ${this.formatAliasingEffect(effect)}`); - }); - this.dedent(); - } else { - this.line('effects: null'); - } - this.line(`loc: ${this.formatLoc(instr.loc)}`); - this.dedent(); - this.line('}'); - } - - formatInstructionValue(instrValue: InstructionValue): void { - switch (instrValue.kind) { - case 'ArrayExpression': { - this.line(`ArrayExpression {`); - this.indent(); - this.line('elements:'); - this.indent(); - instrValue.elements.forEach((element, i) => { - if (element.kind === 'Identifier') { - this.formatPlaceField(`[${i}]`, element); - } else if (element.kind === 'Hole') { - this.line(`[${i}] Hole`); - } else { - this.line(`[${i}] Spread:`); - this.indent(); - this.formatPlaceField('place', element.place); - this.dedent(); - } - }); - this.dedent(); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'ObjectExpression': { - this.line('ObjectExpression {'); - this.indent(); - this.line('properties:'); - this.indent(); - instrValue.properties.forEach((prop, i) => { - if (prop.kind === 'ObjectProperty') { - this.line(`[${i}] ObjectProperty {`); - this.indent(); - this.line(`key: ${this.formatObjectPropertyKey(prop.key)}`); - this.line(`type: "${prop.type}"`); - this.formatPlaceField('place', prop.place); - this.dedent(); - this.line('}'); - } else { - this.line(`[${i}] Spread:`); - this.indent(); - this.formatPlaceField('place', prop.place); - this.dedent(); - } - }); - this.dedent(); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'UnaryExpression': { - this.line(`UnaryExpression {`); - this.indent(); - this.line(`operator: "${instrValue.operator}"`); - this.formatPlaceField('value', instrValue.value); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'BinaryExpression': { - this.line('BinaryExpression {'); - this.indent(); - this.line(`operator: "${instrValue.operator}"`); - this.formatPlaceField('left', instrValue.left); - this.formatPlaceField('right', instrValue.right); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'NewExpression': { - this.line('NewExpression {'); - this.indent(); - this.formatPlaceField('callee', instrValue.callee); - this.line('args:'); - this.indent(); - instrValue.args.forEach((arg, i) => { - this.formatArgument(arg, i); - }); - this.dedent(); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'CallExpression': { - this.line('CallExpression {'); - this.indent(); - this.formatPlaceField('callee', instrValue.callee); - this.line('args:'); - this.indent(); - instrValue.args.forEach((arg, i) => { - this.formatArgument(arg, i); - }); - this.dedent(); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'MethodCall': { - this.line('MethodCall {'); - this.indent(); - this.formatPlaceField('receiver', instrValue.receiver); - this.formatPlaceField('property', instrValue.property); - this.line('args:'); - this.indent(); - instrValue.args.forEach((arg, i) => { - this.formatArgument(arg, i); - }); - this.dedent(); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'JSXText': { - this.line( - `JSXText { value: ${JSON.stringify(instrValue.value)}, loc: ${this.formatLoc(instrValue.loc)} }`, - ); - break; - } - case 'Primitive': { - /* - * JSON.stringify maps non-finite numbers to "null", which both loses - * information and diverges from the Rust debug printer (NaN/Infinity). - */ - const val = - instrValue.value === undefined - ? 'undefined' - : typeof instrValue.value === 'number' && - !Number.isFinite(instrValue.value) - ? String(instrValue.value) - : JSON.stringify(instrValue.value); - this.line( - `Primitive { value: ${val}, loc: ${this.formatLoc(instrValue.loc)} }`, - ); - break; - } - case 'TypeCastExpression': { - this.line('TypeCastExpression {'); - this.indent(); - this.formatPlaceField('value', instrValue.value); - this.line(`type: ${this.formatType(instrValue.type)}`); - this.line(`typeAnnotation: ${instrValue.typeAnnotation.type}`); - this.line(`typeAnnotationKind: "${instrValue.typeAnnotationKind}"`); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'JsxExpression': { - this.line('JsxExpression {'); - this.indent(); - if (instrValue.tag.kind === 'Identifier') { - this.formatPlaceField('tag', instrValue.tag); - } else { - this.line(`tag: BuiltinTag("${instrValue.tag.name}")`); - } - this.line('props:'); - this.indent(); - instrValue.props.forEach((prop, i) => { - if (prop.kind === 'JsxAttribute') { - this.line(`[${i}] JsxAttribute {`); - this.indent(); - this.line(`name: "${prop.name}"`); - this.formatPlaceField('place', prop.place); - this.dedent(); - this.line('}'); - } else { - this.line(`[${i}] JsxSpreadAttribute:`); - this.indent(); - this.formatPlaceField('argument', prop.argument); - this.dedent(); - } - }); - this.dedent(); - if (instrValue.children !== null) { - this.line('children:'); - this.indent(); - instrValue.children.forEach((child, i) => { - this.formatPlaceField(`[${i}]`, child); - }); - this.dedent(); - } else { - this.line('children: null'); - } - this.line(`openingLoc: ${this.formatLoc(instrValue.openingLoc)}`); - this.line(`closingLoc: ${this.formatLoc(instrValue.closingLoc)}`); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'JsxFragment': { - this.line('JsxFragment {'); - this.indent(); - this.line('children:'); - this.indent(); - instrValue.children.forEach((child, i) => { - this.formatPlaceField(`[${i}]`, child); - }); - this.dedent(); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'UnsupportedNode': { - this.line( - `UnsupportedNode { type: "${instrValue.node.type}", loc: ${this.formatLoc(instrValue.loc)} }`, - ); - break; - } - case 'LoadLocal': { - this.line('LoadLocal {'); - this.indent(); - this.formatPlaceField('place', instrValue.place); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'DeclareLocal': { - this.line('DeclareLocal {'); - this.indent(); - this.formatLValue('lvalue', instrValue.lvalue); - this.line( - `type: ${instrValue.type !== null ? instrValue.type.type : 'null'}`, - ); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'DeclareContext': { - this.line('DeclareContext {'); - this.indent(); - this.line('lvalue:'); - this.indent(); - this.line(`kind: ${instrValue.lvalue.kind}`); - this.formatPlaceField('place', instrValue.lvalue.place); - this.dedent(); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'StoreLocal': { - this.line('StoreLocal {'); - this.indent(); - this.formatLValue('lvalue', instrValue.lvalue); - this.formatPlaceField('value', instrValue.value); - this.line( - `type: ${instrValue.type !== null ? instrValue.type.type : 'null'}`, - ); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'LoadContext': { - this.line('LoadContext {'); - this.indent(); - this.formatPlaceField('place', instrValue.place); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'StoreContext': { - this.line('StoreContext {'); - this.indent(); - this.line('lvalue:'); - this.indent(); - this.line(`kind: ${instrValue.lvalue.kind}`); - this.formatPlaceField('place', instrValue.lvalue.place); - this.dedent(); - this.formatPlaceField('value', instrValue.value); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'Destructure': { - this.line('Destructure {'); - this.indent(); - this.line('lvalue:'); - this.indent(); - this.line(`kind: ${instrValue.lvalue.kind}`); - this.formatPattern(instrValue.lvalue.pattern); - this.dedent(); - this.formatPlaceField('value', instrValue.value); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'PropertyLoad': { - this.line('PropertyLoad {'); - this.indent(); - this.formatPlaceField('object', instrValue.object); - this.line(`property: "${instrValue.property}"`); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'PropertyStore': { - this.line('PropertyStore {'); - this.indent(); - this.formatPlaceField('object', instrValue.object); - this.line(`property: "${instrValue.property}"`); - this.formatPlaceField('value', instrValue.value); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'PropertyDelete': { - this.line('PropertyDelete {'); - this.indent(); - this.formatPlaceField('object', instrValue.object); - this.line(`property: "${instrValue.property}"`); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'ComputedLoad': { - this.line('ComputedLoad {'); - this.indent(); - this.formatPlaceField('object', instrValue.object); - this.formatPlaceField('property', instrValue.property); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'ComputedStore': { - this.line('ComputedStore {'); - this.indent(); - this.formatPlaceField('object', instrValue.object); - this.formatPlaceField('property', instrValue.property); - this.formatPlaceField('value', instrValue.value); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'ComputedDelete': { - this.line('ComputedDelete {'); - this.indent(); - this.formatPlaceField('object', instrValue.object); - this.formatPlaceField('property', instrValue.property); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'LoadGlobal': { - this.line('LoadGlobal {'); - this.indent(); - this.line(`binding: ${this.formatNonLocalBinding(instrValue.binding)}`); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'StoreGlobal': { - this.line('StoreGlobal {'); - this.indent(); - this.line(`name: "${instrValue.name}"`); - this.formatPlaceField('value', instrValue.value); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'ObjectMethod': - case 'FunctionExpression': { - const kind = instrValue.kind; - this.line(`${kind} {`); - this.indent(); - if (instrValue.kind === 'FunctionExpression') { - this.line( - `name: ${instrValue.name !== null ? `"${instrValue.name}"` : 'null'}`, - ); - this.line( - `nameHint: ${instrValue.nameHint !== null ? `"${instrValue.nameHint}"` : 'null'}`, - ); - this.line(`type: "${instrValue.type}"`); - } - this.line(`loweredFunc:`); - this.formatFunction(instrValue.loweredFunc.func); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'TaggedTemplateExpression': { - this.line('TaggedTemplateExpression {'); - this.indent(); - this.formatPlaceField('tag', instrValue.tag); - this.line(`raw: ${JSON.stringify(instrValue.value.raw)}`); - this.line( - `cooked: ${instrValue.value.cooked !== undefined ? JSON.stringify(instrValue.value.cooked) : 'undefined'}`, - ); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'TemplateLiteral': { - this.line('TemplateLiteral {'); - this.indent(); - this.line('subexprs:'); - this.indent(); - instrValue.subexprs.forEach((sub, i) => { - this.formatPlaceField(`[${i}]`, sub); - }); - this.dedent(); - this.line('quasis:'); - this.indent(); - instrValue.quasis.forEach((q, i) => { - this.line( - `[${i}] { raw: ${JSON.stringify(q.raw)}, cooked: ${q.cooked !== undefined ? JSON.stringify(q.cooked) : 'undefined'} }`, - ); - }); - this.dedent(); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'RegExpLiteral': { - this.line( - `RegExpLiteral { pattern: "${instrValue.pattern}", flags: "${instrValue.flags}", loc: ${this.formatLoc(instrValue.loc)} }`, - ); - break; - } - case 'MetaProperty': { - this.line( - `MetaProperty { meta: "${instrValue.meta}", property: "${instrValue.property}", loc: ${this.formatLoc(instrValue.loc)} }`, - ); - break; - } - case 'Await': { - this.line('Await {'); - this.indent(); - this.formatPlaceField('value', instrValue.value); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'GetIterator': { - this.line('GetIterator {'); - this.indent(); - this.formatPlaceField('collection', instrValue.collection); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'IteratorNext': { - this.line('IteratorNext {'); - this.indent(); - this.formatPlaceField('iterator', instrValue.iterator); - this.formatPlaceField('collection', instrValue.collection); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'NextPropertyOf': { - this.line('NextPropertyOf {'); - this.indent(); - this.formatPlaceField('value', instrValue.value); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'Debugger': { - this.line(`Debugger { loc: ${this.formatLoc(instrValue.loc)} }`); - break; - } - case 'PostfixUpdate': { - this.line('PostfixUpdate {'); - this.indent(); - this.formatPlaceField('lvalue', instrValue.lvalue); - this.line(`operation: "${instrValue.operation}"`); - this.formatPlaceField('value', instrValue.value); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'PrefixUpdate': { - this.line('PrefixUpdate {'); - this.indent(); - this.formatPlaceField('lvalue', instrValue.lvalue); - this.line(`operation: "${instrValue.operation}"`); - this.formatPlaceField('value', instrValue.value); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'StartMemoize': { - this.line('StartMemoize {'); - this.indent(); - this.line(`manualMemoId: ${instrValue.manualMemoId}`); - if (instrValue.deps !== null) { - this.line('deps:'); - this.indent(); - instrValue.deps.forEach((dep, i) => { - const rootStr = - dep.root.kind === 'Global' - ? `Global("${dep.root.identifierName}")` - : `NamedLocal(${dep.root.value.identifier.id}, constant=${dep.root.constant})`; - const pathStr = dep.path - .map(p => `${p.optional ? '?.' : '.'}${p.property}`) - .join(''); - this.line(`[${i}] ${rootStr}${pathStr}`); - }); - this.dedent(); - } else { - this.line('deps: null'); - } - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'FinishMemoize': { - this.line('FinishMemoize {'); - this.indent(); - this.line(`manualMemoId: ${instrValue.manualMemoId}`); - this.formatPlaceField('decl', instrValue.decl); - this.line(`pruned: ${instrValue.pruned === true}`); - this.line(`loc: ${this.formatLoc(instrValue.loc)}`); - this.dedent(); - this.line('}'); - break; - } - default: { - assertExhaustive( - instrValue, - `Unexpected instruction kind '${(instrValue as any).kind}'`, - ); - } - } - } - - formatTerminal(terminal: Terminal): void { - switch (terminal.kind) { - case 'if': { - this.line('If {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.formatPlaceField('test', terminal.test); - this.line(`consequent: bb${terminal.consequent}`); - this.line(`alternate: bb${terminal.alternate}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'branch': { - this.line('Branch {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.formatPlaceField('test', terminal.test); - this.line(`consequent: bb${terminal.consequent}`); - this.line(`alternate: bb${terminal.alternate}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'logical': { - this.line('Logical {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`operator: "${terminal.operator}"`); - this.line(`test: bb${terminal.test}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'ternary': { - this.line('Ternary {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`test: bb${terminal.test}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'optional': { - this.line('Optional {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`optional: ${terminal.optional}`); - this.line(`test: bb${terminal.test}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'throw': { - this.line('Throw {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.formatPlaceField('value', terminal.value); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'return': { - this.line('Return {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`returnVariant: ${terminal.returnVariant}`); - this.formatPlaceField('value', terminal.value); - if (terminal.effects !== null) { - this.line('effects:'); - this.indent(); - terminal.effects.forEach((effect, i) => { - this.line(`[${i}] ${this.formatAliasingEffect(effect)}`); - }); - this.dedent(); - } else { - this.line('effects: null'); - } - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'goto': { - this.line('Goto {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`block: bb${terminal.block}`); - this.line(`variant: ${terminal.variant}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'switch': { - this.line('Switch {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.formatPlaceField('test', terminal.test); - this.line('cases:'); - this.indent(); - terminal.cases.forEach((case_, i) => { - if (case_.test !== null) { - this.line(`[${i}] Case {`); - this.indent(); - this.formatPlaceField('test', case_.test); - this.line(`block: bb${case_.block}`); - this.dedent(); - this.line('}'); - } else { - this.line(`[${i}] Default { block: bb${case_.block} }`); - } - }); - this.dedent(); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'do-while': { - this.line('DoWhile {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`loop: bb${terminal.loop}`); - this.line(`test: bb${terminal.test}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'while': { - this.line('While {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`test: bb${terminal.test}`); - this.line(`loop: bb${terminal.loop}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'for': { - this.line('For {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`init: bb${terminal.init}`); - this.line(`test: bb${terminal.test}`); - this.line( - `update: ${terminal.update !== null ? `bb${terminal.update}` : 'null'}`, - ); - this.line(`loop: bb${terminal.loop}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'for-of': { - this.line('ForOf {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`init: bb${terminal.init}`); - this.line(`test: bb${terminal.test}`); - this.line(`loop: bb${terminal.loop}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'for-in': { - this.line('ForIn {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`init: bb${terminal.init}`); - this.line(`loop: bb${terminal.loop}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'label': { - this.line('Label {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`block: bb${terminal.block}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'sequence': { - this.line('Sequence {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`block: bb${terminal.block}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'unreachable': { - this.line( - `Unreachable { id: ${terminal.id}, loc: ${this.formatLoc(terminal.loc)} }`, - ); - break; - } - case 'unsupported': { - this.line( - `Unsupported { id: ${terminal.id}, loc: ${this.formatLoc(terminal.loc)} }`, - ); - break; - } - case 'maybe-throw': { - this.line('MaybeThrow {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`continuation: bb${terminal.continuation}`); - this.line( - `handler: ${terminal.handler !== null ? `bb${terminal.handler}` : 'null'}`, - ); - if (terminal.effects !== null) { - this.line('effects:'); - this.indent(); - terminal.effects.forEach((effect, i) => { - this.line(`[${i}] ${this.formatAliasingEffect(effect)}`); - }); - this.dedent(); - } else { - this.line('effects: null'); - } - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'scope': { - this.line('Scope {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.formatScopeField('scope', terminal.scope); - this.line(`block: bb${terminal.block}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'pruned-scope': { - this.line('PrunedScope {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.formatScopeField('scope', terminal.scope); - this.line(`block: bb${terminal.block}`); - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'try': { - this.line('Try {'); - this.indent(); - this.line(`id: ${terminal.id}`); - this.line(`block: bb${terminal.block}`); - this.line(`handler: bb${terminal.handler}`); - if (terminal.handlerBinding !== null) { - this.formatPlaceField('handlerBinding', terminal.handlerBinding); - } else { - this.line('handlerBinding: null'); - } - this.line(`fallthrough: bb${terminal.fallthrough}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - } - - /** - * Print a Place as a named field. If the identifier is first-seen, expands to multiple lines. - * If abbreviated, stays on one line. - */ - formatPlaceField(fieldName: string, place: Place): void { - const isSeen = this.seenIdentifiers.has(place.identifier.id); - if (isSeen) { - this.line( - `${fieldName}: Place { identifier: Identifier(${place.identifier.id}), effect: ${place.effect}, reactive: ${place.reactive}, loc: ${this.formatLoc(place.loc)} }`, - ); - } else { - this.line(`${fieldName}: Place {`); - this.indent(); - this.line('identifier:'); - this.indent(); - this.formatIdentifier(place.identifier); - this.dedent(); - this.line(`effect: ${place.effect}`); - this.line(`reactive: ${place.reactive}`); - this.line(`loc: ${this.formatLoc(place.loc)}`); - this.dedent(); - this.line('}'); - } - } - - formatIdentifier(id: Identifier): void { - this.seenIdentifiers.add(id.id); - this.line('Identifier {'); - this.indent(); - this.line(`id: ${id.id}`); - this.line(`declarationId: ${id.declarationId}`); - if (id.name !== null) { - this.line(`name: { kind: "${id.name.kind}", value: "${id.name.value}" }`); - } else { - this.line('name: null'); - } - this.line( - `mutableRange: [${id.mutableRange.start}:${id.mutableRange.end}]`, - ); - if (id.scope !== null) { - this.formatScopeField('scope', id.scope); - } else { - this.line('scope: null'); - } - this.line(`type: ${this.formatType(id.type)}`); - this.line(`loc: ${this.formatLoc(id.loc)}`); - this.dedent(); - this.line('}'); - } - - formatScopeField(fieldName: string, scope: ReactiveScope): void { - const isSeen = this.seenScopes.has(scope.id); - if (isSeen) { - this.line(`${fieldName}: Scope(${scope.id})`); - } else { - this.seenScopes.add(scope.id); - this.line(`${fieldName}: Scope {`); - this.indent(); - this.line(`id: ${scope.id}`); - this.line(`range: [${scope.range.start}:${scope.range.end}]`); - this.line('dependencies:'); - this.indent(); - let depIndex = 0; - for (const dep of scope.dependencies) { - const pathStr = dep.path - .map(p => `${p.optional ? '?.' : '.'}${p.property}`) - .join(''); - this.line( - `[${depIndex}] { identifier: ${dep.identifier.id}, reactive: ${dep.reactive}, path: "${pathStr}" }`, - ); - depIndex++; - } - this.dedent(); - this.line('declarations:'); - this.indent(); - for (const [identId, decl] of scope.declarations) { - this.line( - `${identId}: { identifier: ${decl.identifier.id}, scope: ${decl.scope.id} }`, - ); - } - this.dedent(); - this.line('reassignments:'); - this.indent(); - for (const ident of scope.reassignments) { - this.line(`${ident.id}`); - } - this.dedent(); - if (scope.earlyReturnValue !== null) { - this.line('earlyReturnValue:'); - this.indent(); - this.line(`value: ${scope.earlyReturnValue.value.id}`); - this.line(`loc: ${this.formatLoc(scope.earlyReturnValue.loc)}`); - this.line(`label: bb${scope.earlyReturnValue.label}`); - this.dedent(); - } else { - this.line('earlyReturnValue: null'); - } - this.line(`merged: [${[...scope.merged].join(', ')}]`); - this.line(`loc: ${this.formatLoc(scope.loc)}`); - this.dedent(); - this.line('}'); - } - } - - formatType(type: Type): string { - switch (type.kind) { - case 'Primitive': - return 'Primitive'; - case 'Function': - return `Function { shapeId: ${type.shapeId !== null ? `"${type.shapeId}"` : 'null'}, return: ${this.formatType(type.return)}, isConstructor: ${type.isConstructor} }`; - case 'Object': - return `Object { shapeId: ${type.shapeId !== null ? `"${type.shapeId}"` : 'null'} }`; - case 'Type': - return `Type(${type.id})`; - case 'Poly': - return 'Poly'; - case 'Phi': - return `Phi { operands: [${type.operands.map(op => this.formatType(op)).join(', ')}] }`; - case 'Property': - return `Property { objectType: ${this.formatType(type.objectType)}, objectName: "${type.objectName}", propertyName: ${type.propertyName.kind === 'literal' ? `"${type.propertyName.value}"` : `computed(${this.formatType(type.propertyName.value)})`} }`; - case 'ObjectMethod': - return 'ObjectMethod'; - default: - assertExhaustive(type, `Unexpected type kind '${(type as any).kind}'`); - } - } - - formatLoc(loc: SourceLocation): string { - if (typeof loc === 'symbol') { - return 'generated'; - } - return `${loc.start.line}:${loc.start.column}-${loc.end.line}:${loc.end.column}`; - } - - formatAliasingEffect(effect: AliasingEffect): string { - switch (effect.kind) { - case 'Assign': - return `Assign { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`; - case 'Alias': - return `Alias { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`; - case 'MaybeAlias': - return `MaybeAlias { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`; - case 'Capture': - return `Capture { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`; - case 'ImmutableCapture': - return `ImmutableCapture { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`; - case 'Create': - return `Create { into: ${effect.into.identifier.id}, value: ${effect.value}, reason: ${effect.reason} }`; - case 'CreateFrom': - return `CreateFrom { into: ${effect.into.identifier.id}, from: ${effect.from.identifier.id} }`; - case 'CreateFunction': { - const captures = effect.captures.map(c => c.identifier.id).join(', '); - return `CreateFunction { into: ${effect.into.identifier.id}, captures: [${captures}] }`; - } - case 'Apply': { - const args = effect.args - .map(arg => { - if (arg.kind === 'Identifier') { - return String(arg.identifier.id); - } else if (arg.kind === 'Hole') { - return 'Hole'; - } - return `...${arg.place.identifier.id}`; - }) - .join(', '); - return `Apply { into: ${effect.into.identifier.id}, receiver: ${effect.receiver.identifier.id}, function: ${effect.function.identifier.id}, mutatesFunction: ${effect.mutatesFunction}, args: [${args}], loc: ${this.formatLoc(effect.loc)} }`; - } - case 'Freeze': - return `Freeze { value: ${effect.value.identifier.id}, reason: ${effect.reason} }`; - case 'Mutate': - return `Mutate { value: ${effect.value.identifier.id}${effect.reason?.kind === 'AssignCurrentProperty' ? ', reason: AssignCurrentProperty' : ''} }`; - case 'MutateConditionally': - return `MutateConditionally { value: ${effect.value.identifier.id} }`; - case 'MutateTransitive': - return `MutateTransitive { value: ${effect.value.identifier.id} }`; - case 'MutateTransitiveConditionally': - return `MutateTransitiveConditionally { value: ${effect.value.identifier.id} }`; - case 'MutateFrozen': - return `MutateFrozen { place: ${effect.place.identifier.id}, reason: ${JSON.stringify(effect.error.reason)} }`; - case 'MutateGlobal': - return `MutateGlobal { place: ${effect.place.identifier.id}, reason: ${JSON.stringify(effect.error.reason)} }`; - case 'Impure': - return `Impure { place: ${effect.place.identifier.id}, reason: ${JSON.stringify(effect.error.reason)} }`; - case 'Render': - return `Render { place: ${effect.place.identifier.id} }`; - default: - assertExhaustive( - effect, - `Unexpected effect kind '${(effect as any).kind}'`, - ); - } - } - - formatLValue(fieldName: string, lvalue: LValue): void { - this.line(`${fieldName}:`); - this.indent(); - this.line(`kind: ${lvalue.kind}`); - this.formatPlaceField('place', lvalue.place); - this.dedent(); - } - - formatPattern(pattern: Pattern): void { - switch (pattern.kind) { - case 'ArrayPattern': { - this.line('pattern: ArrayPattern {'); - this.indent(); - this.line('items:'); - this.indent(); - pattern.items.forEach((item, i) => { - if (item.kind === 'Hole') { - this.line(`[${i}] Hole`); - } else if (item.kind === 'Identifier') { - this.formatPlaceField(`[${i}]`, item); - } else { - this.line(`[${i}] Spread:`); - this.indent(); - this.formatPlaceField('place', item.place); - this.dedent(); - } - }); - this.dedent(); - this.line(`loc: ${this.formatLoc(pattern.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'ObjectPattern': { - this.line('pattern: ObjectPattern {'); - this.indent(); - this.line('properties:'); - this.indent(); - pattern.properties.forEach((prop, i) => { - if (prop.kind === 'ObjectProperty') { - this.line(`[${i}] ObjectProperty {`); - this.indent(); - this.line(`key: ${this.formatObjectPropertyKey(prop.key)}`); - this.line(`type: "${prop.type}"`); - this.formatPlaceField('place', prop.place); - this.dedent(); - this.line('}'); - } else { - this.line(`[${i}] Spread:`); - this.indent(); - this.formatPlaceField('place', prop.place); - this.dedent(); - } - }); - this.dedent(); - this.line(`loc: ${this.formatLoc(pattern.loc)}`); - this.dedent(); - this.line('}'); - break; - } - default: - assertExhaustive( - pattern, - `Unexpected pattern kind '${(pattern as any).kind}'`, - ); - } - } - - formatObjectPropertyKey(key: ObjectPropertyKey): string { - switch (key.kind) { - case 'identifier': - return `Identifier("${key.name}")`; - case 'string': - return `String("${key.name}")`; - case 'computed': - return `Computed(${key.name.identifier.id})`; - case 'number': - return `Number(${key.name})`; - } - } - - formatNonLocalBinding(binding: NonLocalBinding): string { - switch (binding.kind) { - case 'Global': - return `Global { name: "${binding.name}" }`; - case 'ModuleLocal': - return `ModuleLocal { name: "${binding.name}" }`; - case 'ImportDefault': - return `ImportDefault { name: "${binding.name}", module: "${binding.module}" }`; - case 'ImportNamespace': - return `ImportNamespace { name: "${binding.name}", module: "${binding.module}" }`; - case 'ImportSpecifier': - return `ImportSpecifier { name: "${binding.name}", module: "${binding.module}", imported: "${binding.imported}" }`; - default: - assertExhaustive( - binding, - `Unexpected binding kind '${(binding as any).kind}'`, - ); - } - } - - formatErrors(errors: { - details: Array<CompilerErrorDetail | CompilerDiagnostic>; - }): void { - if (errors.details.length === 0) { - this.line('Errors: []'); - return; - } - this.line('Errors:'); - this.indent(); - errors.details.forEach((detail, i) => { - this.line(`[${i}] {`); - this.indent(); - this.line(`severity: ${detail.severity}`); - this.line(`reason: ${JSON.stringify(detail.reason)}`); - this.line( - `description: ${detail.description !== null && detail.description !== undefined ? JSON.stringify(detail.description) : 'null'}`, - ); - this.line(`category: ${detail.category}`); - const loc = detail.primaryLocation(); - this.line(`loc: ${loc !== null ? this.formatLoc(loc) : 'null'}`); - this.dedent(); - this.line('}'); - }); - this.dedent(); - } - - private formatArgument(arg: Place | SpreadPattern, index: number): void { - if (arg.kind === 'Identifier') { - this.formatPlaceField(`[${index}]`, arg); - } else { - this.line(`[${index}] Spread:`); - this.indent(); - this.formatPlaceField('place', arg.place); - this.dedent(); - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/DebugPrintReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/DebugPrintReactiveFunction.ts deleted file mode 100644 index 9bc9af350bbe..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/DebugPrintReactiveFunction.ts +++ /dev/null @@ -1,512 +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 {assertExhaustive} from '../Utils/utils'; -import type { - ReactiveBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveStatement, - ReactiveTerminal, - ReactiveValue, - ReactiveScopeBlock, - PrunedReactiveScopeBlock, -} from './HIR'; -import {DebugPrinter} from './DebugPrintHIR'; - -export function printDebugReactiveFunction(fn: ReactiveFunction): string { - const printer = new ReactiveDebugPrinter(); - printer.formatReactiveFunction(fn); - - const outlined = fn.env.getOutlinedFunctions(); - for (let i = 0; i < outlined.length; i++) { - const outlinedFn = outlined[i].fn; - /* - * Only print outlined functions that have been converted to reactive form - * (have an array body, not a HIR body with blocks) - */ - if (Array.isArray(outlinedFn.body)) { - printer.line(''); - printer.formatReactiveFunction(outlinedFn as unknown as ReactiveFunction); - } - } - - printer.line(''); - printer.line('Environment:'); - printer.indent(); - const errors = fn.env.aggregateErrors(); - printer.formatErrors(errors); - printer.dedent(); - - return printer.toString(); -} - -class ReactiveDebugPrinter extends DebugPrinter { - formatReactiveFunction(fn: ReactiveFunction): void { - this.indent(); - this.line(`id: ${fn.id !== null ? `"${fn.id}"` : 'null'}`); - this.line( - `name_hint: ${fn.nameHint !== null ? `"${fn.nameHint}"` : 'null'}`, - ); - this.line(`generator: ${fn.generator}`); - this.line(`is_async: ${fn.async}`); - this.line(`loc: ${this.formatLoc(fn.loc)}`); - - this.line('params:'); - this.indent(); - fn.params.forEach((param, i) => { - if (param.kind === 'Identifier') { - this.formatPlaceField(`[${i}]`, param); - } else { - this.line(`[${i}] Spread:`); - this.indent(); - this.formatPlaceField('place', param.place); - this.dedent(); - } - }); - this.dedent(); - - this.line('directives:'); - this.indent(); - fn.directives.forEach((d, i) => { - this.line(`[${i}] "${d}"`); - }); - this.dedent(); - - this.line(''); - this.line('Body:'); - this.indent(); - this.formatReactiveBlock(fn.body); - this.dedent(); - this.dedent(); - } - - formatReactiveBlock(block: ReactiveBlock): void { - for (const stmt of block) { - this.formatReactiveStatement(stmt); - } - } - - formatReactiveStatement(stmt: ReactiveStatement): void { - switch (stmt.kind) { - case 'instruction': { - this.formatReactiveInstruction(stmt.instruction); - break; - } - case 'scope': { - this.formatReactiveScopeBlock(stmt); - break; - } - case 'pruned-scope': { - this.formatPrunedReactiveScopeBlock(stmt); - break; - } - case 'terminal': { - this.line('ReactiveTerminalStatement {'); - this.indent(); - if (stmt.label !== null) { - this.line( - `label: { id: bb${stmt.label.id}, implicit: ${stmt.label.implicit} }`, - ); - } else { - this.line('label: null'); - } - this.line('terminal:'); - this.indent(); - this.formatReactiveTerminal(stmt.terminal); - this.dedent(); - this.dedent(); - this.line('}'); - break; - } - default: { - assertExhaustive( - stmt, - `Unexpected reactive statement kind \`${(stmt as any).kind}\``, - ); - } - } - } - - formatReactiveInstruction(instr: ReactiveInstruction): void { - this.line('ReactiveInstruction {'); - this.indent(); - this.line(`id: ${instr.id}`); - if (instr.lvalue !== null) { - this.formatPlaceField('lvalue', instr.lvalue); - } else { - this.line('lvalue: null'); - } - this.line('value:'); - this.indent(); - this.formatReactiveValue(instr.value); - this.dedent(); - if (instr.effects != null) { - this.line('effects:'); - this.indent(); - instr.effects.forEach((effect, i) => { - this.line(`[${i}] ${this.formatAliasingEffect(effect)}`); - }); - this.dedent(); - } else { - this.line('effects: null'); - } - this.line(`loc: ${this.formatLoc(instr.loc)}`); - this.dedent(); - this.line('}'); - } - - formatReactiveScopeBlock(block: ReactiveScopeBlock): void { - this.line('ReactiveScopeBlock {'); - this.indent(); - this.formatScopeField('scope', block.scope); - this.line('instructions:'); - this.indent(); - this.formatReactiveBlock(block.instructions); - this.dedent(); - this.dedent(); - this.line('}'); - } - - formatPrunedReactiveScopeBlock(block: PrunedReactiveScopeBlock): void { - this.line('PrunedReactiveScopeBlock {'); - this.indent(); - this.formatScopeField('scope', block.scope); - this.line('instructions:'); - this.indent(); - this.formatReactiveBlock(block.instructions); - this.dedent(); - this.dedent(); - this.line('}'); - } - - formatReactiveValue(value: ReactiveValue): void { - switch (value.kind) { - case 'LogicalExpression': { - this.line('LogicalExpression {'); - this.indent(); - this.line(`operator: "${value.operator}"`); - this.line('left:'); - this.indent(); - this.formatReactiveValue(value.left); - this.dedent(); - this.line('right:'); - this.indent(); - this.formatReactiveValue(value.right); - this.dedent(); - this.line(`loc: ${this.formatLoc(value.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'ConditionalExpression': { - this.line('ConditionalExpression {'); - this.indent(); - this.line('test:'); - this.indent(); - this.formatReactiveValue(value.test); - this.dedent(); - this.line('consequent:'); - this.indent(); - this.formatReactiveValue(value.consequent); - this.dedent(); - this.line('alternate:'); - this.indent(); - this.formatReactiveValue(value.alternate); - this.dedent(); - this.line(`loc: ${this.formatLoc(value.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'SequenceExpression': { - this.line('SequenceExpression {'); - this.indent(); - this.line('instructions:'); - this.indent(); - value.instructions.forEach((instr, i) => { - this.line(`[${i}]:`); - this.indent(); - this.formatReactiveInstruction(instr); - this.dedent(); - }); - this.dedent(); - this.line(`id: ${value.id}`); - this.line('value:'); - this.indent(); - this.formatReactiveValue(value.value); - this.dedent(); - this.line(`loc: ${this.formatLoc(value.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'OptionalExpression': { - this.line('OptionalExpression {'); - this.indent(); - this.line(`id: ${value.id}`); - this.line('value:'); - this.indent(); - this.formatReactiveValue(value.value); - this.dedent(); - this.line(`optional: ${value.optional}`); - this.line(`loc: ${this.formatLoc(value.loc)}`); - this.dedent(); - this.line('}'); - break; - } - default: { - // Base InstructionValue kinds - delegate to existing formatter - this.formatInstructionValue(value); - break; - } - } - } - - formatReactiveTerminal(terminal: ReactiveTerminal): void { - switch (terminal.kind) { - case 'break': { - this.line('Break {'); - this.indent(); - this.line(`target: bb${terminal.target}`); - this.line(`id: ${terminal.id}`); - this.line(`targetKind: "${terminal.targetKind}"`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'continue': { - this.line('Continue {'); - this.indent(); - this.line(`target: bb${terminal.target}`); - this.line(`id: ${terminal.id}`); - this.line(`targetKind: "${terminal.targetKind}"`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'return': { - this.line('Return {'); - this.indent(); - this.formatPlaceField('value', terminal.value); - this.line(`id: ${terminal.id}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'throw': { - this.line('Throw {'); - this.indent(); - this.formatPlaceField('value', terminal.value); - this.line(`id: ${terminal.id}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'switch': { - this.line('Switch {'); - this.indent(); - this.formatPlaceField('test', terminal.test); - this.line('cases:'); - this.indent(); - terminal.cases.forEach((case_, i) => { - this.line(`[${i}] {`); - this.indent(); - if (case_.test !== null) { - this.formatPlaceField('test', case_.test); - } else { - this.line('test: null'); - } - if (case_.block !== undefined) { - this.line('block:'); - this.indent(); - this.formatReactiveBlock(case_.block); - this.dedent(); - } else { - this.line('block: undefined'); - } - this.dedent(); - this.line('}'); - }); - this.dedent(); - this.line(`id: ${terminal.id}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'do-while': { - this.line('DoWhile {'); - this.indent(); - this.line('loop:'); - this.indent(); - this.formatReactiveBlock(terminal.loop); - this.dedent(); - this.line('test:'); - this.indent(); - this.formatReactiveValue(terminal.test); - this.dedent(); - this.line(`id: ${terminal.id}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'while': { - this.line('While {'); - this.indent(); - this.line('test:'); - this.indent(); - this.formatReactiveValue(terminal.test); - this.dedent(); - this.line('loop:'); - this.indent(); - this.formatReactiveBlock(terminal.loop); - this.dedent(); - this.line(`id: ${terminal.id}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'for': { - this.line('For {'); - this.indent(); - this.line('init:'); - this.indent(); - this.formatReactiveValue(terminal.init); - this.dedent(); - this.line('test:'); - this.indent(); - this.formatReactiveValue(terminal.test); - this.dedent(); - if (terminal.update !== null) { - this.line('update:'); - this.indent(); - this.formatReactiveValue(terminal.update); - this.dedent(); - } else { - this.line('update: null'); - } - this.line('loop:'); - this.indent(); - this.formatReactiveBlock(terminal.loop); - this.dedent(); - this.line(`id: ${terminal.id}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'for-of': { - this.line('ForOf {'); - this.indent(); - this.line('init:'); - this.indent(); - this.formatReactiveValue(terminal.init); - this.dedent(); - this.line('test:'); - this.indent(); - this.formatReactiveValue(terminal.test); - this.dedent(); - this.line('loop:'); - this.indent(); - this.formatReactiveBlock(terminal.loop); - this.dedent(); - this.line(`id: ${terminal.id}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'for-in': { - this.line('ForIn {'); - this.indent(); - this.line('init:'); - this.indent(); - this.formatReactiveValue(terminal.init); - this.dedent(); - this.line('loop:'); - this.indent(); - this.formatReactiveBlock(terminal.loop); - this.dedent(); - this.line(`id: ${terminal.id}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'if': { - this.line('If {'); - this.indent(); - this.formatPlaceField('test', terminal.test); - this.line('consequent:'); - this.indent(); - this.formatReactiveBlock(terminal.consequent); - this.dedent(); - if (terminal.alternate !== null) { - this.line('alternate:'); - this.indent(); - this.formatReactiveBlock(terminal.alternate); - this.dedent(); - } else { - this.line('alternate: null'); - } - this.line(`id: ${terminal.id}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'label': { - this.line('Label {'); - this.indent(); - this.line('block:'); - this.indent(); - this.formatReactiveBlock(terminal.block); - this.dedent(); - this.line(`id: ${terminal.id}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - case 'try': { - this.line('Try {'); - this.indent(); - this.line('block:'); - this.indent(); - this.formatReactiveBlock(terminal.block); - this.dedent(); - if (terminal.handlerBinding !== null) { - this.formatPlaceField('handlerBinding', terminal.handlerBinding); - } else { - this.line('handlerBinding: null'); - } - this.line('handler:'); - this.indent(); - this.formatReactiveBlock(terminal.handler); - this.dedent(); - this.line(`id: ${terminal.id}`); - this.line(`loc: ${this.formatLoc(terminal.loc)}`); - this.dedent(); - this.line('}'); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected reactive terminal kind \`${(terminal as any).kind}\``, - ); - } - } - } -} 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 9e908b2a6434..98cf1ed57d9f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -14,12 +14,7 @@ import { CompilerErrorDetail, ErrorCategory, } from '../CompilerError'; -import { - CompilerOutputMode, - Logger, - ProgramContext, - formatDetailForLogging, -} from '../Entrypoint'; +import {CompilerOutputMode, Logger, ProgramContext} from '../Entrypoint'; import {Err, Ok, Result} from '../Utils/Result'; import { DEFAULT_GLOBALS, @@ -712,7 +707,7 @@ export class Environment { for (const error of errors.unwrapErr().details) { this.logger.logEvent(this.filename, { kind: 'CompileError', - detail: formatDetailForLogging(error), + detail: error, fnLoc: null, }); } 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 b1ccf34d81a0..bbc9b325d477 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts @@ -31,7 +31,5 @@ export { } from './HIRBuilder'; export {mergeConsecutiveBlocks} from './MergeConsecutiveBlocks'; export {mergeOverlappingReactiveScopesHIR} from './MergeOverlappingReactiveScopesHIR'; -export {printDebugHIR} from './DebugPrintHIR'; -export {printDebugReactiveFunction} from './DebugPrintReactiveFunction'; export {printFunction, printHIR, printFunctionWithOutlined} from './PrintHIR'; export {pruneUnusedLabelsHIR} from './PruneUnusedLabelsHIR'; 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 a4c37d09b599..b5f586032b0e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -96,20 +96,6 @@ class SSABuilder { return newPlace; } - /** - * A function's context places capture a *binding*, not a value: the - * variable is only read when the function is later called, so a context - * place may reference a binding that is declared after the function - * expression itself (eg `const colgroup = useMemo(() => <colgroup>...)`, - * where the JSX tag name resolves to the variable being assigned). Unmark - * such identifiers so the later declaration doesn't error; if the function - * body actually *reads* the variable before it is defined, visiting the - * body re-marks it and the hoisting bailout in definePlace still applies. - */ - unmarkUnknown(place: Place): void { - this.#unknown.delete(place.identifier); - } - definePlace(oldPlace: Place): Place { const oldId = oldPlace.identifier; if (this.#unknown.has(oldId)) { @@ -299,9 +285,6 @@ function enterSSAImpl( instr.value.kind === 'ObjectMethod' ) { const loweredFunc = instr.value.loweredFunc.func; - for (const place of loweredFunc.context) { - builder.unmarkUnknown(place); - } const entry = loweredFunc.body.blocks.get(loweredFunc.body.entry)!; CompilerError.invariant(entry.preds.size === 0, { reason: diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/Logger-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/Logger-test.ts index 07e9f013181a..1ab6f49895ff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/Logger-test.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/Logger-test.ts @@ -57,12 +57,12 @@ it('logs failed compilation', () => { invariant(event.kind === 'CompileError', 'typescript be smarter'); expect(event.detail.severity).toEqual('Error'); - const errorDetail = event.detail.details?.find(d => d.kind === 'error'); - expect(errorDetail).toBeDefined(); - const loc = errorDetail!.loc as t.SourceLocation; - expect(loc.start).toEqual({column: 28, index: 28, line: 1}); - expect(loc.end).toEqual({column: 33, index: 33, line: 1}); - expect(loc.identifierName).toEqual('props'); + //@ts-ignore + const {start, end, identifierName} = + event.detail.primaryLocation() as t.SourceLocation; + expect(start).toEqual({column: 28, index: 28, line: 1}); + expect(end).toEqual({column: 33, index: 33, line: 1}); + expect(identifierName).toEqual('props'); // Make sure event.fnLoc is different from event.detail.loc expect(event.fnLoc?.start).toEqual({column: 0, index: 0, line: 1}); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased_usememo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased_usememo.expect.md deleted file mode 100644 index ab444bc38db2..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased_usememo.expect.md +++ /dev/null @@ -1,38 +0,0 @@ - -## Input - -```javascript -import {useMemo as myMemo} from 'react'; - -function Component({x}) { - const v = myMemo(() => x * 2, [x]); - return <div>{v}</div>; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -import { useMemo as myMemo } from "react"; - -function Component(t0) { - const $ = _c(2); - const { x } = t0; - const v = x * 2; - let t1; - if ($[0] !== v) { - t1 = <div>{v}</div>; - $[0] = v; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased_usememo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased_usememo.js deleted file mode 100644 index ee79e908bd25..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased_usememo.js +++ /dev/null @@ -1,6 +0,0 @@ -import {useMemo as myMemo} from 'react'; - -function Component({x}) { - const v = myMemo(() => x * 2, [x]); - return <div>{v}</div>; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-nan-infinity-as-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-nan-infinity-as-identifiers.expect.md deleted file mode 100644 index 0d18fbbd5fcd..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-nan-infinity-as-identifiers.expect.md +++ /dev/null @@ -1,64 +0,0 @@ - -## Input - -```javascript -// Constant propagation can produce NaN/Infinity from arithmetic. -// These must be emitted as Identifier("NaN")/Identifier("Infinity"), -// not NumericLiteral(NaN) which serializes to null in JSON. - -function Component({x}) { - const nan = 0 / 0; - const inf = 1 / 0; - const negInf = -1 / 0; - return ( - <div> - {x ? nan : inf} - {negInf} - </div> - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{x: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // Constant propagation can produce NaN/Infinity from arithmetic. -// These must be emitted as Identifier("NaN")/Identifier("Infinity"), -// not NumericLiteral(NaN) which serializes to null in JSON. - -function Component(t0) { - const $ = _c(2); - const { x } = t0; - - const t1 = x ? NaN : Infinity; - let t2; - if ($[0] !== t1) { - t2 = ( - <div> - {t1} - {-Infinity} - </div> - ); - $[0] = t1; - $[1] = t2; - } else { - t2 = $[1]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ x: true }], -}; - -``` - -### Eval output -(kind: ok) <div>NaN-Infinity</div> \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-nan-infinity-as-identifiers.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-nan-infinity-as-identifiers.js deleted file mode 100644 index 9d366206d769..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-nan-infinity-as-identifiers.js +++ /dev/null @@ -1,20 +0,0 @@ -// Constant propagation can produce NaN/Infinity from arithmetic. -// These must be emitted as Identifier("NaN")/Identifier("Infinity"), -// not NumericLiteral(NaN) which serializes to null in JSON. - -function Component({x}) { - const nan = 0 / 0; - const inf = 1 / 0; - const negInf = -1 / 0; - return ( - <div> - {x ? nan : inf} - {negInf} - </div> - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{x: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/compiled-function-nested-in-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/compiled-function-nested-in-jsx.expect.md deleted file mode 100644 index bc8e8caa9246..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/compiled-function-nested-in-jsx.expect.md +++ /dev/null @@ -1,59 +0,0 @@ - -## Input - -```javascript -function Component({dataSource, viewRenderer}) { - return ( - <Search - filters={[ - { - getConfig: (() => { - function useConfig() { - return {dataSource, viewRenderer}; - } - return useConfig; - })(), - }, - ]} - /> - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component(t0) { - const $ = _c(5); - const { dataSource, viewRenderer } = t0; - let t1; - if ($[0] !== dataSource || $[1] !== viewRenderer) { - t1 = function useConfig() { - return { dataSource, viewRenderer }; - }; - $[0] = dataSource; - $[1] = viewRenderer; - $[2] = t1; - } else { - t1 = $[2]; - } - const useConfig = t1; - - const t2 = useConfig; - let t3; - if ($[3] !== t2) { - t3 = <Search filters={[{ getConfig: t2 }]} />; - $[3] = t2; - $[4] = t3; - } else { - t3 = $[4]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/compiled-function-nested-in-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/compiled-function-nested-in-jsx.js deleted file mode 100644 index 7c1e6b90996b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/compiled-function-nested-in-jsx.js +++ /dev/null @@ -1,16 +0,0 @@ -function Component({dataSource, viewRenderer}) { - return ( - <Search - filters={[ - { - getConfig: (() => { - function useConfig() { - return {dataSource, viewRenderer}; - } - return useConfig; - })(), - }, - ]} - /> - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-in-object-method-body.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-in-object-method-body.flow.expect.md deleted file mode 100644 index 122c083004fe..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-in-object-method-body.flow.expect.md +++ /dev/null @@ -1,83 +0,0 @@ - -## Input - -```javascript -// @flow @compilationMode:"syntax" -// Component declarations nested inside object method bodies should be found -export const examples = [ - { - title: 'Example 1', - render(): React.MixedElement { - component Demo1() { - const x = useFoo(); - return <div>{x}</div>; - } - return <Demo1 />; - }, - }, - { - title: 'Example 2', - render(): React.MixedElement { - component Demo2() { - const y = useBar(); - return <span>{y}</span>; - } - return <Demo2 />; - }, - }, -]; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; - -export const examples = [ - { - title: "Example 1", - render(): React.MixedElement { - function Demo1() { - const $ = _c(2); - const x = useFoo(); - let t0; - if ($[0] !== x) { - t0 = <div>{x}</div>; - $[0] = x; - $[1] = t0; - } else { - t0 = $[1]; - } - return t0; - } - - return <Demo1 />; - }, - }, - { - title: "Example 2", - render(): React.MixedElement { - function Demo2() { - const $ = _c(2); - const y = useBar(); - let t0; - if ($[0] !== y) { - t0 = <span>{y}</span>; - $[0] = y; - $[1] = t0; - } else { - t0 = $[1]; - } - return t0; - } - - return <Demo2 />; - }, - }, -]; - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-in-object-method-body.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-in-object-method-body.flow.js deleted file mode 100644 index 53b42f74a93b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-in-object-method-body.flow.js +++ /dev/null @@ -1,24 +0,0 @@ -// @flow @compilationMode:"syntax" -// Component declarations nested inside object method bodies should be found -export const examples = [ - { - title: 'Example 1', - render(): React.MixedElement { - component Demo1() { - const x = useFoo(); - return <div>{x}</div>; - } - return <Demo1 />; - }, - }, - { - title: 'Example 2', - render(): React.MixedElement { - component Demo2() { - const y = useBar(); - return <span>{y}</span>; - } - return <Demo2 />; - }, - }, -]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-scientific-notation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-scientific-notation.expect.md deleted file mode 100644 index 2567107d45f7..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-scientific-notation.expect.md +++ /dev/null @@ -1,38 +0,0 @@ - -## Input - -```javascript -// Template literal constant folding uses format_js_number to convert -// numbers to strings. Without the shared implementation, large numbers -// may format incorrectly (e.g. 1e21 as "1e21" instead of "1e+21"). - -function Component() { - const x = `value is ${1e21}`; - return <div>{x}</div>; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // Template literal constant folding uses format_js_number to convert -// numbers to strings. Without the shared implementation, large numbers -// may format incorrectly (e.g. 1e21 as "1e21" instead of "1e+21"). - -function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = <div>{"value is 1e+21"}</div>; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-scientific-notation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-scientific-notation.js deleted file mode 100644 index 7b8d13c80a29..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-scientific-notation.js +++ /dev/null @@ -1,8 +0,0 @@ -// Template literal constant folding uses format_js_number to convert -// numbers to strings. Without the shared implementation, large numbers -// may format incorrectly (e.g. 1e21 as "1e21" instead of "1e+21"). - -function Component() { - const x = `value is ${1e21}`; - return <div>{x}</div>; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/debugger-memoized.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/debugger-memoized.expect.md index 6d52fed074ba..ef4b8ce7b471 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/debugger-memoized.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/debugger-memoized.expect.md @@ -27,6 +27,7 @@ function Component(props) { if ($[0] !== props.value) { x = []; debugger; + x.push(props.value); $[0] = props.value; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/debugger.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/debugger.expect.md index dcfdb8245479..b1eddc5d4394 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/debugger.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/debugger.expect.md @@ -27,6 +27,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function Component(props) { debugger; + if (props.cond) { debugger; } else { @@ -34,7 +35,6 @@ function Component(props) { debugger; } } - debugger; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-jsx-recursion-limit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-jsx-recursion-limit.expect.md deleted file mode 100644 index b805d6eacd80..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-jsx-recursion-limit.expect.md +++ /dev/null @@ -1,245 +0,0 @@ - -## Input - -```javascript -// Deeply nested JSX that exercises the JSON recursion limit. -// At the NAPI bridge, serde_json has a default limit of 128 which -// is exceeded by the nested structure of the serialized AST. - -function DeeplyNestedComponent() { - return ( - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <span> - leaf - </span> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // Deeply nested JSX that exercises the JSON recursion limit. -// At the NAPI bridge, serde_json has a default limit of 128 which -// is exceeded by the nested structure of the serialized AST. - -function DeeplyNestedComponent() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ( - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <span> - leaf - </span> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - ); - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-jsx-recursion-limit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-jsx-recursion-limit.js deleted file mode 100644 index e2436bb0bc69..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/deeply-nested-jsx-recursion-limit.js +++ /dev/null @@ -1,111 +0,0 @@ -// Deeply nested JSX that exercises the JSON recursion limit. -// At the NAPI bridge, serde_json has a default limit of 128 which -// is exceeded by the nested structure of the serialized AST. - -function DeeplyNestedComponent() { - return ( - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <div> - <span> - leaf - </span> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/drop-manual-memoization-react-module.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/drop-manual-memoization-react-module.expect.md deleted file mode 100644 index 1836cdafb41e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/drop-manual-memoization-react-module.expect.md +++ /dev/null @@ -1,55 +0,0 @@ - -## Input - -```javascript -// useMemo imported from 'react' should be detected and dropped. -// The module name matching must handle 'react' correctly. - -import {useMemo} from 'react'; - -function Component({items}) { - const sorted = useMemo(() => [...items].sort(), [items]); - return <div>{sorted}</div>; -} - -export default Component; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // useMemo imported from 'react' should be detected and dropped. -// The module name matching must handle 'react' correctly. - -import { useMemo } from "react"; - -function Component(t0) { - const $ = _c(4); - const { items } = t0; - let t1; - if ($[0] !== items) { - t1 = [...items].sort(); - $[0] = items; - $[1] = t1; - } else { - t1 = $[1]; - } - const sorted = t1; - let t2; - if ($[2] !== sorted) { - t2 = <div>{sorted}</div>; - $[2] = sorted; - $[3] = t2; - } else { - t2 = $[3]; - } - return t2; -} - -export default Component; - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/drop-manual-memoization-react-module.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/drop-manual-memoization-react-module.js deleted file mode 100644 index efb8948a1987..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/drop-manual-memoization-react-module.js +++ /dev/null @@ -1,11 +0,0 @@ -// useMemo imported from 'react' should be detected and dropped. -// The module name matching must handle 'react' correctly. - -import {useMemo} from 'react'; - -function Component({items}) { - const sorted = useMemo(() => [...items].sort(), [items]); - return <div>{sorted}</div>; -} - -export default Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-conditionally-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-conditionally-in-effect.expect.md index 18be3b3889f4..fa5ae370e67e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-conditionally-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-conditionally-in-effect.expect.md @@ -56,7 +56,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [value]\n\nData Flow Tree:\n└── value (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":6,"index":263},"end":{"line":9,"column":19,"index":276},"filename":"derived-state-conditionally-in-effect.ts","identifierName":"setLocalValue"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [value]\n\nData Flow Tree:\n└── value (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":9,"column":6,"index":263},"end":{"line":9,"column":19,"index":276},"filename":"derived-state-conditionally-in-effect.ts","identifierName":"setLocalValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":16,"column":1,"index":397},"filename":"derived-state-conditionally-in-effect.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-default-props.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-default-props.expect.md index b3ddc4346e20..4db10f4df4cf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-default-props.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-default-props.expect.md @@ -50,7 +50,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [input]\n\nData Flow Tree:\n└── input (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":4,"index":295},"end":{"line":9,"column":16,"index":307},"filename":"derived-state-from-default-props.ts","identifierName":"setCurrInput"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [input]\n\nData Flow Tree:\n└── input (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":9,"column":4,"index":295},"end":{"line":9,"column":16,"index":307},"filename":"derived-state-from-default-props.ts","identifierName":"setCurrInput"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":15,"index":141},"end":{"line":13,"column":1,"index":391},"filename":"derived-state-from-default-props.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-local-state-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-local-state-in-effect.expect.md index 2121087e6f16..afddca39e9a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-local-state-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-local-state-in-effect.expect.md @@ -44,7 +44,7 @@ function Component({ shouldChange }) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [count]\n\nData Flow Tree:\n└── count (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":10,"column":6,"index":256},"end":{"line":10,"column":14,"index":264},"filename":"derived-state-from-local-state-in-effect.ts","identifierName":"setCount"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [count]\n\nData Flow Tree:\n└── count (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":10,"column":6,"index":256},"end":{"line":10,"column":14,"index":264},"filename":"derived-state-from-local-state-in-effect.ts","identifierName":"setCount"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":127},"end":{"line":15,"column":1,"index":329},"filename":"derived-state-from-local-state-in-effect.ts"},"fnName":"Component","memoSlots":7,"memoBlocks":3,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-local-state-and-component-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-local-state-and-component-scope.expect.md index 41e64d8149db..e1c33a6c73f4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-local-state-and-component-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-local-state-and-component-scope.expect.md @@ -64,7 +64,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [firstName]\nState: [lastName]\n\nData Flow Tree:\n├── firstName (Prop)\n└── lastName (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":11,"column":4,"index":316},"end":{"line":11,"column":15,"index":327},"filename":"derived-state-from-prop-local-state-and-component-scope.ts","identifierName":"setFullName"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [firstName]\nState: [lastName]\n\nData Flow Tree:\n├── firstName (Prop)\n└── lastName (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":11,"column":4,"index":316},"end":{"line":11,"column":15,"index":327},"filename":"derived-state-from-prop-local-state-and-component-scope.ts","identifierName":"setFullName"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":20,"column":1,"index":561},"filename":"derived-state-from-prop-local-state-and-component-scope.ts"},"fnName":"Component","memoSlots":12,"memoBlocks":5,"memoValues":6,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-with-side-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-with-side-effect.expect.md index 61c4e2b82de8..fc4d86a3f292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-with-side-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-with-side-effect.expect.md @@ -50,7 +50,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [value]\n\nData Flow Tree:\n└── value (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":8,"column":4,"index":233},"end":{"line":8,"column":17,"index":246},"filename":"derived-state-from-prop-with-side-effect.ts","identifierName":"setLocalValue"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [value]\n\nData Flow Tree:\n└── value (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":8,"column":4,"index":233},"end":{"line":8,"column":17,"index":246},"filename":"derived-state-from-prop-with-side-effect.ts","identifierName":"setLocalValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":13,"column":1,"index":346},"filename":"derived-state-from-prop-with-side-effect.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-contains-local-function-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-contains-local-function-call.expect.md index cdac7ffc63ac..080aa8e04dcd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-contains-local-function-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-contains-local-function-call.expect.md @@ -58,7 +58,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [propValue]\n\nData Flow Tree:\n└── propValue (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":12,"column":4,"index":298},"end":{"line":12,"column":12,"index":306},"filename":"effect-contains-local-function-call.ts","identifierName":"setValue"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [propValue]\n\nData Flow Tree:\n└── propValue (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":12,"column":4,"index":298},"end":{"line":12,"column":12,"index":306},"filename":"effect-contains-local-function-call.ts","identifierName":"setValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":17,"column":1,"index":390},"filename":"effect-contains-local-function-call.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":3,"memoValues":4,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-used-in-dep-array-still-errors.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-used-in-dep-array-still-errors.expect.md index 954feeaf6386..1bd8fa23faa7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-used-in-dep-array-still-errors.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-used-in-dep-array-still-errors.expect.md @@ -34,7 +34,7 @@ function Component({ prop }) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [prop]\n\nData Flow Tree:\n└── prop (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":6,"column":4,"index":169},"end":{"line":6,"column":8,"index":173},"filename":"effect-used-in-dep-array-still-errors.ts","identifierName":"setS"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [prop]\n\nData Flow Tree:\n└── prop (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":6,"column":4,"index":169},"end":{"line":6,"column":8,"index":173},"filename":"effect-used-in-dep-array-still-errors.ts","identifierName":"setS"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":3,"column":0,"index":83},"end":{"line":10,"column":1,"index":231},"filename":"effect-used-in-dep-array-still-errors.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/function-expression-mutation-edge-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/function-expression-mutation-edge-case.expect.md index 9e03cfb1bc43..c1b99a95ab89 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/function-expression-mutation-edge-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/function-expression-mutation-edge-case.expect.md @@ -78,7 +78,7 @@ function Component() { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [foo, bar]\n\nData Flow Tree:\n└── newData\n ├── foo (State)\n └── bar (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":23,"column":6,"index":682},"end":{"line":23,"column":12,"index":688},"filename":"function-expression-mutation-edge-case.ts","identifierName":"setFoo"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [foo, bar]\n\nData Flow Tree:\n└── newData\n ├── foo (State)\n └── bar (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":23,"column":6,"index":682},"end":{"line":23,"column":12,"index":688},"filename":"function-expression-mutation-edge-case.ts","identifierName":"setFoo"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":3,"column":0,"index":83},"end":{"line":32,"column":1,"index":781},"filename":"function-expression-mutation-edge-case.ts"},"fnName":"Component","memoSlots":9,"memoBlocks":4,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-computation-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-computation-in-effect.expect.md index 585ddcc5732e..928b7e9f7129 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-computation-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-computation-in-effect.expect.md @@ -54,7 +54,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [firstName]\n\nData Flow Tree:\n└── firstName (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":11,"column":4,"index":360},"end":{"line":11,"column":15,"index":371},"filename":"invalid-derived-computation-in-effect.ts","identifierName":"setFullName"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [firstName]\n\nData Flow Tree:\n└── firstName (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":11,"column":4,"index":360},"end":{"line":11,"column":15,"index":371},"filename":"invalid-derived-computation-in-effect.ts","identifierName":"setFullName"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":15,"column":1,"index":464},"filename":"invalid-derived-computation-in-effect.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-state-from-computed-props.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-state-from-computed-props.expect.md index a4bcc1f1090c..c627b583b25b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-state-from-computed-props.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-state-from-computed-props.expect.md @@ -50,7 +50,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [props]\n\nData Flow Tree:\n└── computed\n └── props (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":4,"index":314},"end":{"line":9,"column":19,"index":329},"filename":"invalid-derived-state-from-computed-props.ts","identifierName":"setDisplayValue"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [props]\n\nData Flow Tree:\n└── computed\n └── props (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":9,"column":4,"index":314},"end":{"line":9,"column":19,"index":329},"filename":"invalid-derived-state-from-computed-props.ts","identifierName":"setDisplayValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":15,"index":141},"end":{"line":13,"column":1,"index":428},"filename":"invalid-derived-state-from-computed-props.ts"},"fnName":"Component","memoSlots":7,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-state-from-destructured-props.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-state-from-destructured-props.expect.md index d89b3091b512..858daba50230 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-state-from-destructured-props.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/invalid-derived-state-from-destructured-props.expect.md @@ -52,7 +52,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [props]\n\nData Flow Tree:\n└── props (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":10,"column":4,"index":288},"end":{"line":10,"column":15,"index":299},"filename":"invalid-derived-state-from-destructured-props.ts","identifierName":"setFullName"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [props]\n\nData Flow Tree:\n└── props (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":10,"column":4,"index":288},"end":{"line":10,"column":15,"index":299},"filename":"invalid-derived-state-from-destructured-props.ts","identifierName":"setFullName"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":15,"index":141},"end":{"line":14,"column":1,"index":416},"filename":"invalid-derived-state-from-destructured-props.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/usestate-derived-from-prop-no-show-in-data-flow-tree.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/usestate-derived-from-prop-no-show-in-data-flow-tree.expect.md index 1a1e668b9532..690574e4429b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/usestate-derived-from-prop-no-show-in-data-flow-tree.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/usestate-derived-from-prop-no-show-in-data-flow-tree.expect.md @@ -50,7 +50,7 @@ function Component({ prop }) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [second]\n\nData Flow Tree:\n└── second (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":14,"column":4,"index":462},"end":{"line":14,"column":8,"index":466},"filename":"usestate-derived-from-prop-no-show-in-data-flow-tree.ts","identifierName":"setS"},"message":"This should be computed during render, not in an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [second]\n\nData Flow Tree:\n└── second (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":14,"column":4,"index":462},"end":{"line":14,"column":8,"index":466},"filename":"usestate-derived-from-prop-no-show-in-data-flow-tree.ts","identifierName":"setS"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":3,"column":0,"index":83},"end":{"line":18,"column":1,"index":519},"filename":"usestate-derived-from-prop-no-show-in-data-flow-tree.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-context-variable-catch-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-context-variable-catch-in-lambda.expect.md deleted file mode 100644 index 437bcae9288d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-context-variable-catch-in-lambda.expect.md +++ /dev/null @@ -1,36 +0,0 @@ - -## Input - -```javascript -// @flow -function Foo() { - try { - doSomething(); - } catch (e) { - foo(() => e); - } - return <div />; -} - -``` - - -## Error - -``` -Found 1 error: - -Invariant: Expected all references to a variable to be consistently local or context references - -Identifier <unknown> e$2 is referenced as a context variable, but was previously referenced as a local variable. - - 4 | doSomething(); - 5 | } catch (e) { -> 6 | foo(() => e); - | ^ this is local - 7 | } - 8 | return <div />; - 9 | } -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-context-variable-catch-in-lambda.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-context-variable-catch-in-lambda.js deleted file mode 100644 index 6d672ee6d826..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-context-variable-catch-in-lambda.js +++ /dev/null @@ -1,9 +0,0 @@ -// @flow -function Foo() { - try { - doSomething(); - } catch (e) { - foo(() => e); - } - return <div />; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_loc_diff_2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_loc_diff_2.expect.md deleted file mode 100644 index 194493ca28d1..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_loc_diff_2.expect.md +++ /dev/null @@ -1,88 +0,0 @@ - -## Input - -```javascript -/** - * @flow strict-local - * @format - */ - -'use strict'; - -import type {SelectorStrict} from 'SelectorStrict'; -import type LoadObject from 'LoadObject'; - -import DataAtom from 'DataAtom'; -// eslint-disable-next-line custom/no-restricted-imports -import BaseSingleDataStore from 'BaseSingleDataStore'; - -import storeToSelector from 'storeToSelector'; -import invariant from 'invariant'; -import createLoadObjectSelector from 'createLoadObjectSelector'; - -/** - * Create a ad-hoc store + selector that lives as long as it's used - * - * Allows the easy use of Suspense and caching without having to define a - * new store/selector file every time we need to load data from a - * controller - * - * Use: - * const dogSelector = adHocSelector(() => promiseAsyncRequest(DogsController.getURIBuilder().getURI()), - * - * const dogComponent = createSuspenseContainer(dogSelector, dog => <Dog dog={dog} />); -); - */ - -const stores = new Set<string>(); - -function adHocSelector<T>( - storeName: string, - genFunction: () => Promise<empty> -): SelectorStrict<LoadObject<unknown>, unknown, unknown> { - invariant( - !stores.has(storeName), - 'adHocSelector was run multiple times for store name %s', - storeName - ); - stores.add(storeName); - class AdHocStore extends BaseSingleDataStore<T, void> { - __loadPromise: () => Promise<empty> = genFunction; - } - - const adHocStore = new AdHocStore(storeName, DataAtom); - - return createLoadObjectSelector([storeToSelector(adHocStore)], () => - adHocStore.getData() - ); -} - -// $FlowFixMe[incompatible-type] -export default adHocSelector as <T>( - storeName: string, - genFunction: () => Promise<empty> -) => SelectorStrict<LoadObject<T>>; - -``` - - -## Error - -``` -Found 1 error: - -Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized - -<unknown> AdHocStore$16:TFunction. - -error.bug-hir_loc_diff_2.ts:49:25 - 47 | } - 48 | -> 49 | const adHocStore = new AdHocStore(storeName, DataAtom); - | ^^^^^^^^^^ this is uninitialized - 50 | - 51 | return createLoadObjectSelector([storeToSelector(adHocStore)], () => - 52 | adHocStore.getData() -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_loc_diff_2.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_loc_diff_2.js deleted file mode 100644 index 850cd523a4cc..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_loc_diff_2.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @flow strict-local - * @format - */ - -'use strict'; - -import type {SelectorStrict} from 'SelectorStrict'; -import type LoadObject from 'LoadObject'; - -import DataAtom from 'DataAtom'; -// eslint-disable-next-line custom/no-restricted-imports -import BaseSingleDataStore from 'BaseSingleDataStore'; - -import storeToSelector from 'storeToSelector'; -import invariant from 'invariant'; -import createLoadObjectSelector from 'createLoadObjectSelector'; - -/** - * Create a ad-hoc store + selector that lives as long as it's used - * - * Allows the easy use of Suspense and caching without having to define a - * new store/selector file every time we need to load data from a - * controller - * - * Use: - * const dogSelector = adHocSelector(() => promiseAsyncRequest(DogsController.getURIBuilder().getURI()), - * - * const dogComponent = createSuspenseContainer(dogSelector, dog => <Dog dog={dog} />); -); - */ - -const stores = new Set<string>(); - -function adHocSelector<T>( - storeName: string, - genFunction: () => Promise<empty> -): SelectorStrict<LoadObject<unknown>, unknown, unknown> { - invariant( - !stores.has(storeName), - 'adHocSelector was run multiple times for store name %s', - storeName - ); - stores.add(storeName); - class AdHocStore extends BaseSingleDataStore<T, void> { - __loadPromise: () => Promise<empty> = genFunction; - } - - const adHocStore = new AdHocStore(storeName, DataAtom); - - return createLoadObjectSelector([storeToSelector(adHocStore)], () => - adHocStore.getData() - ); -} - -// $FlowFixMe[incompatible-type] -export default adHocSelector as <T>( - storeName: string, - genFunction: () => Promise<empty> -) => SelectorStrict<LoadObject<T>>; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_structural_diff.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_structural_diff.expect.md deleted file mode 100644 index 8451b3712242..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_structural_diff.expect.md +++ /dev/null @@ -1,47 +0,0 @@ - -## Input - -```javascript -/** - * @flow strict-local - * @format - * @providesInline resize_plugin - */ -/*globals message, origin, __nativeHelper*/ -(function () { - // $FlowFixMe[incompatible-use] - // $FlowFixMe[incompatible-use] - if (x) { - } else { - // In iOS, we have to wait a minimum of time before trying to close the - // window, this is most lightly related to the time it takes to animate the - // tab change. - var delay = x; - if (delay) { - x(function () {}, delay); - } else { - } - } -})(); - -``` - - -## Error - -``` -Found 1 error: - -Todo: (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration - -error.bug-hir_structural_diff.ts:15:4 - 13 | // window, this is most lightly related to the time it takes to animate the - 14 | // tab change. -> 15 | var delay = x; - | ^^^^^^^^^^^^^^ (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration - 16 | if (delay) { - 17 | x(function () {}, delay); - 18 | } else { -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_structural_diff.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_structural_diff.js deleted file mode 100644 index 714944ceee8d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-hir_structural_diff.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @flow strict-local - * @format - * @providesInline resize_plugin - */ -/*globals message, origin, __nativeHelper*/ -(function () { - // $FlowFixMe[incompatible-use] - // $FlowFixMe[incompatible-use] - if (x) { - } else { - // In iOS, we have to wait a minimum of time before trying to close the - // window, this is most lightly related to the time it takes to animate the - // tab change. - var delay = x; - if (delay) { - x(function () {}, delay); - } else { - } - } -})(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-infer-mutation-aliasing-function-shadows-own-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-infer-mutation-aliasing-function-shadows-own-name.expect.md deleted file mode 100644 index bf039d1601d8..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-infer-mutation-aliasing-function-shadows-own-name.expect.md +++ /dev/null @@ -1,34 +0,0 @@ - -## Input - -```javascript -// @flow -function Foo() { - function hasError() { - let hasError = false; - return hasError; - } - return <div x={hasError} />; -} - -``` - - -## Error - -``` -Found 1 error: - -Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized - -<unknown> hasError_0$8. - - 5 | return hasError; - 6 | } -> 7 | return <div x={hasError} />; - | ^^^^^^^^ this is uninitialized - 8 | } - 9 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-infer-mutation-aliasing-function-shadows-own-name.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-infer-mutation-aliasing-function-shadows-own-name.js deleted file mode 100644 index a6fc340d26a2..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-infer-mutation-aliasing-function-shadows-own-name.js +++ /dev/null @@ -1,8 +0,0 @@ -// @flow -function Foo() { - function hasError() { - let hasError = false; - return hasError; - } - return <div x={hasError} />; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-local-or-context-references.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-local-or-context-references.expect.md index 7c949c6a854b..bca2e6930749 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-local-or-context-references.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-local-or-context-references.expect.md @@ -3,7 +3,7 @@ ```javascript import {useState} from 'react'; -const bar = () => ({data: null}); +import {bar} from './bar'; export const useFoot = () => { const [, setState] = useState(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-local-or-context-references.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-local-or-context-references.js index ef5edb9b6726..561bc25fb72a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-local-or-context-references.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-local-or-context-references.js @@ -1,5 +1,5 @@ import {useState} from 'react'; -const bar = () => ({data: null}); +import {bar} from './bar'; export const useFoot = () => { const [, setState] = useState(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-transitive-freeze-nested-function-captures.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-transitive-freeze-nested-function-captures.expect.md deleted file mode 100644 index 4a2701241294..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-transitive-freeze-nested-function-captures.expect.md +++ /dev/null @@ -1,45 +0,0 @@ - -## Input - -```javascript -// @enableTransitivelyFreezeFunctionExpressions -function Component(props) { - let x = {value: 0}; - const inner = () => { - return x.value; - }; - const outer = () => { - return inner(); - }; - // Freezing outer should transitively freeze inner AND x (two levels deep). - // x is only reachable through the function chain, not directly in JSX. - const element = <Child fn={outer} />; - // Mutating x after the freeze — TS should detect MutateFrozen, - // Rust may not if transitive freeze didn't reach x. - x.value = 1; - return element; -} - -``` - - -## Error - -``` -Found 1 error: - -Error: This value cannot be modified - -Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX. - -error.bug-transitive-freeze-nested-function-captures.ts:15:2 - 13 | // Mutating x after the freeze — TS should detect MutateFrozen, - 14 | // Rust may not if transitive freeze didn't reach x. -> 15 | x.value = 1; - | ^ value cannot be modified - 16 | return element; - 17 | } - 18 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-transitive-freeze-nested-function-captures.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-transitive-freeze-nested-function-captures.js deleted file mode 100644 index cd131796042d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-transitive-freeze-nested-function-captures.js +++ /dev/null @@ -1,17 +0,0 @@ -// @enableTransitivelyFreezeFunctionExpressions -function Component(props) { - let x = {value: 0}; - const inner = () => { - return x.value; - }; - const outer = () => { - return inner(); - }; - // Freezing outer should transitively freeze inner AND x (two levels deep). - // x is only reachable through the function chain, not directly in JSX. - const element = <Child fn={outer} />; - // Mutating x after the freeze — TS should detect MutateFrozen, - // Rust may not if transitive freeze didn't reach x. - x.value = 1; - return element; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-numeric-literal-computed-property-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-numeric-literal-computed-property-assignment.expect.md deleted file mode 100644 index 6bf7b492c3fe..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-numeric-literal-computed-property-assignment.expect.md +++ /dev/null @@ -1,49 +0,0 @@ - -## Input - -```javascript -// Computed property assignment with numeric literals should use -// precise_value() to avoid float precision loss from JSON parsing. - -function Component({obj}) { - obj[0] = 'first'; - obj[1] = 'second'; - return obj; -} - -``` - - -## Error - -``` -Found 2 errors: - -Error: This value cannot be modified - -Modifying component props or hook arguments is not allowed. Consider using a local variable instead. - -error.invalid-numeric-literal-computed-property-assignment.ts:5:2 - 3 | - 4 | function Component({obj}) { -> 5 | obj[0] = 'first'; - | ^^^ value cannot be modified - 6 | obj[1] = 'second'; - 7 | return obj; - 8 | } - -Error: This value cannot be modified - -Modifying component props or hook arguments is not allowed. Consider using a local variable instead. - -error.invalid-numeric-literal-computed-property-assignment.ts:6:2 - 4 | function Component({obj}) { - 5 | obj[0] = 'first'; -> 6 | obj[1] = 'second'; - | ^^^ value cannot be modified - 7 | return obj; - 8 | } - 9 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-numeric-literal-computed-property-assignment.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-numeric-literal-computed-property-assignment.js deleted file mode 100644 index 0e8dd06b2bd3..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-numeric-literal-computed-property-assignment.js +++ /dev/null @@ -1,8 +0,0 @@ -// Computed property assignment with numeric literals should use -// precise_value() to avoid float precision loss from JSON parsing. - -function Component({obj}) { - obj[0] = 'first'; - obj[1] = 'second'; - return obj; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-multiple-async-callbacks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-multiple-async-callbacks.expect.md deleted file mode 100644 index 436f05b02243..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-multiple-async-callbacks.expect.md +++ /dev/null @@ -1,38 +0,0 @@ - -## Input - -```javascript -function Component() { - let value: number | undefined; - const a = async () => { - value = 1; - }; - const b = async () => { - value = 2; - }; - return <div>{[a, b]}</div>; -} - -``` - - -## Error - -``` -Found 1 error: - -Error: Cannot reassign variable in async function - -Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead. - -error.invalid-reassign-local-variable-in-multiple-async-callbacks.ts:4:4 - 2 | let value: number | undefined; - 3 | const a = async () => { -> 4 | value = 1; - | ^^^^^ Cannot reassign `value` - 5 | }; - 6 | const b = async () => { - 7 | value = 2; -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-multiple-async-callbacks.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-multiple-async-callbacks.tsx deleted file mode 100644 index 47b8ac425e6b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-multiple-async-callbacks.tsx +++ /dev/null @@ -1,10 +0,0 @@ -function Component() { - let value: number | undefined; - const a = async () => { - value = 1; - }; - const b = async () => { - value = 2; - }; - return <div>{[a, b]}</div>; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch.expect.md deleted file mode 100644 index 7b880c1602e5..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch.expect.md +++ /dev/null @@ -1,29 +0,0 @@ - -## Input - -```javascript -// HIR Pattern: FN_TYPE_MISMATCH (10 files, 3%) -// TS classifies as fn_type: Other, Rust classifies as fn_type: Component - -/** - * @flow strict-local - */ -export default component EmbedRich() { - if (media) { - if (post.videoUrl != null && post.videoUrl !== '') { - renderAboveImage('EmbedVideo', <EmbedVideo post={post} />); - } - } - return; -} - -``` - - -## Error - -``` -Missing semicolon. (7:24) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch.js deleted file mode 100644 index 3a9e1fa07ebc..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch.js +++ /dev/null @@ -1,14 +0,0 @@ -// HIR Pattern: FN_TYPE_MISMATCH (10 files, 3%) -// TS classifies as fn_type: Other, Rust classifies as fn_type: Component - -/** - * @flow strict-local - */ -export default component EmbedRich() { - if (media) { - if (post.videoUrl != null && post.videoUrl !== '') { - renderAboveImage('EmbedVideo', <EmbedVideo post={post} />); - } - } - return; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_1.expect.md deleted file mode 100644 index 58122da51064..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_1.expect.md +++ /dev/null @@ -1,88 +0,0 @@ - -## Input - -```javascript -/** - * @flow strict-local - * @format - */ - -'use strict'; - -import type QueryVersion from 'QueryVersion'; - -import QueryTypes from 'QueryTypes'; -import ResultsTabContext from 'ResultsTabContext'; -import ResultsTabs from 'ResultsTabs'; - -import {useContext, useEffect, useRef} from 'react'; -import useDispatchSafe from 'useDispatchSafe'; -/** - * This component is used to let the Event Store know when a Presto query is - * executed in Query Editor. This event is used as a signal for VizAgent. - */ -export default component ClassicQueryExecutionDispatcher( - version: ?QueryVersion -) { - const dispatch = useDispatchSafe(); - - const {selectedTab, shouldShowData} = useContext(ResultsTabContext); - const lastLoggedVersionID = useRef<?string>(null); - - useEffect(() => { - if (version == null) { - return; - } - - const versionID = version.getID(); - if (lastLoggedVersionID.current === versionID) { - // Classic creates a new version for every execution, so we only want to - // log once per execution - return; - } - - const queryType = version.getType().getName(); - if (queryType !== QueryTypes.PRESTO) { - // Only log for Presto queries - return; - } - - let shouldDispatch = false; - switch (selectedTab) { - case ResultsTabs.VISUALIZATION: - shouldDispatch = shouldShowData; - break; - case ResultsTabs.DATA: - shouldDispatch = - shouldShowData && - version?.getStatus() != null && - version?.getStatus() !== 'draft'; - break; - default: - shouldDispatch = false; - } - - if (shouldDispatch && dispatch != null) { - dispatch({ - queryeditorQueryConfig: version.getImmutableConfig(), - queryID: version.getQueryID(), - selectedTab, - type: '[ClassicQueryExecutionDispatcher] Executed Query Editor Presto Query', - versionID, - }); - lastLoggedVersionID.current = versionID; - } - }, [dispatch, selectedTab, shouldShowData, version]); - return; -} - -``` - - -## Error - -``` -Missing semicolon. (20:24) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_1.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_1.js deleted file mode 100644 index c44f164512b7..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_1.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @flow strict-local - * @format - */ - -'use strict'; - -import type QueryVersion from 'QueryVersion'; - -import QueryTypes from 'QueryTypes'; -import ResultsTabContext from 'ResultsTabContext'; -import ResultsTabs from 'ResultsTabs'; - -import {useContext, useEffect, useRef} from 'react'; -import useDispatchSafe from 'useDispatchSafe'; -/** - * This component is used to let the Event Store know when a Presto query is - * executed in Query Editor. This event is used as a signal for VizAgent. - */ -export default component ClassicQueryExecutionDispatcher( - version: ?QueryVersion -) { - const dispatch = useDispatchSafe(); - - const {selectedTab, shouldShowData} = useContext(ResultsTabContext); - const lastLoggedVersionID = useRef<?string>(null); - - useEffect(() => { - if (version == null) { - return; - } - - const versionID = version.getID(); - if (lastLoggedVersionID.current === versionID) { - // Classic creates a new version for every execution, so we only want to - // log once per execution - return; - } - - const queryType = version.getType().getName(); - if (queryType !== QueryTypes.PRESTO) { - // Only log for Presto queries - return; - } - - let shouldDispatch = false; - switch (selectedTab) { - case ResultsTabs.VISUALIZATION: - shouldDispatch = shouldShowData; - break; - case ResultsTabs.DATA: - shouldDispatch = - shouldShowData && - version?.getStatus() != null && - version?.getStatus() !== 'draft'; - break; - default: - shouldDispatch = false; - } - - if (shouldDispatch && dispatch != null) { - dispatch({ - queryeditorQueryConfig: version.getImmutableConfig(), - queryID: version.getQueryID(), - selectedTab, - type: '[ClassicQueryExecutionDispatcher] Executed Query Editor Presto Query', - versionID, - }); - lastLoggedVersionID.current = versionID; - } - }, [dispatch, selectedTab, shouldShowData, version]); - return; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_2.expect.md deleted file mode 100644 index 4c1334d3f6a3..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_2.expect.md +++ /dev/null @@ -1,118 +0,0 @@ - -## Input - -```javascript -/** - * @flow strict-local - * @format - */ - -'use strict'; - -import type {SimpleTooltipMessageTypesType} from 'SimpleTooltipMessageTypes'; -import type {Alignment, Position, Width} from 'AmbientTooltip.react'; - -import * as SimpleTooltipMessage from 'SimpleTooltipMessage'; -import AmbientTooltip from 'AmbientTooltip.react'; - -import * as React from 'react'; - -export type NUXProps = { - alignment?: Alignment, - children: React.Node, - customWidth?: number, - disabled?: boolean, - hideOnXout?: boolean, - position: Position, - showOnce?: boolean, - type: SimpleTooltipMessageTypesType, - width?: Width, -}; - -type CurrentState = { - showNux: boolean, -}; - -type ExposedProps<Props extends {...}> = { - ...$Exact<Props>, - nuxProps: NUXProps, -}; - -export default function WidgetWithTooltip< - Props extends {...}, - WidgetWithTooltipComponent extends React.ComponentType<Props>, ->( - WrappedComponent: WidgetWithTooltipComponent -): Class< - React.Component< - ExposedProps<React.ElementConfig<WidgetWithTooltipComponent>>, - CurrentState, - >, -> { - class WithNux extends React.PureComponent<ExposedProps<Props>, CurrentState> { - state: CurrentState = { - showNux: - this.props.nuxProps.disabled !== true && - !SimpleTooltipMessage.hasUserSeenMessage_LEGACY( - this.props.nuxProps.type - ), - }; - - wrappedRef: { - current: HTMLSpanElement | null, - ... - } = React.createRef(); - - componentDidMount(): void { - if (this.props.nuxProps.showOnce === true && this.state.showNux) { - SimpleTooltipMessage.markMessageSeenByUser(this.props.nuxProps.type); - } - } - - #onNuxClose = (): void => { - if (this.props.nuxProps.hideOnXout === true && this.state.showNux) { - SimpleTooltipMessage.markMessageSeenByUser(this.props.nuxProps.type); - } - this.setState({ - showNux: false, - }); - }; - - #getRef = (): null | HTMLSpanElement => this.wrappedRef.current; - - render(): React.MixedElement { - const {nuxProps, ...passProps} = this.props; - return ( - <> - <span className="uiContextualLayerParent" ref={this.wrappedRef}> - <WrappedComponent {...passProps} /> - </span> - {this.state.showNux ? ( - <AmbientTooltip - alignment={nuxProps.alignment} - children={nuxProps.children} - contextRef={this.#getRef} - customwidth={nuxProps.customWidth} - onCloseButtonClick={this.#onNuxClose} - position={nuxProps.position} - shown={this.state.showNux} - width={nuxProps.width} - /> - ) : null} - </> - ); - } - } - return WithNux; -} - -``` - - -## Error - -``` -Unexpected token (32:33) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_2.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_2.js deleted file mode 100644 index ec1541579de2..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_fn_type_mismatch_2.js +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @flow strict-local - * @format - */ - -'use strict'; - -import type {SimpleTooltipMessageTypesType} from 'SimpleTooltipMessageTypes'; -import type {Alignment, Position, Width} from 'AmbientTooltip.react'; - -import * as SimpleTooltipMessage from 'SimpleTooltipMessage'; -import AmbientTooltip from 'AmbientTooltip.react'; - -import * as React from 'react'; - -export type NUXProps = { - alignment?: Alignment, - children: React.Node, - customWidth?: number, - disabled?: boolean, - hideOnXout?: boolean, - position: Position, - showOnce?: boolean, - type: SimpleTooltipMessageTypesType, - width?: Width, -}; - -type CurrentState = { - showNux: boolean, -}; - -type ExposedProps<Props extends {...}> = { - ...$Exact<Props>, - nuxProps: NUXProps, -}; - -export default function WidgetWithTooltip< - Props extends {...}, - WidgetWithTooltipComponent extends React.ComponentType<Props>, ->( - WrappedComponent: WidgetWithTooltipComponent -): Class< - React.Component< - ExposedProps<React.ElementConfig<WidgetWithTooltipComponent>>, - CurrentState, - >, -> { - class WithNux extends React.PureComponent<ExposedProps<Props>, CurrentState> { - state: CurrentState = { - showNux: - this.props.nuxProps.disabled !== true && - !SimpleTooltipMessage.hasUserSeenMessage_LEGACY( - this.props.nuxProps.type - ), - }; - - wrappedRef: { - current: HTMLSpanElement | null, - ... - } = React.createRef(); - - componentDidMount(): void { - if (this.props.nuxProps.showOnce === true && this.state.showNux) { - SimpleTooltipMessage.markMessageSeenByUser(this.props.nuxProps.type); - } - } - - #onNuxClose = (): void => { - if (this.props.nuxProps.hideOnXout === true && this.state.showNux) { - SimpleTooltipMessage.markMessageSeenByUser(this.props.nuxProps.type); - } - this.setState({ - showNux: false, - }); - }; - - #getRef = (): null | HTMLSpanElement => this.wrappedRef.current; - - render(): React.MixedElement { - const {nuxProps, ...passProps} = this.props; - return ( - <> - <span className="uiContextualLayerParent" ref={this.wrappedRef}> - <WrappedComponent {...passProps} /> - </span> - {this.state.showNux ? ( - <AmbientTooltip - alignment={nuxProps.alignment} - children={nuxProps.children} - contextRef={this.#getRef} - customwidth={nuxProps.customWidth} - onCloseButtonClick={this.#onNuxClose} - position={nuxProps.position} - shown={this.state.showNux} - width={nuxProps.width} - /> - ) : null} - </> - ); - } - } - return WithNux; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_jsxtext_nbsp.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_jsxtext_nbsp.expect.md deleted file mode 100644 index 9fc8b9a8c902..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_jsxtext_nbsp.expect.md +++ /dev/null @@ -1,34 +0,0 @@ - -## Input - -```javascript -// HIR Pattern: JSXTEXT_NBSP_ENCODING (29 files, 9%) -// TS renders   as " ", Rust renders as "\u{a0}" - -/** - * @flow strict-local - */ -export default component HeadlineWithAddOn() { - return ( - <Text> - <Row> - <RowItem> - <Row verticalAlign="center"> - <RowItem xstyle={styles.nonBreakingSpace}> </RowItem> - </Row> - </RowItem> - </Row> - </Text> - ); -} - -``` - - -## Error - -``` -Missing semicolon. (7:24) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_jsxtext_nbsp.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_jsxtext_nbsp.js deleted file mode 100644 index 92b3ee36cdf1..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_jsxtext_nbsp.js +++ /dev/null @@ -1,19 +0,0 @@ -// HIR Pattern: JSXTEXT_NBSP_ENCODING (29 files, 9%) -// TS renders   as " ", Rust renders as "\u{a0}" - -/** - * @flow strict-local - */ -export default component HeadlineWithAddOn() { - return ( - <Text> - <Row> - <RowItem> - <Row verticalAlign="center"> - <RowItem xstyle={styles.nonBreakingSpace}> </RowItem> - </Row> - </RowItem> - </Row> - </Text> - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_loc_diff_1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_loc_diff_1.expect.md deleted file mode 100644 index e76a88adcb2f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_loc_diff_1.expect.md +++ /dev/null @@ -1,60 +0,0 @@ - -## Input - -```javascript -/** - * * @flow strict - * @format - */ - -/** - * creates a cache for Component props so we prevent rendering a component - * sequentially if the props didn't change. Useful to wrap FluxContainer - * with pure calculateState and getStores functions. - */ - -'use strict'; - -import * as React from 'react'; -import {PureComponent} from 'react'; - -export default function createPureComponent< - DefaultProps extends {...} | void, - Props extends {...}, ->( - Component: React.ComponentType<Props> & { - defaultProps?: DefaultProps, - displayName?: string, - } -): React.ComponentType<Props> { - class PureComponentCache extends PureComponent<Props, void> { - static defaultProps: DefaultProps; - - render(): React.MixedElement { - return <Component {...this.props} />; - } - } - - if (Component.defaultProps) { - PureComponentCache.defaultProps = Component.defaultProps; - } - PureComponentCache.displayName = `PureComponentCache(${ - /* $FlowFixMe[incompatible-type] (>=0.66.0 site=www) This comment - * suppresses an error found when Flow v0.66 was deployed. To see the - * error delete this comment and run Flow. */ - Component.displayName - })`; - // $FlowFixMe[incompatible-type] - return PureComponentCache; -} - -``` - - -## Error - -``` -Unexpected token (18:24) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_loc_diff_1.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_loc_diff_1.js deleted file mode 100644 index 2cf403b7381a..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_loc_diff_1.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * * @flow strict - * @format - */ - -/** - * creates a cache for Component props so we prevent rendering a component - * sequentially if the props didn't change. Useful to wrap FluxContainer - * with pure calculateState and getStores functions. - */ - -'use strict'; - -import * as React from 'react'; -import {PureComponent} from 'react'; - -export default function createPureComponent< - DefaultProps extends {...} | void, - Props extends {...}, ->( - Component: React.ComponentType<Props> & { - defaultProps?: DefaultProps, - displayName?: string, - } -): React.ComponentType<Props> { - class PureComponentCache extends PureComponent<Props, void> { - static defaultProps: DefaultProps; - - render(): React.MixedElement { - return <Component {...this.props} />; - } - } - - if (Component.defaultProps) { - PureComponentCache.defaultProps = Component.defaultProps; - } - PureComponentCache.displayName = `PureComponentCache(${ - /* $FlowFixMe[incompatible-type] (>=0.66.0 site=www) This comment - * suppresses an error found when Flow v0.66 was deployed. To see the - * error delete this comment and run Flow. */ - Component.displayName - })`; - // $FlowFixMe[incompatible-type] - return PureComponentCache; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_silent_rust_fail.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_silent_rust_fail.expect.md deleted file mode 100644 index 738694cd282e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_silent_rust_fail.expect.md +++ /dev/null @@ -1,380 +0,0 @@ - -## Input - -```javascript -/** - * @flow strict-local - * @format - */ - -'use strict'; - -import type {BurmeseFontSignals} from 'ZawgyiTypes'; - -import FontCapability from 'FontCapability'; -import * as DOM from 'DOMUtils'; -import ZawgyiDetectionLogEvent from 'ZawgyiDetectionLogEvent'; -import MutationObserver from 'MutationObserver'; -import {detectFont, gatherFontDetectionSignals} from 'ZawgyiDetection'; - -const ZG_TO_UNICODE_RULES = [ - [RegExp('\u200B', 'g'), ''], - [RegExp('(\u103d|\u1087)', 'g'), '\u103e'], - [RegExp('\u103c', 'g'), '\u103d'], - [ - RegExp('(\u103b|\u107e|\u107f|\u1080|\u1081|\u1082|\u1083|\u1084)', 'g'), - '\u103c', - ], - [RegExp('(\u103a|\u107d)', 'g'), '\u103b'], - [RegExp('\u1039', 'g'), '\u103a'], - [RegExp('(\u1066|\u1067)', 'g'), '\u1039\u1006'], - [RegExp('\u106a', 'g'), '\u1009'], - [RegExp('\u106b', 'g'), '\u100a'], - [RegExp('\u106c', 'g'), '\u1039\u100b'], - [RegExp('\u106d', 'g'), '\u1039\u100c'], - [RegExp('\u106e', 'g'), '\u100d\u1039\u100d'], - [RegExp('\u106f', 'g'), '\u100d\u1039\u100e'], - [RegExp('\u1070', 'g'), '\u1039\u100f'], - [RegExp('(\u1071|\u1072)', 'g'), '\u1039\u1010'], - [RegExp('\u1060', 'g'), '\u1039\u1000'], - [RegExp('\u1061', 'g'), '\u1039\u1001'], - [RegExp('\u1062', 'g'), '\u1039\u1002'], - [RegExp('\u1063', 'g'), '\u1039\u1003'], - [RegExp('\u1065', 'g'), '\u1039\u1005'], - [RegExp('\u1068', 'g'), '\u1039\u1007'], - [RegExp('\u1069', 'g'), '\u1039\u1008'], - [RegExp('(\u1073|\u1074)', 'g'), '\u1039\u1011'], - [RegExp('\u1075', 'g'), '\u1039\u1012'], - [RegExp('\u1076', 'g'), '\u1039\u1013'], - [RegExp('\u1077', 'g'), '\u1039\u1014'], - [RegExp('\u1078', 'g'), '\u1039\u1015'], - [RegExp('\u1079', 'g'), '\u1039\u1016'], - [RegExp('\u107a', 'g'), '\u1039\u1017'], - [RegExp('\u107c', 'g'), '\u1039\u1019'], - [RegExp('\u1085', 'g'), '\u1039\u101c'], - [RegExp('\u1033', 'g'), '\u102f'], - [RegExp('\u1034', 'g'), '\u1030'], - [RegExp('\u103f', 'g'), '\u1030'], - [RegExp('\u1086', 'g'), '\u103f'], - [RegExp('\u1036\u1088', 'g'), '\u1088\u1036'], - [RegExp('\u1088', 'g'), '\u103e\u102f'], - [RegExp('\u1089', 'g'), '\u103e\u1030'], - [RegExp('\u108a', 'g'), '\u103d\u103e'], - [RegExp('\u103B\u1064', 'g'), '\u1064\u103B'], - [RegExp('(\u1031)?([\u1000-\u1021])\u1064', 'g'), '\u1004\u103a\u1039$1$2'], - [ - RegExp('(\u1031)?([\u1000-\u1021])\u108b', 'g'), - '\u1004\u103a\u1039$1$2\u102d', - ], - [ - RegExp('(\u1031)?([\u1000-\u1021])\u108c', 'g'), - '\u1004\u103a\u1039$1$2\u102e', - ], - [ - RegExp('(\u1031)?([\u1000-\u1021])\u108d', 'g'), - '\u1004\u103a\u1039$1$2\u1036', - ], - [RegExp('\u108e', 'g'), '\u102d\u1036'], - [RegExp('\u108f', 'g'), '\u1014'], - [RegExp('\u1090', 'g'), '\u101b'], - [RegExp('\u1091', 'g'), '\u100f\u1039\u100d'], - [RegExp('\u1019\u102c(\u107b|\u1093)', 'g'), '\u1019\u1039\u1018\u102c'], - [RegExp('(\u107b|\u1093)', 'g'), '\u1039\u1018'], - [RegExp('(\u1094|\u1095)', 'g'), '\u1037'], - [RegExp('([\u1000-\u1021])\u1037\u1032', 'g'), '$1\u1032\u1037'], - [RegExp('\u1096', 'g'), '\u1039\u1010\u103d'], - [RegExp('\u1097', 'g'), '\u100b\u1039\u100b'], - [RegExp('\u103c([\u1000-\u1021])([\u1000-\u1021])?', 'g'), '$1\u103c$2'], - [RegExp('([\u1000-\u1021])\u103c\u103a', 'g'), '\u103c$1\u103a'], - [ - RegExp('\u1047([\u102c-\u1030\u1032\u1036-\u1038\u103d\u1038])', 'g'), - '\u101b$1', - ], - [RegExp('\u1031\u1047', 'g'), '\u1031\u101b'], - [ - RegExp( - '\u1040(\u102e|\u102f|\u102d\u102f|\u1030|\u1036|\u103d|\u103e)', - 'g' - ), - '\u101d$1', - ], - [ - // Equivalent to: - // /([^\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049])\u1040\u102b/u - RegExp( - '((?:[\0-\u103F\u104A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))\u1040\u102B', - 'g' - ), - '$1\u101d\u102b', - ], - [ - // Equivalent to: - // /([\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049])\u1040\u102b([^\u1038])/u - RegExp( - '([\u1040-\u1049])\u1040\u102B((?:[\0-\u1037\u1039-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))', - 'g' - ), - '$1\u101d\u102b$2', - ], - [RegExp('^\u1040(\u102b)', 'g'), '\u101d$1'], - [ - // Equivalent to: - // /\u1040\u102d([^\u0020]?)/u - RegExp( - '\u1040\u102D((?:[\0-\x1F!-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])?)', - 'g' - ), - '\u101d\u102d$1', - ], - [ - // Equivalent to: - // /([^\u1040-\u1049])\u1040([^\u1040-\u1049\u0020]|[\u104a\u104b])/u - RegExp( - '((?:[\0-\u103F\u104A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))\u1040((?:[\0-\x1F!-\u103F\u104A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])|[\u104A\u104B])', - 'g' - ), - '$1\u101d$2', - ], - [ - // Equivalent to: - // /([^\u1040-\u1049])\u1040([$f$n$r])/u - RegExp( - '((?:[\0-\u103F\u104A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))\u1040([$fnr])', - 'g' - ), - '$1\u101d$2', - ], - [ - // Equivalent to: - // /([^\u1040-\u1049])\u1040$/u - RegExp( - '((?:[\0-\u103F\u104A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))\u1040$', - 'g' - ), - '$1\u101d', - ], - [RegExp('\u1031([\u1000-\u1021])(\u103e)?(\u103b)?', 'g'), '$1$2$3\u1031'], - [ - RegExp('([\u1000-\u1021])\u1031([\u103b\u103c\u103d\u103e]+)', 'g'), - '$1$2\u1031', - ], - [RegExp('\u1032\u103d', 'g'), '\u103d\u1032'], - [RegExp('([\u102d\u102e])\u103b', 'g'), '\u103b$1'], - [RegExp('\u103d\u103b', 'g'), '\u103b\u103d'], - [RegExp('\u103a\u1037', 'g'), '\u1037\u103a'], - [RegExp('\u102f(\u102d|\u102e|\u1036|\u1037)\u102f', 'g'), '\u102f$1'], - [RegExp('(\u102f|\u1030)(\u102d|\u102e)', 'g'), '$2$1'], - [RegExp('(\u103e)(\u103b|\u103c)', 'g'), '$2$1'], - [RegExp('\u1025([\u1037]?[\u103a\u102c])', 'g'), '\u1009$1'], - [RegExp('\u1025\u102e', 'g'), '\u1026'], - [RegExp('\u1005\u103b', 'g'), '\u1008'], - [RegExp('\u1036(\u102f|\u1030)', 'g'), '$1\u1036'], - [RegExp('\u1031\u1037\u103e', 'g'), '\u103e\u1031\u1037'], - [RegExp('\u1031\u103e\u102c', 'g'), '\u103e\u1031\u102c'], - [RegExp('\u105a', 'g'), '\u102b\u103a'], - [RegExp('\u1031\u103b\u103e', 'g'), '\u103b\u103e\u1031'], - [RegExp('(\u102d|\u102e)(\u103d|\u103e)', 'g'), '$2$1'], - [RegExp('\u102c\u1039([\u1000-\u1021])', 'g'), '\u1039$1\u102c'], - [ - RegExp('\u103c\u1004\u103a\u1039([\u1000-\u1021])', 'g'), - '\u1004\u103a\u1039$1\u103c', - ], - [ - RegExp('\u1039\u103c\u103a\u1039([\u1000-\u1021])', 'g'), - '\u103a\u1039$1\u103c', - ], - [RegExp('\u103c\u1039([\u1000-\u1021])', 'g'), '\u1039$1\u103c'], - [RegExp('\u1036\u1039([\u1000-\u1021])', 'g'), '\u1039$1\u1036'], - [RegExp('\u1092', 'g'), '\u100b\u1039\u100c'], - [RegExp('\u104e', 'g'), '\u104e\u1004\u103a\u1038'], - [RegExp('\u1040(\u102b|\u102c|\u1036)', 'g'), '\u101d$1'], - [RegExp('\u1025\u1039', 'g'), '\u1009\u1039'], - [RegExp('([\u1000-\u1021])\u103c\u1031\u103d', 'g'), '$1\u103c\u103d\u1031'], - [ - RegExp('([\u1000-\u1021])\u103b\u1031\u103d(\u103e)?', 'g'), - '$1\u103b\u103d$2\u1031', - ], - [RegExp('([\u1000-\u1021])\u103d\u1031\u103b', 'g'), '$1\u103b\u103d\u1031'], - [RegExp('([\u1000-\u1021])\u1031(\u1039[\u1000-\u1021])', 'g'), '$1$2\u1031'], - [RegExp('\u1038\u103a', 'g'), '\u103a\u1038'], - [RegExp('\u102d\u103a|\u103a\u102d', 'g'), '\u102d'], - [RegExp('\u102d\u102f\u103a', 'g'), '\u102d\u102f'], - [RegExp('\u0020\u1037', 'g'), '\u1037'], - [RegExp('\u1037\u1036', 'g'), '\u1036\u1037'], - [RegExp(' \u1037', 'g'), '\u1037'], - [RegExp('[\u102d]+', 'g'), '\u102d'], - [RegExp('[\u103a]+', 'g'), '\u103a'], - [RegExp('[\u103d]+', 'g'), '\u103d'], - [RegExp('[\u1037]+', 'g'), '\u1037'], - [RegExp('[\u102e]+', 'g'), '\u102e'], - [RegExp('\u102d\u102e|\u102e\u102d', 'g'), '\u102e'], - [RegExp('\u102f\u102d', 'g'), '\u102d\u102f'], - [RegExp('\u1037\u1037', 'g'), '\u1037'], - [RegExp('\u1032\u1032', 'g'), '\u1032'], - [RegExp('\u1044\u1004\u103a\u1038', 'g'), '\u104E\u1004\u103a\u1038'], - [RegExp('([\u102d\u102e])\u1039([\u1000-\u1021])', 'g'), '\u1039$2$1'], - [RegExp('\u1036\u103d', 'g'), '\u103d\u1036'], -]; - -function convertZawgyiToUnicode(text: string): string { - let result = text; - for (let i = 0; i < ZG_TO_UNICODE_RULES.length; i++) { - result = result.replace( - ZG_TO_UNICODE_RULES[i][0], - ZG_TO_UNICODE_RULES[i][1] - ); - } - return result; -} - -function isZawgyiEncodedText(text: string): boolean { - const unicodeMatches = text.match( - /\u103e|\u103f|\u100a\u103a|\u1014\u103a|\u1004\u103a|\u1031\u1038|\u1031\u102c|\u103a\u1038|\u1035|[\u1050-\u1059]|^([\u1000-\u1021]\u103c|[\u1000-\u1021]\u1031)/g - ); - const zawgyiMatches = text.match( - /\u102c\u1039|\u103a\u102c|\p{Z}(\u103b|\u1031|[\u107e-\u1084])[\u1000-\u1021]|^(\u103b|\u1031|[\u107e-\u1084])[\u1000-\u1021]|[\u1000-\u1021]\u1039[^\u1000-\u1021]|\u1025\u1039|\u1039\u1038|[\u102b-\u1030\u1031\u103a\u1038](\u103b|[\u107e-\u1084])[\u1000-\u1021]|\u1036\u102f|[\u1000-\u1021]\u1039\u1031|\u1064|\u1039\p{Z}|\u102c\u1031|[\u102b-\u1030\u103a\u1038]\u1031[\u1000-\u1021]|\u1031\u1031|\u102f\u102d|(\u1039$)/g - ); - const unicodeCount = unicodeMatches == null ? 0 : unicodeMatches.length; - const zawgyiCount = zawgyiMatches == null ? 0 : zawgyiMatches.length; - // Total heuristic: Use the Unicode to Zawgyi pattern ratio. Lean towards - // false positives for now. - return 1.2 * zawgyiCount > unicodeCount; -} - -// Hard-code these since they will only ever be needed for Burmese Unicode. -const TEXT_BURMESE_UNREADABLE_LINK = - '\u1005\u102C\u101E\u102C\u1038\u1019\u1016\u1010\u103A\u101C\u102D\u102F\u1037\u1019\u101B\u1021\u1031\u102C\u1004\u103A?'; -const TEXT_BURMESE_REVERT = - '\u1019\u101C\u102F\u1015\u103A\u1010\u1031\u102C\u1037\u1015\u102B'; - -function convertRecursively(node: Node): void { - if (node.nodeType === Node.TEXT_NODE) { - if (node.parentElement != null) { - node.parentElement.setAttribute( - 'data-zg-pre-conversion', - node.textContent - ); - } - node.textContent = convertZawgyiToUnicode(node.textContent); - } - for (let i = 0; i < node.childNodes.length; i++) { - convertRecursively(node.childNodes[i]); - } -} - -function revertRecursively(node: Node): void { - if (node.nodeType === Node.TEXT_NODE) { - if (node.parentElement != null) { - const previousText = node.parentElement.getAttribute( - 'data-zg-pre-conversion' - ); - if (previousText != null) { - node.textContent = previousText; - } - } - } - for (let i = 0; i < node.childNodes.length; i++) { - revertRecursively(node.childNodes[i]); - } -} - -export function logEvent( - event: string, - fontSignals: ?BurmeseFontSignals -): void { - ZawgyiDetectionLogEvent.log(() => ({ - msite_event: event, - aforementioned_width: String(fontSignals?.aforementioned_width ?? 0), - detected_font: (fontSignals?.detected_font ?? '') as string, - ka_virama_ka_width: String(fontSignals?.ka_virama_ka_width ?? 0), - ka_width: String(fontSignals?.ka_width ?? 0), - msite_user_agent: fontSignals?.user_agent, - requested_with: fontSignals?.requested_with, - })); -} - -function convertOnClick(element: HTMLElement, link: HTMLElement) { - if (element.getAttribute('data-is-zg-converted') === 'false') { - convertRecursively(element); - element.setAttribute('data-is-zg-converted', 'true'); - link.textContent = TEXT_BURMESE_REVERT; - logEvent('uni_to_zg_convert_clicked'); - } else { - revertRecursively(element); - element.setAttribute('data-is-zg-converted', 'false'); - link.textContent = TEXT_BURMESE_UNREADABLE_LINK; - logEvent('uni_to_zg_revert_clicked'); - } -} - -function addConvertLinkToDiv(element: HTMLElement) { - if (element.hasAttribute('data-is-zg-converted')) { - // Already added link. - return; - } - if (!isZawgyiEncodedText(element.textContent)) { - return; - } - element.setAttribute('data-is-zg-converted', 'false'); - const link = DOM.create('a', {href: '#'}, TEXT_BURMESE_UNREADABLE_LINK); - link.style.display = 'inline-block'; - link.style.textAlign = 'center'; - link.style.fontSize = '0.85em'; - link.style.width = '100%'; - link.addEventListener('click', () => convertOnClick(element, link)); - element.insertBefore(link, element.firstChild); -} - -function createConversionLinks() { - const posts = document.getElementsByClassName('story_body_container'); - for (let i = 0; i < posts.length; i++) { - addConvertLinkToDiv(posts[i]); - } -} - -function initializeZawgyiToUnicodeConversion(): void { - // Create conversion links for stories already on the page. - createConversionLinks(); - // Now see if there's a feed and start listening for mutatations that might - // add new stories to it to make sure links are added for newly added stories. - const feed = document.getElementById('viewport'); - if (feed == null) { - return; - } - const observer = new MutationObserver(function () { - createConversionLinks(); - }); - observer.observe(feed, { - childList: true, - subtree: true, - }); -} - -// @ServerCallable -export function logBurmeseFontSignals(requestedWith: ?string) { - const data = gatherFontDetectionSignals(true); - data.requested_with = requestedWith == null ? '' : requestedWith; - data.user_agent = navigator.userAgent; - data.detected_font = detectFont(); - logEvent('font_detected', data); -} -// @ServerCallable -export function enableZawgyiToUnicodeConversionLinks() { - const deviceFont = detectFont(); - // Only add conversion links if device font is Unicode. - if (deviceFont !== FontCapability.UNICODE) { - return; - } - initializeZawgyiToUnicodeConversion(); -} - -``` - - -## Error - -``` -Unexpected token (279:15) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_silent_rust_fail.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_silent_rust_fail.js deleted file mode 100644 index 9fc388006f65..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_silent_rust_fail.js +++ /dev/null @@ -1,365 +0,0 @@ -/** - * @flow strict-local - * @format - */ - -'use strict'; - -import type {BurmeseFontSignals} from 'ZawgyiTypes'; - -import FontCapability from 'FontCapability'; -import * as DOM from 'DOMUtils'; -import ZawgyiDetectionLogEvent from 'ZawgyiDetectionLogEvent'; -import MutationObserver from 'MutationObserver'; -import {detectFont, gatherFontDetectionSignals} from 'ZawgyiDetection'; - -const ZG_TO_UNICODE_RULES = [ - [RegExp('\u200B', 'g'), ''], - [RegExp('(\u103d|\u1087)', 'g'), '\u103e'], - [RegExp('\u103c', 'g'), '\u103d'], - [ - RegExp('(\u103b|\u107e|\u107f|\u1080|\u1081|\u1082|\u1083|\u1084)', 'g'), - '\u103c', - ], - [RegExp('(\u103a|\u107d)', 'g'), '\u103b'], - [RegExp('\u1039', 'g'), '\u103a'], - [RegExp('(\u1066|\u1067)', 'g'), '\u1039\u1006'], - [RegExp('\u106a', 'g'), '\u1009'], - [RegExp('\u106b', 'g'), '\u100a'], - [RegExp('\u106c', 'g'), '\u1039\u100b'], - [RegExp('\u106d', 'g'), '\u1039\u100c'], - [RegExp('\u106e', 'g'), '\u100d\u1039\u100d'], - [RegExp('\u106f', 'g'), '\u100d\u1039\u100e'], - [RegExp('\u1070', 'g'), '\u1039\u100f'], - [RegExp('(\u1071|\u1072)', 'g'), '\u1039\u1010'], - [RegExp('\u1060', 'g'), '\u1039\u1000'], - [RegExp('\u1061', 'g'), '\u1039\u1001'], - [RegExp('\u1062', 'g'), '\u1039\u1002'], - [RegExp('\u1063', 'g'), '\u1039\u1003'], - [RegExp('\u1065', 'g'), '\u1039\u1005'], - [RegExp('\u1068', 'g'), '\u1039\u1007'], - [RegExp('\u1069', 'g'), '\u1039\u1008'], - [RegExp('(\u1073|\u1074)', 'g'), '\u1039\u1011'], - [RegExp('\u1075', 'g'), '\u1039\u1012'], - [RegExp('\u1076', 'g'), '\u1039\u1013'], - [RegExp('\u1077', 'g'), '\u1039\u1014'], - [RegExp('\u1078', 'g'), '\u1039\u1015'], - [RegExp('\u1079', 'g'), '\u1039\u1016'], - [RegExp('\u107a', 'g'), '\u1039\u1017'], - [RegExp('\u107c', 'g'), '\u1039\u1019'], - [RegExp('\u1085', 'g'), '\u1039\u101c'], - [RegExp('\u1033', 'g'), '\u102f'], - [RegExp('\u1034', 'g'), '\u1030'], - [RegExp('\u103f', 'g'), '\u1030'], - [RegExp('\u1086', 'g'), '\u103f'], - [RegExp('\u1036\u1088', 'g'), '\u1088\u1036'], - [RegExp('\u1088', 'g'), '\u103e\u102f'], - [RegExp('\u1089', 'g'), '\u103e\u1030'], - [RegExp('\u108a', 'g'), '\u103d\u103e'], - [RegExp('\u103B\u1064', 'g'), '\u1064\u103B'], - [RegExp('(\u1031)?([\u1000-\u1021])\u1064', 'g'), '\u1004\u103a\u1039$1$2'], - [ - RegExp('(\u1031)?([\u1000-\u1021])\u108b', 'g'), - '\u1004\u103a\u1039$1$2\u102d', - ], - [ - RegExp('(\u1031)?([\u1000-\u1021])\u108c', 'g'), - '\u1004\u103a\u1039$1$2\u102e', - ], - [ - RegExp('(\u1031)?([\u1000-\u1021])\u108d', 'g'), - '\u1004\u103a\u1039$1$2\u1036', - ], - [RegExp('\u108e', 'g'), '\u102d\u1036'], - [RegExp('\u108f', 'g'), '\u1014'], - [RegExp('\u1090', 'g'), '\u101b'], - [RegExp('\u1091', 'g'), '\u100f\u1039\u100d'], - [RegExp('\u1019\u102c(\u107b|\u1093)', 'g'), '\u1019\u1039\u1018\u102c'], - [RegExp('(\u107b|\u1093)', 'g'), '\u1039\u1018'], - [RegExp('(\u1094|\u1095)', 'g'), '\u1037'], - [RegExp('([\u1000-\u1021])\u1037\u1032', 'g'), '$1\u1032\u1037'], - [RegExp('\u1096', 'g'), '\u1039\u1010\u103d'], - [RegExp('\u1097', 'g'), '\u100b\u1039\u100b'], - [RegExp('\u103c([\u1000-\u1021])([\u1000-\u1021])?', 'g'), '$1\u103c$2'], - [RegExp('([\u1000-\u1021])\u103c\u103a', 'g'), '\u103c$1\u103a'], - [ - RegExp('\u1047([\u102c-\u1030\u1032\u1036-\u1038\u103d\u1038])', 'g'), - '\u101b$1', - ], - [RegExp('\u1031\u1047', 'g'), '\u1031\u101b'], - [ - RegExp( - '\u1040(\u102e|\u102f|\u102d\u102f|\u1030|\u1036|\u103d|\u103e)', - 'g' - ), - '\u101d$1', - ], - [ - // Equivalent to: - // /([^\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049])\u1040\u102b/u - RegExp( - '((?:[\0-\u103F\u104A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))\u1040\u102B', - 'g' - ), - '$1\u101d\u102b', - ], - [ - // Equivalent to: - // /([\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049])\u1040\u102b([^\u1038])/u - RegExp( - '([\u1040-\u1049])\u1040\u102B((?:[\0-\u1037\u1039-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))', - 'g' - ), - '$1\u101d\u102b$2', - ], - [RegExp('^\u1040(\u102b)', 'g'), '\u101d$1'], - [ - // Equivalent to: - // /\u1040\u102d([^\u0020]?)/u - RegExp( - '\u1040\u102D((?:[\0-\x1F!-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])?)', - 'g' - ), - '\u101d\u102d$1', - ], - [ - // Equivalent to: - // /([^\u1040-\u1049])\u1040([^\u1040-\u1049\u0020]|[\u104a\u104b])/u - RegExp( - '((?:[\0-\u103F\u104A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))\u1040((?:[\0-\x1F!-\u103F\u104A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])|[\u104A\u104B])', - 'g' - ), - '$1\u101d$2', - ], - [ - // Equivalent to: - // /([^\u1040-\u1049])\u1040([$f$n$r])/u - RegExp( - '((?:[\0-\u103F\u104A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))\u1040([$fnr])', - 'g' - ), - '$1\u101d$2', - ], - [ - // Equivalent to: - // /([^\u1040-\u1049])\u1040$/u - RegExp( - '((?:[\0-\u103F\u104A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))\u1040$', - 'g' - ), - '$1\u101d', - ], - [RegExp('\u1031([\u1000-\u1021])(\u103e)?(\u103b)?', 'g'), '$1$2$3\u1031'], - [ - RegExp('([\u1000-\u1021])\u1031([\u103b\u103c\u103d\u103e]+)', 'g'), - '$1$2\u1031', - ], - [RegExp('\u1032\u103d', 'g'), '\u103d\u1032'], - [RegExp('([\u102d\u102e])\u103b', 'g'), '\u103b$1'], - [RegExp('\u103d\u103b', 'g'), '\u103b\u103d'], - [RegExp('\u103a\u1037', 'g'), '\u1037\u103a'], - [RegExp('\u102f(\u102d|\u102e|\u1036|\u1037)\u102f', 'g'), '\u102f$1'], - [RegExp('(\u102f|\u1030)(\u102d|\u102e)', 'g'), '$2$1'], - [RegExp('(\u103e)(\u103b|\u103c)', 'g'), '$2$1'], - [RegExp('\u1025([\u1037]?[\u103a\u102c])', 'g'), '\u1009$1'], - [RegExp('\u1025\u102e', 'g'), '\u1026'], - [RegExp('\u1005\u103b', 'g'), '\u1008'], - [RegExp('\u1036(\u102f|\u1030)', 'g'), '$1\u1036'], - [RegExp('\u1031\u1037\u103e', 'g'), '\u103e\u1031\u1037'], - [RegExp('\u1031\u103e\u102c', 'g'), '\u103e\u1031\u102c'], - [RegExp('\u105a', 'g'), '\u102b\u103a'], - [RegExp('\u1031\u103b\u103e', 'g'), '\u103b\u103e\u1031'], - [RegExp('(\u102d|\u102e)(\u103d|\u103e)', 'g'), '$2$1'], - [RegExp('\u102c\u1039([\u1000-\u1021])', 'g'), '\u1039$1\u102c'], - [ - RegExp('\u103c\u1004\u103a\u1039([\u1000-\u1021])', 'g'), - '\u1004\u103a\u1039$1\u103c', - ], - [ - RegExp('\u1039\u103c\u103a\u1039([\u1000-\u1021])', 'g'), - '\u103a\u1039$1\u103c', - ], - [RegExp('\u103c\u1039([\u1000-\u1021])', 'g'), '\u1039$1\u103c'], - [RegExp('\u1036\u1039([\u1000-\u1021])', 'g'), '\u1039$1\u1036'], - [RegExp('\u1092', 'g'), '\u100b\u1039\u100c'], - [RegExp('\u104e', 'g'), '\u104e\u1004\u103a\u1038'], - [RegExp('\u1040(\u102b|\u102c|\u1036)', 'g'), '\u101d$1'], - [RegExp('\u1025\u1039', 'g'), '\u1009\u1039'], - [RegExp('([\u1000-\u1021])\u103c\u1031\u103d', 'g'), '$1\u103c\u103d\u1031'], - [ - RegExp('([\u1000-\u1021])\u103b\u1031\u103d(\u103e)?', 'g'), - '$1\u103b\u103d$2\u1031', - ], - [RegExp('([\u1000-\u1021])\u103d\u1031\u103b', 'g'), '$1\u103b\u103d\u1031'], - [RegExp('([\u1000-\u1021])\u1031(\u1039[\u1000-\u1021])', 'g'), '$1$2\u1031'], - [RegExp('\u1038\u103a', 'g'), '\u103a\u1038'], - [RegExp('\u102d\u103a|\u103a\u102d', 'g'), '\u102d'], - [RegExp('\u102d\u102f\u103a', 'g'), '\u102d\u102f'], - [RegExp('\u0020\u1037', 'g'), '\u1037'], - [RegExp('\u1037\u1036', 'g'), '\u1036\u1037'], - [RegExp(' \u1037', 'g'), '\u1037'], - [RegExp('[\u102d]+', 'g'), '\u102d'], - [RegExp('[\u103a]+', 'g'), '\u103a'], - [RegExp('[\u103d]+', 'g'), '\u103d'], - [RegExp('[\u1037]+', 'g'), '\u1037'], - [RegExp('[\u102e]+', 'g'), '\u102e'], - [RegExp('\u102d\u102e|\u102e\u102d', 'g'), '\u102e'], - [RegExp('\u102f\u102d', 'g'), '\u102d\u102f'], - [RegExp('\u1037\u1037', 'g'), '\u1037'], - [RegExp('\u1032\u1032', 'g'), '\u1032'], - [RegExp('\u1044\u1004\u103a\u1038', 'g'), '\u104E\u1004\u103a\u1038'], - [RegExp('([\u102d\u102e])\u1039([\u1000-\u1021])', 'g'), '\u1039$2$1'], - [RegExp('\u1036\u103d', 'g'), '\u103d\u1036'], -]; - -function convertZawgyiToUnicode(text: string): string { - let result = text; - for (let i = 0; i < ZG_TO_UNICODE_RULES.length; i++) { - result = result.replace( - ZG_TO_UNICODE_RULES[i][0], - ZG_TO_UNICODE_RULES[i][1] - ); - } - return result; -} - -function isZawgyiEncodedText(text: string): boolean { - const unicodeMatches = text.match( - /\u103e|\u103f|\u100a\u103a|\u1014\u103a|\u1004\u103a|\u1031\u1038|\u1031\u102c|\u103a\u1038|\u1035|[\u1050-\u1059]|^([\u1000-\u1021]\u103c|[\u1000-\u1021]\u1031)/g - ); - const zawgyiMatches = text.match( - /\u102c\u1039|\u103a\u102c|\p{Z}(\u103b|\u1031|[\u107e-\u1084])[\u1000-\u1021]|^(\u103b|\u1031|[\u107e-\u1084])[\u1000-\u1021]|[\u1000-\u1021]\u1039[^\u1000-\u1021]|\u1025\u1039|\u1039\u1038|[\u102b-\u1030\u1031\u103a\u1038](\u103b|[\u107e-\u1084])[\u1000-\u1021]|\u1036\u102f|[\u1000-\u1021]\u1039\u1031|\u1064|\u1039\p{Z}|\u102c\u1031|[\u102b-\u1030\u103a\u1038]\u1031[\u1000-\u1021]|\u1031\u1031|\u102f\u102d|(\u1039$)/g - ); - const unicodeCount = unicodeMatches == null ? 0 : unicodeMatches.length; - const zawgyiCount = zawgyiMatches == null ? 0 : zawgyiMatches.length; - // Total heuristic: Use the Unicode to Zawgyi pattern ratio. Lean towards - // false positives for now. - return 1.2 * zawgyiCount > unicodeCount; -} - -// Hard-code these since they will only ever be needed for Burmese Unicode. -const TEXT_BURMESE_UNREADABLE_LINK = - '\u1005\u102C\u101E\u102C\u1038\u1019\u1016\u1010\u103A\u101C\u102D\u102F\u1037\u1019\u101B\u1021\u1031\u102C\u1004\u103A?'; -const TEXT_BURMESE_REVERT = - '\u1019\u101C\u102F\u1015\u103A\u1010\u1031\u102C\u1037\u1015\u102B'; - -function convertRecursively(node: Node): void { - if (node.nodeType === Node.TEXT_NODE) { - if (node.parentElement != null) { - node.parentElement.setAttribute( - 'data-zg-pre-conversion', - node.textContent - ); - } - node.textContent = convertZawgyiToUnicode(node.textContent); - } - for (let i = 0; i < node.childNodes.length; i++) { - convertRecursively(node.childNodes[i]); - } -} - -function revertRecursively(node: Node): void { - if (node.nodeType === Node.TEXT_NODE) { - if (node.parentElement != null) { - const previousText = node.parentElement.getAttribute( - 'data-zg-pre-conversion' - ); - if (previousText != null) { - node.textContent = previousText; - } - } - } - for (let i = 0; i < node.childNodes.length; i++) { - revertRecursively(node.childNodes[i]); - } -} - -export function logEvent( - event: string, - fontSignals: ?BurmeseFontSignals -): void { - ZawgyiDetectionLogEvent.log(() => ({ - msite_event: event, - aforementioned_width: String(fontSignals?.aforementioned_width ?? 0), - detected_font: (fontSignals?.detected_font ?? '') as string, - ka_virama_ka_width: String(fontSignals?.ka_virama_ka_width ?? 0), - ka_width: String(fontSignals?.ka_width ?? 0), - msite_user_agent: fontSignals?.user_agent, - requested_with: fontSignals?.requested_with, - })); -} - -function convertOnClick(element: HTMLElement, link: HTMLElement) { - if (element.getAttribute('data-is-zg-converted') === 'false') { - convertRecursively(element); - element.setAttribute('data-is-zg-converted', 'true'); - link.textContent = TEXT_BURMESE_REVERT; - logEvent('uni_to_zg_convert_clicked'); - } else { - revertRecursively(element); - element.setAttribute('data-is-zg-converted', 'false'); - link.textContent = TEXT_BURMESE_UNREADABLE_LINK; - logEvent('uni_to_zg_revert_clicked'); - } -} - -function addConvertLinkToDiv(element: HTMLElement) { - if (element.hasAttribute('data-is-zg-converted')) { - // Already added link. - return; - } - if (!isZawgyiEncodedText(element.textContent)) { - return; - } - element.setAttribute('data-is-zg-converted', 'false'); - const link = DOM.create('a', {href: '#'}, TEXT_BURMESE_UNREADABLE_LINK); - link.style.display = 'inline-block'; - link.style.textAlign = 'center'; - link.style.fontSize = '0.85em'; - link.style.width = '100%'; - link.addEventListener('click', () => convertOnClick(element, link)); - element.insertBefore(link, element.firstChild); -} - -function createConversionLinks() { - const posts = document.getElementsByClassName('story_body_container'); - for (let i = 0; i < posts.length; i++) { - addConvertLinkToDiv(posts[i]); - } -} - -function initializeZawgyiToUnicodeConversion(): void { - // Create conversion links for stories already on the page. - createConversionLinks(); - // Now see if there's a feed and start listening for mutatations that might - // add new stories to it to make sure links are added for newly added stories. - const feed = document.getElementById('viewport'); - if (feed == null) { - return; - } - const observer = new MutationObserver(function () { - createConversionLinks(); - }); - observer.observe(feed, { - childList: true, - subtree: true, - }); -} - -// @ServerCallable -export function logBurmeseFontSignals(requestedWith: ?string) { - const data = gatherFontDetectionSignals(true); - data.requested_with = requestedWith == null ? '' : requestedWith; - data.user_agent = navigator.userAgent; - data.detected_font = detectFont(); - logEvent('font_detected', data); -} -// @ServerCallable -export function enableZawgyiToUnicodeConversionLinks() { - const deviceFont = detectFont(); - // Only add conversion links if device font is Unicode. - if (deviceFont !== FontCapability.UNICODE) { - return; - } - initializeZawgyiToUnicodeConversion(); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_unsupported_node_format.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_unsupported_node_format.expect.md deleted file mode 100644 index fb279783dd3d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_unsupported_node_format.expect.md +++ /dev/null @@ -1,45 +0,0 @@ - -## Input - -```javascript -// HIR Pattern: UNSUPPORTED_NODE_FORMAT (186 files, 56%) -// UnsupportedNode: TS includes type:"Import", Rust omits it -// Also: error reason text differs - -const ConfirmationCodeInput = React.lazy( - () => import('ConfirmationCodeInput.react') -) as React.ComponentType<React.ElementConfig<ConfirmationCodeInputType>>; -export const examples = [ - { - render(): React.MixedElement { - return ( - <ConfirmationCodeInput - helperText={isCompleted && `Your code entry is completed: ${value}`} - label={fbt()} - /> - ); - }, - }, -]; - -``` - - -## Error - -``` -Found 1 error: - -Todo: (BuildHIR::lowerExpression) Handle Import expressions - -error.todo-hir_unsupported_node_format.ts:6:8 - 4 | - 5 | const ConfirmationCodeInput = React.lazy( -> 6 | () => import('ConfirmationCodeInput.react') - | ^^^^^^ (BuildHIR::lowerExpression) Handle Import expressions - 7 | ) as React.ComponentType<React.ElementConfig<ConfirmationCodeInputType>>; - 8 | export const examples = [ - 9 | { -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_unsupported_node_format.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_unsupported_node_format.js deleted file mode 100644 index 2525a1699ad0..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hir_unsupported_node_format.js +++ /dev/null @@ -1,19 +0,0 @@ -// HIR Pattern: UNSUPPORTED_NODE_FORMAT (186 files, 56%) -// UnsupportedNode: TS includes type:"Import", Rust omits it -// Also: error reason text differs - -const ConfirmationCodeInput = React.lazy( - () => import('ConfirmationCodeInput.react') -) as React.ComponentType<React.ElementConfig<ConfirmationCodeInputType>>; -export const examples = [ - { - render(): React.MixedElement { - return ( - <ConfirmationCodeInput - helperText={isCompleted && `Your code entry is completed: ${value}`} - label={fbt()} - /> - ); - }, - }, -]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern2_type_to_object.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern2_type_to_object.expect.md deleted file mode 100644 index 82973d39ac7c..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern2_type_to_object.expect.md +++ /dev/null @@ -1,37 +0,0 @@ - -## Input - -```javascript -// Pattern 2: shapeId null→generated + return Type→Object{BuiltInArray} -// Component with Array.splice -// Divergence: TS has shapeId:null, return:Type(75); Rust has shapeId:"<generated_1>", return:Object{BuiltInArray} - -/** - * @flow strict-local - */ -const styles = stylex.create({ - liftCardsContent: {}, -}); -export default component LayoutRow() { - if (hidden === true) { - } - const children = []; - React.Children.forEach(); - return ( - <Layout className={joinClasses(className, cx('styles/cards/row'))}> - <LayoutColumn className={cx({})}>{children.splice(0, 1)}</LayoutColumn> - <FillColumn {...stylex.props(styles.liftCardsContent)}></FillColumn> - </Layout> - ); -} - -``` - - -## Error - -``` -Missing semicolon. (11:24) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern2_type_to_object.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern2_type_to_object.js deleted file mode 100644 index 249b8e626a89..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern2_type_to_object.js +++ /dev/null @@ -1,22 +0,0 @@ -// Pattern 2: shapeId null→generated + return Type→Object{BuiltInArray} -// Component with Array.splice -// Divergence: TS has shapeId:null, return:Type(75); Rust has shapeId:"<generated_1>", return:Object{BuiltInArray} - -/** - * @flow strict-local - */ -const styles = stylex.create({ - liftCardsContent: {}, -}); -export default component LayoutRow() { - if (hidden === true) { - } - const children = []; - React.Children.forEach(); - return ( - <Layout className={joinClasses(className, cx('styles/cards/row'))}> - <LayoutColumn className={cx({})}>{children.splice(0, 1)}</LayoutColumn> - <FillColumn {...stylex.props(styles.liftCardsContent)}></FillColumn> - </Layout> - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern3_type_to_poly.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern3_type_to_poly.expect.md deleted file mode 100644 index 85854a56ef4f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern3_type_to_poly.expect.md +++ /dev/null @@ -1,30 +0,0 @@ - -## Input - -```javascript -// Pattern 3: shapeId null→generated + return Type→Poly -// Generic function with Object.entries().reduce() -// Divergence: TS has shapeId:null, return:Type(32); Rust has shapeId:"<generated_1>", return:Poly - -/** - * @flow strict - */ -export default function flipAndAggregateObject<TValue extends string>(obj: { - +[key: TKey]: TValue, - ... -}): {} { - return Object.entries(obj).reduce((acc, [currKey, currVal]) => { - return {}; - }, {}); -} - -``` - - -## Error - -``` -Unexpected token (9:2) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern3_type_to_poly.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern3_type_to_poly.js deleted file mode 100644 index d9c828c61a9e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-pattern3_type_to_poly.js +++ /dev/null @@ -1,15 +0,0 @@ -// Pattern 3: shapeId null→generated + return Type→Poly -// Generic function with Object.entries().reduce() -// Divergence: TS has shapeId:null, return:Type(32); Rust has shapeId:"<generated_1>", return:Poly - -/** - * @flow strict - */ -export default function flipAndAggregateObject<TValue extends string>(obj: { - +[key: TKey]: TValue, - ... -}): {} { - return Object.entries(obj).reduce((acc, [currKey, currVal]) => { - return {}; - }, {}); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_float_precision.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_float_precision.expect.md deleted file mode 100644 index 1aa761535fa3..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_float_precision.expect.md +++ /dev/null @@ -1,27 +0,0 @@ - -## Input - -```javascript -// Round 2 HIR: NUMERIC_VALUE_DIFF (2 files) -// Float parsing last-digit precision: TS=1.0666666666666667, Rust=1.0666666666666669 -/** - * @flow strict-local - */ -component RealTimeIcon() renders SVGIconBase { - return ( - <SVGIconBase - aspectRatio={1.0666666666666667} - viewBox="0 0 16 15"></SVGIconBase> - ); -} - -``` - - -## Error - -``` -Missing semicolon. (6:9) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_float_precision.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_float_precision.js deleted file mode 100644 index c32eb9ee0191..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_float_precision.js +++ /dev/null @@ -1,12 +0,0 @@ -// Round 2 HIR: NUMERIC_VALUE_DIFF (2 files) -// Float parsing last-digit precision: TS=1.0666666666666667, Rust=1.0666666666666669 -/** - * @flow strict-local - */ -component RealTimeIcon() renders SVGIconBase { - return ( - <SVGIconBase - aspectRatio={1.0666666666666667} - viewBox="0 0 16 15"></SVGIconBase> - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_fn_type_component_to_other.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_fn_type_component_to_other.expect.md deleted file mode 100644 index a5c6be74ca76..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_fn_type_component_to_other.expect.md +++ /dev/null @@ -1,23 +0,0 @@ - -## Input - -```javascript -// Round 2 HIR: FN_TYPE_MISMATCH (4 files) -// TS=Component, Rust=Other — component with default param using JSX fragment -/** - * @flow strict-local - */ -export default component If( - fallback: React.Node | (() => React.Node) = <></> -) {} - -``` - - -## Error - -``` -Missing semicolon. (6:24) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_fn_type_component_to_other.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_fn_type_component_to_other.js deleted file mode 100644 index 94466eb86129..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_fn_type_component_to_other.js +++ /dev/null @@ -1,8 +0,0 @@ -// Round 2 HIR: FN_TYPE_MISMATCH (4 files) -// TS=Component, Rust=Other — component with default param using JSX fragment -/** - * @flow strict-local - */ -export default component If( - fallback: React.Node | (() => React.Node) = <></> -) {} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_id_numbering.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_id_numbering.expect.md deleted file mode 100644 index 7d4f34e9a8b5..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_id_numbering.expect.md +++ /dev/null @@ -1,29 +0,0 @@ - -## Input - -```javascript -// Round 2 HIR: ID_NUMBERING_DIFF (12 files) -// Identifier IDs diverge — DeclareContext vs LoadGlobal for component ref param -/** - * @flow strict-local - */ -export function makeComponentWithOnScroll< - TElement extends ?HTMLElement = HTMLElement, ->() { - component Wrapper(ref?: TRefFor<TElement>) { - const innerOnScroll = useCallback( - (e: SyntheticEvent<HTMLElement, Event>) => {} - ); - } -} - -``` - - -## Error - -``` -Unexpected token (7:19) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_id_numbering.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_id_numbering.js deleted file mode 100644 index 0b5b0f0ebbcf..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_id_numbering.js +++ /dev/null @@ -1,14 +0,0 @@ -// Round 2 HIR: ID_NUMBERING_DIFF (12 files) -// Identifier IDs diverge — DeclareContext vs LoadGlobal for component ref param -/** - * @flow strict-local - */ -export function makeComponentWithOnScroll< - TElement extends ?HTMLElement = HTMLElement, ->() { - component Wrapper(ref?: TRefFor<TElement>) { - const innerOnScroll = useCallback( - (e: SyntheticEvent<HTMLElement, Event>) => {} - ); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_identifier_diff.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_identifier_diff.expect.md deleted file mode 100644 index 44dbdbbf5e48..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_identifier_diff.expect.md +++ /dev/null @@ -1,36 +0,0 @@ - -## Input - -```javascript -// Round 2 HIR: IDENTIFIER_DIFF (11 files) -// Extra/different context identifiers — class components with this/setState -/** - * @flow strict-local - */ -export default function withRemountOnChange<OuterProps extends {}>( - shouldRemount: () => boolean -): () => React.ComponentType<OuterProps> { - return function withRemountOnChangeInner(WrappedComponent) { - return class Wrapper extends React.Component<OuterProps, WrapperState> { - static displayName: ?string = `withRemountOnChange(${getDisplayName()})`; - state: WrapperState = {}; - componentDidUpdate(prevProps: OuterProps, prevState: WrapperState) { - if (shouldRemount()) { - this.setState(({keyId}) => {}); - } - } - render(): React.MixedElement {} - }; - }; -} - -``` - - -## Error - -``` -Unexpected token (11:26) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_identifier_diff.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_identifier_diff.js deleted file mode 100644 index 4ce6ee503c22..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_identifier_diff.js +++ /dev/null @@ -1,21 +0,0 @@ -// Round 2 HIR: IDENTIFIER_DIFF (11 files) -// Extra/different context identifiers — class components with this/setState -/** - * @flow strict-local - */ -export default function withRemountOnChange<OuterProps extends {}>( - shouldRemount: () => boolean -): () => React.ComponentType<OuterProps> { - return function withRemountOnChangeInner(WrappedComponent) { - return class Wrapper extends React.Component<OuterProps, WrapperState> { - static displayName: ?string = `withRemountOnChange(${getDisplayName()})`; - state: WrapperState = {}; - componentDidUpdate(prevProps: OuterProps, prevState: WrapperState) { - if (shouldRemount()) { - this.setState(({keyId}) => {}); - } - } - render(): React.MixedElement {} - }; - }; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_severity_diff.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_severity_diff.expect.md deleted file mode 100644 index a1e0b46ecde3..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_severity_diff.expect.md +++ /dev/null @@ -1,31 +0,0 @@ - -## Input - -```javascript -// Round 2 HIR: SEVERITY_DIFF (1 file from OTHER) -// delete on optional chain — TS: severity Error/Syntax, Rust: severity Hint/Todo -/** - * @flow strict-local - */ -import {} from 'PreloadingTTL'; -const preloadedRequests: Map<> = new Map(); -export function execute(): Promise<{error?: APIErrorEventArgs['error'], ...}> { - if (request.params != null && !(request.params instanceof FormData)) { - delete request.params?.__entryPointPreloaded; - } - if (!consumers) { - if (APIRequestMatchingUtils.areRequestsEquivalent()) { - } - } -} - -``` - - -## Error - -``` -Type argument list cannot be empty. (7:28) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_severity_diff.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_severity_diff.js deleted file mode 100644 index 7705d7f7606a..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-round2_severity_diff.js +++ /dev/null @@ -1,16 +0,0 @@ -// Round 2 HIR: SEVERITY_DIFF (1 file from OTHER) -// delete on optional chain — TS: severity Error/Syntax, Rust: severity Hint/Todo -/** - * @flow strict-local - */ -import {} from 'PreloadingTTL'; -const preloadedRequests: Map<> = new Map(); -export function execute(): Promise<{error?: APIErrorEventArgs['error'], ...}> { - if (request.params != null && !(request.params instanceof FormData)) { - delete request.params?.__entryPointPreloaded; - } - if (!consumers) { - if (APIRequestMatchingUtils.areRequestsEquivalent()) { - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-rust-as-expression-assignment-target.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-rust-as-expression-assignment-target.expect.md deleted file mode 100644 index c814e2a24c1f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-rust-as-expression-assignment-target.expect.md +++ /dev/null @@ -1,34 +0,0 @@ - -## Input - -```javascript -function Component(props: {obj: {x: unknown}}) { - (props.obj.x as unknown as number) = 1; - return <div>{props.obj.x as number}</div>; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{obj: {x: 0}}], -}; - -``` - - -## Error - -``` -Found 1 error: - -Todo: [FindContextIdentifiers] Cannot handle Object destructuring assignment target TSAsExpression - -error.todo-rust-as-expression-assignment-target.ts:2:3 - 1 | function Component(props: {obj: {x: unknown}}) { -> 2 | (props.obj.x as unknown as number) = 1; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [FindContextIdentifiers] Cannot handle Object destructuring assignment target TSAsExpression - 3 | return <div>{props.obj.x as number}</div>; - 4 | } - 5 | -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-rust-as-expression-assignment-target.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-rust-as-expression-assignment-target.tsx deleted file mode 100644 index fdc18d5a47b4..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-rust-as-expression-assignment-target.tsx +++ /dev/null @@ -1,9 +0,0 @@ -function Component(props: {obj: {x: unknown}}) { - (props.obj.x as unknown as number) = 1; - return <div>{props.obj.x as number}</div>; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{obj: {x: 0}}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-update-expression-context-variable-via-type-annotation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-update-expression-context-variable-via-type-annotation.expect.md deleted file mode 100644 index 9bc91b752f4d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-update-expression-context-variable-via-type-annotation.expect.md +++ /dev/null @@ -1,39 +0,0 @@ - -## Input - -```javascript -// @flow @compilationMode(infer) -function Component(props: {data: Array<[string, mixed]>}) { - let id = 0; - for (const [key, value] of props.data) { - const item = { - key, - id: '' + id++, - }; - } - const getIndex = ((): ((id: string) => number) => { - return (id: string): number => 0; - })(); - return <div />; -} - -``` - - -## Error - -``` -Found 1 error: - -Todo: (BuildHIR::lowerExpression) Handle UpdateExpression to variables captured within lambdas. - - 5 | const item = { - 6 | key, -> 7 | id: '' + id++, - | ^^^^ (BuildHIR::lowerExpression) Handle UpdateExpression to variables captured within lambdas. - 8 | }; - 9 | } - 10 | const getIndex = ((): ((id: string) => number) => { -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-update-expression-context-variable-via-type-annotation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-update-expression-context-variable-via-type-annotation.js deleted file mode 100644 index b5616237363f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-update-expression-context-variable-via-type-annotation.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow @compilationMode(infer) -function Component(props: {data: Array<[string, mixed]>}) { - let id = 0; - for (const [key, value] of props.data) { - const item = { - key, - id: '' + id++, - }; - } - const getIndex = ((): ((id: string) => number) => { - return (id: string): number => 0; - })(); - return <div />; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.typescript-this-param-in-compiled-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.typescript-this-param-in-compiled-function.expect.md deleted file mode 100644 index 6df550f6ced1..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.typescript-this-param-in-compiled-function.expect.md +++ /dev/null @@ -1,31 +0,0 @@ - -## Input - -```javascript -export function Decorate() { - return function ( - _target: object, - _key: string, - descriptor: PropertyDescriptor, - ) { - const original = descriptor.value; - descriptor.value = function (this: unknown) { - return original.apply(this, []); - }; - }; -} - -``` - - -## Error - -``` -Found 1 error: - -Error: Expected a non-reserved identifier name - -`this` is a reserved word in JavaScript and cannot be used as an identifier name. -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.typescript-this-param-in-compiled-function.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.typescript-this-param-in-compiled-function.ts deleted file mode 100644 index fbcf88966465..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.typescript-this-param-in-compiled-function.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function Decorate() { - return function ( - _target: object, - _key: string, - descriptor: PropertyDescriptor, - ) { - const original = descriptor.value; - descriptor.value = function (this: unknown) { - return original.apply(this, []); - }; - }; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt-scope-range-identifier-sync.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt-scope-range-identifier-sync.expect.md deleted file mode 100644 index 24410646b5c8..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt-scope-range-identifier-sync.expect.md +++ /dev/null @@ -1,200 +0,0 @@ - -## Input - -```javascript -import fbt from 'fbt'; - -// Minimized from MDCAIGlassesPDPRxOOSBadge.react.js -// Tests that expand_fbt_scope_range syncs identifier mutable_ranges -// after expanding a scope's range (Rust value semantics vs TS shared reference). - -function Component({selectedColor, selectedProduct, rxData}) { - const rxIds = []; - rxData.forEach(data => data.non_rx_id != null && rxIds.push(data.non_rx_id)); - - const otherSize = selectedColor?.matchingProducts - ?.filter( - product => - product.id != null && - product.tags?.get('bridge') === selectedProduct?.tags?.get('bridge') - ) - .find( - product => - product.tags?.get('size') !== selectedProduct?.tags?.get('size') - ); - - const otherFrameSize = otherSize?.tags?.get('size'); - if ( - otherSize?.id != null && - otherFrameSize != null && - rxIds.includes(otherSize.id) - ) { - return ( - <div> - {fbt( - `Only available in ${fbt.param('frame size', otherFrameSize)} size`, - 'Badge text' - )} - </div> - ); - } - - if (rxData.futureData.includes(selectedProduct?.id)) { - return <div>{fbt('Out of stock', 'Badge text')}</div>; - } - - return <div>{fbt('Not available', 'Badge text')}</div>; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [ - { - selectedColor: { - matchingProducts: [ - { - id: '1', - tags: new Map([ - ['bridge', 'A'], - ['size', 'S'], - ]), - }, - ], - }, - selectedProduct: { - id: '1', - tags: new Map([ - ['bridge', 'A'], - ['size', 'M'], - ]), - }, - rxData: Object.assign([{non_rx_id: '1'}], {futureData: []}), - }, - ], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -import fbt from "fbt"; - -// Minimized from MDCAIGlassesPDPRxOOSBadge.react.js -// Tests that expand_fbt_scope_range syncs identifier mutable_ranges -// after expanding a scope's range (Rust value semantics vs TS shared reference). - -function Component(t0) { - const $ = _c(8); - const { selectedColor, selectedProduct, rxData } = t0; - let rxIds; - if ($[0] !== rxData) { - rxIds = []; - rxData.forEach( - (data) => data.non_rx_id != null && rxIds.push(data.non_rx_id), - ); - $[0] = rxData; - $[1] = rxIds; - } else { - rxIds = $[1]; - } - let t1; - if ( - $[2] !== rxIds || - $[3] !== selectedColor?.matchingProducts || - $[4] !== selectedProduct?.tags - ) { - t1 = Symbol.for("react.early_return_sentinel"); - bb0: { - const otherSize = selectedColor?.matchingProducts - ?.filter( - (product) => - product.id != null && - product.tags?.get("bridge") === - selectedProduct?.tags?.get("bridge"), - ) - .find( - (product_0) => - product_0.tags?.get("size") !== selectedProduct?.tags?.get("size"), - ); - const otherFrameSize = otherSize?.tags?.get("size"); - if ( - otherSize?.id != null && - otherFrameSize != null && - rxIds.includes(otherSize.id) - ) { - t1 = ( - <div> - {fbt._( - "Only available in {frame size} size", - [fbt._param("frame size", otherFrameSize)], - { hk: "4sIu3a" }, - )} - </div> - ); - break bb0; - } - } - $[2] = rxIds; - $[3] = selectedColor?.matchingProducts; - $[4] = selectedProduct?.tags; - $[5] = t1; - } else { - t1 = $[5]; - } - if (t1 !== Symbol.for("react.early_return_sentinel")) { - return t1; - } - - if (rxData.futureData.includes(selectedProduct?.id)) { - let t2; - if ($[6] === Symbol.for("react.memo_cache_sentinel")) { - t2 = <div>{fbt._("Out of stock", null, { hk: "M2wQM" })}</div>; - $[6] = t2; - } else { - t2 = $[6]; - } - return t2; - } - let t2; - if ($[7] === Symbol.for("react.memo_cache_sentinel")) { - t2 = <div>{fbt._("Not available", null, { hk: "43mHFe" })}</div>; - $[7] = t2; - } else { - t2 = $[7]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [ - { - selectedColor: { - matchingProducts: [ - { - id: "1", - tags: new Map([ - ["bridge", "A"], - ["size", "S"], - ]), - }, - ], - }, - selectedProduct: { - id: "1", - tags: new Map([ - ["bridge", "A"], - ["size", "M"], - ]), - }, - rxData: Object.assign([{ non_rx_id: "1" }], { futureData: [] }), - }, - ], -}; - -``` - -### Eval output -(kind: ok) <div>Only available in S size</div> \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt-scope-range-identifier-sync.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt-scope-range-identifier-sync.js deleted file mode 100644 index 680cc375ab3c..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt-scope-range-identifier-sync.js +++ /dev/null @@ -1,70 +0,0 @@ -import fbt from 'fbt'; - -// Minimized from MDCAIGlassesPDPRxOOSBadge.react.js -// Tests that expand_fbt_scope_range syncs identifier mutable_ranges -// after expanding a scope's range (Rust value semantics vs TS shared reference). - -function Component({selectedColor, selectedProduct, rxData}) { - const rxIds = []; - rxData.forEach(data => data.non_rx_id != null && rxIds.push(data.non_rx_id)); - - const otherSize = selectedColor?.matchingProducts - ?.filter( - product => - product.id != null && - product.tags?.get('bridge') === selectedProduct?.tags?.get('bridge') - ) - .find( - product => - product.tags?.get('size') !== selectedProduct?.tags?.get('size') - ); - - const otherFrameSize = otherSize?.tags?.get('size'); - if ( - otherSize?.id != null && - otherFrameSize != null && - rxIds.includes(otherSize.id) - ) { - return ( - <div> - {fbt( - `Only available in ${fbt.param('frame size', otherFrameSize)} size`, - 'Badge text' - )} - </div> - ); - } - - if (rxData.futureData.includes(selectedProduct?.id)) { - return <div>{fbt('Out of stock', 'Badge text')}</div>; - } - - return <div>{fbt('Not available', 'Badge text')}</div>; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [ - { - selectedColor: { - matchingProducts: [ - { - id: '1', - tags: new Map([ - ['bridge', 'A'], - ['size', 'S'], - ]), - }, - ], - }, - selectedProduct: { - id: '1', - tags: new Map([ - ['bridge', 'A'], - ['size', 'M'], - ]), - }, - rxData: Object.assign([{non_rx_id: '1'}], {futureData: []}), - }, - ], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-plural-in-expression-container.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-plural-in-expression-container.expect.md deleted file mode 100644 index 918245be2a11..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-plural-in-expression-container.expect.md +++ /dev/null @@ -1,56 +0,0 @@ - -## Input - -```javascript -// @compilationMode(infer) -function Component(props) { - return ( - <fbt desc="test"> - <fbt:plural count={props.count} many="items" showCount="yes"> - item - </fbt:plural> - <fbt:param name="nested"> - {props.showAlt ? ( - <fbt desc="nested"> - <fbt:plural count={props.altCount} many="things"> - thing - </fbt:plural> - </fbt> - ) : null} - </fbt:param> - </fbt> - ); -} - -``` - - -## Error - -``` -Found 1 error: - -Todo: Support duplicate fbt tags - -Support `<fbt>` tags with multiple `<fbt:plural>` values. - -error.todo-fbt-plural-in-expression-container.ts:5:7 - 3 | return ( - 4 | <fbt desc="test"> -> 5 | <fbt:plural count={props.count} many="items" showCount="yes"> - | ^^^^^^^^^^ Multiple `<fbt:plural>` tags found - 6 | item - 7 | </fbt:plural> - 8 | <fbt:param name="nested"> - -error.todo-fbt-plural-in-expression-container.ts:11:13 - 9 | {props.showAlt ? ( - 10 | <fbt desc="nested"> -> 11 | <fbt:plural count={props.altCount} many="things"> - | ^^^^^^^^^^ Multiple `<fbt:plural>` tags found - 12 | thing - 13 | </fbt:plural> - 14 | </fbt> -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-plural-in-expression-container.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-plural-in-expression-container.js deleted file mode 100644 index 843292c7996c..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-plural-in-expression-container.js +++ /dev/null @@ -1,19 +0,0 @@ -// @compilationMode(infer) -function Component(props) { - return ( - <fbt desc="test"> - <fbt:plural count={props.count} many="items" showCount="yes"> - item - </fbt:plural> - <fbt:param name="nested"> - {props.showAlt ? ( - <fbt desc="nested"> - <fbt:plural count={props.altCount} many="things"> - thing - </fbt:plural> - </fbt> - ) : null} - </fbt:param> - </fbt> - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-locally-require-fbt.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-locally-require-fbt.expect.md index c115fdd7fc2e..2847ad9d5a29 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-locally-require-fbt.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-locally-require-fbt.expect.md @@ -16,17 +16,15 @@ function Component(props) { ``` Found 1 error: -Todo: Support local variables named `fbt` +Invariant: <fbt> tags should be module-level imports -Local variables named `fbt` may conflict with the fbt plugin and are not yet supported. - -error.todo-locally-require-fbt.ts:2:8 - 1 | function Component(props) { -> 2 | const fbt = require('fbt'); - | ^^^ Support local variables named `fbt` +error.todo-locally-require-fbt.ts:4:10 + 2 | const fbt = require('fbt'); 3 | - 4 | return <fbt desc="Description">{'Text'}</fbt>; +> 4 | return <fbt desc="Description">{'Text'}</fbt>; + | ^^^ <fbt> tags should be module-level imports 5 | } + 6 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/flow-reserved-word-as-identifier.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/flow-reserved-word-as-identifier.flow.expect.md deleted file mode 100644 index c94369bd09a9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/flow-reserved-word-as-identifier.flow.expect.md +++ /dev/null @@ -1,57 +0,0 @@ - -## Input - -```javascript -// @flow @compilationMode:"infer" - -function Foo(props: {items: Array<{interface: string}>}) { - const keys = props.items.map(x => x.interface); - return <div>{keys.join(',')}</div>; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{items: [{interface: 'eth0'}, {interface: 'eth1'}]}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; - -function Foo(props) { - const $ = _c(4); - let t0; - if ($[0] !== props.items) { - const keys = props.items.map(_temp); - t0 = keys.join(","); - $[0] = props.items; - $[1] = t0; - } else { - t0 = $[1]; - } - let t1; - if ($[2] !== t0) { - t1 = <div>{t0}</div>; - $[2] = t0; - $[3] = t1; - } else { - t1 = $[3]; - } - return t1; -} -function _temp(x) { - return x.interface; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ items: [{ interface: "eth0" }, { interface: "eth1" }] }], -}; - -``` - -### Eval output -(kind: ok) <div>eth0,eth1</div> \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/flow-reserved-word-as-identifier.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/flow-reserved-word-as-identifier.flow.js deleted file mode 100644 index 91fd656d7e75..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/flow-reserved-word-as-identifier.flow.js +++ /dev/null @@ -1,11 +0,0 @@ -// @flow @compilationMode:"infer" - -function Foo(props: {items: Array<{interface: string}>}) { - const keys = props.items.map(x => x.interface); - return <div>{keys.join(',')}</div>; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{items: [{interface: 'eth0'}, {interface: 'eth1'}]}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fn-name-no-leak-to-nested-arrow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fn-name-no-leak-to-nested-arrow.expect.md deleted file mode 100644 index 245018acd949..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fn-name-no-leak-to-nested-arrow.expect.md +++ /dev/null @@ -1,40 +0,0 @@ - -## Input - -```javascript -// @loggerTestOnly @outputMode:"lint" -function Component() { - return null; -} - -export const ENTRYPOINT = { - fn: Component, - params: [{onChange: () => {}}], -}; - -``` - -## Code - -```javascript -// @loggerTestOnly @outputMode:"lint" -function Component() { - return null; -} - -export const ENTRYPOINT = { - fn: Component, - params: [{ onChange: () => {} }], -}; - -``` - -## Logs - -``` -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":38},"end":{"line":4,"column":1,"index":77},"filename":"fn-name-no-leak-to-nested-arrow.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":8,"column":22,"index":146},"end":{"line":8,"column":30,"index":154},"filename":"fn-name-no-leak-to-nested-arrow.ts"},"fnName":null,"memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fn-name-no-leak-to-nested-arrow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fn-name-no-leak-to-nested-arrow.js deleted file mode 100644 index 5fc4dc2c51b0..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fn-name-no-leak-to-nested-arrow.js +++ /dev/null @@ -1,9 +0,0 @@ -// @loggerTestOnly @outputMode:"lint" -function Component() { - return null; -} - -export const ENTRYPOINT = { - fn: Component, - params: [{onChange: () => {}}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-decl-shadowed-by-inner-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-decl-shadowed-by-inner-const.expect.md deleted file mode 100644 index c33d84c15bc7..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-decl-shadowed-by-inner-const.expect.md +++ /dev/null @@ -1,61 +0,0 @@ - -## Input - -```javascript -function Component(props) { - const ref = useRef(null); - useEffect(() => { - const el = ref.current; - if (el) { - init(el, props.data); - } - function init(el, data) { - const init = makeInit(data); - init.start(); - } - }); - return <div ref={ref} />; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component(props) { - const $ = _c(3); - const ref = useRef(null); - let t0; - if ($[0] !== props.data) { - t0 = () => { - const el = ref.current; - if (el) { - init(el, props.data); - } - - function init(el_0, data) { - const init_0 = makeInit(data); - init_0.start(); - } - }; - $[0] = props.data; - $[1] = t0; - } else { - t0 = $[1]; - } - useEffect(t0); - let t1; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t1 = <div ref={ref} />; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-decl-shadowed-by-inner-const.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-decl-shadowed-by-inner-const.js deleted file mode 100644 index 4f4fdf940a93..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-decl-shadowed-by-inner-const.js +++ /dev/null @@ -1,14 +0,0 @@ -function Component(props) { - const ref = useRef(null); - useEffect(() => { - const el = ref.current; - if (el) { - init(el, props.data); - } - function init(el, data) { - const init = makeInit(data); - init.start(); - } - }); - return <div ref={ref} />; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md index 8444e0120996..535f98e574f3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -58,7 +58,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":255},"end":{"line":16,"column":1,"index":482},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"category":"PreserveManualMemo","reason":"Existing memoization could not be preserved","description":"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. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":337},"end":{"line":9,"column":52,"index":358},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}} +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":255},"end":{"line":16,"column":1,"index":482},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"category":"PreserveManualMemo","reason":"Existing memoization could not be preserved","description":"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. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":337},"end":{"line":9,"column":52,"index":358},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md index 2a7fc94edbd8..4650588cc47f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"category":"Gating","reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"Error","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}} +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"category":"Gating","reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/nested-function-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/nested-function-gating.expect.md deleted file mode 100644 index ea7b5e0d1226..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/nested-function-gating.expect.md +++ /dev/null @@ -1,43 +0,0 @@ - -## Input - -```javascript -// @gating @compilationMode:"annotation" -if (globalThis.__DEV__) { - function useFoo() { - 'use memo'; - return [1, 2, 3]; - } -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" -if (globalThis.__DEV__) { - const useFoo = isForgetEnabled_Fixtures() - ? function useFoo() { - "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = [1, 2, 3]; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; - } - : function useFoo() { - "use memo"; - return [1, 2, 3]; - }; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/nested-function-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/nested-function-gating.js deleted file mode 100644 index 8f57045a4bb3..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/nested-function-gating.js +++ /dev/null @@ -1,7 +0,0 @@ -// @gating @compilationMode:"annotation" -if (globalThis.__DEV__) { - function useFoo() { - 'use memo'; - return [1, 2, 3]; - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_id_numbering.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_id_numbering.expect.md deleted file mode 100644 index 97d2ea217f32..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_id_numbering.expect.md +++ /dev/null @@ -1,49 +0,0 @@ - -## Input - -```javascript -// HIR Pattern: ID_NUMBERING_DIFF (14 files, 4%) -// Identifier IDs diverge (after normalization), with instruction kind changes -// Root cause: async function + try/catch + setTimeout scoping - -export default function withRetries<T>(): Promise<T> { - return new Promise((resolve, reject) => { - async function exec(retries: number) { - try { - resolve(await fn()); - } catch (error: unknown) { - if (retries > 0) { - setTimeout(() => {}, timeoutMs); - reject(error); - } - } - } - }); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // HIR Pattern: ID_NUMBERING_DIFF (14 files, 4%) -// Identifier IDs diverge (after normalization), with instruction kind changes -// Root cause: async function + try/catch + setTimeout scoping - -export default function withRetries() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = new Promise(_temp); - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp(resolve, reject) {} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_id_numbering.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_id_numbering.js deleted file mode 100644 index 99c0a0fc3468..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_id_numbering.js +++ /dev/null @@ -1,18 +0,0 @@ -// HIR Pattern: ID_NUMBERING_DIFF (14 files, 4%) -// Identifier IDs diverge (after normalization), with instruction kind changes -// Root cause: async function + try/catch + setTimeout scoping - -export default function withRetries<T>(): Promise<T> { - return new Promise((resolve, reject) => { - async function exec(retries: number) { - try { - resolve(await fn()); - } catch (error: unknown) { - if (retries > 0) { - setTimeout(() => {}, timeoutMs); - reject(error); - } - } - } - }); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_loc_diff.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_loc_diff.expect.md deleted file mode 100644 index bf5fcdb36141..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_loc_diff.expect.md +++ /dev/null @@ -1,39 +0,0 @@ - -## Input - -```javascript -// HIR Pattern: LOC_DIFF (45 files, 13%) -// Source locations differ + instruction kind differs (LoadGlobal vs DeclareContext) -// Root cause: for-in loop variable scoping handled differently - -window.ClientLogger = (function () { - function init(config: {}): void {} - function getURLFromQueryParams(): string { - for (var key in queryParams) { - if (value != null) { - if (Array.isArray(value)) { - for (let i = 0; i < array.length; i++) { - paramParts.push(key + '[]=' + encodeURIComponent(array[i])); - } - Object.keys(value).forEach(object_key => {}); - } - } - } - } -})(); - -``` - -## Code - -```javascript -// HIR Pattern: LOC_DIFF (45 files, 13%) -// Source locations differ + instruction kind differs (LoadGlobal vs DeclareContext) -// Root cause: for-in loop variable scoping handled differently - -window.ClientLogger = (function () {})(); - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_loc_diff.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_loc_diff.js deleted file mode 100644 index 28bc207a19c9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hir_loc_diff.js +++ /dev/null @@ -1,19 +0,0 @@ -// HIR Pattern: LOC_DIFF (45 files, 13%) -// Source locations differ + instruction kind differs (LoadGlobal vs DeclareContext) -// Root cause: for-in loop variable scoping handled differently - -window.ClientLogger = (function () { - function init(config: {}): void {} - function getURLFromQueryParams(): string { - for (var key in queryParams) { - if (value != null) { - if (Array.isArray(value)) { - for (let i = 0; i < array.length; i++) { - paramParts.push(key + '[]=' + encodeURIComponent(array[i])); - } - Object.keys(value).forEach(object_key => {}); - } - } - } - } -})(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisted-const-in-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisted-const-in-value-block.expect.md deleted file mode 100644 index 4d0db0549b0e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisted-const-in-value-block.expect.md +++ /dev/null @@ -1,78 +0,0 @@ - -## Input - -```javascript -// When a const variable is hoisted (used before declaration in the source), -// the lowering emits a DeclareContext with HoistedConst kind. -// PruneHoistedContexts removes these from top-level blocks, but if the -// DeclareContext ends up inside a SequenceExpression (value block), the -// visitor uses visitInstruction (not transformInstruction) and can't remove it. -// Codegen must convert hoisted kinds to their non-hoisted equivalents. - -function Component({cond, items}) { - const result = cond ? foo(items) : null; - return result; -} - -function foo(items) { - return items.map(x => x.id); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{cond: true, items: [{id: 1}]}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // When a const variable is hoisted (used before declaration in the source), -// the lowering emits a DeclareContext with HoistedConst kind. -// PruneHoistedContexts removes these from top-level blocks, but if the -// DeclareContext ends up inside a SequenceExpression (value block), the -// visitor uses visitInstruction (not transformInstruction) and can't remove it. -// Codegen must convert hoisted kinds to their non-hoisted equivalents. - -function Component(t0) { - const $ = _c(3); - const { cond, items } = t0; - let t1; - if ($[0] !== cond || $[1] !== items) { - t1 = cond ? foo(items) : null; - $[0] = cond; - $[1] = items; - $[2] = t1; - } else { - t1 = $[2]; - } - const result = t1; - return result; -} - -function foo(items) { - const $ = _c(2); - let t0; - if ($[0] !== items) { - t0 = items.map(_temp); - $[0] = items; - $[1] = t0; - } else { - t0 = $[1]; - } - return t0; -} -function _temp(x) { - return x.id; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ cond: true, items: [{ id: 1 }] }], -}; - -``` - -### Eval output -(kind: ok) [1] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisted-const-in-value-block.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisted-const-in-value-block.js deleted file mode 100644 index c14c291e2a3b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisted-const-in-value-block.js +++ /dev/null @@ -1,20 +0,0 @@ -// When a const variable is hoisted (used before declaration in the source), -// the lowering emits a DeclareContext with HoistedConst kind. -// PruneHoistedContexts removes these from top-level blocks, but if the -// DeclareContext ends up inside a SequenceExpression (value block), the -// visitor uses visitInstruction (not transformInstruction) and can't remove it. -// Codegen must convert hoisted kinds to their non-hoisted equivalents. - -function Component({cond, items}) { - const result = cond ? foo(items) : null; - return result; -} - -function foo(items) { - return items.map(x => x.id); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{cond: true, items: [{id: 1}]}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-skip-type-alias-declarations.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-skip-type-alias-declarations.expect.md deleted file mode 100644 index 114923a07f93..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-skip-type-alias-declarations.expect.md +++ /dev/null @@ -1,51 +0,0 @@ - -## Input - -```javascript -// @flow -type Item = {id: string, name: string}; - -function Component({items}: {items: Array<Item>}) { - const onClick = () => { - const mapped: Array<Item> = items.map(item => ({ - id: item.id, - name: item.name.toUpperCase(), - })); - submit(mapped); - }; - return <Button onClick={onClick} />; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -type Item = { id: string, name: string }; - -function Component(t0) { - const $ = _c(2); - const { items } = t0; - let t1; - if ($[0] !== items) { - const onClick = () => { - const mapped = items.map(_temp); - submit(mapped); - }; - t1 = <Button onClick={onClick} />; - $[0] = items; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} -function _temp(item) { - return { id: item.id, name: item.name.toUpperCase() }; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-skip-type-alias-declarations.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-skip-type-alias-declarations.js deleted file mode 100644 index 7f9f1cb1d16d..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-skip-type-alias-declarations.js +++ /dev/null @@ -1,13 +0,0 @@ -// @flow -type Item = {id: string, name: string}; - -function Component({items}: {items: Array<Item>}) { - const onClick = () => { - const mapped: Array<Item> = items.map(item => ({ - id: item.id, - name: item.name.toUpperCase(), - })); - submit(mapped); - }; - return <Button onClick={onClick} />; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md index fd7396901a9e..aec4231c8dd4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md @@ -24,6 +24,7 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; // @customMacros:"idx" function Component(props) { + var _ref2; const $ = _c(4); let t0; if ($[0] !== props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer_component_name_from_named_expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer_component_name_from_named_expr.expect.md deleted file mode 100644 index 444c9e8208f7..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer_component_name_from_named_expr.expect.md +++ /dev/null @@ -1,33 +0,0 @@ - -## Input - -```javascript -const MyComponent = function helper({x}) { - return <div>{x * 2}</div>; -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -const MyComponent = function helper(t0) { - const $ = _c(2); - const { x } = t0; - const t1 = x * 2; - let t2; - if ($[0] !== t1) { - t2 = <div>{t1}</div>; - $[0] = t1; - $[1] = t2; - } else { - t2 = $[1]; - } - return t2; -}; - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer_component_name_from_named_expr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer_component_name_from_named_expr.js deleted file mode 100644 index fb5299bb3746..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer_component_name_from_named_expr.js +++ /dev/null @@ -1,3 +0,0 @@ -const MyComponent = function helper({x}) { - return <div>{x * 2}</div>; -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-function-param-name-not-scoped.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-function-param-name-not-scoped.expect.md deleted file mode 100644 index 950503f77ed6..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-function-param-name-not-scoped.expect.md +++ /dev/null @@ -1,47 +0,0 @@ - -## Input - -```javascript -function Component({items}) { - const mapped = items.map(item => { - return {id: item.id, name: item.name}; - }); - return <List items={mapped} />; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component(t0) { - const $ = _c(4); - const { items } = t0; - let t1; - if ($[0] !== items) { - t1 = items.map(_temp); - $[0] = items; - $[1] = t1; - } else { - t1 = $[1]; - } - const mapped = t1; - let t2; - if ($[2] !== mapped) { - t2 = <List items={mapped} />; - $[2] = mapped; - $[3] = t2; - } else { - t2 = $[3]; - } - return t2; -} -function _temp(item) { - return { id: item.id, name: item.name }; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-function-param-name-not-scoped.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-function-param-name-not-scoped.js deleted file mode 100644 index ef9edf7f0d70..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-function-param-name-not-scoped.js +++ /dev/null @@ -1,6 +0,0 @@ -function Component({items}) { - const mapped = items.map(item => { - return {id: item.id, name: item.name}; - }); - return <List items={mapped} />; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md index ac296964c178..6ac06c1df23c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md @@ -48,7 +48,7 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"ErrorBoundaries","reason":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. 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)","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":11,"column":11,"index":241},"end":{"line":11,"column":32,"index":262},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"ErrorBoundaries","reason":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. 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)","details":[{"kind":"error","loc":{"start":{"line":11,"column":11,"index":241},"end":{"line":11,"column":32,"index":262},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":110},"end":{"line":17,"column":1,"index":317},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md index bca0c666cbc9..1e08cb24a663 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md @@ -34,7 +34,7 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"ErrorBoundaries","reason":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. 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)","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":5,"column":9,"index":123},"end":{"line":5,"column":16,"index":130},"filename":"invalid-jsx-in-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"ErrorBoundaries","reason":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. 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)","details":[{"kind":"error","loc":{"start":{"line":5,"column":9,"index":123},"end":{"line":5,"column":16,"index":130},"filename":"invalid-jsx-in-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":68},"end":{"line":10,"column":1,"index":179},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.expect.md index 12fd9a264d4c..b7f823e46d4a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-namespace.expect.md @@ -34,7 +34,7 @@ function Component() { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":200},"end":{"line":7,"column":12,"index":208},"filename":"invalid-setState-in-useEffect-namespace.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":200},"end":{"line":7,"column":12,"index":208},"filename":"invalid-setState-in-useEffect-namespace.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":100},"end":{"line":10,"column":1,"index":245},"filename":"invalid-setState-in-useEffect-namespace.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":1,"prunedMemoValues":1} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-new-expression-default-param.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-new-expression-default-param.expect.md index 8eb833f6bb84..5f2f3619b88f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-new-expression-default-param.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-new-expression-default-param.expect.md @@ -36,7 +36,7 @@ function Component({ value = new Number() }) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":8,"column":4,"index":313},"end":{"line":8,"column":12,"index":321},"filename":"invalid-setState-in-useEffect-new-expression-default-param.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":8,"column":4,"index":313},"end":{"line":8,"column":12,"index":321},"filename":"invalid-setState-in-useEffect-new-expression-default-param.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":203},"end":{"line":11,"column":1,"index":358},"filename":"invalid-setState-in-useEffect-new-expression-default-param.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":1,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index 27388791ebe4..5cd44a9c851e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -46,7 +46,7 @@ function Component() { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":284},"end":{"line":13,"column":5,"index":285},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":284},"end":{"line":13,"column":5,"index":285},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":16,"column":1,"index":312},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-via-useEffectEvent.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-via-useEffectEvent.expect.md index ac64b57cf7c6..144cb7a52289 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-via-useEffectEvent.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-via-useEffectEvent.expect.md @@ -40,7 +40,7 @@ function Component() { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":10,"column":4,"index":286},"end":{"line":10,"column":15,"index":297},"filename":"invalid-setState-in-useEffect-via-useEffectEvent.ts","identifierName":"effectEvent"},"message":"Avoid calling setState() directly within an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":10,"column":4,"index":286},"end":{"line":10,"column":15,"index":297},"filename":"invalid-setState-in-useEffect-via-useEffectEvent.ts","identifierName":"effectEvent"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":127},"end":{"line":13,"column":1,"index":328},"filename":"invalid-setState-in-useEffect-via-useEffectEvent.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":3,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index c91bfaf89fc0..5022b5517188 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -34,7 +34,7 @@ function Component() { ## Logs ``` -{"kind":"CompileError","detail":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":199},"end":{"line":7,"column":12,"index":207},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":199},"end":{"line":7,"column":12,"index":207},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":10,"column":1,"index":244},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-unused-usememo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-unused-usememo.expect.md index 5cc5470e5f47..a2aba4c7b916 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-unused-usememo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-unused-usememo.expect.md @@ -33,7 +33,7 @@ function Component() { ## Logs ``` -{"kind":"CompileError","detail":{"category":"VoidUseMemo","reason":"useMemo() result is unused","description":"This useMemo() value is unused. useMemo() is for computing and caching values, not for arbitrary side effects","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":3,"column":2,"index":67},"end":{"line":3,"column":9,"index":74},"filename":"invalid-unused-usememo.ts","identifierName":"useMemo"},"message":"useMemo() result is unused"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"VoidUseMemo","reason":"useMemo() result is unused","description":"This useMemo() value is unused. useMemo() is for computing and caching values, not for arbitrary side effects","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":3,"column":2,"index":67},"end":{"line":3,"column":9,"index":74},"filename":"invalid-unused-usememo.ts","identifierName":"useMemo"},"message":"useMemo() result is unused"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":42},"end":{"line":7,"column":1,"index":127},"filename":"invalid-unused-usememo.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-useMemo-no-return-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-useMemo-no-return-value.expect.md index 6dd2268d5e6d..24e62dad2be7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-useMemo-no-return-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-useMemo-no-return-value.expect.md @@ -50,8 +50,8 @@ function Component() { ## Logs ``` -{"kind":"CompileError","detail":{"category":"VoidUseMemo","reason":"useMemo() callbacks must return a value","description":"This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":3,"column":24,"index":89},"end":{"line":5,"column":3,"index":130},"filename":"invalid-useMemo-no-return-value.ts"},"message":"useMemo() callbacks must return a value"}]},"fnLoc":null} -{"kind":"CompileError","detail":{"category":"VoidUseMemo","reason":"useMemo() callbacks must return a value","description":"This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":6,"column":31,"index":168},"end":{"line":8,"column":3,"index":209},"filename":"invalid-useMemo-no-return-value.ts"},"message":"useMemo() callbacks must return a value"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"VoidUseMemo","reason":"useMemo() callbacks must return a value","description":"This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":3,"column":24,"index":89},"end":{"line":5,"column":3,"index":130},"filename":"invalid-useMemo-no-return-value.ts"},"message":"useMemo() callbacks must return a value"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"VoidUseMemo","reason":"useMemo() callbacks must return a value","description":"This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":6,"column":31,"index":168},"end":{"line":8,"column":3,"index":209},"filename":"invalid-useMemo-no-return-value.ts"},"message":"useMemo() callbacks must return a value"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":42},"end":{"line":15,"column":1,"index":283},"filename":"invalid-useMemo-no-return-value.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-useMemo-return-empty.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-useMemo-return-empty.expect.md index 4c59f7490f75..44e70351691b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-useMemo-return-empty.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-useMemo-return-empty.expect.md @@ -25,7 +25,7 @@ function component(a) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"VoidUseMemo","reason":"useMemo() callbacks must return a value","description":"This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":3,"column":18,"index":110},"end":{"line":5,"column":3,"index":136},"filename":"invalid-useMemo-return-empty.ts"},"message":"useMemo() callbacks must return a value"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"VoidUseMemo","reason":"useMemo() callbacks must return a value","description":"This useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":3,"column":18,"index":110},"end":{"line":5,"column":3,"index":136},"filename":"invalid-useMemo-return-empty.ts"},"message":"useMemo() callbacks must return a value"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":68},"end":{"line":7,"column":1,"index":156},"filename":"invalid-useMemo-return-empty.ts"},"fnName":"component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":1,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lone-surrogate-string-values.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lone-surrogate-string-values.expect.md deleted file mode 100644 index da5f9e68ddb6..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lone-surrogate-string-values.expect.md +++ /dev/null @@ -1,30 +0,0 @@ - -## Input - -```javascript -function Component() { - return <Emoji codepoints={['\uD83E', '\uDD21']} size={16} />; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = <Emoji codepoints={["\uD83E", "\uDD21"]} size={16} />; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lone-surrogate-string-values.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lone-surrogate-string-values.js deleted file mode 100644 index e5b499ec4c84..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lone-surrogate-string-values.js +++ /dev/null @@ -1,3 +0,0 @@ -function Component() { - return <Emoji codepoints={['\uD83E', '\uDD21']} size={16} />; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/many-scopes-no-stack-overflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/many-scopes-no-stack-overflow.expect.md deleted file mode 100644 index 65c54b3cc7fc..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/many-scopes-no-stack-overflow.expect.md +++ /dev/null @@ -1,2444 +0,0 @@ - -## Input - -```javascript -// Test that the compiler handles components with many sequential reactive scopes. -// This exercises the iterative (non-recursive) fallthrough traversal in -// BuildReactiveFunction. Previously, each scope's fallthrough was visited via -// a recursive call, causing stack overflow (SIGSEGV) on inputs with hundreds -// of scopes. - -function ManyScopes(props) { - const t0 = props.items[0]; - const t1 = props.items[1]; - const t2 = props.items[2]; - const t3 = props.items[3]; - const t4 = props.items[4]; - const t5 = props.items[5]; - const t6 = props.items[6]; - const t7 = props.items[7]; - const t8 = props.items[8]; - const t9 = props.items[9]; - const t10 = props.items[10]; - const t11 = props.items[11]; - const t12 = props.items[12]; - const t13 = props.items[13]; - const t14 = props.items[14]; - const t15 = props.items[15]; - const t16 = props.items[16]; - const t17 = props.items[17]; - const t18 = props.items[18]; - const t19 = props.items[19]; - const t20 = props.items[20]; - const t21 = props.items[21]; - const t22 = props.items[22]; - const t23 = props.items[23]; - const t24 = props.items[24]; - const t25 = props.items[25]; - const t26 = props.items[26]; - const t27 = props.items[27]; - const t28 = props.items[28]; - const t29 = props.items[29]; - const t30 = props.items[30]; - const t31 = props.items[31]; - const t32 = props.items[32]; - const t33 = props.items[33]; - const t34 = props.items[34]; - const t35 = props.items[35]; - const t36 = props.items[36]; - const t37 = props.items[37]; - const t38 = props.items[38]; - const t39 = props.items[39]; - const t40 = props.items[40]; - const t41 = props.items[41]; - const t42 = props.items[42]; - const t43 = props.items[43]; - const t44 = props.items[44]; - const t45 = props.items[45]; - const t46 = props.items[46]; - const t47 = props.items[47]; - const t48 = props.items[48]; - const t49 = props.items[49]; - const t50 = props.items[50]; - const t51 = props.items[51]; - const t52 = props.items[52]; - const t53 = props.items[53]; - const t54 = props.items[54]; - const t55 = props.items[55]; - const t56 = props.items[56]; - const t57 = props.items[57]; - const t58 = props.items[58]; - const t59 = props.items[59]; - const t60 = props.items[60]; - const t61 = props.items[61]; - const t62 = props.items[62]; - const t63 = props.items[63]; - const t64 = props.items[64]; - const t65 = props.items[65]; - const t66 = props.items[66]; - const t67 = props.items[67]; - const t68 = props.items[68]; - const t69 = props.items[69]; - const t70 = props.items[70]; - const t71 = props.items[71]; - const t72 = props.items[72]; - const t73 = props.items[73]; - const t74 = props.items[74]; - const t75 = props.items[75]; - const t76 = props.items[76]; - const t77 = props.items[77]; - const t78 = props.items[78]; - const t79 = props.items[79]; - const t80 = props.items[80]; - const t81 = props.items[81]; - const t82 = props.items[82]; - const t83 = props.items[83]; - const t84 = props.items[84]; - const t85 = props.items[85]; - const t86 = props.items[86]; - const t87 = props.items[87]; - const t88 = props.items[88]; - const t89 = props.items[89]; - const t90 = props.items[90]; - const t91 = props.items[91]; - const t92 = props.items[92]; - const t93 = props.items[93]; - const t94 = props.items[94]; - const t95 = props.items[95]; - const t96 = props.items[96]; - const t97 = props.items[97]; - const t98 = props.items[98]; - const t99 = props.items[99]; - const t100 = props.items[100]; - const t101 = props.items[101]; - const t102 = props.items[102]; - const t103 = props.items[103]; - const t104 = props.items[104]; - const t105 = props.items[105]; - const t106 = props.items[106]; - const t107 = props.items[107]; - const t108 = props.items[108]; - const t109 = props.items[109]; - const t110 = props.items[110]; - const t111 = props.items[111]; - const t112 = props.items[112]; - const t113 = props.items[113]; - const t114 = props.items[114]; - const t115 = props.items[115]; - const t116 = props.items[116]; - const t117 = props.items[117]; - const t118 = props.items[118]; - const t119 = props.items[119]; - const t120 = props.items[120]; - const t121 = props.items[121]; - const t122 = props.items[122]; - const t123 = props.items[123]; - const t124 = props.items[124]; - const t125 = props.items[125]; - const t126 = props.items[126]; - const t127 = props.items[127]; - const t128 = props.items[128]; - const t129 = props.items[129]; - const t130 = props.items[130]; - const t131 = props.items[131]; - const t132 = props.items[132]; - const t133 = props.items[133]; - const t134 = props.items[134]; - const t135 = props.items[135]; - const t136 = props.items[136]; - const t137 = props.items[137]; - const t138 = props.items[138]; - const t139 = props.items[139]; - const t140 = props.items[140]; - const t141 = props.items[141]; - const t142 = props.items[142]; - const t143 = props.items[143]; - const t144 = props.items[144]; - const t145 = props.items[145]; - const t146 = props.items[146]; - const t147 = props.items[147]; - const t148 = props.items[148]; - const t149 = props.items[149]; - const t150 = props.items[150]; - const t151 = props.items[151]; - const t152 = props.items[152]; - const t153 = props.items[153]; - const t154 = props.items[154]; - const t155 = props.items[155]; - const t156 = props.items[156]; - const t157 = props.items[157]; - const t158 = props.items[158]; - const t159 = props.items[159]; - const t160 = props.items[160]; - const t161 = props.items[161]; - const t162 = props.items[162]; - const t163 = props.items[163]; - const t164 = props.items[164]; - const t165 = props.items[165]; - const t166 = props.items[166]; - const t167 = props.items[167]; - const t168 = props.items[168]; - const t169 = props.items[169]; - const t170 = props.items[170]; - const t171 = props.items[171]; - const t172 = props.items[172]; - const t173 = props.items[173]; - const t174 = props.items[174]; - const t175 = props.items[175]; - const t176 = props.items[176]; - const t177 = props.items[177]; - const t178 = props.items[178]; - const t179 = props.items[179]; - const t180 = props.items[180]; - const t181 = props.items[181]; - const t182 = props.items[182]; - const t183 = props.items[183]; - const t184 = props.items[184]; - const t185 = props.items[185]; - const t186 = props.items[186]; - const t187 = props.items[187]; - const t188 = props.items[188]; - const t189 = props.items[189]; - const t190 = props.items[190]; - const t191 = props.items[191]; - const t192 = props.items[192]; - const t193 = props.items[193]; - const t194 = props.items[194]; - const t195 = props.items[195]; - const t196 = props.items[196]; - const t197 = props.items[197]; - const t198 = props.items[198]; - const t199 = props.items[199]; - const t200 = props.items[200]; - const t201 = props.items[201]; - const t202 = props.items[202]; - const t203 = props.items[203]; - const t204 = props.items[204]; - const t205 = props.items[205]; - const t206 = props.items[206]; - const t207 = props.items[207]; - const t208 = props.items[208]; - const t209 = props.items[209]; - const t210 = props.items[210]; - const t211 = props.items[211]; - const t212 = props.items[212]; - const t213 = props.items[213]; - const t214 = props.items[214]; - const t215 = props.items[215]; - const t216 = props.items[216]; - const t217 = props.items[217]; - const t218 = props.items[218]; - const t219 = props.items[219]; - const t220 = props.items[220]; - const t221 = props.items[221]; - const t222 = props.items[222]; - const t223 = props.items[223]; - const t224 = props.items[224]; - const t225 = props.items[225]; - const t226 = props.items[226]; - const t227 = props.items[227]; - const t228 = props.items[228]; - const t229 = props.items[229]; - const t230 = props.items[230]; - const t231 = props.items[231]; - const t232 = props.items[232]; - const t233 = props.items[233]; - const t234 = props.items[234]; - const t235 = props.items[235]; - const t236 = props.items[236]; - const t237 = props.items[237]; - const t238 = props.items[238]; - const t239 = props.items[239]; - const t240 = props.items[240]; - const t241 = props.items[241]; - const t242 = props.items[242]; - const t243 = props.items[243]; - const t244 = props.items[244]; - const t245 = props.items[245]; - const t246 = props.items[246]; - const t247 = props.items[247]; - const t248 = props.items[248]; - const t249 = props.items[249]; - const t250 = props.items[250]; - const t251 = props.items[251]; - const t252 = props.items[252]; - const t253 = props.items[253]; - const t254 = props.items[254]; - const t255 = props.items[255]; - const t256 = props.items[256]; - const t257 = props.items[257]; - const t258 = props.items[258]; - const t259 = props.items[259]; - const t260 = props.items[260]; - const t261 = props.items[261]; - const t262 = props.items[262]; - const t263 = props.items[263]; - const t264 = props.items[264]; - const t265 = props.items[265]; - const t266 = props.items[266]; - const t267 = props.items[267]; - const t268 = props.items[268]; - const t269 = props.items[269]; - const t270 = props.items[270]; - const t271 = props.items[271]; - const t272 = props.items[272]; - const t273 = props.items[273]; - const t274 = props.items[274]; - const t275 = props.items[275]; - const t276 = props.items[276]; - const t277 = props.items[277]; - const t278 = props.items[278]; - const t279 = props.items[279]; - const t280 = props.items[280]; - const t281 = props.items[281]; - const t282 = props.items[282]; - const t283 = props.items[283]; - const t284 = props.items[284]; - const t285 = props.items[285]; - const t286 = props.items[286]; - const t287 = props.items[287]; - const t288 = props.items[288]; - const t289 = props.items[289]; - const t290 = props.items[290]; - const t291 = props.items[291]; - const t292 = props.items[292]; - const t293 = props.items[293]; - const t294 = props.items[294]; - const t295 = props.items[295]; - const t296 = props.items[296]; - const t297 = props.items[297]; - const t298 = props.items[298]; - const t299 = props.items[299]; - const t300 = props.items[300]; - const t301 = props.items[301]; - const t302 = props.items[302]; - const t303 = props.items[303]; - const t304 = props.items[304]; - const t305 = props.items[305]; - const t306 = props.items[306]; - const t307 = props.items[307]; - const t308 = props.items[308]; - const t309 = props.items[309]; - const t310 = props.items[310]; - const t311 = props.items[311]; - const t312 = props.items[312]; - const t313 = props.items[313]; - const t314 = props.items[314]; - const t315 = props.items[315]; - const t316 = props.items[316]; - const t317 = props.items[317]; - const t318 = props.items[318]; - const t319 = props.items[319]; - const t320 = props.items[320]; - const t321 = props.items[321]; - const t322 = props.items[322]; - const t323 = props.items[323]; - const t324 = props.items[324]; - const t325 = props.items[325]; - const t326 = props.items[326]; - const t327 = props.items[327]; - const t328 = props.items[328]; - const t329 = props.items[329]; - const t330 = props.items[330]; - const t331 = props.items[331]; - const t332 = props.items[332]; - const t333 = props.items[333]; - const t334 = props.items[334]; - const t335 = props.items[335]; - const t336 = props.items[336]; - const t337 = props.items[337]; - const t338 = props.items[338]; - const t339 = props.items[339]; - const t340 = props.items[340]; - const t341 = props.items[341]; - const t342 = props.items[342]; - const t343 = props.items[343]; - const t344 = props.items[344]; - const t345 = props.items[345]; - const t346 = props.items[346]; - const t347 = props.items[347]; - const t348 = props.items[348]; - const t349 = props.items[349]; - const t350 = props.items[350]; - const t351 = props.items[351]; - const t352 = props.items[352]; - const t353 = props.items[353]; - const t354 = props.items[354]; - const t355 = props.items[355]; - const t356 = props.items[356]; - const t357 = props.items[357]; - const t358 = props.items[358]; - const t359 = props.items[359]; - const t360 = props.items[360]; - const t361 = props.items[361]; - const t362 = props.items[362]; - const t363 = props.items[363]; - const t364 = props.items[364]; - const t365 = props.items[365]; - const t366 = props.items[366]; - const t367 = props.items[367]; - const t368 = props.items[368]; - const t369 = props.items[369]; - const t370 = props.items[370]; - const t371 = props.items[371]; - const t372 = props.items[372]; - const t373 = props.items[373]; - const t374 = props.items[374]; - const t375 = props.items[375]; - const t376 = props.items[376]; - const t377 = props.items[377]; - const t378 = props.items[378]; - const t379 = props.items[379]; - const t380 = props.items[380]; - const t381 = props.items[381]; - const t382 = props.items[382]; - const t383 = props.items[383]; - const t384 = props.items[384]; - const t385 = props.items[385]; - const t386 = props.items[386]; - const t387 = props.items[387]; - const t388 = props.items[388]; - const t389 = props.items[389]; - const t390 = props.items[390]; - const t391 = props.items[391]; - const t392 = props.items[392]; - const t393 = props.items[393]; - const t394 = props.items[394]; - const t395 = props.items[395]; - const t396 = props.items[396]; - const t397 = props.items[397]; - const t398 = props.items[398]; - const t399 = props.items[399]; - return [ - t0, - t1, - t2, - t3, - t4, - t5, - t6, - t7, - t8, - t9, - t10, - t11, - t12, - t13, - t14, - t15, - t16, - t17, - t18, - t19, - t20, - t21, - t22, - t23, - t24, - t25, - t26, - t27, - t28, - t29, - t30, - t31, - t32, - t33, - t34, - t35, - t36, - t37, - t38, - t39, - t40, - t41, - t42, - t43, - t44, - t45, - t46, - t47, - t48, - t49, - t50, - t51, - t52, - t53, - t54, - t55, - t56, - t57, - t58, - t59, - t60, - t61, - t62, - t63, - t64, - t65, - t66, - t67, - t68, - t69, - t70, - t71, - t72, - t73, - t74, - t75, - t76, - t77, - t78, - t79, - t80, - t81, - t82, - t83, - t84, - t85, - t86, - t87, - t88, - t89, - t90, - t91, - t92, - t93, - t94, - t95, - t96, - t97, - t98, - t99, - t100, - t101, - t102, - t103, - t104, - t105, - t106, - t107, - t108, - t109, - t110, - t111, - t112, - t113, - t114, - t115, - t116, - t117, - t118, - t119, - t120, - t121, - t122, - t123, - t124, - t125, - t126, - t127, - t128, - t129, - t130, - t131, - t132, - t133, - t134, - t135, - t136, - t137, - t138, - t139, - t140, - t141, - t142, - t143, - t144, - t145, - t146, - t147, - t148, - t149, - t150, - t151, - t152, - t153, - t154, - t155, - t156, - t157, - t158, - t159, - t160, - t161, - t162, - t163, - t164, - t165, - t166, - t167, - t168, - t169, - t170, - t171, - t172, - t173, - t174, - t175, - t176, - t177, - t178, - t179, - t180, - t181, - t182, - t183, - t184, - t185, - t186, - t187, - t188, - t189, - t190, - t191, - t192, - t193, - t194, - t195, - t196, - t197, - t198, - t199, - t200, - t201, - t202, - t203, - t204, - t205, - t206, - t207, - t208, - t209, - t210, - t211, - t212, - t213, - t214, - t215, - t216, - t217, - t218, - t219, - t220, - t221, - t222, - t223, - t224, - t225, - t226, - t227, - t228, - t229, - t230, - t231, - t232, - t233, - t234, - t235, - t236, - t237, - t238, - t239, - t240, - t241, - t242, - t243, - t244, - t245, - t246, - t247, - t248, - t249, - t250, - t251, - t252, - t253, - t254, - t255, - t256, - t257, - t258, - t259, - t260, - t261, - t262, - t263, - t264, - t265, - t266, - t267, - t268, - t269, - t270, - t271, - t272, - t273, - t274, - t275, - t276, - t277, - t278, - t279, - t280, - t281, - t282, - t283, - t284, - t285, - t286, - t287, - t288, - t289, - t290, - t291, - t292, - t293, - t294, - t295, - t296, - t297, - t298, - t299, - t300, - t301, - t302, - t303, - t304, - t305, - t306, - t307, - t308, - t309, - t310, - t311, - t312, - t313, - t314, - t315, - t316, - t317, - t318, - t319, - t320, - t321, - t322, - t323, - t324, - t325, - t326, - t327, - t328, - t329, - t330, - t331, - t332, - t333, - t334, - t335, - t336, - t337, - t338, - t339, - t340, - t341, - t342, - t343, - t344, - t345, - t346, - t347, - t348, - t349, - t350, - t351, - t352, - t353, - t354, - t355, - t356, - t357, - t358, - t359, - t360, - t361, - t362, - t363, - t364, - t365, - t366, - t367, - t368, - t369, - t370, - t371, - t372, - t373, - t374, - t375, - t376, - t377, - t378, - t379, - t380, - t381, - t382, - t383, - t384, - t385, - t386, - t387, - t388, - t389, - t390, - t391, - t392, - t393, - t394, - t395, - t396, - t397, - t398, - t399, - ]; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // Test that the compiler handles components with many sequential reactive scopes. -// This exercises the iterative (non-recursive) fallthrough traversal in -// BuildReactiveFunction. Previously, each scope's fallthrough was visited via -// a recursive call, causing stack overflow (SIGSEGV) on inputs with hundreds -// of scopes. - -function ManyScopes(props) { - const $ = _c(401); - const t0 = props.items[0]; - const t1 = props.items[1]; - const t2 = props.items[2]; - const t3 = props.items[3]; - const t4 = props.items[4]; - const t5 = props.items[5]; - const t6 = props.items[6]; - const t7 = props.items[7]; - const t8 = props.items[8]; - const t9 = props.items[9]; - const t10 = props.items[10]; - const t11 = props.items[11]; - const t12 = props.items[12]; - const t13 = props.items[13]; - const t14 = props.items[14]; - const t15 = props.items[15]; - const t16 = props.items[16]; - const t17 = props.items[17]; - const t18 = props.items[18]; - const t19 = props.items[19]; - const t20 = props.items[20]; - const t21 = props.items[21]; - const t22 = props.items[22]; - const t23 = props.items[23]; - const t24 = props.items[24]; - const t25 = props.items[25]; - const t26 = props.items[26]; - const t27 = props.items[27]; - const t28 = props.items[28]; - const t29 = props.items[29]; - const t30 = props.items[30]; - const t31 = props.items[31]; - const t32 = props.items[32]; - const t33 = props.items[33]; - const t34 = props.items[34]; - const t35 = props.items[35]; - const t36 = props.items[36]; - const t37 = props.items[37]; - const t38 = props.items[38]; - const t39 = props.items[39]; - const t40 = props.items[40]; - const t41 = props.items[41]; - const t42 = props.items[42]; - const t43 = props.items[43]; - const t44 = props.items[44]; - const t45 = props.items[45]; - const t46 = props.items[46]; - const t47 = props.items[47]; - const t48 = props.items[48]; - const t49 = props.items[49]; - const t50 = props.items[50]; - const t51 = props.items[51]; - const t52 = props.items[52]; - const t53 = props.items[53]; - const t54 = props.items[54]; - const t55 = props.items[55]; - const t56 = props.items[56]; - const t57 = props.items[57]; - const t58 = props.items[58]; - const t59 = props.items[59]; - const t60 = props.items[60]; - const t61 = props.items[61]; - const t62 = props.items[62]; - const t63 = props.items[63]; - const t64 = props.items[64]; - const t65 = props.items[65]; - const t66 = props.items[66]; - const t67 = props.items[67]; - const t68 = props.items[68]; - const t69 = props.items[69]; - const t70 = props.items[70]; - const t71 = props.items[71]; - const t72 = props.items[72]; - const t73 = props.items[73]; - const t74 = props.items[74]; - const t75 = props.items[75]; - const t76 = props.items[76]; - const t77 = props.items[77]; - const t78 = props.items[78]; - const t79 = props.items[79]; - const t80 = props.items[80]; - const t81 = props.items[81]; - const t82 = props.items[82]; - const t83 = props.items[83]; - const t84 = props.items[84]; - const t85 = props.items[85]; - const t86 = props.items[86]; - const t87 = props.items[87]; - const t88 = props.items[88]; - const t89 = props.items[89]; - const t90 = props.items[90]; - const t91 = props.items[91]; - const t92 = props.items[92]; - const t93 = props.items[93]; - const t94 = props.items[94]; - const t95 = props.items[95]; - const t96 = props.items[96]; - const t97 = props.items[97]; - const t98 = props.items[98]; - const t99 = props.items[99]; - const t100 = props.items[100]; - const t101 = props.items[101]; - const t102 = props.items[102]; - const t103 = props.items[103]; - const t104 = props.items[104]; - const t105 = props.items[105]; - const t106 = props.items[106]; - const t107 = props.items[107]; - const t108 = props.items[108]; - const t109 = props.items[109]; - const t110 = props.items[110]; - const t111 = props.items[111]; - const t112 = props.items[112]; - const t113 = props.items[113]; - const t114 = props.items[114]; - const t115 = props.items[115]; - const t116 = props.items[116]; - const t117 = props.items[117]; - const t118 = props.items[118]; - const t119 = props.items[119]; - const t120 = props.items[120]; - const t121 = props.items[121]; - const t122 = props.items[122]; - const t123 = props.items[123]; - const t124 = props.items[124]; - const t125 = props.items[125]; - const t126 = props.items[126]; - const t127 = props.items[127]; - const t128 = props.items[128]; - const t129 = props.items[129]; - const t130 = props.items[130]; - const t131 = props.items[131]; - const t132 = props.items[132]; - const t133 = props.items[133]; - const t134 = props.items[134]; - const t135 = props.items[135]; - const t136 = props.items[136]; - const t137 = props.items[137]; - const t138 = props.items[138]; - const t139 = props.items[139]; - const t140 = props.items[140]; - const t141 = props.items[141]; - const t142 = props.items[142]; - const t143 = props.items[143]; - const t144 = props.items[144]; - const t145 = props.items[145]; - const t146 = props.items[146]; - const t147 = props.items[147]; - const t148 = props.items[148]; - const t149 = props.items[149]; - const t150 = props.items[150]; - const t151 = props.items[151]; - const t152 = props.items[152]; - const t153 = props.items[153]; - const t154 = props.items[154]; - const t155 = props.items[155]; - const t156 = props.items[156]; - const t157 = props.items[157]; - const t158 = props.items[158]; - const t159 = props.items[159]; - const t160 = props.items[160]; - const t161 = props.items[161]; - const t162 = props.items[162]; - const t163 = props.items[163]; - const t164 = props.items[164]; - const t165 = props.items[165]; - const t166 = props.items[166]; - const t167 = props.items[167]; - const t168 = props.items[168]; - const t169 = props.items[169]; - const t170 = props.items[170]; - const t171 = props.items[171]; - const t172 = props.items[172]; - const t173 = props.items[173]; - const t174 = props.items[174]; - const t175 = props.items[175]; - const t176 = props.items[176]; - const t177 = props.items[177]; - const t178 = props.items[178]; - const t179 = props.items[179]; - const t180 = props.items[180]; - const t181 = props.items[181]; - const t182 = props.items[182]; - const t183 = props.items[183]; - const t184 = props.items[184]; - const t185 = props.items[185]; - const t186 = props.items[186]; - const t187 = props.items[187]; - const t188 = props.items[188]; - const t189 = props.items[189]; - const t190 = props.items[190]; - const t191 = props.items[191]; - const t192 = props.items[192]; - const t193 = props.items[193]; - const t194 = props.items[194]; - const t195 = props.items[195]; - const t196 = props.items[196]; - const t197 = props.items[197]; - const t198 = props.items[198]; - const t199 = props.items[199]; - const t200 = props.items[200]; - const t201 = props.items[201]; - const t202 = props.items[202]; - const t203 = props.items[203]; - const t204 = props.items[204]; - const t205 = props.items[205]; - const t206 = props.items[206]; - const t207 = props.items[207]; - const t208 = props.items[208]; - const t209 = props.items[209]; - const t210 = props.items[210]; - const t211 = props.items[211]; - const t212 = props.items[212]; - const t213 = props.items[213]; - const t214 = props.items[214]; - const t215 = props.items[215]; - const t216 = props.items[216]; - const t217 = props.items[217]; - const t218 = props.items[218]; - const t219 = props.items[219]; - const t220 = props.items[220]; - const t221 = props.items[221]; - const t222 = props.items[222]; - const t223 = props.items[223]; - const t224 = props.items[224]; - const t225 = props.items[225]; - const t226 = props.items[226]; - const t227 = props.items[227]; - const t228 = props.items[228]; - const t229 = props.items[229]; - const t230 = props.items[230]; - const t231 = props.items[231]; - const t232 = props.items[232]; - const t233 = props.items[233]; - const t234 = props.items[234]; - const t235 = props.items[235]; - const t236 = props.items[236]; - const t237 = props.items[237]; - const t238 = props.items[238]; - const t239 = props.items[239]; - const t240 = props.items[240]; - const t241 = props.items[241]; - const t242 = props.items[242]; - const t243 = props.items[243]; - const t244 = props.items[244]; - const t245 = props.items[245]; - const t246 = props.items[246]; - const t247 = props.items[247]; - const t248 = props.items[248]; - const t249 = props.items[249]; - const t250 = props.items[250]; - const t251 = props.items[251]; - const t252 = props.items[252]; - const t253 = props.items[253]; - const t254 = props.items[254]; - const t255 = props.items[255]; - const t256 = props.items[256]; - const t257 = props.items[257]; - const t258 = props.items[258]; - const t259 = props.items[259]; - const t260 = props.items[260]; - const t261 = props.items[261]; - const t262 = props.items[262]; - const t263 = props.items[263]; - const t264 = props.items[264]; - const t265 = props.items[265]; - const t266 = props.items[266]; - const t267 = props.items[267]; - const t268 = props.items[268]; - const t269 = props.items[269]; - const t270 = props.items[270]; - const t271 = props.items[271]; - const t272 = props.items[272]; - const t273 = props.items[273]; - const t274 = props.items[274]; - const t275 = props.items[275]; - const t276 = props.items[276]; - const t277 = props.items[277]; - const t278 = props.items[278]; - const t279 = props.items[279]; - const t280 = props.items[280]; - const t281 = props.items[281]; - const t282 = props.items[282]; - const t283 = props.items[283]; - const t284 = props.items[284]; - const t285 = props.items[285]; - const t286 = props.items[286]; - const t287 = props.items[287]; - const t288 = props.items[288]; - const t289 = props.items[289]; - const t290 = props.items[290]; - const t291 = props.items[291]; - const t292 = props.items[292]; - const t293 = props.items[293]; - const t294 = props.items[294]; - const t295 = props.items[295]; - const t296 = props.items[296]; - const t297 = props.items[297]; - const t298 = props.items[298]; - const t299 = props.items[299]; - const t300 = props.items[300]; - const t301 = props.items[301]; - const t302 = props.items[302]; - const t303 = props.items[303]; - const t304 = props.items[304]; - const t305 = props.items[305]; - const t306 = props.items[306]; - const t307 = props.items[307]; - const t308 = props.items[308]; - const t309 = props.items[309]; - const t310 = props.items[310]; - const t311 = props.items[311]; - const t312 = props.items[312]; - const t313 = props.items[313]; - const t314 = props.items[314]; - const t315 = props.items[315]; - const t316 = props.items[316]; - const t317 = props.items[317]; - const t318 = props.items[318]; - const t319 = props.items[319]; - const t320 = props.items[320]; - const t321 = props.items[321]; - const t322 = props.items[322]; - const t323 = props.items[323]; - const t324 = props.items[324]; - const t325 = props.items[325]; - const t326 = props.items[326]; - const t327 = props.items[327]; - const t328 = props.items[328]; - const t329 = props.items[329]; - const t330 = props.items[330]; - const t331 = props.items[331]; - const t332 = props.items[332]; - const t333 = props.items[333]; - const t334 = props.items[334]; - const t335 = props.items[335]; - const t336 = props.items[336]; - const t337 = props.items[337]; - const t338 = props.items[338]; - const t339 = props.items[339]; - const t340 = props.items[340]; - const t341 = props.items[341]; - const t342 = props.items[342]; - const t343 = props.items[343]; - const t344 = props.items[344]; - const t345 = props.items[345]; - const t346 = props.items[346]; - const t347 = props.items[347]; - const t348 = props.items[348]; - const t349 = props.items[349]; - const t350 = props.items[350]; - const t351 = props.items[351]; - const t352 = props.items[352]; - const t353 = props.items[353]; - const t354 = props.items[354]; - const t355 = props.items[355]; - const t356 = props.items[356]; - const t357 = props.items[357]; - const t358 = props.items[358]; - const t359 = props.items[359]; - const t360 = props.items[360]; - const t361 = props.items[361]; - const t362 = props.items[362]; - const t363 = props.items[363]; - const t364 = props.items[364]; - const t365 = props.items[365]; - const t366 = props.items[366]; - const t367 = props.items[367]; - const t368 = props.items[368]; - const t369 = props.items[369]; - const t370 = props.items[370]; - const t371 = props.items[371]; - const t372 = props.items[372]; - const t373 = props.items[373]; - const t374 = props.items[374]; - const t375 = props.items[375]; - const t376 = props.items[376]; - const t377 = props.items[377]; - const t378 = props.items[378]; - const t379 = props.items[379]; - const t380 = props.items[380]; - const t381 = props.items[381]; - const t382 = props.items[382]; - const t383 = props.items[383]; - const t384 = props.items[384]; - const t385 = props.items[385]; - const t386 = props.items[386]; - const t387 = props.items[387]; - const t388 = props.items[388]; - const t389 = props.items[389]; - const t390 = props.items[390]; - const t391 = props.items[391]; - const t392 = props.items[392]; - const t393 = props.items[393]; - const t394 = props.items[394]; - const t395 = props.items[395]; - const t396 = props.items[396]; - const t397 = props.items[397]; - const t398 = props.items[398]; - const t399 = props.items[399]; - let t400; - if ( - $[0] !== t0 || - $[1] !== t1 || - $[2] !== t10 || - $[3] !== t100 || - $[4] !== t101 || - $[5] !== t102 || - $[6] !== t103 || - $[7] !== t104 || - $[8] !== t105 || - $[9] !== t106 || - $[10] !== t107 || - $[11] !== t108 || - $[12] !== t109 || - $[13] !== t11 || - $[14] !== t110 || - $[15] !== t111 || - $[16] !== t112 || - $[17] !== t113 || - $[18] !== t114 || - $[19] !== t115 || - $[20] !== t116 || - $[21] !== t117 || - $[22] !== t118 || - $[23] !== t119 || - $[24] !== t12 || - $[25] !== t120 || - $[26] !== t121 || - $[27] !== t122 || - $[28] !== t123 || - $[29] !== t124 || - $[30] !== t125 || - $[31] !== t126 || - $[32] !== t127 || - $[33] !== t128 || - $[34] !== t129 || - $[35] !== t13 || - $[36] !== t130 || - $[37] !== t131 || - $[38] !== t132 || - $[39] !== t133 || - $[40] !== t134 || - $[41] !== t135 || - $[42] !== t136 || - $[43] !== t137 || - $[44] !== t138 || - $[45] !== t139 || - $[46] !== t14 || - $[47] !== t140 || - $[48] !== t141 || - $[49] !== t142 || - $[50] !== t143 || - $[51] !== t144 || - $[52] !== t145 || - $[53] !== t146 || - $[54] !== t147 || - $[55] !== t148 || - $[56] !== t149 || - $[57] !== t15 || - $[58] !== t150 || - $[59] !== t151 || - $[60] !== t152 || - $[61] !== t153 || - $[62] !== t154 || - $[63] !== t155 || - $[64] !== t156 || - $[65] !== t157 || - $[66] !== t158 || - $[67] !== t159 || - $[68] !== t16 || - $[69] !== t160 || - $[70] !== t161 || - $[71] !== t162 || - $[72] !== t163 || - $[73] !== t164 || - $[74] !== t165 || - $[75] !== t166 || - $[76] !== t167 || - $[77] !== t168 || - $[78] !== t169 || - $[79] !== t17 || - $[80] !== t170 || - $[81] !== t171 || - $[82] !== t172 || - $[83] !== t173 || - $[84] !== t174 || - $[85] !== t175 || - $[86] !== t176 || - $[87] !== t177 || - $[88] !== t178 || - $[89] !== t179 || - $[90] !== t18 || - $[91] !== t180 || - $[92] !== t181 || - $[93] !== t182 || - $[94] !== t183 || - $[95] !== t184 || - $[96] !== t185 || - $[97] !== t186 || - $[98] !== t187 || - $[99] !== t188 || - $[100] !== t189 || - $[101] !== t19 || - $[102] !== t190 || - $[103] !== t191 || - $[104] !== t192 || - $[105] !== t193 || - $[106] !== t194 || - $[107] !== t195 || - $[108] !== t196 || - $[109] !== t197 || - $[110] !== t198 || - $[111] !== t199 || - $[112] !== t2 || - $[113] !== t20 || - $[114] !== t200 || - $[115] !== t201 || - $[116] !== t202 || - $[117] !== t203 || - $[118] !== t204 || - $[119] !== t205 || - $[120] !== t206 || - $[121] !== t207 || - $[122] !== t208 || - $[123] !== t209 || - $[124] !== t21 || - $[125] !== t210 || - $[126] !== t211 || - $[127] !== t212 || - $[128] !== t213 || - $[129] !== t214 || - $[130] !== t215 || - $[131] !== t216 || - $[132] !== t217 || - $[133] !== t218 || - $[134] !== t219 || - $[135] !== t22 || - $[136] !== t220 || - $[137] !== t221 || - $[138] !== t222 || - $[139] !== t223 || - $[140] !== t224 || - $[141] !== t225 || - $[142] !== t226 || - $[143] !== t227 || - $[144] !== t228 || - $[145] !== t229 || - $[146] !== t23 || - $[147] !== t230 || - $[148] !== t231 || - $[149] !== t232 || - $[150] !== t233 || - $[151] !== t234 || - $[152] !== t235 || - $[153] !== t236 || - $[154] !== t237 || - $[155] !== t238 || - $[156] !== t239 || - $[157] !== t24 || - $[158] !== t240 || - $[159] !== t241 || - $[160] !== t242 || - $[161] !== t243 || - $[162] !== t244 || - $[163] !== t245 || - $[164] !== t246 || - $[165] !== t247 || - $[166] !== t248 || - $[167] !== t249 || - $[168] !== t25 || - $[169] !== t250 || - $[170] !== t251 || - $[171] !== t252 || - $[172] !== t253 || - $[173] !== t254 || - $[174] !== t255 || - $[175] !== t256 || - $[176] !== t257 || - $[177] !== t258 || - $[178] !== t259 || - $[179] !== t26 || - $[180] !== t260 || - $[181] !== t261 || - $[182] !== t262 || - $[183] !== t263 || - $[184] !== t264 || - $[185] !== t265 || - $[186] !== t266 || - $[187] !== t267 || - $[188] !== t268 || - $[189] !== t269 || - $[190] !== t27 || - $[191] !== t270 || - $[192] !== t271 || - $[193] !== t272 || - $[194] !== t273 || - $[195] !== t274 || - $[196] !== t275 || - $[197] !== t276 || - $[198] !== t277 || - $[199] !== t278 || - $[200] !== t279 || - $[201] !== t28 || - $[202] !== t280 || - $[203] !== t281 || - $[204] !== t282 || - $[205] !== t283 || - $[206] !== t284 || - $[207] !== t285 || - $[208] !== t286 || - $[209] !== t287 || - $[210] !== t288 || - $[211] !== t289 || - $[212] !== t29 || - $[213] !== t290 || - $[214] !== t291 || - $[215] !== t292 || - $[216] !== t293 || - $[217] !== t294 || - $[218] !== t295 || - $[219] !== t296 || - $[220] !== t297 || - $[221] !== t298 || - $[222] !== t299 || - $[223] !== t3 || - $[224] !== t30 || - $[225] !== t300 || - $[226] !== t301 || - $[227] !== t302 || - $[228] !== t303 || - $[229] !== t304 || - $[230] !== t305 || - $[231] !== t306 || - $[232] !== t307 || - $[233] !== t308 || - $[234] !== t309 || - $[235] !== t31 || - $[236] !== t310 || - $[237] !== t311 || - $[238] !== t312 || - $[239] !== t313 || - $[240] !== t314 || - $[241] !== t315 || - $[242] !== t316 || - $[243] !== t317 || - $[244] !== t318 || - $[245] !== t319 || - $[246] !== t32 || - $[247] !== t320 || - $[248] !== t321 || - $[249] !== t322 || - $[250] !== t323 || - $[251] !== t324 || - $[252] !== t325 || - $[253] !== t326 || - $[254] !== t327 || - $[255] !== t328 || - $[256] !== t329 || - $[257] !== t33 || - $[258] !== t330 || - $[259] !== t331 || - $[260] !== t332 || - $[261] !== t333 || - $[262] !== t334 || - $[263] !== t335 || - $[264] !== t336 || - $[265] !== t337 || - $[266] !== t338 || - $[267] !== t339 || - $[268] !== t34 || - $[269] !== t340 || - $[270] !== t341 || - $[271] !== t342 || - $[272] !== t343 || - $[273] !== t344 || - $[274] !== t345 || - $[275] !== t346 || - $[276] !== t347 || - $[277] !== t348 || - $[278] !== t349 || - $[279] !== t35 || - $[280] !== t350 || - $[281] !== t351 || - $[282] !== t352 || - $[283] !== t353 || - $[284] !== t354 || - $[285] !== t355 || - $[286] !== t356 || - $[287] !== t357 || - $[288] !== t358 || - $[289] !== t359 || - $[290] !== t36 || - $[291] !== t360 || - $[292] !== t361 || - $[293] !== t362 || - $[294] !== t363 || - $[295] !== t364 || - $[296] !== t365 || - $[297] !== t366 || - $[298] !== t367 || - $[299] !== t368 || - $[300] !== t369 || - $[301] !== t37 || - $[302] !== t370 || - $[303] !== t371 || - $[304] !== t372 || - $[305] !== t373 || - $[306] !== t374 || - $[307] !== t375 || - $[308] !== t376 || - $[309] !== t377 || - $[310] !== t378 || - $[311] !== t379 || - $[312] !== t38 || - $[313] !== t380 || - $[314] !== t381 || - $[315] !== t382 || - $[316] !== t383 || - $[317] !== t384 || - $[318] !== t385 || - $[319] !== t386 || - $[320] !== t387 || - $[321] !== t388 || - $[322] !== t389 || - $[323] !== t39 || - $[324] !== t390 || - $[325] !== t391 || - $[326] !== t392 || - $[327] !== t393 || - $[328] !== t394 || - $[329] !== t395 || - $[330] !== t396 || - $[331] !== t397 || - $[332] !== t398 || - $[333] !== t399 || - $[334] !== t4 || - $[335] !== t40 || - $[336] !== t41 || - $[337] !== t42 || - $[338] !== t43 || - $[339] !== t44 || - $[340] !== t45 || - $[341] !== t46 || - $[342] !== t47 || - $[343] !== t48 || - $[344] !== t49 || - $[345] !== t5 || - $[346] !== t50 || - $[347] !== t51 || - $[348] !== t52 || - $[349] !== t53 || - $[350] !== t54 || - $[351] !== t55 || - $[352] !== t56 || - $[353] !== t57 || - $[354] !== t58 || - $[355] !== t59 || - $[356] !== t6 || - $[357] !== t60 || - $[358] !== t61 || - $[359] !== t62 || - $[360] !== t63 || - $[361] !== t64 || - $[362] !== t65 || - $[363] !== t66 || - $[364] !== t67 || - $[365] !== t68 || - $[366] !== t69 || - $[367] !== t7 || - $[368] !== t70 || - $[369] !== t71 || - $[370] !== t72 || - $[371] !== t73 || - $[372] !== t74 || - $[373] !== t75 || - $[374] !== t76 || - $[375] !== t77 || - $[376] !== t78 || - $[377] !== t79 || - $[378] !== t8 || - $[379] !== t80 || - $[380] !== t81 || - $[381] !== t82 || - $[382] !== t83 || - $[383] !== t84 || - $[384] !== t85 || - $[385] !== t86 || - $[386] !== t87 || - $[387] !== t88 || - $[388] !== t89 || - $[389] !== t9 || - $[390] !== t90 || - $[391] !== t91 || - $[392] !== t92 || - $[393] !== t93 || - $[394] !== t94 || - $[395] !== t95 || - $[396] !== t96 || - $[397] !== t97 || - $[398] !== t98 || - $[399] !== t99 - ) { - t400 = [ - t0, - t1, - t2, - t3, - t4, - t5, - t6, - t7, - t8, - t9, - t10, - t11, - t12, - t13, - t14, - t15, - t16, - t17, - t18, - t19, - t20, - t21, - t22, - t23, - t24, - t25, - t26, - t27, - t28, - t29, - t30, - t31, - t32, - t33, - t34, - t35, - t36, - t37, - t38, - t39, - t40, - t41, - t42, - t43, - t44, - t45, - t46, - t47, - t48, - t49, - t50, - t51, - t52, - t53, - t54, - t55, - t56, - t57, - t58, - t59, - t60, - t61, - t62, - t63, - t64, - t65, - t66, - t67, - t68, - t69, - t70, - t71, - t72, - t73, - t74, - t75, - t76, - t77, - t78, - t79, - t80, - t81, - t82, - t83, - t84, - t85, - t86, - t87, - t88, - t89, - t90, - t91, - t92, - t93, - t94, - t95, - t96, - t97, - t98, - t99, - t100, - t101, - t102, - t103, - t104, - t105, - t106, - t107, - t108, - t109, - t110, - t111, - t112, - t113, - t114, - t115, - t116, - t117, - t118, - t119, - t120, - t121, - t122, - t123, - t124, - t125, - t126, - t127, - t128, - t129, - t130, - t131, - t132, - t133, - t134, - t135, - t136, - t137, - t138, - t139, - t140, - t141, - t142, - t143, - t144, - t145, - t146, - t147, - t148, - t149, - t150, - t151, - t152, - t153, - t154, - t155, - t156, - t157, - t158, - t159, - t160, - t161, - t162, - t163, - t164, - t165, - t166, - t167, - t168, - t169, - t170, - t171, - t172, - t173, - t174, - t175, - t176, - t177, - t178, - t179, - t180, - t181, - t182, - t183, - t184, - t185, - t186, - t187, - t188, - t189, - t190, - t191, - t192, - t193, - t194, - t195, - t196, - t197, - t198, - t199, - t200, - t201, - t202, - t203, - t204, - t205, - t206, - t207, - t208, - t209, - t210, - t211, - t212, - t213, - t214, - t215, - t216, - t217, - t218, - t219, - t220, - t221, - t222, - t223, - t224, - t225, - t226, - t227, - t228, - t229, - t230, - t231, - t232, - t233, - t234, - t235, - t236, - t237, - t238, - t239, - t240, - t241, - t242, - t243, - t244, - t245, - t246, - t247, - t248, - t249, - t250, - t251, - t252, - t253, - t254, - t255, - t256, - t257, - t258, - t259, - t260, - t261, - t262, - t263, - t264, - t265, - t266, - t267, - t268, - t269, - t270, - t271, - t272, - t273, - t274, - t275, - t276, - t277, - t278, - t279, - t280, - t281, - t282, - t283, - t284, - t285, - t286, - t287, - t288, - t289, - t290, - t291, - t292, - t293, - t294, - t295, - t296, - t297, - t298, - t299, - t300, - t301, - t302, - t303, - t304, - t305, - t306, - t307, - t308, - t309, - t310, - t311, - t312, - t313, - t314, - t315, - t316, - t317, - t318, - t319, - t320, - t321, - t322, - t323, - t324, - t325, - t326, - t327, - t328, - t329, - t330, - t331, - t332, - t333, - t334, - t335, - t336, - t337, - t338, - t339, - t340, - t341, - t342, - t343, - t344, - t345, - t346, - t347, - t348, - t349, - t350, - t351, - t352, - t353, - t354, - t355, - t356, - t357, - t358, - t359, - t360, - t361, - t362, - t363, - t364, - t365, - t366, - t367, - t368, - t369, - t370, - t371, - t372, - t373, - t374, - t375, - t376, - t377, - t378, - t379, - t380, - t381, - t382, - t383, - t384, - t385, - t386, - t387, - t388, - t389, - t390, - t391, - t392, - t393, - t394, - t395, - t396, - t397, - t398, - t399, - ]; - $[0] = t0; - $[1] = t1; - $[2] = t10; - $[3] = t100; - $[4] = t101; - $[5] = t102; - $[6] = t103; - $[7] = t104; - $[8] = t105; - $[9] = t106; - $[10] = t107; - $[11] = t108; - $[12] = t109; - $[13] = t11; - $[14] = t110; - $[15] = t111; - $[16] = t112; - $[17] = t113; - $[18] = t114; - $[19] = t115; - $[20] = t116; - $[21] = t117; - $[22] = t118; - $[23] = t119; - $[24] = t12; - $[25] = t120; - $[26] = t121; - $[27] = t122; - $[28] = t123; - $[29] = t124; - $[30] = t125; - $[31] = t126; - $[32] = t127; - $[33] = t128; - $[34] = t129; - $[35] = t13; - $[36] = t130; - $[37] = t131; - $[38] = t132; - $[39] = t133; - $[40] = t134; - $[41] = t135; - $[42] = t136; - $[43] = t137; - $[44] = t138; - $[45] = t139; - $[46] = t14; - $[47] = t140; - $[48] = t141; - $[49] = t142; - $[50] = t143; - $[51] = t144; - $[52] = t145; - $[53] = t146; - $[54] = t147; - $[55] = t148; - $[56] = t149; - $[57] = t15; - $[58] = t150; - $[59] = t151; - $[60] = t152; - $[61] = t153; - $[62] = t154; - $[63] = t155; - $[64] = t156; - $[65] = t157; - $[66] = t158; - $[67] = t159; - $[68] = t16; - $[69] = t160; - $[70] = t161; - $[71] = t162; - $[72] = t163; - $[73] = t164; - $[74] = t165; - $[75] = t166; - $[76] = t167; - $[77] = t168; - $[78] = t169; - $[79] = t17; - $[80] = t170; - $[81] = t171; - $[82] = t172; - $[83] = t173; - $[84] = t174; - $[85] = t175; - $[86] = t176; - $[87] = t177; - $[88] = t178; - $[89] = t179; - $[90] = t18; - $[91] = t180; - $[92] = t181; - $[93] = t182; - $[94] = t183; - $[95] = t184; - $[96] = t185; - $[97] = t186; - $[98] = t187; - $[99] = t188; - $[100] = t189; - $[101] = t19; - $[102] = t190; - $[103] = t191; - $[104] = t192; - $[105] = t193; - $[106] = t194; - $[107] = t195; - $[108] = t196; - $[109] = t197; - $[110] = t198; - $[111] = t199; - $[112] = t2; - $[113] = t20; - $[114] = t200; - $[115] = t201; - $[116] = t202; - $[117] = t203; - $[118] = t204; - $[119] = t205; - $[120] = t206; - $[121] = t207; - $[122] = t208; - $[123] = t209; - $[124] = t21; - $[125] = t210; - $[126] = t211; - $[127] = t212; - $[128] = t213; - $[129] = t214; - $[130] = t215; - $[131] = t216; - $[132] = t217; - $[133] = t218; - $[134] = t219; - $[135] = t22; - $[136] = t220; - $[137] = t221; - $[138] = t222; - $[139] = t223; - $[140] = t224; - $[141] = t225; - $[142] = t226; - $[143] = t227; - $[144] = t228; - $[145] = t229; - $[146] = t23; - $[147] = t230; - $[148] = t231; - $[149] = t232; - $[150] = t233; - $[151] = t234; - $[152] = t235; - $[153] = t236; - $[154] = t237; - $[155] = t238; - $[156] = t239; - $[157] = t24; - $[158] = t240; - $[159] = t241; - $[160] = t242; - $[161] = t243; - $[162] = t244; - $[163] = t245; - $[164] = t246; - $[165] = t247; - $[166] = t248; - $[167] = t249; - $[168] = t25; - $[169] = t250; - $[170] = t251; - $[171] = t252; - $[172] = t253; - $[173] = t254; - $[174] = t255; - $[175] = t256; - $[176] = t257; - $[177] = t258; - $[178] = t259; - $[179] = t26; - $[180] = t260; - $[181] = t261; - $[182] = t262; - $[183] = t263; - $[184] = t264; - $[185] = t265; - $[186] = t266; - $[187] = t267; - $[188] = t268; - $[189] = t269; - $[190] = t27; - $[191] = t270; - $[192] = t271; - $[193] = t272; - $[194] = t273; - $[195] = t274; - $[196] = t275; - $[197] = t276; - $[198] = t277; - $[199] = t278; - $[200] = t279; - $[201] = t28; - $[202] = t280; - $[203] = t281; - $[204] = t282; - $[205] = t283; - $[206] = t284; - $[207] = t285; - $[208] = t286; - $[209] = t287; - $[210] = t288; - $[211] = t289; - $[212] = t29; - $[213] = t290; - $[214] = t291; - $[215] = t292; - $[216] = t293; - $[217] = t294; - $[218] = t295; - $[219] = t296; - $[220] = t297; - $[221] = t298; - $[222] = t299; - $[223] = t3; - $[224] = t30; - $[225] = t300; - $[226] = t301; - $[227] = t302; - $[228] = t303; - $[229] = t304; - $[230] = t305; - $[231] = t306; - $[232] = t307; - $[233] = t308; - $[234] = t309; - $[235] = t31; - $[236] = t310; - $[237] = t311; - $[238] = t312; - $[239] = t313; - $[240] = t314; - $[241] = t315; - $[242] = t316; - $[243] = t317; - $[244] = t318; - $[245] = t319; - $[246] = t32; - $[247] = t320; - $[248] = t321; - $[249] = t322; - $[250] = t323; - $[251] = t324; - $[252] = t325; - $[253] = t326; - $[254] = t327; - $[255] = t328; - $[256] = t329; - $[257] = t33; - $[258] = t330; - $[259] = t331; - $[260] = t332; - $[261] = t333; - $[262] = t334; - $[263] = t335; - $[264] = t336; - $[265] = t337; - $[266] = t338; - $[267] = t339; - $[268] = t34; - $[269] = t340; - $[270] = t341; - $[271] = t342; - $[272] = t343; - $[273] = t344; - $[274] = t345; - $[275] = t346; - $[276] = t347; - $[277] = t348; - $[278] = t349; - $[279] = t35; - $[280] = t350; - $[281] = t351; - $[282] = t352; - $[283] = t353; - $[284] = t354; - $[285] = t355; - $[286] = t356; - $[287] = t357; - $[288] = t358; - $[289] = t359; - $[290] = t36; - $[291] = t360; - $[292] = t361; - $[293] = t362; - $[294] = t363; - $[295] = t364; - $[296] = t365; - $[297] = t366; - $[298] = t367; - $[299] = t368; - $[300] = t369; - $[301] = t37; - $[302] = t370; - $[303] = t371; - $[304] = t372; - $[305] = t373; - $[306] = t374; - $[307] = t375; - $[308] = t376; - $[309] = t377; - $[310] = t378; - $[311] = t379; - $[312] = t38; - $[313] = t380; - $[314] = t381; - $[315] = t382; - $[316] = t383; - $[317] = t384; - $[318] = t385; - $[319] = t386; - $[320] = t387; - $[321] = t388; - $[322] = t389; - $[323] = t39; - $[324] = t390; - $[325] = t391; - $[326] = t392; - $[327] = t393; - $[328] = t394; - $[329] = t395; - $[330] = t396; - $[331] = t397; - $[332] = t398; - $[333] = t399; - $[334] = t4; - $[335] = t40; - $[336] = t41; - $[337] = t42; - $[338] = t43; - $[339] = t44; - $[340] = t45; - $[341] = t46; - $[342] = t47; - $[343] = t48; - $[344] = t49; - $[345] = t5; - $[346] = t50; - $[347] = t51; - $[348] = t52; - $[349] = t53; - $[350] = t54; - $[351] = t55; - $[352] = t56; - $[353] = t57; - $[354] = t58; - $[355] = t59; - $[356] = t6; - $[357] = t60; - $[358] = t61; - $[359] = t62; - $[360] = t63; - $[361] = t64; - $[362] = t65; - $[363] = t66; - $[364] = t67; - $[365] = t68; - $[366] = t69; - $[367] = t7; - $[368] = t70; - $[369] = t71; - $[370] = t72; - $[371] = t73; - $[372] = t74; - $[373] = t75; - $[374] = t76; - $[375] = t77; - $[376] = t78; - $[377] = t79; - $[378] = t8; - $[379] = t80; - $[380] = t81; - $[381] = t82; - $[382] = t83; - $[383] = t84; - $[384] = t85; - $[385] = t86; - $[386] = t87; - $[387] = t88; - $[388] = t89; - $[389] = t9; - $[390] = t90; - $[391] = t91; - $[392] = t92; - $[393] = t93; - $[394] = t94; - $[395] = t95; - $[396] = t96; - $[397] = t97; - $[398] = t98; - $[399] = t99; - $[400] = t400; - } else { - t400 = $[400]; - } - return t400; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/many-scopes-no-stack-overflow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/many-scopes-no-stack-overflow.js deleted file mode 100644 index 9a5359fa3fa7..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/many-scopes-no-stack-overflow.js +++ /dev/null @@ -1,810 +0,0 @@ -// Test that the compiler handles components with many sequential reactive scopes. -// This exercises the iterative (non-recursive) fallthrough traversal in -// BuildReactiveFunction. Previously, each scope's fallthrough was visited via -// a recursive call, causing stack overflow (SIGSEGV) on inputs with hundreds -// of scopes. - -function ManyScopes(props) { - const t0 = props.items[0]; - const t1 = props.items[1]; - const t2 = props.items[2]; - const t3 = props.items[3]; - const t4 = props.items[4]; - const t5 = props.items[5]; - const t6 = props.items[6]; - const t7 = props.items[7]; - const t8 = props.items[8]; - const t9 = props.items[9]; - const t10 = props.items[10]; - const t11 = props.items[11]; - const t12 = props.items[12]; - const t13 = props.items[13]; - const t14 = props.items[14]; - const t15 = props.items[15]; - const t16 = props.items[16]; - const t17 = props.items[17]; - const t18 = props.items[18]; - const t19 = props.items[19]; - const t20 = props.items[20]; - const t21 = props.items[21]; - const t22 = props.items[22]; - const t23 = props.items[23]; - const t24 = props.items[24]; - const t25 = props.items[25]; - const t26 = props.items[26]; - const t27 = props.items[27]; - const t28 = props.items[28]; - const t29 = props.items[29]; - const t30 = props.items[30]; - const t31 = props.items[31]; - const t32 = props.items[32]; - const t33 = props.items[33]; - const t34 = props.items[34]; - const t35 = props.items[35]; - const t36 = props.items[36]; - const t37 = props.items[37]; - const t38 = props.items[38]; - const t39 = props.items[39]; - const t40 = props.items[40]; - const t41 = props.items[41]; - const t42 = props.items[42]; - const t43 = props.items[43]; - const t44 = props.items[44]; - const t45 = props.items[45]; - const t46 = props.items[46]; - const t47 = props.items[47]; - const t48 = props.items[48]; - const t49 = props.items[49]; - const t50 = props.items[50]; - const t51 = props.items[51]; - const t52 = props.items[52]; - const t53 = props.items[53]; - const t54 = props.items[54]; - const t55 = props.items[55]; - const t56 = props.items[56]; - const t57 = props.items[57]; - const t58 = props.items[58]; - const t59 = props.items[59]; - const t60 = props.items[60]; - const t61 = props.items[61]; - const t62 = props.items[62]; - const t63 = props.items[63]; - const t64 = props.items[64]; - const t65 = props.items[65]; - const t66 = props.items[66]; - const t67 = props.items[67]; - const t68 = props.items[68]; - const t69 = props.items[69]; - const t70 = props.items[70]; - const t71 = props.items[71]; - const t72 = props.items[72]; - const t73 = props.items[73]; - const t74 = props.items[74]; - const t75 = props.items[75]; - const t76 = props.items[76]; - const t77 = props.items[77]; - const t78 = props.items[78]; - const t79 = props.items[79]; - const t80 = props.items[80]; - const t81 = props.items[81]; - const t82 = props.items[82]; - const t83 = props.items[83]; - const t84 = props.items[84]; - const t85 = props.items[85]; - const t86 = props.items[86]; - const t87 = props.items[87]; - const t88 = props.items[88]; - const t89 = props.items[89]; - const t90 = props.items[90]; - const t91 = props.items[91]; - const t92 = props.items[92]; - const t93 = props.items[93]; - const t94 = props.items[94]; - const t95 = props.items[95]; - const t96 = props.items[96]; - const t97 = props.items[97]; - const t98 = props.items[98]; - const t99 = props.items[99]; - const t100 = props.items[100]; - const t101 = props.items[101]; - const t102 = props.items[102]; - const t103 = props.items[103]; - const t104 = props.items[104]; - const t105 = props.items[105]; - const t106 = props.items[106]; - const t107 = props.items[107]; - const t108 = props.items[108]; - const t109 = props.items[109]; - const t110 = props.items[110]; - const t111 = props.items[111]; - const t112 = props.items[112]; - const t113 = props.items[113]; - const t114 = props.items[114]; - const t115 = props.items[115]; - const t116 = props.items[116]; - const t117 = props.items[117]; - const t118 = props.items[118]; - const t119 = props.items[119]; - const t120 = props.items[120]; - const t121 = props.items[121]; - const t122 = props.items[122]; - const t123 = props.items[123]; - const t124 = props.items[124]; - const t125 = props.items[125]; - const t126 = props.items[126]; - const t127 = props.items[127]; - const t128 = props.items[128]; - const t129 = props.items[129]; - const t130 = props.items[130]; - const t131 = props.items[131]; - const t132 = props.items[132]; - const t133 = props.items[133]; - const t134 = props.items[134]; - const t135 = props.items[135]; - const t136 = props.items[136]; - const t137 = props.items[137]; - const t138 = props.items[138]; - const t139 = props.items[139]; - const t140 = props.items[140]; - const t141 = props.items[141]; - const t142 = props.items[142]; - const t143 = props.items[143]; - const t144 = props.items[144]; - const t145 = props.items[145]; - const t146 = props.items[146]; - const t147 = props.items[147]; - const t148 = props.items[148]; - const t149 = props.items[149]; - const t150 = props.items[150]; - const t151 = props.items[151]; - const t152 = props.items[152]; - const t153 = props.items[153]; - const t154 = props.items[154]; - const t155 = props.items[155]; - const t156 = props.items[156]; - const t157 = props.items[157]; - const t158 = props.items[158]; - const t159 = props.items[159]; - const t160 = props.items[160]; - const t161 = props.items[161]; - const t162 = props.items[162]; - const t163 = props.items[163]; - const t164 = props.items[164]; - const t165 = props.items[165]; - const t166 = props.items[166]; - const t167 = props.items[167]; - const t168 = props.items[168]; - const t169 = props.items[169]; - const t170 = props.items[170]; - const t171 = props.items[171]; - const t172 = props.items[172]; - const t173 = props.items[173]; - const t174 = props.items[174]; - const t175 = props.items[175]; - const t176 = props.items[176]; - const t177 = props.items[177]; - const t178 = props.items[178]; - const t179 = props.items[179]; - const t180 = props.items[180]; - const t181 = props.items[181]; - const t182 = props.items[182]; - const t183 = props.items[183]; - const t184 = props.items[184]; - const t185 = props.items[185]; - const t186 = props.items[186]; - const t187 = props.items[187]; - const t188 = props.items[188]; - const t189 = props.items[189]; - const t190 = props.items[190]; - const t191 = props.items[191]; - const t192 = props.items[192]; - const t193 = props.items[193]; - const t194 = props.items[194]; - const t195 = props.items[195]; - const t196 = props.items[196]; - const t197 = props.items[197]; - const t198 = props.items[198]; - const t199 = props.items[199]; - const t200 = props.items[200]; - const t201 = props.items[201]; - const t202 = props.items[202]; - const t203 = props.items[203]; - const t204 = props.items[204]; - const t205 = props.items[205]; - const t206 = props.items[206]; - const t207 = props.items[207]; - const t208 = props.items[208]; - const t209 = props.items[209]; - const t210 = props.items[210]; - const t211 = props.items[211]; - const t212 = props.items[212]; - const t213 = props.items[213]; - const t214 = props.items[214]; - const t215 = props.items[215]; - const t216 = props.items[216]; - const t217 = props.items[217]; - const t218 = props.items[218]; - const t219 = props.items[219]; - const t220 = props.items[220]; - const t221 = props.items[221]; - const t222 = props.items[222]; - const t223 = props.items[223]; - const t224 = props.items[224]; - const t225 = props.items[225]; - const t226 = props.items[226]; - const t227 = props.items[227]; - const t228 = props.items[228]; - const t229 = props.items[229]; - const t230 = props.items[230]; - const t231 = props.items[231]; - const t232 = props.items[232]; - const t233 = props.items[233]; - const t234 = props.items[234]; - const t235 = props.items[235]; - const t236 = props.items[236]; - const t237 = props.items[237]; - const t238 = props.items[238]; - const t239 = props.items[239]; - const t240 = props.items[240]; - const t241 = props.items[241]; - const t242 = props.items[242]; - const t243 = props.items[243]; - const t244 = props.items[244]; - const t245 = props.items[245]; - const t246 = props.items[246]; - const t247 = props.items[247]; - const t248 = props.items[248]; - const t249 = props.items[249]; - const t250 = props.items[250]; - const t251 = props.items[251]; - const t252 = props.items[252]; - const t253 = props.items[253]; - const t254 = props.items[254]; - const t255 = props.items[255]; - const t256 = props.items[256]; - const t257 = props.items[257]; - const t258 = props.items[258]; - const t259 = props.items[259]; - const t260 = props.items[260]; - const t261 = props.items[261]; - const t262 = props.items[262]; - const t263 = props.items[263]; - const t264 = props.items[264]; - const t265 = props.items[265]; - const t266 = props.items[266]; - const t267 = props.items[267]; - const t268 = props.items[268]; - const t269 = props.items[269]; - const t270 = props.items[270]; - const t271 = props.items[271]; - const t272 = props.items[272]; - const t273 = props.items[273]; - const t274 = props.items[274]; - const t275 = props.items[275]; - const t276 = props.items[276]; - const t277 = props.items[277]; - const t278 = props.items[278]; - const t279 = props.items[279]; - const t280 = props.items[280]; - const t281 = props.items[281]; - const t282 = props.items[282]; - const t283 = props.items[283]; - const t284 = props.items[284]; - const t285 = props.items[285]; - const t286 = props.items[286]; - const t287 = props.items[287]; - const t288 = props.items[288]; - const t289 = props.items[289]; - const t290 = props.items[290]; - const t291 = props.items[291]; - const t292 = props.items[292]; - const t293 = props.items[293]; - const t294 = props.items[294]; - const t295 = props.items[295]; - const t296 = props.items[296]; - const t297 = props.items[297]; - const t298 = props.items[298]; - const t299 = props.items[299]; - const t300 = props.items[300]; - const t301 = props.items[301]; - const t302 = props.items[302]; - const t303 = props.items[303]; - const t304 = props.items[304]; - const t305 = props.items[305]; - const t306 = props.items[306]; - const t307 = props.items[307]; - const t308 = props.items[308]; - const t309 = props.items[309]; - const t310 = props.items[310]; - const t311 = props.items[311]; - const t312 = props.items[312]; - const t313 = props.items[313]; - const t314 = props.items[314]; - const t315 = props.items[315]; - const t316 = props.items[316]; - const t317 = props.items[317]; - const t318 = props.items[318]; - const t319 = props.items[319]; - const t320 = props.items[320]; - const t321 = props.items[321]; - const t322 = props.items[322]; - const t323 = props.items[323]; - const t324 = props.items[324]; - const t325 = props.items[325]; - const t326 = props.items[326]; - const t327 = props.items[327]; - const t328 = props.items[328]; - const t329 = props.items[329]; - const t330 = props.items[330]; - const t331 = props.items[331]; - const t332 = props.items[332]; - const t333 = props.items[333]; - const t334 = props.items[334]; - const t335 = props.items[335]; - const t336 = props.items[336]; - const t337 = props.items[337]; - const t338 = props.items[338]; - const t339 = props.items[339]; - const t340 = props.items[340]; - const t341 = props.items[341]; - const t342 = props.items[342]; - const t343 = props.items[343]; - const t344 = props.items[344]; - const t345 = props.items[345]; - const t346 = props.items[346]; - const t347 = props.items[347]; - const t348 = props.items[348]; - const t349 = props.items[349]; - const t350 = props.items[350]; - const t351 = props.items[351]; - const t352 = props.items[352]; - const t353 = props.items[353]; - const t354 = props.items[354]; - const t355 = props.items[355]; - const t356 = props.items[356]; - const t357 = props.items[357]; - const t358 = props.items[358]; - const t359 = props.items[359]; - const t360 = props.items[360]; - const t361 = props.items[361]; - const t362 = props.items[362]; - const t363 = props.items[363]; - const t364 = props.items[364]; - const t365 = props.items[365]; - const t366 = props.items[366]; - const t367 = props.items[367]; - const t368 = props.items[368]; - const t369 = props.items[369]; - const t370 = props.items[370]; - const t371 = props.items[371]; - const t372 = props.items[372]; - const t373 = props.items[373]; - const t374 = props.items[374]; - const t375 = props.items[375]; - const t376 = props.items[376]; - const t377 = props.items[377]; - const t378 = props.items[378]; - const t379 = props.items[379]; - const t380 = props.items[380]; - const t381 = props.items[381]; - const t382 = props.items[382]; - const t383 = props.items[383]; - const t384 = props.items[384]; - const t385 = props.items[385]; - const t386 = props.items[386]; - const t387 = props.items[387]; - const t388 = props.items[388]; - const t389 = props.items[389]; - const t390 = props.items[390]; - const t391 = props.items[391]; - const t392 = props.items[392]; - const t393 = props.items[393]; - const t394 = props.items[394]; - const t395 = props.items[395]; - const t396 = props.items[396]; - const t397 = props.items[397]; - const t398 = props.items[398]; - const t399 = props.items[399]; - return [ - t0, - t1, - t2, - t3, - t4, - t5, - t6, - t7, - t8, - t9, - t10, - t11, - t12, - t13, - t14, - t15, - t16, - t17, - t18, - t19, - t20, - t21, - t22, - t23, - t24, - t25, - t26, - t27, - t28, - t29, - t30, - t31, - t32, - t33, - t34, - t35, - t36, - t37, - t38, - t39, - t40, - t41, - t42, - t43, - t44, - t45, - t46, - t47, - t48, - t49, - t50, - t51, - t52, - t53, - t54, - t55, - t56, - t57, - t58, - t59, - t60, - t61, - t62, - t63, - t64, - t65, - t66, - t67, - t68, - t69, - t70, - t71, - t72, - t73, - t74, - t75, - t76, - t77, - t78, - t79, - t80, - t81, - t82, - t83, - t84, - t85, - t86, - t87, - t88, - t89, - t90, - t91, - t92, - t93, - t94, - t95, - t96, - t97, - t98, - t99, - t100, - t101, - t102, - t103, - t104, - t105, - t106, - t107, - t108, - t109, - t110, - t111, - t112, - t113, - t114, - t115, - t116, - t117, - t118, - t119, - t120, - t121, - t122, - t123, - t124, - t125, - t126, - t127, - t128, - t129, - t130, - t131, - t132, - t133, - t134, - t135, - t136, - t137, - t138, - t139, - t140, - t141, - t142, - t143, - t144, - t145, - t146, - t147, - t148, - t149, - t150, - t151, - t152, - t153, - t154, - t155, - t156, - t157, - t158, - t159, - t160, - t161, - t162, - t163, - t164, - t165, - t166, - t167, - t168, - t169, - t170, - t171, - t172, - t173, - t174, - t175, - t176, - t177, - t178, - t179, - t180, - t181, - t182, - t183, - t184, - t185, - t186, - t187, - t188, - t189, - t190, - t191, - t192, - t193, - t194, - t195, - t196, - t197, - t198, - t199, - t200, - t201, - t202, - t203, - t204, - t205, - t206, - t207, - t208, - t209, - t210, - t211, - t212, - t213, - t214, - t215, - t216, - t217, - t218, - t219, - t220, - t221, - t222, - t223, - t224, - t225, - t226, - t227, - t228, - t229, - t230, - t231, - t232, - t233, - t234, - t235, - t236, - t237, - t238, - t239, - t240, - t241, - t242, - t243, - t244, - t245, - t246, - t247, - t248, - t249, - t250, - t251, - t252, - t253, - t254, - t255, - t256, - t257, - t258, - t259, - t260, - t261, - t262, - t263, - t264, - t265, - t266, - t267, - t268, - t269, - t270, - t271, - t272, - t273, - t274, - t275, - t276, - t277, - t278, - t279, - t280, - t281, - t282, - t283, - t284, - t285, - t286, - t287, - t288, - t289, - t290, - t291, - t292, - t293, - t294, - t295, - t296, - t297, - t298, - t299, - t300, - t301, - t302, - t303, - t304, - t305, - t306, - t307, - t308, - t309, - t310, - t311, - t312, - t313, - t314, - t315, - t316, - t317, - t318, - t319, - t320, - t321, - t322, - t323, - t324, - t325, - t326, - t327, - t328, - t329, - t330, - t331, - t332, - t333, - t334, - t335, - t336, - t337, - t338, - t339, - t340, - t341, - t342, - t343, - t344, - t345, - t346, - t347, - t348, - t349, - t350, - t351, - t352, - t353, - t354, - t355, - t356, - t357, - t358, - t359, - t360, - t361, - t362, - t363, - t364, - t365, - t366, - t367, - t368, - t369, - t370, - t371, - t372, - t373, - t374, - t375, - t376, - t377, - t378, - t379, - t380, - t381, - t382, - t383, - t384, - t385, - t386, - t387, - t388, - t389, - t390, - t391, - t392, - t393, - t394, - t395, - t396, - t397, - t398, - t399, - ]; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-captured-var.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-captured-var.flow.expect.md deleted file mode 100644 index 204e6e9bfe9b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-captured-var.flow.expect.md +++ /dev/null @@ -1,46 +0,0 @@ - -## Input - -```javascript -// @flow -// Match expression with optional chaining in discriminant. -// Hermes desugars this into a synthetic IIFE that captures `y` from the outer scope. -// The IIFE has start=end=0, requiring synthetic scope resolution to find captured context. - -export default component MatchExprCapturedVar(x: ?{v: string}, y: number) { - return match (x?.v) { - 'a' => y + 1, - _ => y + 2, - }; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; - -export default function MatchExprCapturedVar(t0) { - const $ = _c(2); - const { x, y } = t0; - let t1; - if ($[0] !== y) { - t1 = ($$gen$m0) => { - if ($$gen$m0 === "a") { - return y + 1; - } - return y + 2; - }; - $[0] = y; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1(x?.v); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-captured-var.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-captured-var.flow.js deleted file mode 100644 index 736490552006..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-captured-var.flow.js +++ /dev/null @@ -1,11 +0,0 @@ -// @flow -// Match expression with optional chaining in discriminant. -// Hermes desugars this into a synthetic IIFE that captures `y` from the outer scope. -// The IIFE has start=end=0, requiring synthetic scope resolution to find captured context. - -export default component MatchExprCapturedVar(x: ?{v: string}, y: number) { - return match (x?.v) { - 'a' => y + 1, - _ => y + 2, - }; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-jsx-spread.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-jsx-spread.flow.expect.md deleted file mode 100644 index 3676c5d227e0..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-jsx-spread.flow.expect.md +++ /dev/null @@ -1,70 +0,0 @@ - -## Input - -```javascript -// @flow -// Match expression with JSX spread attribute inside an arm. -// The spread attribute ({...props}) references a captured variable from the -// outer component scope. collect_identifier_positions_from_expr must handle -// JSXSpreadAttribute to detect this capture. - -export default component MatchExprJsxSpread( - label: ?{outcome: string}, - ...props: {color?: string} -) { - return match (label?.outcome) { - 'A' => <div color="green" {...props} />, - _ => <div color="gray" {...props} />, - }; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; - -export default function MatchExprJsxSpread(t0) { - const $ = _c(8); - let label; - let props; - if ($[0] !== t0) { - ({ label, ...props } = t0); - $[0] = t0; - $[1] = label; - $[2] = props; - } else { - label = $[1]; - props = $[2]; - } - let t1; - if ($[3] !== props) { - t1 = ($$gen$m0) => { - if ($$gen$m0 === "A") { - return <div color="green" {...props} />; - } - return <div color="gray" {...props} />; - }; - $[3] = props; - $[4] = t1; - } else { - t1 = $[4]; - } - const t2 = label?.outcome; - let t3; - if ($[5] !== t1 || $[6] !== t2) { - t3 = t1(t2); - $[5] = t1; - $[6] = t2; - $[7] = t3; - } else { - t3 = $[7]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-jsx-spread.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-jsx-spread.flow.js deleted file mode 100644 index 2818eed8c89f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-jsx-spread.flow.js +++ /dev/null @@ -1,15 +0,0 @@ -// @flow -// Match expression with JSX spread attribute inside an arm. -// The spread attribute ({...props}) references a captured variable from the -// outer component scope. collect_identifier_positions_from_expr must handle -// JSXSpreadAttribute to detect this capture. - -export default component MatchExprJsxSpread( - label: ?{outcome: string}, - ...props: {color?: string} -) { - return match (label?.outcome) { - 'A' => <div color="green" {...props} />, - _ => <div color="gray" {...props} />, - }; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-multi-gen-bindings.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-multi-gen-bindings.flow.expect.md deleted file mode 100644 index aecb2c6cdf1c..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-multi-gen-bindings.flow.expect.md +++ /dev/null @@ -1,77 +0,0 @@ - -## Input - -```javascript -// @flow -// Match expression with multiple match arms that generate separate $$gen$m -// bindings ($$gen$m0, $$gen$m1). Hermes match desugar places all synthetic -// identifiers at position 0. The scope resolver must not corrupt bindings -// when multiple $$gen$m names share the same source position. - -export default component MatchExprMultiGenBindings( - x: ?{v: string}, - y: ?{w: number} -) { - const a = match (x?.v) { - 'yes' => 1, - _ => 0, - }; - const b = match (y?.w) { - 42 => 'found', - _ => 'not found', - }; - return ( - <div> - {a} - {b} - </div> - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; - -export default function MatchExprMultiGenBindings(t0) { - const $ = _c(3); - const { x, y } = t0; - - const a = _temp(x?.v); - - const b = _temp2(y?.w); - let t1; - if ($[0] !== a || $[1] !== b) { - t1 = ( - <div> - {a} - {b} - </div> - ); - $[0] = a; - $[1] = b; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} -function _temp2($$gen$m1) { - if ($$gen$m1 === 42) { - return "found"; - } - return "not found"; -} -function _temp($$gen$m0) { - if ($$gen$m0 === "yes") { - return 1; - } - return 0; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-multi-gen-bindings.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-multi-gen-bindings.flow.js deleted file mode 100644 index f681fcf497ed..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-multi-gen-bindings.flow.js +++ /dev/null @@ -1,25 +0,0 @@ -// @flow -// Match expression with multiple match arms that generate separate $$gen$m -// bindings ($$gen$m0, $$gen$m1). Hermes match desugar places all synthetic -// identifiers at position 0. The scope resolver must not corrupt bindings -// when multiple $$gen$m names share the same source position. - -export default component MatchExprMultiGenBindings( - x: ?{v: string}, - y: ?{w: number} -) { - const a = match (x?.v) { - 'yes' => 1, - _ => 0, - }; - const b = match (y?.w) { - 42 => 'found', - _ => 'not found', - }; - return ( - <div> - {a} - {b} - </div> - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-outlined-jsx.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-outlined-jsx.flow.expect.md deleted file mode 100644 index fccfd4f90f87..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-outlined-jsx.flow.expect.md +++ /dev/null @@ -1,67 +0,0 @@ - -## Input - -```javascript -// @flow @enableJsxOutlining -// Match expression inside a component with JSX outlining enabled. -// Hermes desugars match into a synthetic arrow with parameter $$gen$m0 at -// position 0. When JSX outlining moves this into an outlined _temp function, -// $$gen$m0 must NOT get a $0 suffix from rename_variables — it is a local -// parameter, not a global. - -export default component MatchExprOutlinedJsx( - item: ?{status: string}, - label: string -) { - return ( - <div> - { - match (item?.status) { - 'active' => <span>{label}</span>, - _ => <span>{'none'}</span>, - } - } - </div> - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; - -export default function MatchExprOutlinedJsx(t0) { - const $ = _c(5); - const { item, label } = t0; - let t1; - if ($[0] !== label) { - t1 = ($$gen$m0) => { - if ($$gen$m0 === "active") { - return <span>{label}</span>; - } - return <span>{"none"}</span>; - }; - $[0] = label; - $[1] = t1; - } else { - t1 = $[1]; - } - const t2 = item?.status; - let t3; - if ($[2] !== t1 || $[3] !== t2) { - t3 = <div>{t1(t2)}</div>; - $[2] = t1; - $[3] = t2; - $[4] = t3; - } else { - t3 = $[4]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-outlined-jsx.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-outlined-jsx.flow.js deleted file mode 100644 index d678839b94f9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expr-outlined-jsx.flow.js +++ /dev/null @@ -1,22 +0,0 @@ -// @flow @enableJsxOutlining -// Match expression inside a component with JSX outlining enabled. -// Hermes desugars match into a synthetic arrow with parameter $$gen$m0 at -// position 0. When JSX outlining moves this into an outlined _temp function, -// $$gen$m0 must NOT get a $0 suffix from rename_variables — it is a local -// parameter, not a global. - -export default component MatchExprOutlinedJsx( - item: ?{status: string}, - label: string -) { - return ( - <div> - { - match (item?.status) { - 'active' => <span>{label}</span>, - _ => <span>{'none'}</span>, - } - } - </div> - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expression-with-tuple-and-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expression-with-tuple-and-early-return.expect.md deleted file mode 100644 index bc1090974588..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expression-with-tuple-and-early-return.expect.md +++ /dev/null @@ -1,145 +0,0 @@ - -## Input - -```javascript -// @flow -import {useState} from 'react'; - -/** - * Test that match expressions with tuple patterns don't produce overly - * conservative mutation effects on the matched values. Without the fix - * for ModuleLocal global type resolution, Array.isArray inside the match - * IIFE body would not get its signature resolved, causing - * MutateTransitiveConditionally on the match argument and wider mutable - * ranges that prevent fine-grained memoization. - */ -function useFoo(data: {status: string, priority: string}) { - const [count] = useState(0); - const active = count > 0; - - if (data.status === 'closed') { - return active ? 'closed_active' : 'closed'; - } - - return match ([data.priority, active]) { - ['high', true] => 'high_active', - ['high', false] => 'high_inactive', - ['medium', true] => 'medium_active', - ['medium', false] => 'medium_inactive', - ['low', true] => 'low_active', - ['low', false] => 'low_inactive', - [_, true] => 'other_active', - [_, false] => 'other_inactive', - }; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{status: 'open', priority: 'high'}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -import { useState } from "react"; - -function useFoo(data) { - const $ = _c(3); - const [count] = useState(0); - const active = count > 0; - - if (data.status === "closed") { - return active ? "closed_active" : "closed"; - } - let t0; - if ($[0] !== active || $[1] !== data.priority) { - t0 = [data.priority, active]; - $[0] = active; - $[1] = data.priority; - $[2] = t0; - } else { - t0 = $[2]; - } - return _temp(t0); -} -function _temp($$gen$m0) { - if ( - Array.isArray($$gen$m0) && - $$gen$m0.length === 2 && - $$gen$m0[0] === "high" && - $$gen$m0[1] === true - ) { - return "high_active"; - } - if ( - Array.isArray($$gen$m0) && - $$gen$m0.length === 2 && - $$gen$m0[0] === "high" && - $$gen$m0[1] === false - ) { - return "high_inactive"; - } - if ( - Array.isArray($$gen$m0) && - $$gen$m0.length === 2 && - $$gen$m0[0] === "medium" && - $$gen$m0[1] === true - ) { - return "medium_active"; - } - if ( - Array.isArray($$gen$m0) && - $$gen$m0.length === 2 && - $$gen$m0[0] === "medium" && - $$gen$m0[1] === false - ) { - return "medium_inactive"; - } - if ( - Array.isArray($$gen$m0) && - $$gen$m0.length === 2 && - $$gen$m0[0] === "low" && - $$gen$m0[1] === true - ) { - return "low_active"; - } - if ( - Array.isArray($$gen$m0) && - $$gen$m0.length === 2 && - $$gen$m0[0] === "low" && - $$gen$m0[1] === false - ) { - return "low_inactive"; - } - if ( - Array.isArray($$gen$m0) && - $$gen$m0.length === 2 && - $$gen$m0[1] === true - ) { - return "other_active"; - } - if ( - Array.isArray($$gen$m0) && - $$gen$m0.length === 2 && - $$gen$m0[1] === false - ) { - return "other_inactive"; - } - throw Error( - "Match: No case succesfully matched. Make exhaustive or add a wildcard case using '_'. Argument: " + - $$gen$m0, - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{ status: "open", priority: "high" }], -}; - -``` - -### Eval output -(kind: ok) "high_inactive" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expression-with-tuple-and-early-return.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expression-with-tuple-and-early-return.js deleted file mode 100644 index ca47e8f2b1ec..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-expression-with-tuple-and-early-return.js +++ /dev/null @@ -1,35 +0,0 @@ -// @flow -import {useState} from 'react'; - -/** - * Test that match expressions with tuple patterns don't produce overly - * conservative mutation effects on the matched values. Without the fix - * for ModuleLocal global type resolution, Array.isArray inside the match - * IIFE body would not get its signature resolved, causing - * MutateTransitiveConditionally on the match argument and wider mutable - * ranges that prevent fine-grained memoization. - */ -function useFoo(data: {status: string, priority: string}) { - const [count] = useState(0); - const active = count > 0; - - if (data.status === 'closed') { - return active ? 'closed_active' : 'closed'; - } - - return match ([data.priority, active]) { - ['high', true] => 'high_active', - ['high', false] => 'high_inactive', - ['medium', true] => 'medium_active', - ['medium', false] => 'medium_inactive', - ['low', true] => 'low_active', - ['low', false] => 'low_inactive', - [_, true] => 'other_active', - [_, false] => 'other_inactive', - }; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useFoo, - params: [{status: 'open', priority: 'high'}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-stmt-self-ref-const.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-stmt-self-ref-const.flow.expect.md deleted file mode 100644 index 373a47977f0e..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-stmt-self-ref-const.flow.expect.md +++ /dev/null @@ -1,55 +0,0 @@ - -## Input - -```javascript -// @flow -// Match statement with const arrow function that references itself inside an arm. -// Hermes desugars the match into a labeled block with synthetic if-bodies at -// position 0. The const declaration needs correct block scope resolution (not -// the Program scope) to avoid broken hoisting. - -export default component MatchStmtSelfRefConst(x: string) { - match (x) { - 'a' => { - const handler = () => { - handler(); - }; - document.addEventListener('click', handler); - } - _ => {} - } - return <div />; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; - -export default function MatchStmtSelfRefConst(t0) { - const $ = _c(1); - const { x } = t0; - bb0: if (x === "a") { - const handler = () => { - handler(); - }; - - document.addEventListener("click", handler); - break bb0; - } - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = <div />; - $[0] = t1; - } else { - t1 = $[0]; - } - return t1; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-stmt-self-ref-const.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-stmt-self-ref-const.flow.js deleted file mode 100644 index 9b188e158b09..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/match-stmt-self-ref-const.flow.js +++ /dev/null @@ -1,18 +0,0 @@ -// @flow -// Match statement with const arrow function that references itself inside an arm. -// Hermes desugars the match into a labeled block with synthetic if-bodies at -// position 0. The const declaration needs correct block scope resolution (not -// the Program scope) to avoid broken hoisting. - -export default component MatchStmtSelfRefConst(x: string) { - match (x) { - 'a' => { - const handler = () => { - handler(); - }; - document.addEventListener('click', handler); - } - _ => {} - } - return <div />; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-scope-merge-mutable-range-sync.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-scope-merge-mutable-range-sync.expect.md deleted file mode 100644 index 128d8cd87ef9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-scope-merge-mutable-range-sync.expect.md +++ /dev/null @@ -1,323 +0,0 @@ - -## Input - -```javascript -import fbt from 'fbt'; -import * as React from 'react'; -import {useState} from 'react'; - -// Minimized from MusicPartnerManagerMusicWorkDocumentStoreRenderer.react.js -// Tests AlignMethodCallScopes mutable_range sync after scope merging. - -function nullthrows(x) { - if (x != null) return x; - throw new Error('nullthrows'); -} - -function Component({store}) { - const [isShown, setIsShown] = useState(false); - const id = nullthrows(store?.id); - const storeUri = nullthrows(store?.store_uri); - const licensedGeos = nullthrows(store?.licensed_geos); - const documentSchema = nullthrows(store?.document_schema); - const ingestionEnabled = nullthrows(store?.ingestion_enabled); - const cwrSenderIds = store?.cwr_sender_ids; - - if (!store.hasOwnProperty('store_uri')) { - return; - } - - return ( - <div key={store.id}> - <span> - <span> - {fbt( - `Directory: ${fbt.param( - 'store URI', - storeUri.replace('sftp://sftp.fb.com', '') - )}`, - 'directory info' - )} - </span> - <span> - {fbt(`Schema: ${fbt.param('schema', documentSchema)}`, 'schema')},{' '} - {fbt(`Geos: ${fbt.param('geos', licensedGeos.length)}`, 'geos count')} - ,{' '} - </span> - {cwrSenderIds != null && <span>Sender ID: {cwrSenderIds[0]}</span>} - <span>Ingestion: {ingestionEnabled ? 'Enabled' : 'Disabled'}</span> - <span> - {[...licensedGeos].sort().join(',')} - <button - onClick={() => { - console.log('copied'); - }}> - Copy - </button> - </span> - </span> - <span> - <button onClick={() => setIsShown(true)} /> - <button onClick={() => console.log(id.toString())} /> - {isShown && <button onClick={() => setIsShown(false)} />} - </span> - </div> - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [ - { - store: { - id: '1', - store_uri: 'sftp://sftp.fb.com/Music', - licensed_geos: ['US', 'GB'], - document_schema: 'CWR', - ingestion_enabled: true, - cwr_sender_ids: ['123'], - }, - }, - ], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -import fbt from "fbt"; -import * as React from "react"; -import { useState } from "react"; - -// Minimized from MusicPartnerManagerMusicWorkDocumentStoreRenderer.react.js -// Tests AlignMethodCallScopes mutable_range sync after scope merging. - -function nullthrows(x) { - if (x != null) { - return x; - } - throw new Error("nullthrows"); -} - -function Component(t0) { - const $ = _c(37); - const { store } = t0; - const [isShown, setIsShown] = useState(false); - const t1 = store?.id; - let t2; - if ($[0] !== t1) { - t2 = nullthrows(t1); - $[0] = t1; - $[1] = t2; - } else { - t2 = $[1]; - } - const id = t2; - let t3; - let t4; - let t5; - let t6; - let t7; - let t8; - let t9; - if ($[2] !== store) { - t9 = Symbol.for("react.early_return_sentinel"); - bb0: { - const storeUri = nullthrows(store?.store_uri); - const licensedGeos = nullthrows(store?.licensed_geos); - const documentSchema = nullthrows(store?.document_schema); - const ingestionEnabled = nullthrows(store?.ingestion_enabled); - const cwrSenderIds = store?.cwr_sender_ids; - if (!store.hasOwnProperty("store_uri")) { - t9 = undefined; - break bb0; - } - t8 = store.id; - const t10 = fbt._( - "Directory: {store URI}", - [fbt._param("store URI", storeUri.replace("sftp://sftp.fb.com", ""))], - { hk: "y1DI6" }, - ); - if ($[10] !== t10) { - t4 = <span>{t10}</span>; - $[10] = t10; - $[11] = t4; - } else { - t4 = $[11]; - } - t5 = ( - <span> - {fbt._("Schema: {schema}", [fbt._param("schema", documentSchema)], { - hk: "1Ua9F9", - })} - ,{" "} - {fbt._("Geos: {geos}", [fbt._param("geos", licensedGeos.length)], { - hk: "1PRSzD", - })} - ,{" "} - </span> - ); - if ($[12] !== cwrSenderIds) { - t6 = cwrSenderIds != null && <span>Sender ID: {cwrSenderIds[0]}</span>; - $[12] = cwrSenderIds; - $[13] = t6; - } else { - t6 = $[13]; - } - const t11 = ingestionEnabled ? "Enabled" : "Disabled"; - if ($[14] !== t11) { - t7 = <span>Ingestion: {t11}</span>; - $[14] = t11; - $[15] = t7; - } else { - t7 = $[15]; - } - t3 = [...licensedGeos].sort().join(","); - } - $[2] = store; - $[3] = t3; - $[4] = t4; - $[5] = t5; - $[6] = t6; - $[7] = t7; - $[8] = t8; - $[9] = t9; - } else { - t3 = $[3]; - t4 = $[4]; - t5 = $[5]; - t6 = $[6]; - t7 = $[7]; - t8 = $[8]; - t9 = $[9]; - } - if (t9 !== Symbol.for("react.early_return_sentinel")) { - return t9; - } - let t10; - if ($[16] === Symbol.for("react.memo_cache_sentinel")) { - t10 = <button onClick={_temp}>Copy</button>; - $[16] = t10; - } else { - t10 = $[16]; - } - let t11; - if ($[17] !== t3) { - t11 = ( - <span> - {t3} - {t10} - </span> - ); - $[17] = t3; - $[18] = t11; - } else { - t11 = $[18]; - } - let t12; - if ( - $[19] !== t11 || - $[20] !== t4 || - $[21] !== t5 || - $[22] !== t6 || - $[23] !== t7 - ) { - t12 = ( - <span> - {t4} - {t5} - {t6} - {t7} - {t11} - </span> - ); - $[19] = t11; - $[20] = t4; - $[21] = t5; - $[22] = t6; - $[23] = t7; - $[24] = t12; - } else { - t12 = $[24]; - } - let t13; - if ($[25] === Symbol.for("react.memo_cache_sentinel")) { - t13 = <button onClick={() => setIsShown(true)} />; - $[25] = t13; - } else { - t13 = $[25]; - } - let t14; - if ($[26] !== id) { - t14 = <button onClick={() => console.log(id.toString())} />; - $[26] = id; - $[27] = t14; - } else { - t14 = $[27]; - } - let t15; - if ($[28] !== isShown) { - t15 = isShown && <button onClick={() => setIsShown(false)} />; - $[28] = isShown; - $[29] = t15; - } else { - t15 = $[29]; - } - let t16; - if ($[30] !== t14 || $[31] !== t15) { - t16 = ( - <span> - {t13} - {t14} - {t15} - </span> - ); - $[30] = t14; - $[31] = t15; - $[32] = t16; - } else { - t16 = $[32]; - } - let t17; - if ($[33] !== t12 || $[34] !== t16 || $[35] !== t8) { - t17 = ( - <div key={t8}> - {t12} - {t16} - </div> - ); - $[33] = t12; - $[34] = t16; - $[35] = t8; - $[36] = t17; - } else { - t17 = $[36]; - } - return t17; -} -function _temp() { - console.log("copied"); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [ - { - store: { - id: "1", - store_uri: "sftp://sftp.fb.com/Music", - licensed_geos: ["US", "GB"], - document_schema: "CWR", - ingestion_enabled: true, - cwr_sender_ids: ["123"], - }, - }, - ], -}; - -``` - -### Eval output -(kind: ok) <div><span><span>Directory: /Music</span><span>Schema: CWR, Geos: 2, </span><span>Sender ID: 123</span><span>Ingestion: Enabled</span><span>GB,US<button>Copy</button></span></span><span><button></button><button></button></span></div> \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-scope-merge-mutable-range-sync.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-scope-merge-mutable-range-sync.js deleted file mode 100644 index 92cb36c8a83f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-scope-merge-mutable-range-sync.js +++ /dev/null @@ -1,78 +0,0 @@ -import fbt from 'fbt'; -import * as React from 'react'; -import {useState} from 'react'; - -// Minimized from MusicPartnerManagerMusicWorkDocumentStoreRenderer.react.js -// Tests AlignMethodCallScopes mutable_range sync after scope merging. - -function nullthrows(x) { - if (x != null) return x; - throw new Error('nullthrows'); -} - -function Component({store}) { - const [isShown, setIsShown] = useState(false); - const id = nullthrows(store?.id); - const storeUri = nullthrows(store?.store_uri); - const licensedGeos = nullthrows(store?.licensed_geos); - const documentSchema = nullthrows(store?.document_schema); - const ingestionEnabled = nullthrows(store?.ingestion_enabled); - const cwrSenderIds = store?.cwr_sender_ids; - - if (!store.hasOwnProperty('store_uri')) { - return; - } - - return ( - <div key={store.id}> - <span> - <span> - {fbt( - `Directory: ${fbt.param( - 'store URI', - storeUri.replace('sftp://sftp.fb.com', '') - )}`, - 'directory info' - )} - </span> - <span> - {fbt(`Schema: ${fbt.param('schema', documentSchema)}`, 'schema')},{' '} - {fbt(`Geos: ${fbt.param('geos', licensedGeos.length)}`, 'geos count')} - ,{' '} - </span> - {cwrSenderIds != null && <span>Sender ID: {cwrSenderIds[0]}</span>} - <span>Ingestion: {ingestionEnabled ? 'Enabled' : 'Disabled'}</span> - <span> - {[...licensedGeos].sort().join(',')} - <button - onClick={() => { - console.log('copied'); - }}> - Copy - </button> - </span> - </span> - <span> - <button onClick={() => setIsShown(true)} /> - <button onClick={() => console.log(id.toString())} /> - {isShown && <button onClick={() => setIsShown(false)} />} - </span> - </div> - ); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [ - { - store: { - id: '1', - store_uri: 'sftp://sftp.fb.com/Music', - licensed_geos: ['US', 'GB'], - document_schema: 'CWR', - ingestion_enabled: true, - cwr_sender_ids: ['123'], - }, - }, - ], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-annotation-mode.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-annotation-mode.expect.md deleted file mode 100644 index edf34a1da4fd..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-annotation-mode.expect.md +++ /dev/null @@ -1,37 +0,0 @@ - -## Input - -```javascript -// @compilationMode:"annotation" -if (globalThis.__DEV__) { - function useFoo() { - 'use memo'; - return [1, 2, 3]; - } -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode:"annotation" -if (globalThis.__DEV__) { - function useFoo() { - "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = [1, 2, 3]; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; - } -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-annotation-mode.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-annotation-mode.js deleted file mode 100644 index e7cbda31c930..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-annotation-mode.js +++ /dev/null @@ -1,7 +0,0 @@ -// @compilationMode:"annotation" -if (globalThis.__DEV__) { - function useFoo() { - 'use memo'; - return [1, 2, 3]; - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-for-init.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-for-init.expect.md deleted file mode 100644 index 2ed9bb851851..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-for-init.expect.md +++ /dev/null @@ -1,41 +0,0 @@ - -## Input - -```javascript -// @compilationMode:"annotation" -for ( - var useFoo = function useFoo() { - 'use memo'; - return [1, 2, 3]; - }; - false; - -) {} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode:"annotation" -for ( - var useFoo = function useFoo() { - "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = [1, 2, 3]; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; - }; - false; - -) {} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-for-init.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-for-init.js deleted file mode 100644 index b885a4ce84d8..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-for-init.js +++ /dev/null @@ -1,9 +0,0 @@ -// @compilationMode:"annotation" -for ( - var useFoo = function useFoo() { - 'use memo'; - return [1, 2, 3]; - }; - false; - -) {} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-if-test-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-if-test-expr.expect.md deleted file mode 100644 index 00ae113da61a..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-if-test-expr.expect.md +++ /dev/null @@ -1,39 +0,0 @@ - -## Input - -```javascript -// @compilationMode:"annotation" -if ( - function useFoo() { - 'use memo'; - return [1, 2, 3]; - } -) { -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode:"annotation" -if ( - function useFoo() { - "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = [1, 2, 3]; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; - } -) { -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-if-test-expr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-if-test-expr.js deleted file mode 100644 index 84c84e8ce020..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-if-test-expr.js +++ /dev/null @@ -1,8 +0,0 @@ -// @compilationMode:"annotation" -if ( - function useFoo() { - 'use memo'; - return [1, 2, 3]; - } -) { -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-while-test-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-while-test-expr.expect.md deleted file mode 100644 index e0866bd902a7..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-while-test-expr.expect.md +++ /dev/null @@ -1,41 +0,0 @@ - -## Input - -```javascript -// @compilationMode:"annotation" -while ( - function useFoo() { - 'use memo'; - return [1, 2, 3]; - } -) { - break; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode:"annotation" -while ( - function useFoo() { - "use memo"; - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = [1, 2, 3]; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; - } -) { - break; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-while-test-expr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-while-test-expr.js deleted file mode 100644 index 3e3e9aeeb21b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-discovery-while-test-expr.js +++ /dev/null @@ -1,9 +0,0 @@ -// @compilationMode:"annotation" -while ( - function useFoo() { - 'use memo'; - return [1, 2, 3]; - } -) { - break; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/no-cache-slots-no-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/no-cache-slots-no-import.expect.md deleted file mode 100644 index f2c7c5d539df..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/no-cache-slots-no-import.expect.md +++ /dev/null @@ -1,34 +0,0 @@ - -## Input - -```javascript -// @compilationMode(all) -function useMyHook({a, b}) { - return a + b; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useMyHook, - params: [{a: 1, b: 2}], -}; - -``` - -## Code - -```javascript -// @compilationMode(all) -function useMyHook(t0) { - const { a, b } = t0; - return a + b; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useMyHook, - params: [{ a: 1, b: 2 }], -}; - -``` - -### Eval output -(kind: ok) 3 \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/no-cache-slots-no-import.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/no-cache-slots-no-import.js deleted file mode 100644 index 2dbafff156ba..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/no-cache-slots-no-import.js +++ /dev/null @@ -1,9 +0,0 @@ -// @compilationMode(all) -function useMyHook({a, b}) { - return a + b; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useMyHook, - params: [{a: 1, b: 2}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-with-independently-memoizable-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-with-independently-memoizable-arg.expect.md index 6d25c5ee7140..b313cc20762e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-with-independently-memoizable-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-with-independently-memoizable-arg.expect.md @@ -38,7 +38,6 @@ function Component(props) { t0 = $[1]; } const y = t0; - return y; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-chain-on-known-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-chain-on-known-nonnull.expect.md deleted file mode 100644 index a1cc75f53630..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-chain-on-known-nonnull.expect.md +++ /dev/null @@ -1,141 +0,0 @@ - -## Input - -```javascript -// @enablePropagateDepsInHIR -function Component({data}) { - const geography = nullthrows(data.details); - const section = geography.sections?.nodes[0]; - const geoCountry = geography?.country ?? geography?.stateCountry; - const getTriggerType = () => { - if (geoCountry != null) { - return 'COUNTRY'; - } - if (geography?.region != null) { - return 'REGION'; - } - return 'GLOBAL'; - }; - const conditions = geography?.conditions?.nodes ?? []; - const [name, setName] = useState(section?.name); - const [isSaving, setIsSaving] = useState(false); - const triggerType = getTriggerType(); - - const controls = - geography?.controls?.nodes.map(c => ({ - title: c.name ?? '', - uniqueID: c.id ?? '', - })) ?? []; - - const handleSubmit = () => { - setIsSaving(true); - mutate({ - geography_id: nullthrows(geography?.id), - section_id: section?.id, - name, - triggerType, - controls, - }); - }; - - return ( - <div onClick={handleSubmit}> - {name} - {geoCountry} - {conditions} - </div> - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR -function Component(t0) { - const $ = _c(12); - const { data } = t0; - const geography = nullthrows(data.details); - const section = geography.sections?.nodes[0]; - const geoCountry = geography?.country ?? geography?.stateCountry; - const getTriggerType = () => { - if (geoCountry != null) { - return "COUNTRY"; - } - - if (geography?.region != null) { - return "REGION"; - } - - return "GLOBAL"; - }; - - const conditions = geography?.conditions?.nodes ?? []; - const [name] = useState(section?.name); - const [, setIsSaving] = useState(false); - const triggerType = getTriggerType(); - - const controls = geography?.controls?.nodes.map(_temp) ?? []; - let t1; - if ( - $[0] !== controls || - $[1] !== geography?.id || - $[2] !== name || - $[3] !== section?.id || - $[4] !== setIsSaving || - $[5] !== triggerType - ) { - t1 = () => { - setIsSaving(true); - mutate({ - geography_id: nullthrows(geography?.id), - section_id: section?.id, - name, - triggerType, - controls, - }); - }; - $[0] = controls; - $[1] = geography?.id; - $[2] = name; - $[3] = section?.id; - $[4] = setIsSaving; - $[5] = triggerType; - $[6] = t1; - } else { - t1 = $[6]; - } - const handleSubmit = t1; - let t2; - if ( - $[7] !== conditions || - $[8] !== geoCountry || - $[9] !== handleSubmit || - $[10] !== name - ) { - t2 = ( - <div onClick={handleSubmit}> - {name} - {geoCountry} - {conditions} - </div> - ); - $[7] = conditions; - $[8] = geoCountry; - $[9] = handleSubmit; - $[10] = name; - $[11] = t2; - } else { - t2 = $[11]; - } - return t2; -} -function _temp(c) { - return { title: c.name ?? "", uniqueID: c.id ?? "" }; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-chain-on-known-nonnull.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-chain-on-known-nonnull.js deleted file mode 100644 index 4a19e446d7a9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-chain-on-known-nonnull.js +++ /dev/null @@ -1,44 +0,0 @@ -// @enablePropagateDepsInHIR -function Component({data}) { - const geography = nullthrows(data.details); - const section = geography.sections?.nodes[0]; - const geoCountry = geography?.country ?? geography?.stateCountry; - const getTriggerType = () => { - if (geoCountry != null) { - return 'COUNTRY'; - } - if (geography?.region != null) { - return 'REGION'; - } - return 'GLOBAL'; - }; - const conditions = geography?.conditions?.nodes ?? []; - const [name, setName] = useState(section?.name); - const [isSaving, setIsSaving] = useState(false); - const triggerType = getTriggerType(); - - const controls = - geography?.controls?.nodes.map(c => ({ - title: c.name ?? '', - uniqueID: c.id ?? '', - })) ?? []; - - const handleSubmit = () => { - setIsSaving(true); - mutate({ - geography_id: nullthrows(geography?.id), - section_id: section?.id, - name, - triggerType, - controls, - }); - }; - - return ( - <div onClick={handleSubmit}> - {name} - {geoCountry} - {conditions} - </div> - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-in-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-in-iife.expect.md deleted file mode 100644 index 59aed5cdfb57..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-in-iife.expect.md +++ /dev/null @@ -1,41 +0,0 @@ - -## Input - -```javascript -function Component() { - return (function () { - function Inner() { - return <div onClick={() => null} />; - } - return <Inner />; - })(); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const Inner = function Inner() { - return <div onClick={_temp} />; - }; - t0 = <Inner />; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -} -function _temp() { - return null; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-in-iife.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-in-iife.js deleted file mode 100644 index 2d2c53fad795..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-in-iife.js +++ /dev/null @@ -1,8 +0,0 @@ -function Component() { - return (function () { - function Inner() { - return <div onClick={() => null} />; - } - return <Inner />; - })(); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-param-shadowing.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-param-shadowing.expect.md deleted file mode 100644 index 44b997797dd5..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-param-shadowing.expect.md +++ /dev/null @@ -1,57 +0,0 @@ - -## Input - -```javascript -function Component(props) { - const {value} = props; - const items = props.list.filter(value => value > 0); - return ( - <div> - {items.length} - {value} - </div> - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component(props) { - const $ = _c(5); - const { value } = props; - let t0; - if ($[0] !== props.list) { - t0 = props.list.filter(_temp); - $[0] = props.list; - $[1] = t0; - } else { - t0 = $[1]; - } - const items = t0; - let t1; - if ($[2] !== items.length || $[3] !== value) { - t1 = ( - <div> - {items.length} - {value} - </div> - ); - $[2] = items.length; - $[3] = value; - $[4] = t1; - } else { - t1 = $[4]; - } - return t1; -} -function _temp(value_0) { - return value_0 > 0; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-param-shadowing.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-param-shadowing.js deleted file mode 100644 index 86a5fd39cce1..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function-param-shadowing.js +++ /dev/null @@ -1,10 +0,0 @@ -function Component(props) { - const {value} = props; - const items = props.list.filter(value => value > 0); - return ( - <div> - {items.length} - {value} - </div> - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern1a_type_to_primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern1a_type_to_primitive.expect.md deleted file mode 100644 index 9bcfaf2bf65b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern1a_type_to_primitive.expect.md +++ /dev/null @@ -1,32 +0,0 @@ - -## Input - -```javascript -// Pattern 1a: shapeId null→generated + return Type→Primitive -// Arrow function with forEach callback -// Divergence: TS has shapeId:null, return:Type(22); Rust has shapeId:"<generated_0>", return:Primitive - -const getItemKeyMap = (): Map<string, string> => { - const itemKeys = []; - itemKeys.forEach((itemKey: string) => {}); -}; - -``` - -## Code - -```javascript -// Pattern 1a: shapeId null→generated + return Type→Primitive -// Arrow function with forEach callback -// Divergence: TS has shapeId:null, return:Type(22); Rust has shapeId:"<generated_0>", return:Primitive - -const getItemKeyMap = () => { - const itemKeys = []; - itemKeys.forEach(_temp); -}; -function _temp(itemKey) {} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern1a_type_to_primitive.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern1a_type_to_primitive.js deleted file mode 100644 index 7129b7c797a9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern1a_type_to_primitive.js +++ /dev/null @@ -1,8 +0,0 @@ -// Pattern 1a: shapeId null→generated + return Type→Primitive -// Arrow function with forEach callback -// Divergence: TS has shapeId:null, return:Type(22); Rust has shapeId:"<generated_0>", return:Primitive - -const getItemKeyMap = (): Map<string, string> => { - const itemKeys = []; - itemKeys.forEach((itemKey: string) => {}); -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern4_bare_type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern4_bare_type.expect.md deleted file mode 100644 index 0a14af7d049a..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern4_bare_type.expect.md +++ /dev/null @@ -1,56 +0,0 @@ - -## Input - -```javascript -// Pattern 4: Bare Type(N) vs Primitive (no shapeId difference) -// Object method with sort/filter chain -// Divergence: TS has type:Type(21); Rust has type:Primitive - -const formatNumber = function (x: number, y: number): number { - return Math.round((x - y) * 1000); -}; -const PerfHelper = { - formatMetrics(): Metrics { - const sortedLogs = logs.sort((entry1, entry2) => {}).filter(); - if (sortedLogs[0].begin < traceBeginTimeSec) { - } - sortedLogs.forEach((entry: TimeSliceEntry) => {}); - }, -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // Pattern 4: Bare Type(N) vs Primitive (no shapeId difference) -// Object method with sort/filter chain -// Divergence: TS has type:Type(21); Rust has type:Primitive - -const formatNumber = function (x, y) { - const $ = _c(3); - let t0; - if ($[0] !== x || $[1] !== y) { - t0 = Math.round((x - y) * 1000); - $[0] = x; - $[1] = y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -}; - -const PerfHelper = { - formatMetrics(): Metrics { - const sortedLogs = logs.sort((entry1, entry2) => {}).filter(); - if (sortedLogs[0].begin < traceBeginTimeSec) { - } - sortedLogs.forEach((entry: TimeSliceEntry) => {}); - }, -}; - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern4_bare_type.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern4_bare_type.js deleted file mode 100644 index ada66e9f0972..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pattern4_bare_type.js +++ /dev/null @@ -1,15 +0,0 @@ -// Pattern 4: Bare Type(N) vs Primitive (no shapeId difference) -// Object method with sort/filter chain -// Divergence: TS has type:Type(21); Rust has type:Primitive - -const formatNumber = function (x: number, y: number): number { - return Math.round((x - y) * 1000); -}; -const PerfHelper = { - formatMetrics(): Metrics { - const sortedLogs = logs.sort((entry1, entry2) => {}).filter(); - if (sortedLogs[0].begin < traceBeginTimeSec) { - } - sortedLogs.forEach((entry: TimeSliceEntry) => {}); - }, -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-type-convergence-with-phi-join.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-type-convergence-with-phi-join.expect.md deleted file mode 100644 index 54990d00f531..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-type-convergence-with-phi-join.expect.md +++ /dev/null @@ -1,61 +0,0 @@ - -## Input - -```javascript -// When phi nodes join Ref types with different ref_ids, the join creates a -// fresh ref_id. The fixpoint equality check must ignore ref_ids on Ref and -// RefValue variants (matching TS tyEqual semantics) so the environment -// stabilizes. Without this, the analysis never converges. - -import {useRef} from 'react'; - -function Component({cond}) { - const ref1 = useRef(null); - const ref2 = useRef(null); - const chosen = cond ? ref1 : ref2; - return <div ref={chosen} />; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // When phi nodes join Ref types with different ref_ids, the join creates a -// fresh ref_id. The fixpoint equality check must ignore ref_ids on Ref and -// RefValue variants (matching TS tyEqual semantics) so the environment -// stabilizes. Without this, the analysis never converges. - -import { useRef } from "react"; - -function Component(t0) { - const $ = _c(2); - const { cond } = t0; - const ref1 = useRef(null); - const ref2 = useRef(null); - const chosen = cond ? ref1 : ref2; - let t1; - if ($[0] !== chosen) { - t1 = <div ref={chosen} />; - $[0] = chosen; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok) <div></div> \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-type-convergence-with-phi-join.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-type-convergence-with-phi-join.js deleted file mode 100644 index 624c1f283cae..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-type-convergence-with-phi-join.js +++ /dev/null @@ -1,18 +0,0 @@ -// When phi nodes join Ref types with different ref_ids, the join creates a -// fresh ref_id. The fixpoint equality check must ignore ref_ids on Ref and -// RefValue variants (matching TS tyEqual semantics) so the environment -// stabilizes. Without this, the analysis never converges. - -import {useRef} from 'react'; - -function Component({cond}) { - const ref1 = useRef(null); - const ref2 = useRef(null); - const chosen = cond ? ref1 : ref2; - return <div ref={chosen} />; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round2_loc_diff.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round2_loc_diff.expect.md deleted file mode 100644 index 5ffb707e7c13..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round2_loc_diff.expect.md +++ /dev/null @@ -1,46 +0,0 @@ - -## Input - -```javascript -// Round 2 HIR: LOC_DIFF (10 files) -// Source locations differ for for-in loop variables with forEach callbacks -/** - */ -window.ClientLogger = (function () { - function getURLFromQueryParams(): string { - for (var key in queryParams) { - if (value != null) { - if (Array.isArray(value)) { - for (let i = 0; i < array.length; i++) { - paramParts.push(key + '[]=' + encodeURIComponent(array[i])); - } - Object.keys(value).forEach(object_key => { - paramParts.push( - key + `[${object_key}]=` + encodeURIComponent(value[object_key]) - ); - }); - } - } - } - } - function sendBeacon(url: string): boolean { - if (window.navigator && window.navigator.sendBeacon) { - } - } -})(); - -``` - -## Code - -```javascript -// Round 2 HIR: LOC_DIFF (10 files) -// Source locations differ for for-in loop variables with forEach callbacks -/** - */ -window.ClientLogger = (function () {})(); - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round2_loc_diff.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round2_loc_diff.js deleted file mode 100644 index 5057e252dbe1..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round2_loc_diff.js +++ /dev/null @@ -1,26 +0,0 @@ -// Round 2 HIR: LOC_DIFF (10 files) -// Source locations differ for for-in loop variables with forEach callbacks -/** - */ -window.ClientLogger = (function () { - function getURLFromQueryParams(): string { - for (var key in queryParams) { - if (value != null) { - if (Array.isArray(value)) { - for (let i = 0; i < array.length; i++) { - paramParts.push(key + '[]=' + encodeURIComponent(array[i])); - } - Object.keys(value).forEach(object_key => { - paramParts.push( - key + `[${object_key}]=` + encodeURIComponent(value[object_key]) - ); - }); - } - } - } - } - function sendBeacon(url: string): boolean { - if (window.navigator && window.navigator.sendBeacon) { - } - } -})(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture.expect.md deleted file mode 100644 index 6a41f0569ee3..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture.expect.md +++ /dev/null @@ -1,40 +0,0 @@ - -## Input - -```javascript -// Round 3: InferMutationAliasingRanges — location difference cascading to effect/reactive diffs -// Root cause: loc diff at statement level (e.g., 26:8-26:16 vs 26:2-26:22) -// This propagates into effect annotation (read vs capture) and reactive flag diffs -// Frontier: InferMutationAliasingRanges pass -// Source: ParseBusinessProfile.js, EditorAdgroup...PluginCommon.js -function useParseProfile(node) { - Object.keys(profile).forEach(key => {}); - const commands = []; - node.forEachChildWithTag('command', commandNode => { - commands.push({name, description}); - }); -} - -``` - -## Code - -```javascript -// Round 3: InferMutationAliasingRanges — location difference cascading to effect/reactive diffs -// Root cause: loc diff at statement level (e.g., 26:8-26:16 vs 26:2-26:22) -// This propagates into effect annotation (read vs capture) and reactive flag diffs -// Frontier: InferMutationAliasingRanges pass -// Source: ParseBusinessProfile.js, EditorAdgroup...PluginCommon.js -function useParseProfile(node) { - Object.keys(profile).forEach(_temp); - const commands = []; - node.forEachChildWithTag("command", (commandNode) => { - commands.push({ name, description }); - }); -} -function _temp(key) {} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture.js deleted file mode 100644 index f2f7e88205b8..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture.js +++ /dev/null @@ -1,12 +0,0 @@ -// Round 3: InferMutationAliasingRanges — location difference cascading to effect/reactive diffs -// Root cause: loc diff at statement level (e.g., 26:8-26:16 vs 26:2-26:22) -// This propagates into effect annotation (read vs capture) and reactive flag diffs -// Frontier: InferMutationAliasingRanges pass -// Source: ParseBusinessProfile.js, EditorAdgroup...PluginCommon.js -function useParseProfile(node) { - Object.keys(profile).forEach(key => {}); - const commands = []; - node.forEachChildWithTag('command', commandNode => { - commands.push({name, description}); - }); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture_v2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture_v2.expect.md deleted file mode 100644 index 6f364b6d8376..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture_v2.expect.md +++ /dev/null @@ -1,53 +0,0 @@ - -## Input - -```javascript -// Round 3: effect read vs capture — v2 -// Pattern: variable from ternary → object literal → forEach lambda mutation -// Source: ParseBusinessProfile.js -function useProfile(node) { - const childNode = node.maybeChild('key'); - const value = childNode ? childNode.contentString() : undefined; - const profile = { - key_value: value, - }; - Object.keys(profile).forEach(key => { - if (profile[key] == null) { - delete profile[key]; - } - }); - return profile; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // Round 3: effect read vs capture — v2 -// Pattern: variable from ternary → object literal → forEach lambda mutation -// Source: ParseBusinessProfile.js -function useProfile(node) { - const $ = _c(2); - let profile; - if ($[0] !== node) { - const childNode = node.maybeChild("key"); - const value = childNode ? childNode.contentString() : undefined; - profile = { key_value: value }; - Object.keys(profile).forEach((key) => { - if (profile[key] == null) { - delete profile[key]; - } - }); - $[0] = node; - $[1] = profile; - } else { - profile = $[1]; - } - return profile; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture_v2.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture_v2.js deleted file mode 100644 index 4b46f3a17763..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_effect_read_vs_capture_v2.js +++ /dev/null @@ -1,16 +0,0 @@ -// Round 3: effect read vs capture — v2 -// Pattern: variable from ternary → object literal → forEach lambda mutation -// Source: ParseBusinessProfile.js -function useProfile(node) { - const childNode = node.maybeChild('key'); - const value = childNode ? childNode.contentString() : undefined; - const profile = { - key_value: value, - }; - Object.keys(profile).forEach(key => { - if (profile[key] == null) { - delete profile[key]; - } - }); - return profile; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_outlined_naming.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_outlined_naming.expect.md deleted file mode 100644 index c85b6fed1d93..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_outlined_naming.expect.md +++ /dev/null @@ -1,43 +0,0 @@ - -## Input - -```javascript -// @enableFunctionOutlining -// Minimized repro for C5: outlined function naming divergence. -// When source code has variables named _temp/_temp2, Babel's generateUid -// skips those names, but the Rust compiler's blind counter doesn't. -function Component(props) { - const _temp = props.a; - const _temp2 = props.b; - return props.items.map(() => <div />); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableFunctionOutlining -// Minimized repro for C5: outlined function naming divergence. -// When source code has variables named _temp/_temp2, Babel's generateUid -// skips those names, but the Rust compiler's blind counter doesn't. -function Component(props) { - const $ = _c(2); - let t0; - if ($[0] !== props.items) { - t0 = props.items.map(_temp3); - $[0] = props.items; - $[1] = t0; - } else { - t0 = $[1]; - } - return t0; -} -function _temp3() { - return <div />; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_outlined_naming.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_outlined_naming.js deleted file mode 100644 index 688055bbc294..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/round3_outlined_naming.js +++ /dev/null @@ -1,9 +0,0 @@ -// @enableFunctionOutlining -// Minimized repro for C5: outlined function naming divergence. -// When source code has variables named _temp/_temp2, Babel's generateUid -// skips those names, but the Rust compiler's blind counter doesn't. -function Component(props) { - const _temp = props.a; - const _temp2 = props.b; - return props.items.map(() => <div />); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/scope-boundary-at-if-branch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/scope-boundary-at-if-branch.expect.md deleted file mode 100644 index 1f9e8c84f630..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/scope-boundary-at-if-branch.expect.md +++ /dev/null @@ -1,93 +0,0 @@ - -## Input - -```javascript -function Component(props) { - const label = <span>Label</span>; - if (props.type === 'link') { - const uri = createURI(props.id); - return ( - <a - href={uri.toString()} - onClick={e => { - e.preventDefault(); - uri.navigate(); - }}> - {label} - </a> - ); - } else { - return <>{label}</>; - } -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component(props) { - const $ = _c(10); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = <span>Label</span>; - $[0] = t0; - } else { - t0 = $[0]; - } - const label = t0; - if (props.type === "link") { - let t1; - let uri; - if ($[1] !== props.id) { - uri = createURI(props.id); - t1 = uri.toString(); - $[1] = props.id; - $[2] = t1; - $[3] = uri; - } else { - t1 = $[2]; - uri = $[3]; - } - let t2; - if ($[4] !== uri) { - t2 = (e) => { - e.preventDefault(); - uri.navigate(); - }; - $[4] = uri; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t1 || $[7] !== t2) { - t3 = ( - <a href={t1} onClick={t2}> - {label} - </a> - ); - $[6] = t1; - $[7] = t2; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; - } else { - let t1; - if ($[9] === Symbol.for("react.memo_cache_sentinel")) { - t1 = <>{label}</>; - $[9] = t1; - } else { - t1 = $[9]; - } - return t1; - } -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/scope-boundary-at-if-branch.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/scope-boundary-at-if-branch.js deleted file mode 100644 index 6fe139aa000f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/scope-boundary-at-if-branch.js +++ /dev/null @@ -1,18 +0,0 @@ -function Component(props) { - const label = <span>Label</span>; - if (props.type === 'link') { - const uri = createURI(props.id); - return ( - <a - href={uri.toString()} - onClick={e => { - e.preventDefault(); - uri.navigate(); - }}> - {label} - </a> - ); - } else { - return <>{label}</>; - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/self-referencing-const-arrow-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/self-referencing-const-arrow-function.expect.md deleted file mode 100644 index 3c07be154951..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/self-referencing-const-arrow-function.expect.md +++ /dev/null @@ -1,68 +0,0 @@ - -## Input - -```javascript -function Component(props) { - useEffect(() => { - const pathMap = new Map(); - const collectPaths = obj => { - if (obj != null && typeof obj === 'object') { - if (Array.isArray(obj)) { - obj.forEach(item => collectPaths(item)); - } else { - Object.values(obj).forEach(value => collectPaths(value)); - } - } - }; - collectPaths(props.data); - }, [props.data]); - return <div />; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component(props) { - const $ = _c(4); - let t0; - let t1; - if ($[0] !== props.data) { - t0 = () => { - new Map(); - const collectPaths = (obj) => { - if (obj != null && typeof obj === "object") { - if (Array.isArray(obj)) { - obj.forEach((item) => collectPaths(item)); - } else { - Object.values(obj).forEach((value) => collectPaths(value)); - } - } - }; - collectPaths(props.data); - }; - t1 = [props.data]; - $[0] = props.data; - $[1] = t0; - $[2] = t1; - } else { - t0 = $[1]; - t1 = $[2]; - } - useEffect(t0, t1); - let t2; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { - t2 = <div />; - $[3] = t2; - } else { - t2 = $[3]; - } - return t2; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/self-referencing-const-arrow-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/self-referencing-const-arrow-function.js deleted file mode 100644 index db7e2c0459a1..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/self-referencing-const-arrow-function.js +++ /dev/null @@ -1,16 +0,0 @@ -function Component(props) { - useEffect(() => { - const pathMap = new Map(); - const collectPaths = obj => { - if (obj != null && typeof obj === 'object') { - if (Array.isArray(obj)) { - obj.forEach(item => collectPaths(item)); - } else { - Object.values(obj).forEach(value => collectPaths(value)); - } - } - }; - collectPaths(props.data); - }, [props.data]); - return <div />; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/optimize-ssr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/optimize-ssr.expect.md index b4d0c0712415..48a0a92be70f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/optimize-ssr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/optimize-ssr.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @outputMode:"ssr" +// @enableOptimizeForSSR function Component() { const [state, setState] = useState(0); const ref = useRef(null); @@ -20,11 +20,40 @@ function Component() { ## Code ```javascript -// @outputMode:"ssr" +import { c as _c } from "react/compiler-runtime"; // @enableOptimizeForSSR function Component() { - const state = 0; - - return <input value={state} />; + const $ = _c(4); + const [state, setState] = useState(0); + const ref = useRef(null); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = (e) => { + setState(e.target.value); + }; + $[0] = t0; + } else { + t0 = $[0]; + } + const onChange = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { + log(ref.current.value); + }; + $[1] = t1; + } else { + t1 = $[1]; + } + useEffect(t1); + let t2; + if ($[2] !== state) { + t2 = <input value={state} onChange={onChange} ref={ref} />; + $[2] = state; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; } ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/optimize-ssr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/optimize-ssr.js index e28a937a1826..d9fba0f39033 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/optimize-ssr.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/optimize-ssr.js @@ -1,4 +1,4 @@ -// @outputMode:"ssr" +// @enableOptimizeForSSR function Component() { const [state, setState] = useState(0); const ref = useRef(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-setState.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-setState.expect.md index 75f1d672cabc..80884d845308 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-setState.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-setState.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @outputMode:"ssr" +// @enableOptimizeForSSR function Component() { const [state, setState] = useState(0); const ref = useRef(null); @@ -22,13 +22,40 @@ function Component() { ## Code ```javascript -// @outputMode:"ssr" +import { c as _c } from "react/compiler-runtime"; // @enableOptimizeForSSR function Component() { - const state = 0; + const $ = _c(4); + const [state, setState] = useState(0); const ref = useRef(null); - const onChange = undefined; - - return <CustomInput value={state} onChange={onChange} ref={ref} />; + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = (e) => { + setState(e.target.value); + }; + $[0] = t0; + } else { + t0 = $[0]; + } + const onChange = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { + log(ref.current.value); + }; + $[1] = t1; + } else { + t1 = $[1]; + } + useEffect(t1); + let t2; + if ($[2] !== state) { + t2 = <CustomInput value={state} onChange={onChange} ref={ref} />; + $[2] = state; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; } ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-setState.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-setState.js index 5213d80073da..c67f026c040c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-setState.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-setState.js @@ -1,4 +1,4 @@ -// @outputMode:"ssr" +// @enableOptimizeForSSR function Component() { const [state, setState] = useState(0); const ref = useRef(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-startTransition.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-startTransition.expect.md index e8175f247907..ccfdccb28885 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-startTransition.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-startTransition.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @outputMode:"ssr" +// @enableOptimizeForSSR function Component() { const [, startTransition] = useTransition(); const [state, setState] = useState(0); @@ -25,14 +25,43 @@ function Component() { ## Code ```javascript -// @outputMode:"ssr" +import { c as _c } from "react/compiler-runtime"; // @enableOptimizeForSSR function Component() { - useTransition(); - const state = 0; + const $ = _c(4); + const [, startTransition] = useTransition(); + const [state, setState] = useState(0); const ref = useRef(null); - const onChange = undefined; - - return <CustomInput value={state} onChange={onChange} ref={ref} />; + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = (e) => { + startTransition(() => { + setState.call(null, e.target.value); + }); + }; + $[0] = t0; + } else { + t0 = $[0]; + } + const onChange = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { + log(ref.current.value); + }; + $[1] = t1; + } else { + t1 = $[1]; + } + useEffect(t1); + let t2; + if ($[2] !== state) { + t2 = <CustomInput value={state} onChange={onChange} ref={ref} />; + $[2] = state; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; } ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-startTransition.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-startTransition.js index 9920305ba0c7..f6f6f3914dc7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-startTransition.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-infer-event-handlers-from-startTransition.js @@ -1,4 +1,4 @@ -// @outputMode:"ssr" +// @enableOptimizeForSSR function Component() { const [, startTransition] = useTransition(); const [state, setState] = useState(0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer-initializer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer-initializer.expect.md index b052966ab066..780e1f3963c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer-initializer.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer-initializer.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @outputMode:"ssr" +// @enableOptimizeForSSR import {useReducer} from 'react'; @@ -25,7 +25,7 @@ function Component() { ## Code ```javascript -// @outputMode:"ssr" +import { c as _c } from "react/compiler-runtime"; // @enableOptimizeForSSR import { useReducer } from "react"; @@ -34,9 +34,41 @@ const initializer = (x) => { }; function Component() { - const state = initializer(0); - - return <input value={state} />; + const $ = _c(4); + const [state, dispatch] = useReducer(_temp, 0, initializer); + const ref = useRef(null); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = (e) => { + dispatch(e.target.value); + }; + $[0] = t0; + } else { + t0 = $[0]; + } + const onChange = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { + log(ref.current.value); + }; + $[1] = t1; + } else { + t1 = $[1]; + } + useEffect(t1); + let t2; + if ($[2] !== state) { + t2 = <input value={state} onChange={onChange} ref={ref} />; + $[2] = state; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} +function _temp(_, next) { + return next; } ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer-initializer.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer-initializer.js index ed94e4e2b835..91844def22da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer-initializer.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer-initializer.js @@ -1,4 +1,4 @@ -// @outputMode:"ssr" +// @enableOptimizeForSSR import {useReducer} from 'react'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer.expect.md index 916539c6c173..3c48b27f8613 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @outputMode:"ssr" +// @enableOptimizeForSSR import {useReducer} from 'react'; @@ -23,14 +23,46 @@ function Component() { ## Code ```javascript -// @outputMode:"ssr" +import { c as _c } from "react/compiler-runtime"; // @enableOptimizeForSSR import { useReducer } from "react"; function Component() { - const state = 0; - - return <input value={state} />; + const $ = _c(4); + const [state, dispatch] = useReducer(_temp, 0); + const ref = useRef(null); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = (e) => { + dispatch(e.target.value); + }; + $[0] = t0; + } else { + t0 = $[0]; + } + const onChange = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { + log(ref.current.value); + }; + $[1] = t1; + } else { + t1 = $[1]; + } + useEffect(t1); + let t2; + if ($[2] !== state) { + t2 = <input value={state} onChange={onChange} ref={ref} />; + $[2] = state; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} +function _temp(_, next) { + return next; } ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer.js index b7e81fdbab74..4223ebe4f559 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssr/ssr-use-reducer.js @@ -1,4 +1,4 @@ -// @outputMode:"ssr" +// @enableOptimizeForSSR import {useReducer} from 'react'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md index 91853b85f758..6625f0153e8a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md @@ -34,7 +34,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"StaticComponents","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":10,"index":221},"end":{"line":9,"column":19,"index":230},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":5,"column":16,"index":143},"end":{"line":5,"column":33,"index":160},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"The component is created during render here"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render","details":[{"kind":"error","loc":{"start":{"line":9,"column":10,"index":221},"end":{"line":9,"column":19,"index":230},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":5,"column":16,"index":143},"end":{"line":5,"column":33,"index":160},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":64},"end":{"line":10,"column":1,"index":236},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md index 760b47c506d2..c6441bc4cb1e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md @@ -24,7 +24,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"StaticComponents","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":139},"end":{"line":4,"column":19,"index":148},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":110},"end":{"line":3,"column":37,"index":127},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"The component is created during render here"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":139},"end":{"line":4,"column":19,"index":148},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":110},"end":{"line":3,"column":37,"index":127},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":64},"end":{"line":5,"column":1,"index":154},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md index 03d3e95272de..0882c4a10037 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md @@ -28,7 +28,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"StaticComponents","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":6,"column":10,"index":149},"end":{"line":6,"column":19,"index":158},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":2,"index":92},"end":{"line":5,"column":3,"index":138},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"The component is created during render here"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render","details":[{"kind":"error","loc":{"start":{"line":6,"column":10,"index":149},"end":{"line":6,"column":19,"index":158},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":2,"index":92},"end":{"line":5,"column":3,"index":138},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":64},"end":{"line":7,"column":1,"index":164},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md index 7c3b1a96bbcc..707a0a958502 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md @@ -24,7 +24,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"StaticComponents","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":137},"end":{"line":4,"column":19,"index":146},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":110},"end":{"line":3,"column":35,"index":125},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"The component is created during render here"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":137},"end":{"line":4,"column":19,"index":146},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":110},"end":{"line":3,"column":35,"index":125},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":64},"end":{"line":5,"column":1,"index":152},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md index 875e687b7d07..2607ef63d8da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md @@ -24,7 +24,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"category":"StaticComponents","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render","severity":"Error","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":144},"end":{"line":4,"column":19,"index":153},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":110},"end":{"line":3,"column":42,"index":132},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"The component is created during render here"}]},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":144},"end":{"line":4,"column":19,"index":153},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":110},"end":{"line":3,"column":42,"index":132},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":64},"end":{"line":5,"column":1,"index":159},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_identifier_diff.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_identifier_diff.expect.md deleted file mode 100644 index beeb4ebbe213..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_identifier_diff.expect.md +++ /dev/null @@ -1,55 +0,0 @@ - -## Input - -```javascript -// HIR Pattern: IDENTIFIER_DIFF (20 files, 6%) -// Extra identifier in Rust's context/params for jest.mock factory functions - -/** - * @flow strict-local - */ -/* eslint-disable no-shadow */ -jest.mock('RouterRootContextFactory.react', () => { - const React = require('react'); - const tracePolicyCtxMod: any = jest.requireActual(); - return function MockRouterRootContextFactory(props: { - children: React.Node, - routeInfo: {}, - }) { - return ( - <RouterRenderTypeContext.Provider - value={{}}></RouterRenderTypeContext.Provider> - ); - }; -}); - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // HIR Pattern: IDENTIFIER_DIFF (20 files, 6%) -// Extra identifier in Rust's context/params for jest.mock factory functions - -/** - * @flow strict-local - */ -/* eslint-disable no-shadow */ -jest.mock("RouterRootContextFactory.react", () => { - const $ = _c(1); - require("react"); - jest.requireActual(); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = function MockRouterRootContextFactory(props) { - return <RouterRenderTypeContext.Provider value={{}} />; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - return t0; -}); - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_identifier_diff.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_identifier_diff.js deleted file mode 100644 index ff2f594ebdeb..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_identifier_diff.js +++ /dev/null @@ -1,20 +0,0 @@ -// HIR Pattern: IDENTIFIER_DIFF (20 files, 6%) -// Extra identifier in Rust's context/params for jest.mock factory functions - -/** - * @flow strict-local - */ -/* eslint-disable no-shadow */ -jest.mock('RouterRootContextFactory.react', () => { - const React = require('react'); - const tracePolicyCtxMod: any = jest.requireActual(); - return function MockRouterRootContextFactory(props: { - children: React.Node, - routeInfo: {}, - }) { - return ( - <RouterRenderTypeContext.Provider - value={{}}></RouterRenderTypeContext.Provider> - ); - }; -}); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_numeric_format.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_numeric_format.expect.md deleted file mode 100644 index 01794369fe67..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_numeric_format.expect.md +++ /dev/null @@ -1,39 +0,0 @@ - -## Input - -```javascript -// HIR Pattern: NUMERIC_FORMAT_DIFF (5 files, 1.5%) -// TS: 2.18739127891275e+22 (scientific), Rust: 21873912789127500000000 (decimal) - -describe('logUnsafeIntResponse', () => { - afterEach(() => {}); - it('logs when unsafe integer found in response', () => { - logUnsafeIntResponse('GET', '/api/v1/test/', { - abc: 21873912789127498217412, - }); - expect(MockLogger.Logger).toHaveBeenCalledWith(); - expect(MockLogger.warn).toHaveBeenNthCalledWith(); - }); -}); - -``` - -## Code - -```javascript -// HIR Pattern: NUMERIC_FORMAT_DIFF (5 files, 1.5%) -// TS: 2.18739127891275e+22 (scientific), Rust: 21873912789127500000000 (decimal) - -describe("logUnsafeIntResponse", () => { - afterEach(_temp); - it("logs when unsafe integer found in response", _temp2); -}); -function _temp() {} -function _temp2() { - logUnsafeIntResponse("GET", "/api/v1/test/", { abc: 2.18739127891275e22 }); - expect(MockLogger.Logger).toHaveBeenCalledWith(); - expect(MockLogger.warn).toHaveBeenNthCalledWith(); -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_numeric_format.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_numeric_format.js deleted file mode 100644 index 43d92ee6bc80..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hir_numeric_format.js +++ /dev/null @@ -1,13 +0,0 @@ -// HIR Pattern: NUMERIC_FORMAT_DIFF (5 files, 1.5%) -// TS: 2.18739127891275e+22 (scientific), Rust: 21873912789127500000000 (decimal) - -describe('logUnsafeIntResponse', () => { - afterEach(() => {}); - it('logs when unsafe integer found in response', () => { - logUnsafeIntResponse('GET', '/api/v1/test/', { - abc: 21873912789127498217412, - }); - expect(MockLogger.Logger).toHaveBeenCalledWith(); - expect(MockLogger.warn).toHaveBeenNthCalledWith(); - }); -}); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hoist-type-alias-before-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hoist-type-alias-before-declaration.expect.md deleted file mode 100644 index 46fc3f2d4095..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hoist-type-alias-before-declaration.expect.md +++ /dev/null @@ -1,48 +0,0 @@ - -## Input - -```javascript -// @flow @compilationMode(infer) -function Component(props: {data: Array<{label: string, value: number}>}) { - const getLabel = (item: ItemType): string => item.label; - const items = props.data.map(getLabel); - type ItemType = {label: string, value: number}; - return <div>{items}</div>; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component(props) { - const $ = _c(4); - const getLabel = _temp; - let t0; - if ($[0] !== props.data) { - t0 = props.data.map(getLabel); - $[0] = props.data; - $[1] = t0; - } else { - t0 = $[1]; - } - const items = t0; - let t1; - if ($[2] !== items) { - t1 = <div>{items}</div>; - $[2] = items; - $[3] = t1; - } else { - t1 = $[3]; - } - return t1; -} -function _temp(item) { - return item.label; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hoist-type-alias-before-declaration.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hoist-type-alias-before-declaration.js deleted file mode 100644 index 7c2ae6271027..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-hoist-type-alias-before-declaration.js +++ /dev/null @@ -1,7 +0,0 @@ -// @flow @compilationMode(infer) -function Component(props: {data: Array<{label: string, value: number}>}) { - const getLabel = (item: ItemType): string => item.label; - const items = props.data.map(getLabel); - type ItemType = {label: string, value: number}; - return <div>{items}</div>; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-jsx-intrinsic-tag-matches-local-binding.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-jsx-intrinsic-tag-matches-local-binding.expect.md deleted file mode 100644 index cc1d8b39a558..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-jsx-intrinsic-tag-matches-local-binding.expect.md +++ /dev/null @@ -1,79 +0,0 @@ - -## Input - -```javascript -function Component({items}) { - const colgroup = useMemo( - () => ( - <colgroup> - {items.map(item => ( - <col key={item.id} /> - ))} - </colgroup> - ), - [items] - ); - return ( - <table> - {colgroup} - <tbody /> - </table> - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component(t0) { - const $ = _c(7); - const { items } = t0; - let t1; - if ($[0] !== items) { - t1 = items.map(_temp); - $[0] = items; - $[1] = t1; - } else { - t1 = $[1]; - } - let t2; - if ($[2] !== t1) { - t2 = <colgroup>{t1}</colgroup>; - $[2] = t1; - $[3] = t2; - } else { - t2 = $[3]; - } - const colgroup = t2; - let t3; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - t3 = <tbody />; - $[4] = t3; - } else { - t3 = $[4]; - } - let t4; - if ($[5] !== colgroup) { - t4 = ( - <table> - {colgroup} - {t3} - </table> - ); - $[5] = colgroup; - $[6] = t4; - } else { - t4 = $[6]; - } - return t4; -} -function _temp(item) { - return <col key={item.id} />; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-jsx-intrinsic-tag-matches-local-binding.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-jsx-intrinsic-tag-matches-local-binding.js deleted file mode 100644 index cba3d8c01c78..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-jsx-intrinsic-tag-matches-local-binding.js +++ /dev/null @@ -1,18 +0,0 @@ -function Component({items}) { - const colgroup = useMemo( - () => ( - <colgroup> - {items.map(item => ( - <col key={item.id} /> - ))} - </colgroup> - ), - [items] - ); - return ( - <table> - {colgroup} - <tbody /> - </table> - ); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-pattern1b_type_to_primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-pattern1b_type_to_primitive.expect.md deleted file mode 100644 index 76d883dc8c65..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-pattern1b_type_to_primitive.expect.md +++ /dev/null @@ -1,39 +0,0 @@ - -## Input - -```javascript -// Pattern 1b: shapeId null→generated + return Type→Primitive -// Test with forEach callback -// Divergence: TS has shapeId:null, return:Type(53); Rust has shapeId:"<generated_0>", return:Primitive - -describe('equalsIterable', () => { - const TEST_CASES: Array<{}> = []; - TEST_CASES.forEach(testCase => { - it(testCase.name, () => { - expect(equalsIterable(mapOne, mapTwo, compareKeyValuePair)).toBe(); - }); - }); -}); - -``` - -## Code - -```javascript -// Pattern 1b: shapeId null→generated + return Type→Primitive -// Test with forEach callback -// Divergence: TS has shapeId:null, return:Type(53); Rust has shapeId:"<generated_0>", return:Primitive - -describe("equalsIterable", () => { - const TEST_CASES = []; - TEST_CASES.forEach(_temp2); -}); -function _temp() { - expect(equalsIterable(mapOne, mapTwo, compareKeyValuePair)).toBe(); -} -function _temp2(testCase) { - it(testCase.name, _temp); -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-pattern1b_type_to_primitive.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-pattern1b_type_to_primitive.js deleted file mode 100644 index c99cbdc8e198..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-pattern1b_type_to_primitive.js +++ /dev/null @@ -1,12 +0,0 @@ -// Pattern 1b: shapeId null→generated + return Type→Primitive -// Test with forEach callback -// Divergence: TS has shapeId:null, return:Type(53); Rust has shapeId:"<generated_0>", return:Primitive - -describe('equalsIterable', () => { - const TEST_CASES: Array<{}> = []; - TEST_CASES.forEach(testCase => { - it(testCase.name, () => { - expect(equalsIterable(mapOne, mapTwo, compareKeyValuePair)).toBe(); - }); - }); -}); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round2_unicode_string.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round2_unicode_string.expect.md deleted file mode 100644 index b3e94644feb8..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round2_unicode_string.expect.md +++ /dev/null @@ -1,29 +0,0 @@ - -## Input - -```javascript -// Round 2 HIR: VALUE_DIFF/UNICODE (1 file) -// Latin Extended-A characters: TS renders literal, Rust escapes as \uXXXX -test('Story permalink vanity slug mobile user heartbeat test', async () => { - await device.navigate( - '/user123/posts/ju\xc5\xbc-za-chwil\xc4\x99-wleci-suszarnia-je\xc5\x9bli-komu\xc5\x9b-brakuje-walentynkowych-mi\xc5\x82osnych-unies/744545433701765/', - {} - ); -}); - -``` - -## Code - -```javascript -// Round 2 HIR: VALUE_DIFF/UNICODE (1 file) -// Latin Extended-A characters: TS renders literal, Rust escapes as \uXXXX -test("Story permalink vanity slug mobile user heartbeat test", async () => { - await device.navigate( - "/user123/posts/ju\xC5\xBC-za-chwil\xC4\x99-wleci-suszarnia-je\xC5\x9Bli-komu\xC5\x9B-brakuje-walentynkowych-mi\xC5\x82osnych-unies/744545433701765/", - {}, - ); -}); - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round2_unicode_string.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round2_unicode_string.js deleted file mode 100644 index ee5d1b98e6f5..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round2_unicode_string.js +++ /dev/null @@ -1,8 +0,0 @@ -// Round 2 HIR: VALUE_DIFF/UNICODE (1 file) -// Latin Extended-A characters: TS renders literal, Rust escapes as \uXXXX -test('Story permalink vanity slug mobile user heartbeat test', async () => { - await device.navigate( - '/user123/posts/ju\xc5\xbc-za-chwil\xc4\x99-wleci-suszarnia-je\xc5\x9bli-komu\xc5\x9b-brakuje-walentynkowych-mi\xc5\x82osnych-unies/744545433701765/', - {} - ); -}); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_outlined_naming.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_outlined_naming.expect.md deleted file mode 100644 index 99f8f6b82685..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_outlined_naming.expect.md +++ /dev/null @@ -1,51 +0,0 @@ - -## Input - -```javascript -// @enableFunctionOutlining -// Minimized repro for C5: outlined function naming divergence. -// When source code has variables named _temp/_temp2, Babel's generateUid -// skips those names, but the Rust compiler's blind counter doesn't. -function Component(props) { - const _temp = props.a; - const _temp2 = props.b; - return props.items.map(() => <div />); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{a: 1, b: 2, items: [1, 2, 3]}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @enableFunctionOutlining -// Minimized repro for C5: outlined function naming divergence. -// When source code has variables named _temp/_temp2, Babel's generateUid -// skips those names, but the Rust compiler's blind counter doesn't. -function Component(props) { - const $ = _c(2); - let t0; - if ($[0] !== props.items) { - t0 = props.items.map(_temp3); - $[0] = props.items; - $[1] = t0; - } else { - t0 = $[1]; - } - return t0; -} -function _temp3() { - return <div />; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ a: 1, b: 2, items: [1, 2, 3] }], -}; - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_outlined_naming.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_outlined_naming.js deleted file mode 100644 index b6fd657895e1..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_outlined_naming.js +++ /dev/null @@ -1,14 +0,0 @@ -// @enableFunctionOutlining -// Minimized repro for C5: outlined function naming divergence. -// When source code has variables named _temp/_temp2, Babel's generateUid -// skips those names, but the Rust compiler's blind counter doesn't. -function Component(props) { - const _temp = props.a; - const _temp2 = props.b; - return props.items.map(() => <div />); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{a: 1, b: 2, items: [1, 2, 3]}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_promote_used_temps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_promote_used_temps.expect.md deleted file mode 100644 index ff80279d7e26..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_promote_used_temps.expect.md +++ /dev/null @@ -1,401 +0,0 @@ - -## Input - -```javascript -// Round 3: PromoteUsedTemporaries divergence -// TS promotes temporary to named "#t142" / "t9" -// Rust leaves name as null -// Frontier: PromoteUsedTemporaries pass -// Source: AutoEmbedPlugin.prod.js, JoinedActionPopover.react.js -// NOTE: This file is minified prod code - the minimizer couldn't reduce further -'use strict'; -var e = require('LexicalLink'), - t = require('LexicalComposerContext'), - n = require('LexicalNodeMenuPlugin'), - o = require('LexicalUtils'), - i = require('Lexical'), - r = require('react'), - s = require('react'); -const l = i.createCommand('INSERT_EMBED_COMMAND'); -class u extends n.MenuOption { - title; - onSelect; - constructor(e, t) { - super(e), (this.title = e), (this.onSelect = t.onSelect.bind(this)); - } -} -(exports.AutoEmbedOption = u), - (exports.INSERT_EMBED_COMMAND = l), - (exports.LexicalAutoEmbedPlugin = function ({ - embedConfigs: u, - onOpenEmbedModalForConfig: a, - getMenuOptions: c, - menuRenderFn: d, - menuCommandPriority: m = i.COMMAND_PRIORITY_LOW, - }) { - const [p] = t.useLexicalComposerContext(), - [C, L] = r.useState(null), - [f, M] = r.useState(null), - g = r.useCallback(() => { - L(null), M(null); - }, []), - E = r.useCallback( - async t => { - const n = p.getEditorState().read(function () { - const n = i.$getNodeByKey(t); - if (e.$isLinkNode(n)) return n.getURL(); - }); - if (void 0 !== n) - for (const e of u) { - null != (await Promise.resolve(e.parseUrl(n))) && (M(e), L(t)); - } - }, - [p, u] - ); - r.useEffect( - () => - o.mergeRegister( - ...[e.LinkNode, e.AutoLinkNode].map(e => - p.registerMutationListener( - e, - (...e) => - ((e, {updateTags: t, dirtyLeaves: n}) => { - for (const [o, r] of e) - 'created' === r && t.has(i.PASTE_TAG) && n.size <= 3 - ? E(o) - : o === C && g(); - })(...e), - {skipInitialization: !0} - ) - ) - ), - [E, p, u, C, g] - ), - r.useEffect( - () => - p.registerCommand( - l, - e => { - const t = u.find(({type: t}) => t === e); - return !!t && (a(t), !0); - }, - i.COMMAND_PRIORITY_EDITOR - ), - [p, u, a] - ); - const x = r.useCallback( - async function () { - if (null != f && null != C) { - const t = p.getEditorState().read(() => { - const t = i.$getNodeByKey(C); - return e.$isLinkNode(t) ? t : null; - }); - if (e.$isLinkNode(t)) { - const e = await Promise.resolve(f.parseUrl(t.__url)); - null != e && - p.update(() => { - i.$getSelection() || t.selectEnd(), - f.insertNode(p, e), - t.isAttached() && t.remove(); - }); - } - } - }, - [f, p, C] - ), - N = r.useMemo( - () => (null != f && null != C ? c(f, x, g) : []), - [f, x, c, C, g] - ), - A = r.useCallback( - (e, t, n) => { - p.update(() => { - e.onSelect(t), n(); - }); - }, - [p] - ); - return null != C - ? s.jsx(n.LexicalNodeMenuPlugin, { - nodeKey: C, - onClose: g, - onSelectOption: A, - options: N, - menuRenderFn: d, - commandPriority: m, - }) - : null; - }), - (exports.URL_MATCHER = - /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/); - -``` - -## Code - -```javascript -// Round 3: PromoteUsedTemporaries divergence -// TS promotes temporary to named "#t142" / "t9" -// Rust leaves name as null -// Frontier: PromoteUsedTemporaries pass -// Source: AutoEmbedPlugin.prod.js, JoinedActionPopover.react.js -// NOTE: This file is minified prod code - the minimizer couldn't reduce further -"use strict"; -import { c as _c } from "react/compiler-runtime"; -var e = require("LexicalLink"), - t = require("LexicalComposerContext"), - n = require("LexicalNodeMenuPlugin"), - o = require("LexicalUtils"), - i = require("Lexical"), - r = require("react"), - s = require("react"); -const l = i.createCommand("INSERT_EMBED_COMMAND"); -class u extends n.MenuOption { - title; - onSelect; - constructor(e, t) { - super(e), (this.title = e), (this.onSelect = t.onSelect.bind(this)); - } -} -(exports.AutoEmbedOption = u), - (exports.INSERT_EMBED_COMMAND = l), - (exports.LexicalAutoEmbedPlugin = function (t0) { - const $ = _c(43); - const { - embedConfigs: u, - onOpenEmbedModalForConfig: a, - getMenuOptions: c, - menuRenderFn: d, - menuCommandPriority: t1, - } = t0; - const m = t1 === undefined ? i.COMMAND_PRIORITY_LOW : t1; - const [t2] = t.useLexicalComposerContext(); - const p = t2; - const [C, t3] = r.useState(null); - const L = t3; - const [f, t4] = r.useState(null); - const M = t4; - let t5; - if ($[0] !== L || $[1] !== M) { - t5 = () => { - L(null), M(null); - }; - $[0] = L; - $[1] = M; - $[2] = t5; - } else { - t5 = $[2]; - } - let t6; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { - t6 = []; - $[3] = t6; - } else { - t6 = $[3]; - } - const g = r.useCallback(t5, t6); - let t7; - if ($[4] !== L || $[5] !== M || $[6] !== p || $[7] !== u) { - t7 = async (t$0) => { - const n_0 = p.getEditorState().read(function () { - const n$0 = i.$getNodeByKey(t$0); - if (e.$isLinkNode(n$0)) { - return n$0.getURL(); - } - }); - if (void 0 !== n_0) { - for (const e$0 of u) { - null != (await Promise.resolve(e$0.parseUrl(n_0))) && - (M(e$0), L(t$0)); - } - } - }; - $[4] = L; - $[5] = M; - $[6] = p; - $[7] = u; - $[8] = t7; - } else { - t7 = $[8]; - } - let t8; - if ($[9] !== p || $[10] !== u) { - t8 = [p, u]; - $[9] = p; - $[10] = u; - $[11] = t8; - } else { - t8 = $[11]; - } - const E = r.useCallback(t7, t8); - r.useEffect( - () => - o.mergeRegister( - ...[e.LinkNode, e.AutoLinkNode].map((e_0) => - p.registerMutationListener( - e_0, - (...t9) => { - const e_1 = t9; - return ((e_2, t10) => { - const { updateTags: t_0, dirtyLeaves: n_1 } = t10; - for (const [o$0, r$0] of e_2) { - "created" === r$0 && t_0.has(i.PASTE_TAG) && n_1.size <= 3 - ? E(o$0) - : o$0 === C && g(); - } - })(...e_1); - }, - { skipInitialization: true }, - ), - ), - ), - [E, p, u, C, g], - ), - r.useEffect( - () => - p.registerCommand( - l, - (e_3) => { - const t_2 = u.find((t11) => { - const { type: t_1 } = t11; - return t_1 === e_3; - }); - return !!t_2 && (a(t_2), true); - }, - - i.COMMAND_PRIORITY_EDITOR, - ), - [p, u, a], - ); - let t12; - if ($[12] !== C || $[13] !== f || $[14] !== p) { - t12 = async function () { - if (null != f && null != C) { - const t_4 = p.getEditorState().read(() => { - const t_3 = i.$getNodeByKey(C); - return e.$isLinkNode(t_3) ? t_3 : null; - }); - if (e.$isLinkNode(t_4)) { - const e_4 = await Promise.resolve(f.parseUrl(t_4.__url)); - null != e_4 && - p.update(() => { - i.$getSelection() || t_4.selectEnd(), - f.insertNode(p, e_4), - t_4.isAttached() && t_4.remove(); - }); - } - } - }; - $[12] = C; - $[13] = f; - $[14] = p; - $[15] = t12; - } else { - t12 = $[15]; - } - let t13; - if ($[16] !== C || $[17] !== f || $[18] !== p) { - t13 = [f, p, C]; - $[16] = C; - $[17] = f; - $[18] = p; - $[19] = t13; - } else { - t13 = $[19]; - } - const x = r.useCallback(t12, t13); - let t14; - if ( - $[20] !== C || - $[21] !== c || - $[22] !== f || - $[23] !== g || - $[24] !== x - ) { - t14 = () => (null != f && null != C ? c(f, x, g) : []); - $[20] = C; - $[21] = c; - $[22] = f; - $[23] = g; - $[24] = x; - $[25] = t14; - } else { - t14 = $[25]; - } - let t15; - if ( - $[26] !== C || - $[27] !== c || - $[28] !== f || - $[29] !== g || - $[30] !== x - ) { - t15 = [f, x, c, C, g]; - $[26] = C; - $[27] = c; - $[28] = f; - $[29] = g; - $[30] = x; - $[31] = t15; - } else { - t15 = $[31]; - } - const N = r.useMemo(t14, t15); - let t16; - if ($[32] !== p) { - t16 = (e_5, t_5, n_2) => { - p.update(() => { - e_5.onSelect(t_5), n_2(); - }); - }; - $[32] = p; - $[33] = t16; - } else { - t16 = $[33]; - } - let t17; - if ($[34] !== p) { - t17 = [p]; - $[34] = p; - $[35] = t17; - } else { - t17 = $[35]; - } - const A = r.useCallback(t16, t17); - let t18; - if ( - $[36] !== A || - $[37] !== C || - $[38] !== N || - $[39] !== d || - $[40] !== g || - $[41] !== m - ) { - t18 = - null != C - ? s.jsx(n.LexicalNodeMenuPlugin, { - nodeKey: C, - onClose: g, - onSelectOption: A, - options: N, - menuRenderFn: d, - commandPriority: m, - }) - : null; - $[36] = A; - $[37] = C; - $[38] = N; - $[39] = d; - $[40] = g; - $[41] = m; - $[42] = t18; - } else { - t18 = $[42]; - } - return t18; - }), - (exports.URL_MATCHER = - /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/); - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_promote_used_temps.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_promote_used_temps.js deleted file mode 100644 index fee644c946ea..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-round3_promote_used_temps.js +++ /dev/null @@ -1,126 +0,0 @@ -// Round 3: PromoteUsedTemporaries divergence -// TS promotes temporary to named "#t142" / "t9" -// Rust leaves name as null -// Frontier: PromoteUsedTemporaries pass -// Source: AutoEmbedPlugin.prod.js, JoinedActionPopover.react.js -// NOTE: This file is minified prod code - the minimizer couldn't reduce further -'use strict'; -var e = require('LexicalLink'), - t = require('LexicalComposerContext'), - n = require('LexicalNodeMenuPlugin'), - o = require('LexicalUtils'), - i = require('Lexical'), - r = require('react'), - s = require('react'); -const l = i.createCommand('INSERT_EMBED_COMMAND'); -class u extends n.MenuOption { - title; - onSelect; - constructor(e, t) { - super(e), (this.title = e), (this.onSelect = t.onSelect.bind(this)); - } -} -(exports.AutoEmbedOption = u), - (exports.INSERT_EMBED_COMMAND = l), - (exports.LexicalAutoEmbedPlugin = function ({ - embedConfigs: u, - onOpenEmbedModalForConfig: a, - getMenuOptions: c, - menuRenderFn: d, - menuCommandPriority: m = i.COMMAND_PRIORITY_LOW, - }) { - const [p] = t.useLexicalComposerContext(), - [C, L] = r.useState(null), - [f, M] = r.useState(null), - g = r.useCallback(() => { - L(null), M(null); - }, []), - E = r.useCallback( - async t => { - const n = p.getEditorState().read(function () { - const n = i.$getNodeByKey(t); - if (e.$isLinkNode(n)) return n.getURL(); - }); - if (void 0 !== n) - for (const e of u) { - null != (await Promise.resolve(e.parseUrl(n))) && (M(e), L(t)); - } - }, - [p, u] - ); - r.useEffect( - () => - o.mergeRegister( - ...[e.LinkNode, e.AutoLinkNode].map(e => - p.registerMutationListener( - e, - (...e) => - ((e, {updateTags: t, dirtyLeaves: n}) => { - for (const [o, r] of e) - 'created' === r && t.has(i.PASTE_TAG) && n.size <= 3 - ? E(o) - : o === C && g(); - })(...e), - {skipInitialization: !0} - ) - ) - ), - [E, p, u, C, g] - ), - r.useEffect( - () => - p.registerCommand( - l, - e => { - const t = u.find(({type: t}) => t === e); - return !!t && (a(t), !0); - }, - i.COMMAND_PRIORITY_EDITOR - ), - [p, u, a] - ); - const x = r.useCallback( - async function () { - if (null != f && null != C) { - const t = p.getEditorState().read(() => { - const t = i.$getNodeByKey(C); - return e.$isLinkNode(t) ? t : null; - }); - if (e.$isLinkNode(t)) { - const e = await Promise.resolve(f.parseUrl(t.__url)); - null != e && - p.update(() => { - i.$getSelection() || t.selectEnd(), - f.insertNode(p, e), - t.isAttached() && t.remove(); - }); - } - } - }, - [f, p, C] - ), - N = r.useMemo( - () => (null != f && null != C ? c(f, x, g) : []), - [f, x, c, C, g] - ), - A = r.useCallback( - (e, t, n) => { - p.update(() => { - e.onSelect(t), n(); - }); - }, - [p] - ); - return null != C - ? s.jsx(n.LexicalNodeMenuPlugin, { - nodeKey: C, - onClose: g, - onSelectOption: A, - options: N, - menuRenderFn: d, - commandPriority: m, - }) - : null; - }), - (exports.URL_MATCHER = - /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-export-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-export-assignment.expect.md deleted file mode 100644 index 28796358a5ce..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-export-assignment.expect.md +++ /dev/null @@ -1,36 +0,0 @@ - -## Input - -```javascript -function useValue(value: number) { - return [value + 1]; -} - -export = useValue; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function useValue(value) { - const $ = _c(2); - const t0 = value + 1; - let t1; - if ($[0] !== t0) { - t1 = [t0]; - $[0] = t0; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} - -export = useValue; - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-export-assignment.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-export-assignment.ts deleted file mode 100644 index 0d20ad075d5f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-export-assignment.ts +++ /dev/null @@ -1,5 +0,0 @@ -function useValue(value: number) { - return [value + 1]; -} - -export = useValue; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-import-equals-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-import-equals-declaration.expect.md deleted file mode 100644 index 3dc9330eb300..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-import-equals-declaration.expect.md +++ /dev/null @@ -1,35 +0,0 @@ - -## Input - -```javascript -import lib = require('shared-runtime'); - -function useValue(value: number) { - return lib.identity(value); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -import lib = require("shared-runtime"); - -function useValue(value) { - const $ = _c(2); - let t0; - if ($[0] !== value) { - t0 = lib.identity(value); - $[0] = value; - $[1] = t0; - } else { - t0 = $[1]; - } - return t0; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-import-equals-declaration.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-import-equals-declaration.ts deleted file mode 100644 index effbc5b1c971..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-import-equals-declaration.ts +++ /dev/null @@ -1,5 +0,0 @@ -import lib = require('shared-runtime'); - -function useValue(value: number) { - return lib.identity(value); -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-namespace-export-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-namespace-export-declaration.expect.md deleted file mode 100644 index a01bd1398f16..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-namespace-export-declaration.expect.md +++ /dev/null @@ -1,33 +0,0 @@ - -## Input - -```javascript -export as namespace Foo; - -function useValue(value: number) { - return {value}; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -export as namespace Foo; - -function useValue(value) { - const $ = _c(2); - let t0; - if ($[0] !== value) { - t0 = { value }; - $[0] = value; - $[1] = t0; - } else { - t0 = $[1]; - } - return t0; -} - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-namespace-export-declaration.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-namespace-export-declaration.ts deleted file mode 100644 index af6d6abce0c9..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-namespace-export-declaration.ts +++ /dev/null @@ -1,5 +0,0 @@ -export as namespace Foo; - -function useValue(value: number) { - return {value}; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-indexer-no-rename-in-outlined.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-indexer-no-rename-in-outlined.expect.md deleted file mode 100644 index 6cf638bf84b0..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-indexer-no-rename-in-outlined.expect.md +++ /dev/null @@ -1,54 +0,0 @@ - -## Input - -```javascript -// @flow -function Component({items}) { - const onClick = () => { - const result = items.reduce( - (acc, item) => { - acc[item.order] = item; - return acc; - }, - ({}: {[displayOrder: number]: {order: number, name: string}}) - ); - submit(result); - }; - return <Button onClick={onClick} />; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component(t0) { - const $ = _c(2); - const { items } = t0; - let t1; - if ($[0] !== items) { - const onClick = () => { - const result = items.reduce( - _temp, - ({}: { [displayOrder: number]: { order: number, name: string } }), - ); - submit(result); - }; - t1 = <Button onClick={onClick} />; - $[0] = items; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} -function _temp(acc, item) { - acc[item.order] = item; - return acc; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-indexer-no-rename-in-outlined.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-indexer-no-rename-in-outlined.js deleted file mode 100644 index a51e5ee774dd..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-indexer-no-rename-in-outlined.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -function Component({items}) { - const onClick = () => { - const result = items.reduce( - (acc, item) => { - acc[item.order] = item; - return acc; - }, - ({}: {[displayOrder: number]: {order: number, name: string}}) - ); - submit(result); - }; - return <Button onClick={onClick} />; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-in-type-cast.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-in-type-cast.expect.md deleted file mode 100644 index 8c829bc37d09..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-in-type-cast.expect.md +++ /dev/null @@ -1,61 +0,0 @@ - -## Input - -```javascript -// @flow @compilationMode(infer) -function Component(props: {items: Array<{isRead: boolean, id: string}>}) { - const results = props.items.map(item => { - const {isRead, id} = item; - return ({ - isRead, - id, - label: isRead ? 'read' : 'unread', - }: { - isRead: boolean, - id: string, - label: string, - }); - }); - return <div>{results.length}</div>; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component(props) { - const $ = _c(4); - let t0; - if ($[0] !== props.items) { - t0 = props.items.map(_temp); - $[0] = props.items; - $[1] = t0; - } else { - t0 = $[1]; - } - const results = t0; - let t1; - if ($[2] !== results.length) { - t1 = <div>{results.length}</div>; - $[2] = results.length; - $[3] = t1; - } else { - t1 = $[3]; - } - return t1; -} -function _temp(item) { - const { isRead, id } = item; - return ({ isRead, id, label: isRead ? "read" : "unread" }: { - isRead: boolean, - id: string, - label: string, - }); -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-in-type-cast.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-in-type-cast.js deleted file mode 100644 index 50381698b356..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-in-type-cast.js +++ /dev/null @@ -1,16 +0,0 @@ -// @flow @compilationMode(infer) -function Component(props: {items: Array<{isRead: boolean, id: string}>}) { - const results = props.items.map(item => { - const {isRead, id} = item; - return ({ - isRead, - id, - label: isRead ? 'read' : 'unread', - }: { - isRead: boolean, - id: string, - label: string, - }); - }); - return <div>{results.length}</div>; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-position-based.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-position-based.expect.md deleted file mode 100644 index 085bddfa427b..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-position-based.expect.md +++ /dev/null @@ -1,41 +0,0 @@ - -## Input - -```javascript -// @flow -function Component({config}: {config: {[key: string]: unknown}}) { - const items = []; - for (const [key, value] of Object.entries(config)) { - items.push((value: {[key: string]: string})); - } - return <List items={items} />; -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -function Component(t0) { - const $ = _c(2); - const { config } = t0; - let t1; - if ($[0] !== config) { - const items = []; - for (const [key, value] of Object.entries(config)) { - items.push((value: { [key: string]: string })); - } - t1 = <List items={items} />; - $[0] = config; - $[1] = t1; - } else { - t1 = $[1]; - } - return t1; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-position-based.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-position-based.js deleted file mode 100644 index 049532513437..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-annotation-rename-position-based.js +++ /dev/null @@ -1,8 +0,0 @@ -// @flow -function Component({config}: {config: {[key: string]: unknown}}) { - const items = []; - for (const [key, value] of Object.entries(config)) { - items.push((value: {[key: string]: string})); - } - return <List items={items} />; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/typescript-this-param-in-uncompiled-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/typescript-this-param-in-uncompiled-function.expect.md deleted file mode 100644 index 22c56eaff05f..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/typescript-this-param-in-uncompiled-function.expect.md +++ /dev/null @@ -1,41 +0,0 @@ - -## Input - -```javascript -// @expectNothingCompiled @compilationMode:"infer" -export function Decorate() { - return function ( - _target: object, - _key: string, - descriptor: PropertyDescriptor, - ) { - const original = descriptor.value; - descriptor.value = function (this: unknown) { - return original.apply(this, []); - }; - }; -} - -``` - -## Code - -```javascript -// @expectNothingCompiled @compilationMode:"infer" -export function Decorate() { - return function ( - _target: object, - _key: string, - descriptor: PropertyDescriptor, - ) { - const original = descriptor.value; - descriptor.value = function (this: unknown) { - return original.apply(this, []); - }; - }; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/typescript-this-param-in-uncompiled-function.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/typescript-this-param-in-uncompiled-function.ts deleted file mode 100644 index 2c33b414ab13..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/typescript-this-param-in-uncompiled-function.ts +++ /dev/null @@ -1,13 +0,0 @@ -// @expectNothingCompiled @compilationMode:"infer" -export function Decorate() { - return function ( - _target: object, - _key: string, - descriptor: PropertyDescriptor, - ) { - const original = descriptor.value; - descriptor.value = function (this: unknown) { - return original.apply(this, []); - }; - }; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/uid-collision-across-functions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/uid-collision-across-functions.expect.md deleted file mode 100644 index 603dc25953ab..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/uid-collision-across-functions.expect.md +++ /dev/null @@ -1,87 +0,0 @@ - -## Input - -```javascript -// When multiple functions in the same file generate UIDs, they should not -// collide. Babel's generateUid accumulates names at the program scope, so -// the second function's UIDs start where the first function left off. - -function Component1({items}) { - const mapped = items.map(x => x.id); - return <div>{mapped}</div>; -} - -function Component2({items}) { - const mapped = items.map(x => x.name); - return <span>{mapped}</span>; -} - -export {Component1, Component2}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // When multiple functions in the same file generate UIDs, they should not -// collide. Babel's generateUid accumulates names at the program scope, so -// the second function's UIDs start where the first function left off. - -function Component1(t0) { - const $ = _c(4); - const { items } = t0; - let t1; - if ($[0] !== items) { - t1 = items.map(_temp); - $[0] = items; - $[1] = t1; - } else { - t1 = $[1]; - } - const mapped = t1; - let t2; - if ($[2] !== mapped) { - t2 = <div>{mapped}</div>; - $[2] = mapped; - $[3] = t2; - } else { - t2 = $[3]; - } - return t2; -} -function _temp(x) { - return x.id; -} - -function Component2(t0) { - const $ = _c(4); - const { items } = t0; - let t1; - if ($[0] !== items) { - t1 = items.map(_temp2); - $[0] = items; - $[1] = t1; - } else { - t1 = $[1]; - } - const mapped = t1; - let t2; - if ($[2] !== mapped) { - t2 = <span>{mapped}</span>; - $[2] = mapped; - $[3] = t2; - } else { - t2 = $[3]; - } - return t2; -} -function _temp2(x) { - return x.name; -} - -export { Component1, Component2 }; - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/uid-collision-across-functions.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/uid-collision-across-functions.js deleted file mode 100644 index 09284b432d78..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/uid-collision-across-functions.js +++ /dev/null @@ -1,15 +0,0 @@ -// When multiple functions in the same file generate UIDs, they should not -// collide. Babel's generateUid accumulates names at the program scope, so -// the second function's UIDs start where the first function left off. - -function Component1({items}) { - const mapped = items.map(x => x.id); - return <div>{mapped}</div>; -} - -function Component2({items}) { - const mapped = items.map(x => x.name); - return <span>{mapped}</span>; -} - -export {Component1, Component2}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index a7ec08cc597e..31757c04298c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -11,7 +11,6 @@ export { CompilerErrorDetail, CompilerDiagnostic, CompilerSuggestionOperation, - type CompilerSuggestion, ErrorSeverity, ErrorCategory, LintRules, diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/RustBackend-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/RustBackend-test.ts deleted file mode 100644 index e33e96541922..000000000000 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/RustBackend-test.ts +++ /dev/null @@ -1,169 +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 { - normalizeIndent, - testRule, - makeTestCaseError, - TestRecommendedRules, -} from './shared-utils'; -import {RuleTester as ESLintTester} from 'eslint'; -import {allRules, recommendedRules} from '../src/rules/ReactCompilerRule'; -import {configs} from '../src/index'; -import type {Rule} from 'eslint'; - -/** - * Check if the Rust compiler native module is available. - * Tests in this file are skipped if the module is not built. - */ -let rustAvailable = false; -try { - require('babel-plugin-react-compiler-rust'); - rustAvailable = true; -} catch { - rustAvailable = false; -} - -const describeIfRust = rustAvailable ? describe : describe.skip; - -/** - * Aggregates all recommended rules but passes __unstable_useRustCompiler - * to each rule via the options. - */ -const TestRecommendedRulesRust: Rule.RuleModule = { - meta: { - type: 'problem', - docs: { - description: 'Test recommended rules with Rust backend', - category: 'Possible Errors', - recommended: true, - }, - schema: [{type: 'object', additionalProperties: true}], - }, - create(context) { - for (const ruleConfig of Object.values( - configs.recommended.plugins['react-compiler'].rules, - )) { - const listener = ruleConfig.rule.create(context); - if (Object.entries(listener).length !== 0) { - throw new Error('TODO: handle rules that return listeners to eslint'); - } - } - return {}; - }, -}; - -function testRuleWithRust( - name: string, - rule: Rule.RuleModule, - tests: { - valid: ESLintTester.ValidTestCase[]; - invalid: ESLintTester.InvalidTestCase[]; - }, -): void { - const eslintTester = new ESLintTester({ - // @ts-ignore[2353] - outdated types - parser: require.resolve('hermes-eslint'), - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, - }); - - // Inject __unstable_useRustCompiler into all test cases - const withRust = ( - cases: ESLintTester.ValidTestCase[], - ): ESLintTester.ValidTestCase[] => - cases.map(tc => ({ - ...tc, - options: [{...(tc.options?.[0] ?? {}), __unstable_useRustCompiler: true}], - })); - - const withRustInvalid = ( - cases: ESLintTester.InvalidTestCase[], - ): ESLintTester.InvalidTestCase[] => - cases.map(tc => ({ - ...tc, - options: [{...(tc.options?.[0] ?? {}), __unstable_useRustCompiler: true}], - })); - - eslintTester.run(name, rule, { - valid: withRust(tests.valid), - invalid: withRustInvalid(tests.invalid), - }); -} - -describeIfRust('Rust backend', () => { - testRuleWithRust('rust-backend-recommended', TestRecommendedRulesRust, { - valid: [ - { - name: 'Basic component compiles without errors', - code: normalizeIndent` - function Component(props) { - return <div>{props.text}</div>; - } - `, - }, - { - name: 'Component with hooks compiles without errors', - code: normalizeIndent` - import {useState} from 'react'; - function Component(props) { - const [state, setState] = useState(0); - return <div onClick={() => setState(state + 1)}>{state}</div>; - } - `, - }, - { - name: "Classes don't throw", - code: normalizeIndent` - class Foo { - #bar() {} - } - `, - }, - ], - invalid: [ - { - name: 'Conditional hook call detected by Rust backend', - code: normalizeIndent` - function Component() { - const result = cond ?? useConditionalHook(); - return <div>{result}</div>; - } - `, - errors: [ - makeTestCaseError( - 'Hooks must always be called in a consistent order', - ), - ], - }, - { - name: 'Multiple diagnostics detected by Rust backend', - code: normalizeIndent` - function useConditional1() { - 'use memo'; - return cond ?? useConditionalHook(); - } - function useConditional2(props) { - 'use memo'; - return props.cond && useConditionalHook(); - } - `, - errors: [ - makeTestCaseError( - 'Hooks must always be called in a consistent order', - ), - makeTestCaseError( - 'Hooks must always be called in a consistent order', - ), - ], - }, - ], - }); -}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/RustBackendComparison-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/RustBackendComparison-test.ts deleted file mode 100644 index c117695f5e70..000000000000 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/RustBackendComparison-test.ts +++ /dev/null @@ -1,564 +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. - */ - -/** - * Comparison test: runs every ESLint test case with both the TS and Rust - * backends and asserts the diagnostics (message + line) are identical. - * - * Uses ESLint's Linter API directly (not RuleTester) so we can capture - * the full list of diagnostics per backend without throwing on the first - * mismatch. - */ - -import {Linter} from 'eslint'; -import {configs} from '../src/index'; -import { - allRules, - recommendedRules, - mapErrorSeverityToESlint, -} from '../src/rules/ReactCompilerRule'; - -// -------------------------------------------------------------------------- -// Check Rust availability -// -------------------------------------------------------------------------- -let rustAvailable = false; -try { - require('babel-plugin-react-compiler-rust'); - rustAvailable = true; -} catch { - // Rust native module not built — skip all comparison tests -} - -const describeIfRust = rustAvailable ? describe : describe.skip; - -// -------------------------------------------------------------------------- -// Helpers -// -------------------------------------------------------------------------- - -/** - * Remove leading indentation (same as normalizeIndent in shared-utils). - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1]?.match(/\s+/)?.[0] ?? ''; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -interface DiagnosticSummary { - message: string; - line: number | null; -} - -/** - * Lint `code` using all recommended rules and return sorted diagnostics. - */ -function lintWithBackend( - code: string, - filename: string, - useRust: boolean, -): DiagnosticSummary[] { - const linter = new Linter(); - - // Register all rules from the plugin - for (const [name, {rule}] of Object.entries(allRules)) { - linter.defineRule(`react-compiler/${name}`, rule); - } - - // Build the rule config: enable all recommended rules at their default severity - const ruleConfig: Record<string, Linter.RuleEntry> = {}; - for (const [name, ruleEntry] of Object.entries(recommendedRules)) { - const severity = mapErrorSeverityToESlint(ruleEntry.severity); - if (severity === 'off') continue; - const opts: Record<string, unknown> = {}; - if (useRust) { - opts.__unstable_useRustCompiler = true; - } - ruleConfig[`react-compiler/${name}`] = [severity, opts]; - } - - const messages = linter.verify( - code, - { - parser: 'hermes-eslint', - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, - rules: ruleConfig, - }, - {filename}, - ); - - // Filter out parser errors — only keep rule diagnostics - const diagnostics: DiagnosticSummary[] = messages - .filter(m => m.ruleId != null) - .map(m => ({ - message: m.message, - line: m.line ?? null, - })); - - // Sort deterministically by line then message - diagnostics.sort((a, b) => { - const lineDiff = (a.line ?? 0) - (b.line ?? 0); - if (lineDiff !== 0) return lineDiff; - return a.message.localeCompare(b.message); - }); - - return diagnostics; -} - -/** - * Lint with a specific single rule (not recommended set). - */ -function lintWithRule( - code: string, - filename: string, - ruleName: string, - useRust: boolean, -): DiagnosticSummary[] { - const linter = new Linter(); - - const ruleEntry = allRules[ruleName]; - if (!ruleEntry) throw new Error(`Unknown rule: ${ruleName}`); - - linter.defineRule(`react-compiler/${ruleName}`, ruleEntry.rule); - - const opts: Record<string, unknown> = {}; - if (useRust) { - opts.__unstable_useRustCompiler = true; - } - const severity = mapErrorSeverityToESlint(ruleEntry.severity); - if (severity === 'off') return []; - - const messages = linter.verify( - code, - { - parser: 'hermes-eslint', - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, - rules: { - [`react-compiler/${ruleName}`]: [severity, opts], - }, - }, - {filename}, - ); - - const diagnostics: DiagnosticSummary[] = messages - .filter(m => m.ruleId != null) - .map(m => ({ - message: m.message, - line: m.line ?? null, - })); - - diagnostics.sort((a, b) => { - const lineDiff = (a.line ?? 0) - (b.line ?? 0); - if (lineDiff !== 0) return lineDiff; - return a.message.localeCompare(b.message); - }); - - return diagnostics; -} - -// -------------------------------------------------------------------------- -// Test case catalog — every test case from the existing test files -// -------------------------------------------------------------------------- - -interface ComparisonTestCase { - name: string; - code: string; - filename?: string; - /** Which rule to test in isolation, or 'recommended' for all */ - rule: string; - expectedErrorCount: number; -} - -// Gather all test cases from across the test suite. We replicate the test -// data here so the comparison is self-contained. - -const testCases: ComparisonTestCase[] = [ - // ---- PluginTest-test.ts (recommended rules) ---- - { - name: '[PluginTest] Basic example with component syntax', - code: normalizeIndent` - export default component HelloWorld( - text: string = 'Hello!', - onClick: () => void, - ) { - return <div onClick={onClick}>{text}</div>; - } - `, - rule: 'recommended', - expectedErrorCount: 0, - }, - { - name: '[PluginTest] [Invariant] Defined after use', - code: normalizeIndent` - function Component(props) { - let y = function () { - m(x); - }; - - let x = { a }; - m(x); - return y; - } - `, - rule: 'recommended', - expectedErrorCount: 0, - }, - { - name: "[PluginTest] Classes don't throw", - code: normalizeIndent` - class Foo { - #bar() {} - } - `, - rule: 'recommended', - expectedErrorCount: 0, - }, - { - name: '[PluginTest] Multiple diagnostic kinds from the same function', - code: normalizeIndent` - import Child from './Child'; - function Component() { - const result = cond ?? useConditionalHook(); - return <> - {Child(result)} - </>; - } - `, - rule: 'recommended', - expectedErrorCount: 2, - }, - { - name: '[PluginTest] Multiple diagnostics within the same file', - code: normalizeIndent` - function useConditional1() { - 'use memo'; - return cond ?? useConditionalHook(); - } - function useConditional2(props) { - 'use memo'; - return props.cond && useConditionalHook(); - } - `, - rule: 'recommended', - expectedErrorCount: 2, - }, - { - name: "[PluginTest] 'use no forget' does not disable eslint rule", - code: normalizeIndent` - let count = 0; - function Component() { - 'use no forget'; - return cond ?? useConditionalHook(); - - } - `, - rule: 'recommended', - expectedErrorCount: 1, - }, - { - name: '[PluginTest] Multiple non-fatal useMemo diagnostics', - code: normalizeIndent` - import {useMemo, useState} from 'react'; - - function Component({item, cond}) { - const [prevItem, setPrevItem] = useState(item); - const [state, setState] = useState(0); - - useMemo(() => { - if (cond) { - setPrevItem(item); - setState(0); - } - }, [cond, item, init]); - - return <Child x={state} />; - } - `, - rule: 'recommended', - expectedErrorCount: 4, - }, - - // ---- InvalidHooksRule-test.ts ---- - { - name: '[InvalidHooks] Basic example (valid)', - code: normalizeIndent` - function Component() { - useHook(); - return <div>Hello world</div>; - } - `, - rule: 'recommended', - expectedErrorCount: 0, - }, - { - name: '[InvalidHooks] Violation with Flow suppression (valid)', - code: ` - // Valid since error already suppressed with flow. - function useHook() { - if (cond) { - // $FlowFixMe[react-rule-hook] - useConditionalHook(); - } - } - `, - rule: 'recommended', - expectedErrorCount: 0, - }, - { - name: '[InvalidHooks] Simple violation', - code: normalizeIndent` - function useConditional() { - if (cond) { - useConditionalHook(); - } - } - `, - rule: 'recommended', - expectedErrorCount: 1, - }, - { - name: '[InvalidHooks] Multiple diagnostics within the same function', - code: normalizeIndent` - function useConditional() { - cond ?? useConditionalHook(); - props.cond && useConditionalHook(); - return <div>Hello world</div>; - } - `, - rule: 'recommended', - expectedErrorCount: 2, - }, - - // ---- ImpureFunctionCallsRule-test.ts ---- - { - name: '[ImpureFunctionCalls] Known impure function calls are caught', - code: normalizeIndent` - function Component() { - const date = Date.now(); - const now = performance.now(); - const rand = Math.random(); - return <Foo date={date} now={now} rand={rand} />; - } - `, - rule: 'recommended', - expectedErrorCount: 3, - }, - - // ---- NoCapitalizedCallsRule-test.ts ---- - { - name: '[NoCapitalizedCalls] Simple violation', - code: normalizeIndent` - import Child from './Child'; - function Component() { - return <> - {Child()} - </>; - } - `, - rule: 'recommended', - expectedErrorCount: 1, - }, - { - name: '[NoCapitalizedCalls] Method call violation', - code: normalizeIndent` - import myModule from './MyModule'; - function Component() { - return <> - {myModule.Child()} - </>; - } - `, - rule: 'recommended', - expectedErrorCount: 1, - }, - { - name: '[NoCapitalizedCalls] Multiple diagnostics', - code: normalizeIndent` - import Child1 from './Child1'; - import MyModule from './MyModule'; - function Component() { - return <> - {Child1()} - {MyModule.Child2()} - </>; - } - `, - rule: 'recommended', - expectedErrorCount: 2, - }, - - // ---- NoAmbiguousJsxRule-test.ts ---- - { - name: '[NoAmbiguousJsx] JSX in try blocks', - code: normalizeIndent` - function Component(props) { - let el; - try { - el = <Child />; - } catch { - return null; - } - return el; - } - `, - rule: 'recommended', - expectedErrorCount: 1, - }, - - // ---- NoRefAccessInRender-tests.ts ---- - { - name: '[NoRefAccessInRender] Simple ref access in render', - code: normalizeIndent` - function Component(props) { - const ref = useRef(null); - const value = ref.current; - return value; - } - `, - rule: 'recommended', - expectedErrorCount: 1, - }, - - // ---- ReactCompilerRuleTypescript-test.ts ---- - { - name: '[TypeScript] Basic example (valid)', - code: normalizeIndent` - function Button(props) { - return null; - } - `, - filename: 'test.tsx', - rule: 'recommended', - expectedErrorCount: 0, - }, - { - name: '[TypeScript] Repro for hooks as normal values', - code: normalizeIndent` - function Button(props) { - const scrollview = React.useRef<ScrollView>(null); - return <Button thing={scrollview} />; - } - `, - filename: 'test.tsx', - rule: 'recommended', - expectedErrorCount: 0, - }, - { - name: '[TypeScript] Mutating useState value', - code: ` - import { useState } from 'react'; - function Component(props) { - // typescript syntax that hermes-parser doesn't understand yet - const x: \`foo\${1}\` = 'foo1'; - const [state, setState] = useState({a: 0}); - state.a = 1; - return <div>{props.foo}</div>; - } - `, - filename: 'test.tsx', - rule: 'recommended', - expectedErrorCount: 1, - }, -]; - -// -------------------------------------------------------------------------- -// Tests -// -------------------------------------------------------------------------- - -describeIfRust('TS vs Rust backend comparison', () => { - const results: Array<{ - name: string; - ts: DiagnosticSummary[]; - rust: DiagnosticSummary[]; - match: boolean; - }> = []; - - for (const tc of testCases) { - test(tc.name, () => { - const filename = tc.filename ?? 'test.js'; - const tsDiags = lintWithBackend(tc.code, filename, false); - const rustDiags = lintWithBackend(tc.code, filename, true); - - results.push({ - name: tc.name, - ts: tsDiags, - rust: rustDiags, - match: JSON.stringify(tsDiags) === JSON.stringify(rustDiags), - }); - - // First check: both backends agree on error count - if (tsDiags.length !== rustDiags.length) { - const tsMessages = tsDiags - .map(d => ` L${d.line}: ${d.message}`) - .join('\n'); - const rustMessages = rustDiags - .map(d => ` L${d.line}: ${d.message}`) - .join('\n'); - console.log( - `\n⚠️ DIAGNOSTIC COUNT MISMATCH: ${tc.name}\n` + - ` TS (${tsDiags.length}):\n${tsMessages || ' (none)'}\n` + - ` Rust (${rustDiags.length}):\n${rustMessages || ' (none)'}\n`, - ); - } - - // Second check: messages match in content - // We compare sorted diagnostics — message text should be identical - const tsMessages = tsDiags.map(d => d.message); - const rustMessages = rustDiags.map(d => d.message); - - // Log detailed diff for any mismatch - if (JSON.stringify(tsDiags) !== JSON.stringify(rustDiags)) { - console.log( - `\n⚠️ DIAGNOSTIC MISMATCH: ${tc.name}\n` + - ` TS diagnostics:\n${tsDiags.map(d => ` L${d.line}: ${d.message}`).join('\n') || ' (none)'}\n` + - ` Rust diagnostics:\n${rustDiags.map(d => ` L${d.line}: ${d.message}`).join('\n') || ' (none)'}\n`, - ); - } - - // Assert equality — both count and messages should match - expect(rustDiags.length).toBe(tsDiags.length); - expect(rustMessages).toEqual(tsMessages); - }); - } - - // Summary — printed once after all tests - afterAll(() => { - const total = results.length; - const matches = results.filter(r => r.match).length; - const mismatches = results.filter(r => !r.match); - - console.log('\n' + '='.repeat(70)); - console.log(`TS vs Rust ESLint Backend Comparison`); - console.log('='.repeat(70)); - console.log(`Total test cases: ${total}`); - console.log(`Matching: ${matches}`); - console.log(`Mismatches: ${mismatches.length}`); - - if (mismatches.length > 0) { - console.log('\nMismatched cases:'); - for (const m of mismatches) { - console.log(`\n ❌ ${m.name}`); - console.log( - ` TS (${m.ts.length}): ${m.ts.map(d => `L${d.line}:${d.message.slice(0, 60)}`).join(' | ') || '(none)'}`, - ); - console.log( - ` Rust(${m.rust.length}): ${m.rust.map(d => `L${d.line}:${d.message.slice(0, 60)}`).join(' | ') || '(none)'}`, - ); - } - } else { - console.log('\n✅ All diagnostics match between TS and Rust backends!'); - } - console.log('='.repeat(70) + '\n'); - }); -}); diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index ea18b0af9851..3dd77d0e8bf3 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -35,17 +35,11 @@ "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" }, "peerDependencies": { - "eslint": ">=7", - "babel-plugin-react-compiler-rust": "*" - }, - "peerDependenciesMeta": { - "babel-plugin-react-compiler-rust": { - "optional": true - } + "eslint": ">=7" }, "repository": { "type": "git", - "url": "git+https://github.com/react/react.git", + "url": "git+https://github.com/facebook/react.git", "directory": "compiler/packages/eslint-plugin-react-compiler" }, "license": "MIT" diff --git a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts index 58a95751701c..8f41b3afaba4 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -7,8 +7,9 @@ import type {SourceLocation as BabelSourceLocation} from '@babel/types'; import { + CompilerDiagnosticOptions, + CompilerErrorDetailOptions, CompilerSuggestionOperation, - type CompileErrorDetail, } from 'babel-plugin-react-compiler/src'; import type {Linter, Rule} from 'eslint'; import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler'; @@ -23,40 +24,12 @@ function assertExhaustive(_: never, errorMsg: string): never { throw new Error(errorMsg); } -/** - * Get the primary source location from a CompileErrorDetail. - * Handles both the new format (details array) and legacy format (flat loc). - */ -function primaryLocation( - detail: CompileErrorDetail, -): BabelSourceLocation | null { - if (detail.details != null) { - const firstError = detail.details.find(d => d.kind === 'error'); - if (firstError != null) { - return firstError.loc ?? null; - } - } - return detail.loc ?? null; -} - -/** - * Format an error message from a CompileErrorDetail, matching the old - * CompilerErrorDetail.printErrorMessage() / CompilerDiagnostic.printErrorMessage() behavior. - */ -function printErrorMessage(detail: CompileErrorDetail): string { - const buffer = [`[ReactCompilerError] ${detail.reason}`]; - if (detail.description != null) { - buffer.push(`\n\n${detail.description}.`); - } - return buffer.join(''); -} - function makeSuggestions( - detail: CompileErrorDetail, + detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, ): Array<Rule.SuggestionReportDescriptor> { const suggest: Array<Rule.SuggestionReportDescriptor> = []; if (Array.isArray(detail.suggestions)) { - for (const suggestion of detail.suggestions as Array<any>) { + for (const suggestion of detail.suggestions) { switch (suggestion.op) { case CompilerSuggestionOperation.InsertBefore: suggest.push({ @@ -143,8 +116,8 @@ function makeRule(rule: LintRule): Rule.RuleModule { if (event.kind === 'CompileError') { const detail = event.detail; if (detail.category === rule.category) { - const loc = primaryLocation(detail); - if (loc == null) { + const loc = detail.primaryLocation(); + if (loc == null || typeof loc === 'symbol') { continue; } if ( @@ -161,9 +134,11 @@ function makeRule(rule: LintRule): Rule.RuleModule { * we should deduplicate them with a "reported" set */ context.report({ - message: printErrorMessage(detail), + message: detail.printErrorMessage(result.sourceCode, { + eslint: true, + }), loc, - suggest: makeSuggestions(detail), + suggest: makeSuggestions(detail.options), }); } } diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts index cf8e350d3ade..aa55c64237e1 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -20,34 +20,6 @@ import * as HermesParser from 'hermes-parser'; import {isDeepStrictEqual} from 'util'; import type {ParseResult} from '@babel/parser'; -/** - * Lazy-loaded Rust compiler Babel plugin. - * Only loaded when __unstable_useRustCompiler is enabled. - */ -let _rustPluginLoaded = false; -let _rustPlugin: ((babel: any) => any) | null = null; - -function getRustPlugin(): (babel: any) => any { - if (!_rustPluginLoaded) { - _rustPluginLoaded = true; - try { - _rustPlugin = - // eslint-disable-next-line no-restricted-syntax - require('babel-plugin-react-compiler-rust').default; - } catch { - _rustPlugin = null; - } - } - if (_rustPlugin == null) { - throw new Error( - 'eslint-plugin-react-compiler: __unstable_useRustCompiler is enabled but ' + - 'babel-plugin-react-compiler-rust is not available. ' + - 'Make sure the package is installed and its native module is built.', - ); - } - return _rustPlugin; -} - const COMPILER_OPTIONS: PluginOptions = { outputMode: 'lint', panicThreshold: 'none', @@ -110,9 +82,6 @@ function runReactCompilerImpl({ filename, userOpts, }: RunParams): RunCacheEntry { - const useRustCompiler = - (userOpts as Record<string, unknown>).__unstable_useRustCompiler === true; - // Compat with older versions of eslint const options: PluginOptions = parsePluginOptions({ ...COMPILER_OPTIONS, @@ -169,60 +138,18 @@ function runReactCompilerImpl({ if (babelAST != null) { results.flowSuppressions = getFlowSuppressions(sourceCode); - - if (useRustCompiler) { - // Rust compiler path: use the Rust NAPI Babel plugin instead of the - // TS compiler. The Rust plugin handles scope extraction, compilation, - // and event forwarding internally via its own resolveOptions + - // compileWithRust pipeline. - const RustPlugin = getRustPlugin(); - const rustOpts: Record<string, unknown> = { - ...COMPILER_OPTIONS, - ...userOpts, - environment: { - ...(COMPILER_OPTIONS.environment as Record<string, unknown>), - ...((userOpts.environment as Record<string, unknown>) ?? {}), - }, - logger: { - logEvent: ( - eventFilename: string | null, - event: LoggerEvent, - ): void => { - userLogger?.logEvent(eventFilename ?? '', event); - results.events.push(event); - }, - }, - }; - // Don't pass the ESLint-only flag to the compiler - delete rustOpts.__unstable_useRustCompiler; - - try { - transformFromAstSync(babelAST, sourceCode.text, { - filename, - highlightCode: false, - retainLines: true, - plugins: [[RustPlugin, rustOpts]], - sourceType: 'module', - configFile: false, - babelrc: false, - }); - } catch { - /* errors handled by injected logger */ - } - } else { - try { - transformFromAstSync(babelAST, sourceCode.text, { - filename, - highlightCode: false, - retainLines: true, - plugins: [[BabelPluginReactCompiler, options]], - sourceType: 'module', - configFile: false, - babelrc: false, - }); - } catch (err) { - /* errors handled by injected logger */ - } + try { + transformFromAstSync(babelAST, sourceCode.text, { + filename, + highlightCode: false, + retainLines: true, + plugins: [[BabelPluginReactCompiler, options]], + sourceType: 'module', + configFile: false, + babelrc: false, + }); + } catch (err) { + /* errors handled by injected logger */ } } diff --git a/compiler/packages/make-read-only-util/package.json b/compiler/packages/make-read-only-util/package.json index 5474f3c0480c..20a4643cd529 100644 --- a/compiler/packages/make-read-only-util/package.json +++ b/compiler/packages/make-read-only-util/package.json @@ -24,7 +24,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/react/react.git", + "url": "git+https://github.com/facebook/react.git", "directory": "compiler/packages/make-read-only-util" } } diff --git a/compiler/packages/react-compiler-healthcheck/package.json b/compiler/packages/react-compiler-healthcheck/package.json index 6b3cb6b57c5f..5c3d2f412d97 100644 --- a/compiler/packages/react-compiler-healthcheck/package.json +++ b/compiler/packages/react-compiler-healthcheck/package.json @@ -27,7 +27,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/react/react.git", + "url": "git+https://github.com/facebook/react.git", "directory": "compiler/packages/react-compiler-healthcheck" } } diff --git a/compiler/packages/react-compiler-runtime/package.json b/compiler/packages/react-compiler-runtime/package.json index ffb591d163c5..60a192b0a7ca 100644 --- a/compiler/packages/react-compiler-runtime/package.json +++ b/compiler/packages/react-compiler-runtime/package.json @@ -19,7 +19,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/react/react.git", + "url": "git+https://github.com/facebook/react.git", "directory": "compiler/packages/react-compiler-runtime" } } diff --git a/compiler/packages/react-forgive/client/package.json b/compiler/packages/react-forgive/client/package.json index d6f3f019fd2e..a975439726f2 100644 --- a/compiler/packages/react-forgive/client/package.json +++ b/compiler/packages/react-forgive/client/package.json @@ -10,7 +10,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/react/react.git", + "url": "git+https://github.com/facebook/react.git", "directory": "compiler/packages/react-forgive" }, "dependencies": { diff --git a/compiler/packages/react-forgive/package.json b/compiler/packages/react-forgive/package.json index 94f0f6ce008e..fc7d71095c51 100644 --- a/compiler/packages/react-forgive/package.json +++ b/compiler/packages/react-forgive/package.json @@ -6,7 +6,7 @@ "version": "0.0.0", "repository": { "type": "git", - "url": "git+https://github.com/react/react.git", + "url": "git+https://github.com/facebook/react.git", "directory": "compiler/packages/react-forgive" }, "categories": [ diff --git a/compiler/packages/react-forgive/server/package.json b/compiler/packages/react-forgive/server/package.json index d7806f2c5cbb..c91258607205 100644 --- a/compiler/packages/react-forgive/server/package.json +++ b/compiler/packages/react-forgive/server/package.json @@ -10,7 +10,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/react/react.git", + "url": "git+https://github.com/facebook/react.git", "directory": "compiler/packages/react-forgive" }, "dependencies": { diff --git a/compiler/packages/react-mcp-server/package.json b/compiler/packages/react-mcp-server/package.json index f0d25b2e5ace..07dc378de12e 100644 --- a/compiler/packages/react-mcp-server/package.json +++ b/compiler/packages/react-mcp-server/package.json @@ -35,7 +35,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/react/react.git", + "url": "git+https://github.com/facebook/react.git", "directory": "compiler/packages/react-mcp-server" } } diff --git a/compiler/packages/snap/package.json b/compiler/packages/snap/package.json index e2edb778167b..92a170b365d5 100644 --- a/compiler/packages/snap/package.json +++ b/compiler/packages/snap/package.json @@ -17,7 +17,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/react/react.git", + "url": "git+https://github.com/facebook/react.git", "directory": "compiler/packages/snap" }, "dependencies": { @@ -33,7 +33,7 @@ "chalk": "4", "fbt": "^1.0.2", "glob": "^10.3.10", - "hermes-parser": "^0.32.0", + "hermes-parser": "^0.25.1", "jsdom": "^22.1.0", "react": "0.0.0-experimental-4beb1fd8-20241118", "react-dom": "0.0.0-experimental-4beb1fd8-20241118", diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 84fde8ce2fd8..531c3cf27f63 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -6,26 +6,6 @@ */ const skipFilter = new Set([ - /** - * Fixtures using external modules (jest, Lexical, etc.) that can't be evaluated - * in the test harness. These were previously error.todo-* but now compile in both TS and Rust. - */ - 'todo-hir_identifier_diff', - 'todo-hir_numeric_format', - 'todo-pattern1b_type_to_primitive', - 'todo-round2_unicode_string', - 'todo-round3_outlined_naming', - 'todo-round3_promote_used_temps', - - /** - * `export as namespace` is a .d.ts-shaped construct that breaks sprout's - * second-stage TS->CJS evaluator transform itself. The sibling ts-* interop - * fixtures are deliberately not skipped: that transform handles their - * syntax, so they pass sprout (still producing the standard "Fixture not - * implemented" eval result). - */ - 'ts-namespace-export-declaration', - /** * Observable different in logging between Forget and non-Forget */ diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 1beedef8a685..0ee2ee0945b0 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -116,7 +116,6 @@ export function parseInput( sourceFilename: filename, sourceType, enableExperimentalComponentSyntax: true, - enableExperimentalFlowMatchSyntax: true, }); } else { return BabelParser.parse(input, { diff --git a/compiler/packages/snap/src/constants.ts b/compiler/packages/snap/src/constants.ts index b5bd40f6b85a..788a2a6865da 100644 --- a/compiler/packages/snap/src/constants.ts +++ b/compiler/packages/snap/src/constants.ts @@ -28,13 +28,3 @@ export const FIXTURES_PATH = path.join( 'compiler', ); export const SNAPSHOT_EXTENSION = '.expect.md'; - -export const BABEL_PLUGIN_RUST_ROOT = path.normalize( - path.join(PROJECT_ROOT, 'packages', 'babel-plugin-react-compiler-rust'), -); -export const BABEL_PLUGIN_RUST_SRC = path.normalize( - path.join(BABEL_PLUGIN_RUST_ROOT, 'dist', 'index.js'), -); -export const CRATES_PATH = path.normalize( - path.join(PROJECT_ROOT, '..', 'crates'), -); diff --git a/compiler/packages/snap/src/minimize.ts b/compiler/packages/snap/src/minimize.ts index e9574b1759c1..c734c9306d98 100644 --- a/compiler/packages/snap/src/minimize.ts +++ b/compiler/packages/snap/src/minimize.ts @@ -12,11 +12,7 @@ import traverse from '@babel/traverse'; import * as t from '@babel/types'; import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils'; import {parseInput} from './compiler.js'; -import { - PARSE_CONFIG_PRAGMA_IMPORT, - BABEL_PLUGIN_SRC, - BABEL_PLUGIN_RUST_SRC, -} from './constants.js'; +import {PARSE_CONFIG_PRAGMA_IMPORT, BABEL_PLUGIN_SRC} from './constants.js'; type CompileSuccess = {kind: 'success'}; type CompileParseError = {kind: 'parse_error'; message: string}; @@ -2051,11 +2047,12 @@ export function minimize( filename: string, language: 'flow' | 'typescript', sourceType: 'module' | 'script', - useRust: boolean = false, ): MinimizeResult { // Load the compiler plugin - const pluginSrc = useRust ? BABEL_PLUGIN_RUST_SRC : BABEL_PLUGIN_SRC; - const importedCompilerPlugin = require(pluginSrc) as Record<string, unknown>; + const importedCompilerPlugin = require(BABEL_PLUGIN_SRC) as Record< + string, + unknown + >; const BabelPluginReactCompiler = importedCompilerPlugin[ 'default' ] as PluginObj; @@ -2143,157 +2140,3 @@ export function minimize( return {kind: 'minimized', source: currentCode}; } - -/** - * Compile code with a given plugin and return the output code (or error string). - */ -function compileAndGetOutput( - code: string, - filename: string, - language: 'flow' | 'typescript', - sourceType: 'module' | 'script', - plugin: PluginObj, - parseConfigPragmaFn: typeof ParseConfigPragma, -): string | null { - let ast: t.File; - try { - ast = parseInput(code, filename, language, sourceType); - } catch { - return null; - } - - const firstLine = code.substring(0, code.indexOf('\n')); - const config = parseConfigPragmaFn(firstLine, {compilationMode: 'all'}); - const options = { - ...config, - environment: { - ...config.environment, - }, - logger: { - logEvent: () => {}, - debugLogIRs: () => {}, - }, - enableReanimatedCheck: false, - }; - - try { - const result = transformFromAstSync(ast, code, { - filename: '/' + filename, - highlightCode: false, - retainLines: true, - compact: true, - plugins: [[plugin, options]], - sourceType: 'module', - ast: false, - cloneInputAst: true, - configFile: false, - babelrc: false, - }); - return result?.code ?? null; - } catch (e: unknown) { - return `ERROR: ${(e as Error).message}`; - } -} - -type MinimizeRustDeltaResult = - | {kind: 'no_delta'} - | {kind: 'minimal'} - | {kind: 'minimized'; source: string}; - -/** - * Core minimization loop that attempts to reduce the input source code - * while preserving a delta (difference in output) between the TS and Rust compilers. - */ -export function minimizeRustDelta( - input: string, - filename: string, - language: 'flow' | 'typescript', - sourceType: 'module' | 'script', -): MinimizeRustDeltaResult { - // Load both compiler plugins - const importedTsPlugin = require(BABEL_PLUGIN_SRC) as Record<string, unknown>; - const importedRustPlugin = require(BABEL_PLUGIN_RUST_SRC) as Record< - string, - unknown - >; - const tsPlugin = importedTsPlugin['default'] as PluginObj; - const rustPlugin = importedRustPlugin['default'] as PluginObj; - const parseConfigPragmaForTests = importedTsPlugin[ - PARSE_CONFIG_PRAGMA_IMPORT - ] as typeof ParseConfigPragma; - - // Helper: check if TS and Rust outputs differ for given code - function hasDelta(code: string): boolean { - const tsOutput = compileAndGetOutput( - code, - filename, - language, - sourceType, - tsPlugin, - parseConfigPragmaForTests, - ); - const rustOutput = compileAndGetOutput( - code, - filename, - language, - sourceType, - rustPlugin, - parseConfigPragmaForTests, - ); - // Both null (e.g. parse error) means no delta - if (tsOutput == null && rustOutput == null) return false; - return tsOutput !== rustOutput; - } - - // Verify the initial input has a delta - if (!hasDelta(input)) { - return {kind: 'no_delta'}; - } - - // Parse the initial AST - let currentAst = parseInput(input, filename, language, sourceType); - let currentCode = input; - let changed = true; - let iterations = 0; - const maxIterations = 1000; - - process.stdout.write('\nMinimizing'); - - while (changed && iterations < maxIterations) { - changed = false; - iterations++; - - for (const strategy of simplificationStrategies) { - const generator = strategy.generator(currentAst); - - for (const candidateAst of generator) { - let candidateCode: string; - try { - candidateCode = astToCode(candidateAst); - } catch { - continue; - } - - if (hasDelta(candidateCode)) { - currentAst = candidateAst; - currentCode = candidateCode; - changed = true; - process.stdout.write('.'); - break; - } - } - - if (changed) { - break; - } - } - } - - console.log('\n'); - - if (currentCode === input) { - return {kind: 'minimal'}; - } - - return {kind: 'minimized', source: currentCode}; -} diff --git a/compiler/packages/snap/src/reporter.ts b/compiler/packages/snap/src/reporter.ts index 412e4b523440..29ca81e34597 100644 --- a/compiler/packages/snap/src/reporter.ts +++ b/compiler/packages/snap/src/reporter.ts @@ -18,48 +18,6 @@ ${s} } const SPROUT_SEPARATOR = '\n### Eval output\n'; -/** - * Normalize blank lines in the ## Code section of a snapshot. - * Strips blank lines that appear inside code blocks so that - * whitespace-only differences don't cause test failures. - */ -export function normalizeCodeBlankLines(snapshot: string): string { - const codeStart = snapshot.indexOf('## Code\n'); - if (codeStart === -1) return snapshot; - const codeBlockStart = snapshot.indexOf('```javascript\n', codeStart); - if (codeBlockStart === -1) return snapshot; - const contentStart = codeBlockStart + '```javascript\n'.length; - const codeBlockEnd = snapshot.indexOf('\n```', contentStart); - if (codeBlockEnd === -1) return snapshot; - - const before = snapshot.slice(0, contentStart); - const code = snapshot.slice(contentStart, codeBlockEnd); - const after = snapshot.slice(codeBlockEnd); - - const lines = code.split('\n'); - const filtered = lines.filter(line => { - if (line.trim() === '') return false; - // Strip unused var declarations from babel-plugin-idx (e.g., `var _ref2;`) - // These are generated by generateUidIdentifier and scope.push() in - // babel-plugin-idx, but the actual references may use a different _refN. - // The TS and Rust compilers interact differently with Babel's scope UID - // counter, producing different _refN numbering for unused declarations. - const match = line.match(/^\s*var (_ref\d*);$/); - if (match) { - const varName = match[1]; - // Check if this identifier is used anywhere else in the code - const regex = new RegExp('\\b' + varName + '\\b'); - const otherLines = lines.filter(l => l !== line); - const isUsed = otherLines.some(l => regex.test(l)); - if (!isUsed) return false; - } - return true; - }); - const normalized = filtered.join('\n'); - - return before + normalized + after; -} - export function writeOutputToString( input: string, compilerOutput: string | null, @@ -181,40 +139,13 @@ export async function update(results: TestResults): Promise<void> { * Report test results to the user * @returns boolean indicatig whether all tests passed */ -// Fixtures where TS and Rust produce different output. Snapshots reflect Rust -// output (source of truth). Skipped when running the TS compiler. -const TS_SKIP_FIXTURES: Set<string> = new Set([ - // Rust compiles successfully, TS would error. Renamed from error.todo-/error.bug-. - 'todo-hoist-type-alias-before-declaration', - // Error message/format divergences - 'fbt/error.todo-locally-require-fbt', - // Minor output difference (TS adds unused runtime import) - 'use-no-forget-multiple-with-eslint-suppression', - // Cosmetic blank-line/unused-var differences between Rust and TS codegen - 'debugger', - 'debugger-memoized', - 'idx-no-outlining', - 'optional-call-with-independently-memoizable-arg', -]); export function report( results: TestResults, verbose: boolean = false, - rust: boolean = false, ): boolean { const failures: Array<[string, TestResult]> = []; for (const [basename, result] of results) { - if (!rust && TS_SKIP_FIXTURES.has(basename)) { - continue; - } - const actual = - rust && result.actual - ? normalizeCodeBlankLines(result.actual) - : result.actual; - const expected = - rust && result.expected - ? normalizeCodeBlankLines(result.expected) - : result.expected; - if (actual === expected && result.unexpectedError == null) { + if (result.actual === result.expected && result.unexpectedError == null) { if (verbose) { console.log( chalk.green.inverse.bold(' PASS ') + ' ' + chalk.dim(basename), @@ -240,27 +171,19 @@ export function report( ` >> Unexpected error during test: \n${result.unexpectedError}`, ); } else { - const actual = - rust && result.actual - ? normalizeCodeBlankLines(result.actual) - : result.actual; - const expected = - rust && result.expected - ? normalizeCodeBlankLines(result.expected) - : result.expected; - if (expected == null) { - invariant(actual != null, '[Tester] Internal failure.'); + if (result.expected == null) { + invariant(result.actual != null, '[Tester] Internal failure.'); console.log( chalk.red('[ expected fixture output is absent ]') + '\n', ); - } else if (actual == null) { - invariant(expected != null, '[Tester] Internal failure.'); + } else if (result.actual == null) { + invariant(result.expected != null, '[Tester] Internal failure.'); console.log( chalk.red(`[ fixture input for ${result.outputPath} is absent ]`) + '\n', ); } else { - console.log(diff(expected, actual) + '\n'); + console.log(diff(result.expected, result.actual) + '\n'); } } } diff --git a/compiler/packages/snap/src/runner-watch.ts b/compiler/packages/snap/src/runner-watch.ts index 65abaabed9f6..dcec52689471 100644 --- a/compiler/packages/snap/src/runner-watch.ts +++ b/compiler/packages/snap/src/runner-watch.ts @@ -8,15 +8,9 @@ import watcher from '@parcel/watcher'; import path from 'path'; import ts from 'typescript'; -import { - FIXTURES_PATH, - BABEL_PLUGIN_ROOT, - BABEL_PLUGIN_RUST_ROOT, - CRATES_PATH, -} from './constants'; +import {FIXTURES_PATH, BABEL_PLUGIN_ROOT} from './constants'; import {TestFilter, getFixtures} from './fixture-utils'; import {execSync} from 'child_process'; -import fs from 'fs'; export function watchSrc( onStart: () => void, @@ -161,7 +155,6 @@ function subscribeFixtures( function subscribeTsc( state: RunnerState, onChange: (state: RunnerState) => void, - enableRust: boolean = false, ) { // Run TS in incremental watch mode watchSrc( @@ -180,10 +173,6 @@ function subscribeTsc( console.warn('Failed to build compiler with tsup:', e); } } - // When using Rust, also build the Rust compiler after TS build succeeds - if (isCompilerBuildValid && enableRust) { - isCompilerBuildValid = buildRust(); - } // Bump the compiler version after a build finishes // and re-run tests if (isCompilerBuildValid) { @@ -196,77 +185,6 @@ function subscribeTsc( ); } -export function buildRust(): boolean { - const compilerRoot = path.join(BABEL_PLUGIN_ROOT, '..', '..'); - try { - execSync('cargo build -p react_compiler_napi', { - cwd: compilerRoot, - stdio: 'inherit', - }); - } catch (e) { - console.error('Failed to build Rust compiler with cargo:', e); - return false; - } - - // Copy the built native module to the babel plugin package - const platform = process.platform; - const ext = platform === 'darwin' ? 'dylib' : 'so'; - const libName = - platform === 'darwin' - ? 'libreact_compiler_napi.dylib' - : 'libreact_compiler_napi.so'; - const sourcePath = path.join(compilerRoot, 'target', 'debug', libName); - const destPath = path.join(BABEL_PLUGIN_RUST_ROOT, 'native', 'index.node'); - - try { - fs.copyFileSync(sourcePath, destPath); - } catch (e) { - console.error( - `Failed to copy native module (${sourcePath} -> ${destPath}):`, - e, - ); - return false; - } - - // Build the TypeScript wrapper - try { - execSync('yarn build', {cwd: BABEL_PLUGIN_RUST_ROOT, stdio: 'inherit'}); - console.log('Built Rust compiler successfully'); - } catch (e) { - console.error('Failed to build Rust babel plugin with tsc:', e); - return false; - } - - return true; -} - -function subscribeRustCrates( - state: RunnerState, - onChange: (state: RunnerState) => void, -) { - watcher.subscribe(CRATES_PATH, async (err, events) => { - if (err) { - console.error(err); - process.exit(1); - } - // Only rebuild on .rs file changes - const hasRustChanges = events.some(e => e.path.endsWith('.rs')); - if (!hasRustChanges) { - return; - } - console.log('\nRust source changed, rebuilding...'); - if (buildRust()) { - state.compilerVersion++; - state.isCompilerBuildValid = true; - state.mode.action = RunnerAction.Test; - onChange(state); - } else { - state.isCompilerBuildValid = false; - console.error('Rust build failed, waiting for changes...'); - } - }); -} - /** * Levenshtein edit distance between two strings */ @@ -499,7 +417,6 @@ export async function makeWatchRunner( onChange: (state: RunnerState) => void, debugMode: boolean, initialPattern?: string, - enableRust: boolean = false, ): Promise<void> { // Determine initial filter state let filter: TestFilter | null = null; @@ -528,10 +445,7 @@ export async function makeWatchRunner( fixtureLastRunStatus: new Map(), }; - subscribeTsc(state, onChange, enableRust); + subscribeTsc(state, onChange); subscribeFixtures(state, onChange); subscribeKeyEvents(state, onChange); - if (enableRust) { - subscribeRustCrates(state, onChange); - } } diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index 7c4077bf2cef..fe76f6ccd3aa 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -15,7 +15,6 @@ import { PRINT_HIR_IMPORT, PRINT_REACTIVE_IR_IMPORT, BABEL_PLUGIN_SRC, - BABEL_PLUGIN_RUST_SRC, } from './constants'; import {TestFixture, getBasename, isExpectError} from './fixture-utils'; import {TestResult, writeOutputToString} from './reporter'; @@ -34,15 +33,10 @@ const originalConsoleError = console.error; // contains ~1250 files. This assumes that no dependencies have global caches // that may need to be invalidated across Forget reloads. const invalidationSubpath = 'packages/babel-plugin-react-compiler/dist'; -const rustInvalidationSubpath = - 'packages/babel-plugin-react-compiler-rust/dist'; let version: number | null = null; export function clearRequireCache() { Object.keys(require.cache).forEach(function (path) { - if ( - path.includes(invalidationSubpath) || - path.includes(rustInvalidationSubpath) - ) { + if (path.includes(invalidationSubpath)) { delete require.cache[path]; } }); @@ -54,7 +48,6 @@ async function compile( compilerVersion: number, shouldLog: boolean, includeEvaluator: boolean, - enableRust: boolean = false, ): Promise<{ error: string | null; compileResult: TransformResult | null; @@ -71,21 +64,16 @@ async function compile( let compileResult: TransformResult | null = null; let error: string | null = null; try { - // Always load TS compiler for utilities (parseConfigPragmaForTests, print functions) const importedCompilerPlugin = require(BABEL_PLUGIN_SRC) as Record< string, unknown >; - // Load the appropriate babel plugin - const pluginSrc = enableRust ? BABEL_PLUGIN_RUST_SRC : BABEL_PLUGIN_SRC; - const importedPlugin = enableRust - ? (require(pluginSrc) as Record<string, unknown>) - : importedCompilerPlugin; - // NOTE: we intentionally require lazily here so that we can clear the require cache // and load fresh versions of the compiler when `compilerVersion` changes. - const BabelPluginReactCompiler = importedPlugin['default'] as PluginObj; + const BabelPluginReactCompiler = importedCompilerPlugin[ + 'default' + ] as PluginObj; const EffectEnum = importedCompilerPlugin['Effect'] as typeof Effect; const ValueKindEnum = importedCompilerPlugin[ 'ValueKind' @@ -179,7 +167,6 @@ export async function transformFixture( compilerVersion: number, shouldLog: boolean, includeEvaluator: boolean, - enableRust: boolean = false, ): Promise<TestResult> { const {input, snapshot: expected, snapshotPath: outputPath} = fixture; const basename = getBasename(fixture); @@ -201,7 +188,6 @@ export async function transformFixture( compilerVersion, shouldLog, includeEvaluator, - enableRust, ); let unexpectedError: string | null = null; diff --git a/compiler/packages/snap/src/runner.ts b/compiler/packages/snap/src/runner.ts index 1ef66229fdb6..3d6e5b4fc156 100644 --- a/compiler/packages/snap/src/runner.ts +++ b/compiler/packages/snap/src/runner.ts @@ -14,17 +14,10 @@ import yargs from 'yargs'; import {hideBin} from 'yargs/helpers'; import {BABEL_PLUGIN_ROOT, PROJECT_ROOT} from './constants'; import {TestFilter, getFixtures} from './fixture-utils'; -import { - TestResult, - TestResults, - normalizeCodeBlankLines, - report, - update, -} from './reporter'; +import {TestResult, TestResults, report, update} from './reporter'; import { RunnerAction, RunnerState, - buildRust, makeWatchRunner, watchSrc, } from './runner-watch'; @@ -32,7 +25,7 @@ import * as runnerWorker from './runner-worker'; import {execSync} from 'child_process'; import fs from 'fs'; import path from 'path'; -import {minimize, minimizeRustDelta} from './minimize'; +import {minimize} from './minimize'; import {parseInput, parseLanguage, parseSourceType} from './compiler'; import { PARSE_CONFIG_PRAGMA_IMPORT, @@ -55,18 +48,11 @@ type TestOptions = { pattern?: string; debug: boolean; verbose: boolean; - rust: boolean; }; type MinimizeOptions = { path: string; update: boolean; - rust: boolean; -}; - -type MinimizeRustDeltaOptions = { - path: string; - update: boolean; }; type CompileOptions = { @@ -75,12 +61,6 @@ type CompileOptions = { }; async function runTestCommand(opts: TestOptions): Promise<void> { - // Rust native module doesn't load in jest-worker child processes, - // so force sync mode when using the Rust backend. - if (opts.rust) { - opts.sync = true; - } - const worker: Worker & typeof runnerWorker = new Worker(WORKER_PATH, { enableWorkerThreads: opts.workerThreads, numWorkers: NUM_WORKERS, @@ -93,10 +73,9 @@ async function runTestCommand(opts: TestOptions): Promise<void> { if (shouldWatch) { makeWatchRunner( - state => onChange(worker, state, opts.sync, opts.verbose, opts.rust), + state => onChange(worker, state, opts.sync, opts.verbose), opts.debug, opts.pattern, - opts.rust, ); if (opts.pattern) { /** @@ -122,7 +101,6 @@ async function runTestCommand(opts: TestOptions): Promise<void> { 0, false, false, - opts.rust, ); } } @@ -143,10 +121,6 @@ async function runTestCommand(opts: TestOptions): Promise<void> { execSync('yarn build', {cwd: BABEL_PLUGIN_ROOT}); console.log('Built compiler successfully with tsup'); - if (opts.rust && !buildRust()) { - throw new Error('Failed to build Rust compiler'); - } - // Determine which filter to use let testFilter: TestFilter | null = null; if (opts.pattern) { @@ -162,13 +136,12 @@ async function runTestCommand(opts: TestOptions): Promise<void> { opts.debug, false, // no requireSingleFixture in non-watch mode opts.sync, - opts.rust, ); if (opts.update) { update(results); isSuccess = true; } else { - isSuccess = report(results, opts.verbose, opts.rust); + isSuccess = report(results, opts.verbose); } } catch (e) { console.warn('Failed to build compiler with tsup:', e); @@ -201,19 +174,12 @@ async function runMinimizeCommand(opts: MinimizeOptions): Promise<void> { const language = parseLanguage(firstLine); const sourceType = parseSourceType(firstLine); - if (opts.rust && !buildRust()) { - console.error('Error: Failed to build Rust compiler'); - process.exit(1); - } - - console.log( - `Minimizing: ${inputPath}${opts.rust ? ' (using Rust compiler)' : ''}`, - ); + console.log(`Minimizing: ${inputPath}`); const originalLines = input.split('\n').length; // Run the minimization - const result = minimize(input, filename, language, sourceType, opts.rust); + const result = minimize(input, filename, language, sourceType); if (result.kind === 'success') { console.log('Could not minimize: the input compiles successfully.'); @@ -242,65 +208,6 @@ async function runMinimizeCommand(opts: MinimizeOptions): Promise<void> { } } -async function runMinimizeRustDeltaCommand( - opts: MinimizeRustDeltaOptions, -): Promise<void> { - const inputPath = path.isAbsolute(opts.path) - ? opts.path - : path.resolve(PROJECT_ROOT, opts.path); - - if (!fs.existsSync(inputPath)) { - console.error(`Error: File not found: ${inputPath}`); - process.exit(1); - } - - // Build both compilers - execSync('yarn build', {cwd: BABEL_PLUGIN_ROOT}); - if (!buildRust()) { - console.error('Error: Failed to build Rust compiler'); - process.exit(1); - } - - const input = fs.readFileSync(inputPath, 'utf-8'); - const filename = path.basename(inputPath); - const firstLine = input.substring(0, input.indexOf('\n')); - const language = parseLanguage(firstLine); - const sourceType = parseSourceType(firstLine); - - console.log(`Minimizing TS/Rust delta: ${inputPath}`); - - const originalLines = input.split('\n').length; - - const result = minimizeRustDelta(input, filename, language, sourceType); - - if (result.kind === 'no_delta') { - console.log( - 'Could not minimize: TS and Rust compilers produce the same output.', - ); - process.exit(0); - } - - if (result.kind === 'minimal') { - console.log( - 'Could not minimize: the delta exists but the input is already minimal.', - ); - process.exit(0); - } - - console.log('--- Minimized Code ---'); - console.log(result.source); - - const minimizedLines = result.source.split('\n').length; - console.log( - `\nReduced from ${originalLines} lines to ${minimizedLines} lines`, - ); - - if (opts.update) { - fs.writeFileSync(inputPath, result.source, 'utf-8'); - console.log(`\nUpdated ${inputPath} with minimized code.`); - } -} - async function runCompileCommand(opts: CompileOptions): Promise<void> { // Resolve the input path const inputPath = path.isAbsolute(opts.path) @@ -465,10 +372,7 @@ yargs(hideBin(process.argv)) .boolean('verbose') .alias('v', 'verbose') .describe('verbose', 'Print individual test results') - .default('verbose', false) - .boolean('rust') - .describe('rust', 'Use the Rust compiler backend instead of TypeScript') - .default('rust', false); + .default('verbose', false); }, async argv => { await runTestCommand(argv as TestOptions); @@ -477,31 +381,6 @@ yargs(hideBin(process.argv)) .command( 'minimize <path>', 'Minimize a test case to reproduce a compiler error', - yargs => { - return yargs - .positional('path', { - describe: 'Path to the file to minimize', - type: 'string', - demandOption: true, - }) - .boolean('update') - .alias('u', 'update') - .describe( - 'update', - 'Update the input file in-place with the minimized version', - ) - .default('update', false) - .boolean('rust') - .describe('rust', 'Use the Rust compiler backend instead of TypeScript') - .default('rust', false); - }, - async argv => { - await runMinimizeCommand(argv as unknown as MinimizeOptions); - }, - ) - .command( - 'minimize-rust-delta <path>', - 'Minimize a test case to the smallest code that still produces different output between TS and Rust compilers', yargs => { return yargs .positional('path', { @@ -518,9 +397,7 @@ yargs(hideBin(process.argv)) .default('update', false); }, async argv => { - await runMinimizeRustDeltaCommand( - argv as unknown as MinimizeRustDeltaOptions, - ); + await runMinimizeCommand(argv as unknown as MinimizeOptions); }, ) .command( @@ -557,7 +434,6 @@ async function runFixtures( debug: boolean, requireSingleFixture: boolean, sync: boolean, - enableRust: boolean = false, ): Promise<TestResults> { // We could in theory be fancy about tracking the contents of the fixtures // directory via our file subscription, but it's simpler to just re-read @@ -573,13 +449,7 @@ async function runFixtures( for (const [fixtureName, fixture] of fixtures) { work.push( worker - .transformFixture( - fixture, - compilerVersion, - shouldLog, - true, - enableRust, - ) + .transformFixture(fixture, compilerVersion, shouldLog, true) .then(result => [fixtureName, result]), ); } @@ -593,7 +463,6 @@ async function runFixtures( compilerVersion, shouldLog, true, - enableRust, ); entries.push([fixtureName, output]); } @@ -608,7 +477,6 @@ async function onChange( state: RunnerState, sync: boolean, verbose: boolean, - enableRust: boolean = false, ) { const {compilerVersion, isCompilerBuildValid, mode, filter, debug} = state; if (isCompilerBuildValid) { @@ -627,21 +495,13 @@ async function onChange( debug, true, // requireSingleFixture in watch mode sync, - enableRust, ); const end = performance.now(); // Track fixture status for autocomplete suggestions for (const [basename, result] of results) { - const actual = - enableRust && result.actual - ? normalizeCodeBlankLines(result.actual) - : result.actual; - const expected = - enableRust && result.expected - ? normalizeCodeBlankLines(result.expected) - : result.expected; - const failed = actual !== expected || result.unexpectedError != null; + const failed = + result.actual !== result.expected || result.unexpectedError != null; state.fixtureLastRunStatus.set(basename, failed ? 'fail' : 'pass'); } @@ -649,7 +509,7 @@ async function onChange( update(results); state.lastUpdate = end; } else { - report(results, verbose, enableRust); + report(results, verbose); } console.log(`Completed in ${Math.floor(end - start)} ms`); } else { diff --git a/compiler/packages/snap/src/types.d.ts b/compiler/packages/snap/src/types.d.ts index 103ed5c93a0e..b624caf19c54 100644 --- a/compiler/packages/snap/src/types.d.ts +++ b/compiler/packages/snap/src/types.d.ts @@ -12,7 +12,6 @@ declare module 'hermes-parser' { babel?: boolean; flow?: 'all' | 'detect'; enableExperimentalComponentSyntax?: boolean; - enableExperimentalFlowMatchSyntax?: boolean; sourceFilename?: string; sourceType?: 'module' | 'script' | 'unambiguous'; tokens?: boolean; diff --git a/compiler/scripts/babel-ast-to-json.mjs b/compiler/scripts/babel-ast-to-json.mjs deleted file mode 100644 index 6112c90907b0..000000000000 --- a/compiler/scripts/babel-ast-to-json.mjs +++ /dev/null @@ -1,343 +0,0 @@ -import { parse } from "@babel/parser"; -import _traverse from "@babel/traverse"; -const traverse = _traverse.default || _traverse; -import fs from "fs"; -import path from "path"; -import fg from "fast-glob"; -const { globSync } = fg; - -const FIXTURE_DIR = process.argv[2]; // source dir with JS/TS files -const OUTPUT_DIR = process.argv[3]; // output dir for JSON files - -if (!FIXTURE_DIR || !OUTPUT_DIR) { - console.error( - "Usage: node babel-ast-to-json.mjs <fixtures-dir> <output-dir>" - ); - process.exit(1); -} - -// Find all fixture source files -const fixtures = globSync("**/*.{js,ts,tsx,jsx}", { cwd: FIXTURE_DIR }); - -function getScopeKind(babelScope) { - const blockType = babelScope.block.type; - switch (blockType) { - case "Program": - return "program"; - case "FunctionDeclaration": - case "FunctionExpression": - case "ArrowFunctionExpression": - case "ObjectMethod": - case "ClassMethod": - case "ClassPrivateMethod": - return "function"; - case "BlockStatement": - return "block"; - case "ForStatement": - case "ForInStatement": - case "ForOfStatement": - return "for"; - case "ClassDeclaration": - case "ClassExpression": - return "class"; - case "SwitchStatement": - return "switch"; - case "CatchClause": - return "catch"; - default: - return "block"; - } -} - -function getBindingKind(babelKind) { - switch (babelKind) { - case "var": - return "var"; - case "let": - return "let"; - case "const": - return "const"; - case "param": - return "param"; - case "module": - return "module"; - case "hoisted": - return "hoisted"; - case "local": - return "local"; - default: - return "unknown"; - } -} - -function getImportData(binding) { - if (binding.path.isImportSpecifier()) { - const imported = binding.path.node.imported; - return { - source: binding.path.parent.source.value, - kind: "named", - imported: imported.type === "StringLiteral" ? imported.value : imported.name, - }; - } else if (binding.path.isImportDefaultSpecifier()) { - return { - source: binding.path.parent.source.value, - kind: "default", - }; - } else if (binding.path.isImportNamespaceSpecifier()) { - return { - source: binding.path.parent.source.value, - kind: "namespace", - }; - } - return null; -} - -function collectScopeInfo(ast) { - const scopeMap = new Map(); // Babel scope -> ScopeId - const bindingMap = new Map(); // Babel binding -> BindingId - const scopes = []; - const bindings = []; - const nodeToScope = {}; - const nodeToScopeEnd = {}; - const referenceToBinding = {}; - const refNodeIdToBinding = {}; - const nodeIdToScope = {}; - let nextScopeId = 0; - let nextBindingId = 0; - let nextNodeId = 1; - - function getOrAssignNodeId(node) { - if (node._nodeId == null) { - node._nodeId = nextNodeId++; - } - return node._nodeId; - } - - function mapRef(start, bindingId, node) { - referenceToBinding[String(start)] = bindingId; - const nodeId = getOrAssignNodeId(node); - refNodeIdToBinding[String(nodeId)] = bindingId; - } - - function ensureScope(babelScope) { - if (scopeMap.has(babelScope)) return scopeMap.get(babelScope); - - // Ensure parent is registered first (preorder: parent gets lower ID) - if (babelScope.parent) { - ensureScope(babelScope.parent); - } - - const id = nextScopeId++; - scopeMap.set(babelScope, id); - - const parentId = babelScope.parent ? scopeMap.get(babelScope.parent) : null; - const kind = getScopeKind(babelScope); - const bindingsMap = {}; - - // Register all bindings in this scope - for (const [name, binding] of Object.entries(babelScope.bindings)) { - if (!bindingMap.has(binding)) { - const bid = nextBindingId++; - bindingMap.set(binding, bid); - const declarationNodeId = getOrAssignNodeId(binding.identifier); - const bindingData = { - id: bid, - name, - kind: getBindingKind(binding.kind), - scope: id, - declarationType: binding.path.node.type, - declarationStart: binding.identifier.start, - declarationNodeId, - }; - - // Import bindings - if (binding.kind === "module") { - bindingData.import = getImportData(binding); - } - - bindings.push(bindingData); - } - bindingsMap[name] = bindingMap.get(binding); - } - - scopes.push({ - id, - parent: parentId, - kind, - bindings: bindingsMap, - }); - - // Record node_to_scope and node_to_scope_end - const blockNode = babelScope.block; - if (blockNode.start != null) { - nodeToScope[String(blockNode.start)] = id; - if (blockNode.end != null) { - nodeToScopeEnd[String(blockNode.start)] = blockNode.end; - } - const scopeNodeId = getOrAssignNodeId(blockNode); - nodeIdToScope[String(scopeNodeId)] = id; - } - - return id; - } - - traverse(ast, { - enter(path) { - ensureScope(path.scope); - }, - Identifier(path) { - getOrAssignNodeId(path.node); - if (!path.isReferencedIdentifier()) return; - const binding = path.scope.getBinding(path.node.name); - if (binding && bindingMap.has(binding) && path.node.start != null) { - mapRef(path.node.start, bindingMap.get(binding), path.node); - } - }, - JSXIdentifier(path) { - getOrAssignNodeId(path.node); - }, - AssignmentExpression(path) { - const left = path.get("left"); - if (left.isLVal()) { - mapLValToBindings(left, bindingMap); - } - }, - UpdateExpression(path) { - const argument = path.get("argument"); - if (argument.isLVal()) { - mapLValToBindings(argument, bindingMap); - } - }, - }); - - // Map identifiers in assignment targets (LVal positions) to their bindings. - function mapLValToBindings(lvalPath, bindingMap) { - const node = lvalPath.node; - if (!node) return; - switch (node.type) { - case "Identifier": { - const binding = lvalPath.scope.getBinding(node.name); - if (binding && bindingMap.has(binding) && node.start != null) { - mapRef(node.start, bindingMap.get(binding), node); - } - break; - } - case "ArrayPattern": { - for (const element of lvalPath.get("elements")) { - if (element.node) mapLValToBindings(element, bindingMap); - } - break; - } - case "ObjectPattern": { - for (const property of lvalPath.get("properties")) { - if (property.isObjectProperty()) { - mapLValToBindings(property.get("value"), bindingMap); - } else if (property.isRestElement()) { - mapLValToBindings(property, bindingMap); - } - } - break; - } - case "AssignmentPattern": { - mapLValToBindings(lvalPath.get("left"), bindingMap); - break; - } - case "RestElement": { - mapLValToBindings(lvalPath.get("argument"), bindingMap); - break; - } - default: - break; - } - } - - // Record declaration identifiers in reference_to_binding and refNodeIdToBinding - for (const [binding, bid] of bindingMap) { - if (binding.identifier && binding.identifier.start != null) { - mapRef(binding.identifier.start, bid, binding.identifier); - } - } - - const result = { - scopes, - bindings, - nodeToScope, - referenceToBinding, - programScope: 0, - }; - // Only include new fields when non-empty, matching Rust skip_serializing_if - if (Object.keys(nodeToScopeEnd).length > 0) { - result.nodeToScopeEnd = nodeToScopeEnd; - } - if (Object.keys(refNodeIdToBinding).length > 0) { - result.refNodeIdToBinding = refNodeIdToBinding; - } - if (Object.keys(nodeIdToScope).length > 0) { - result.nodeIdToScope = nodeIdToScope; - } - return result; -} - -function renameIdentifiers(ast, scopeInfo) { - traverse(ast, { - Identifier(path) { - const nodeId = path.node._nodeId; - if (nodeId != null && String(nodeId) in scopeInfo.refNodeIdToBinding) { - const bindingId = scopeInfo.refNodeIdToBinding[String(nodeId)]; - const binding = scopeInfo.bindings[bindingId]; - path.node.name = `${path.node.name}_${binding.scope}_${bindingId}`; - } - }, - }); -} - -let parsed = 0; -let errors = 0; - -for (const fixture of fixtures) { - const input = fs.readFileSync(path.join(FIXTURE_DIR, fixture), "utf8"); - const isFlow = input.includes("@flow"); - - const plugins = isFlow ? ["flow", "jsx"] : ["typescript", "jsx"]; - // Default to module unless there's an indicator it should be script - const sourceType = "module"; - - try { - const ast = parse(input, { - sourceFilename: fixture, - plugins, - sourceType, - allowReturnOutsideFunction: true, - errorRecovery: true, - }); - - // Collect scope info first — this assigns _nodeId to Identifier nodes - const scopeInfo = collectScopeInfo(ast); - - // Serialize AST after scope collection so _nodeId fields are included - const outPath = path.join(OUTPUT_DIR, fixture + ".json"); - fs.mkdirSync(path.dirname(outPath), { recursive: true }); - fs.writeFileSync(outPath, JSON.stringify(ast, null, 2)); - - // Write scope info - const scopeOutPath = path.join(OUTPUT_DIR, fixture + ".scope.json"); - fs.writeFileSync(scopeOutPath, JSON.stringify(scopeInfo, null, 2)); - - // Create renamed AST for scope resolution verification - renameIdentifiers(ast, scopeInfo); - const renamedOutPath = path.join(OUTPUT_DIR, fixture + ".renamed.json"); - fs.writeFileSync(renamedOutPath, JSON.stringify(ast, null, 2)); - - parsed++; - } catch (e) { - // Parse errors are expected for some fixtures - const outPath = path.join(OUTPUT_DIR, fixture + ".parse-error"); - fs.mkdirSync(path.dirname(outPath), { recursive: true }); - fs.writeFileSync(outPath, e.message); - errors++; - } -} - -console.log( - `Parsed ${parsed} fixtures, ${errors} parse errors, ${fixtures.length} total` -); diff --git a/compiler/scripts/debug-print-error.mjs b/compiler/scripts/debug-print-error.mjs deleted file mode 100644 index 48a9389ef9bb..000000000000 --- a/compiler/scripts/debug-print-error.mjs +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/** - * Debug error printer for the Rust port testing infrastructure. - * - * Prints a detailed representation of CompilerError/CompilerDiagnostic objects, - * including all fields: category, severity, reason, description, loc, - * suggestions, and nested details. - * - * Format matches the testing infrastructure plan: - * - * Error: - * category: InvalidReact - * severity: InvalidReact - * reason: "Hooks must be called unconditionally" - * description: "Cannot call a hook (useState) conditionally" - * loc: 3:4-3:20 - * suggestions: [] - * details: - * - kind: error - * loc: 2:2-5:3 - * message: "This is a conditional" - */ - -/** - * Format a source location for debug output. - * @param {object|symbol|null} loc - * @returns {string} - */ -export function formatSourceLocation(loc) { - if (loc == null || typeof loc === "symbol") { - return "generated"; - } - return `${loc.start.line}:${loc.start.column}-${loc.end.line}:${loc.end.column}`; -} - -/** - * Format a CompilerError (with details array) into a debug string. - * @param {object} error - A CompilerError instance - * @returns {string} - */ -export function debugPrintError(error) { - const lines = []; - - if (error.details && error.details.length > 0) { - for (const detail of error.details) { - lines.push("Error:"); - lines.push(` category: ${detail.category ?? "unknown"}`); - lines.push(` severity: ${detail.severity ?? "unknown"}`); - lines.push(` reason: ${JSON.stringify(detail.reason ?? "")}`); - - if (detail.description != null) { - lines.push(` description: ${JSON.stringify(detail.description)}`); - } else { - lines.push(` description: null`); - } - - // Handle loc: CompilerDiagnostic uses primaryLocation(), CompilerErrorDetail uses .loc - const loc = - typeof detail.primaryLocation === "function" - ? detail.primaryLocation() - : detail.loc; - lines.push(` loc: ${formatSourceLocation(loc)}`); - - const suggestions = detail.suggestions ?? []; - if (suggestions.length === 0) { - lines.push(` suggestions: []`); - } else { - lines.push(` suggestions:`); - for (const s of suggestions) { - lines.push(` - op: ${s.op}`); - lines.push(` range: [${s.range[0]}, ${s.range[1]}]`); - lines.push(` description: ${JSON.stringify(s.description)}`); - if (s.text != null) { - lines.push(` text: ${JSON.stringify(s.text)}`); - } - } - } - - // Handle details array for CompilerDiagnostic (new-style errors) - if ( - detail.options && - detail.options.details && - detail.options.details.length > 0 - ) { - lines.push(` details:`); - for (const d of detail.options.details) { - if (d.kind === "error") { - lines.push(` - kind: error`); - lines.push(` loc: ${formatSourceLocation(d.loc)}`); - lines.push(` message: ${JSON.stringify(d.message)}`); - } else if (d.kind === "hint") { - lines.push(` - kind: hint`); - lines.push(` message: ${JSON.stringify(d.message)}`); - } - } - } - } - } else { - lines.push("Error:"); - lines.push(` message: ${JSON.stringify(error.message)}`); - } - - return lines.join("\n") + "\n"; -} diff --git a/compiler/scripts/debug-print-hir.mjs b/compiler/scripts/debug-print-hir.mjs deleted file mode 100644 index 846e0bcba736..000000000000 --- a/compiler/scripts/debug-print-hir.mjs +++ /dev/null @@ -1,1193 +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. - */ - -/** - * Debug HIR printer for the Rust port testing infrastructure. - * - * Custom printer that walks the HIRFunction structure and prints every field - * of every identifier, instruction, terminal, and block. Also includes - * outlined functions (from FunctionExpression instruction values). - * - * This does NOT delegate to printFunctionWithOutlined() — it is a standalone - * walker that produces a detailed, deterministic representation suitable for - * cross-compiler comparison between the TS and Rust implementations. - * - * @param {Function} _printFunctionWithOutlined - Unused (kept for API compat) - * @param {object} hirFunction - The HIRFunction to print - * @returns {string} The debug representation - */ -export function debugPrintHIR(_printFunctionWithOutlined, hirFunction) { - const outlined = []; - const result = printHIRFunction(hirFunction, 0, outlined); - const parts = [result]; - for (let i = 0; i < outlined.length; i++) { - parts.push(printHIRFunction(outlined[i], i + 1, outlined)); - } - return parts.join("\n\n"); -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -function indent(depth) { - return " ".repeat(depth); -} - -function formatLoc(loc) { - if (loc == null || typeof loc === "symbol") { - return "generated"; - } - return `${loc.start.line}:${loc.start.column}-${loc.end.line}:${loc.end.column}`; -} - -function formatEffect(effect) { - // Effect enum values in the TS compiler are lowercase strings like - // "read", "mutate", "<unknown>", etc. - return String(effect); -} - -function formatType(type) { - if (type == null) return "Type"; - switch (type.kind) { - case "Type": - return "Type"; - case "Primitive": - return "Primitive"; - case "Object": - return type.shapeId != null ? `Object<${type.shapeId}>` : "Object"; - case "Function": { - const ret = formatType(type.return); - const base = - type.shapeId != null ? `Function<${type.shapeId}>` : "Function"; - return ret !== "Type" ? `${base}():${ret}` : base; - } - case "Poly": - return "Poly"; - case "Phi": { - const ops = type.operands.map(formatType).join(", "); - return `Phi(${ops})`; - } - case "Property": { - const objType = formatType(type.objectType); - return `Property(${objType}.${type.objectName})`; - } - case "ObjectMethod": - return "ObjectMethod"; - default: - return "Type"; - } -} - -function formatIdentifierName(name) { - if (name == null) return "null"; - if (typeof name === "object" && name.value != null) { - return JSON.stringify(name.value); - } - return JSON.stringify(String(name)); -} - -function formatMutableRange(range) { - if (range == null) return "[0:0]"; - return `[${range.start}:${range.end}]`; -} - -function formatScopeId(scope) { - if (scope == null) return "null"; - return `@${scope.id}`; -} - -function formatDeclarationId(id) { - if (id == null) return "null"; - return String(id); -} - -// --------------------------------------------------------------------------- -// Place printing -// --------------------------------------------------------------------------- - -function printPlaceInline(place, depth) { - const id = place.identifier; - return [ - `${indent(depth)}Place {`, - `${indent(depth + 1)}identifier: $${id.id}`, - `${indent(depth + 1)}effect: ${formatEffect(place.effect)}`, - `${indent(depth + 1)}reactive: ${place.reactive}`, - `${indent(depth + 1)}loc: ${formatLoc(place.loc)}`, - `${indent(depth)}}`, - ].join("\n"); -} - -// --------------------------------------------------------------------------- -// Identifier printing -// --------------------------------------------------------------------------- - -function printIdentifierEntry(identifier, depth) { - return [ - `${indent(depth)}$${identifier.id}: Identifier {`, - `${indent(depth + 1)}id: ${identifier.id}`, - `${indent(depth + 1)}declarationId: ${formatDeclarationId(identifier.declarationId)}`, - `${indent(depth + 1)}name: ${formatIdentifierName(identifier.name)}`, - `${indent(depth + 1)}mutableRange: ${formatMutableRange(identifier.mutableRange)}`, - `${indent(depth + 1)}scope: ${formatScopeId(identifier.scope)}`, - `${indent(depth + 1)}type: ${formatType(identifier.type)}`, - `${indent(depth + 1)}loc: ${formatLoc(identifier.loc)}`, - `${indent(depth)}}`, - ].join("\n"); -} - -// --------------------------------------------------------------------------- -// InstructionValue printing -// --------------------------------------------------------------------------- - -function printObjectPropertyKey(key) { - switch (key.kind) { - case "identifier": - return key.name; - case "string": - return `"${key.name}"`; - case "computed": - return `[$${key.name.identifier.id}]`; - case "number": - return String(key.name); - default: - return String(key.name ?? key.kind); - } -} - -function printPattern(pattern) { - switch (pattern.kind) { - case "ArrayPattern": - return `[${pattern.items.map((item) => (item.kind === "Hole" ? "<hole>" : item.kind === "Spread" ? `...$${item.place.identifier.id}` : `$${item.identifier.id}`)).join(", ")}]`; - case "ObjectPattern": - return `{${pattern.properties.map((p) => p.kind === "Spread" ? `...$${p.place.identifier.id}` : `${printObjectPropertyKey(p.key)}: $${p.place.identifier.id}`).join(", ")}}`; - default: - return String(pattern); - } -} - -function printPlaceOrSpread(ps) { - if (ps.kind === "Identifier") return `$${ps.identifier.id}`; - if (ps.kind === "Spread") return `...$${ps.place.identifier.id}`; - return "<hole>"; -} - -function printInstructionValueFields(value, depth) { - const d = depth; - const lines = []; - const kind = value.kind; - - switch (kind) { - case "LoadLocal": - lines.push(`${indent(d)}LoadLocal {`); - lines.push(`${indent(d + 1)}place: $${value.place.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "LoadContext": - lines.push(`${indent(d)}LoadContext {`); - lines.push(`${indent(d + 1)}place: $${value.place.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "DeclareLocal": - lines.push(`${indent(d)}DeclareLocal {`); - lines.push(`${indent(d + 1)}lvalue.kind: ${value.lvalue.kind}`); - lines.push( - `${indent(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}` - ); - lines.push(`${indent(d)}}`); - break; - case "DeclareContext": - lines.push(`${indent(d)}DeclareContext {`); - lines.push(`${indent(d + 1)}lvalue.kind: ${value.lvalue.kind}`); - lines.push( - `${indent(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}` - ); - lines.push(`${indent(d)}}`); - break; - case "StoreLocal": - lines.push(`${indent(d)}StoreLocal {`); - lines.push(`${indent(d + 1)}lvalue.kind: ${value.lvalue.kind}`); - lines.push( - `${indent(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}` - ); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "StoreContext": - lines.push(`${indent(d)}StoreContext {`); - lines.push(`${indent(d + 1)}lvalue.kind: ${value.lvalue.kind}`); - lines.push( - `${indent(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}` - ); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "Destructure": - lines.push(`${indent(d)}Destructure {`); - lines.push(`${indent(d + 1)}lvalue.kind: ${value.lvalue.kind}`); - lines.push( - `${indent(d + 1)}lvalue.pattern: ${printPattern(value.lvalue.pattern)}` - ); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "Primitive": - lines.push(`${indent(d)}Primitive {`); - lines.push( - `${indent(d + 1)}value: ${value.value === undefined ? "undefined" : JSON.stringify(value.value)}` - ); - lines.push(`${indent(d)}}`); - break; - case "JSXText": - lines.push(`${indent(d)}JSXText {`); - lines.push(`${indent(d + 1)}value: ${JSON.stringify(value.value)}`); - lines.push(`${indent(d)}}`); - break; - case "BinaryExpression": - lines.push(`${indent(d)}BinaryExpression {`); - lines.push(`${indent(d + 1)}operator: ${value.operator}`); - lines.push(`${indent(d + 1)}left: $${value.left.identifier.id}`); - lines.push(`${indent(d + 1)}right: $${value.right.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "UnaryExpression": - lines.push(`${indent(d)}UnaryExpression {`); - lines.push(`${indent(d + 1)}operator: ${value.operator}`); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "CallExpression": - lines.push(`${indent(d)}CallExpression {`); - lines.push(`${indent(d + 1)}callee: $${value.callee.identifier.id}`); - lines.push( - `${indent(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]` - ); - lines.push(`${indent(d)}}`); - break; - case "MethodCall": - lines.push(`${indent(d)}MethodCall {`); - lines.push( - `${indent(d + 1)}receiver: $${value.receiver.identifier.id}` - ); - lines.push( - `${indent(d + 1)}property: $${value.property.identifier.id}` - ); - lines.push( - `${indent(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]` - ); - lines.push(`${indent(d)}}`); - break; - case "NewExpression": - lines.push(`${indent(d)}NewExpression {`); - lines.push(`${indent(d + 1)}callee: $${value.callee.identifier.id}`); - lines.push( - `${indent(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]` - ); - lines.push(`${indent(d)}}`); - break; - case "ObjectExpression": - lines.push(`${indent(d)}ObjectExpression {`); - if (value.properties != null) { - lines.push(`${indent(d + 1)}properties:`); - for (const prop of value.properties) { - if (prop.kind === "ObjectProperty") { - lines.push( - `${indent(d + 2)}${printObjectPropertyKey(prop.key)}: $${prop.place.identifier.id}` - ); - } else { - lines.push(`${indent(d + 2)}...$${prop.place.identifier.id}`); - } - } - } else { - lines.push(`${indent(d + 1)}properties: null`); - } - lines.push(`${indent(d)}}`); - break; - case "ArrayExpression": - lines.push(`${indent(d)}ArrayExpression {`); - lines.push( - `${indent(d + 1)}elements: [${value.elements.map((e) => (e.kind === "Hole" ? "<hole>" : e.kind === "Spread" ? `...$${e.place.identifier.id}` : `$${e.identifier.id}`)).join(", ")}]` - ); - lines.push(`${indent(d)}}`); - break; - case "PropertyLoad": - lines.push(`${indent(d)}PropertyLoad {`); - lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`); - lines.push(`${indent(d + 1)}property: ${value.property}`); - lines.push(`${indent(d)}}`); - break; - case "PropertyStore": - lines.push(`${indent(d)}PropertyStore {`); - lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`); - lines.push(`${indent(d + 1)}property: ${value.property}`); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "PropertyDelete": - lines.push(`${indent(d)}PropertyDelete {`); - lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`); - lines.push(`${indent(d + 1)}property: ${value.property}`); - lines.push(`${indent(d)}}`); - break; - case "ComputedLoad": - lines.push(`${indent(d)}ComputedLoad {`); - lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`); - lines.push( - `${indent(d + 1)}property: $${value.property.identifier.id}` - ); - lines.push(`${indent(d)}}`); - break; - case "ComputedStore": - lines.push(`${indent(d)}ComputedStore {`); - lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`); - lines.push( - `${indent(d + 1)}property: $${value.property.identifier.id}` - ); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "ComputedDelete": - lines.push(`${indent(d)}ComputedDelete {`); - lines.push(`${indent(d + 1)}object: $${value.object.identifier.id}`); - lines.push( - `${indent(d + 1)}property: $${value.property.identifier.id}` - ); - lines.push(`${indent(d)}}`); - break; - case "LoadGlobal": { - lines.push(`${indent(d)}LoadGlobal {`); - const b = value.binding; - lines.push(`${indent(d + 1)}binding.kind: ${b.kind}`); - lines.push(`${indent(d + 1)}binding.name: ${b.name}`); - if (b.module != null) { - lines.push(`${indent(d + 1)}binding.module: ${b.module}`); - } - if (b.imported != null) { - lines.push(`${indent(d + 1)}binding.imported: ${b.imported}`); - } - lines.push(`${indent(d)}}`); - break; - } - case "StoreGlobal": - lines.push(`${indent(d)}StoreGlobal {`); - lines.push(`${indent(d + 1)}name: ${value.name}`); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "TypeCastExpression": - lines.push(`${indent(d)}TypeCastExpression {`); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d + 1)}type: ${formatType(value.type)}`); - lines.push(`${indent(d)}}`); - break; - case "JsxExpression": { - lines.push(`${indent(d)}JsxExpression {`); - if (value.tag.kind === "Identifier") { - lines.push(`${indent(d + 1)}tag: $${value.tag.identifier.id}`); - } else { - lines.push(`${indent(d + 1)}tag: "${value.tag.name}"`); - } - lines.push(`${indent(d + 1)}props:`); - for (const attr of value.props) { - if (attr.kind === "JsxAttribute") { - lines.push( - `${indent(d + 2)}${attr.name}: $${attr.place.identifier.id}` - ); - } else { - lines.push( - `${indent(d + 2)}...$${attr.argument.identifier.id}` - ); - } - } - if (value.children != null) { - lines.push( - `${indent(d + 1)}children: [${value.children.map((c) => `$${c.identifier.id}`).join(", ")}]` - ); - } else { - lines.push(`${indent(d + 1)}children: null`); - } - lines.push(`${indent(d)}}`); - break; - } - case "JsxFragment": - lines.push(`${indent(d)}JsxFragment {`); - lines.push( - `${indent(d + 1)}children: [${value.children.map((c) => `$${c.identifier.id}`).join(", ")}]` - ); - lines.push(`${indent(d)}}`); - break; - case "FunctionExpression": - case "ObjectMethod": { - const label = - kind === "FunctionExpression" ? "FunctionExpression" : "ObjectMethod"; - lines.push(`${indent(d)}${label} {`); - if (kind === "FunctionExpression") { - lines.push( - `${indent(d + 1)}name: ${value.name != null ? JSON.stringify(value.name) : "null"}` - ); - } - lines.push( - `${indent(d + 1)}loweredFunc.id: ${value.loweredFunc.func.id ?? "null"}` - ); - // context - const ctx = value.loweredFunc.func.context; - lines.push( - `${indent(d + 1)}context: [${ctx.map((c) => `$${c.identifier.id}`).join(", ")}]` - ); - // aliasing effects - const ae = value.loweredFunc.func.aliasingEffects; - lines.push( - `${indent(d + 1)}aliasingEffects: ${ae != null ? `[${ae.length} effects]` : "null"}` - ); - lines.push(`${indent(d)}}`); - break; - } - case "TaggedTemplateExpression": - lines.push(`${indent(d)}TaggedTemplateExpression {`); - lines.push(`${indent(d + 1)}tag: $${value.tag.identifier.id}`); - lines.push(`${indent(d + 1)}value.raw: ${JSON.stringify(value.value.raw)}`); - lines.push(`${indent(d)}}`); - break; - case "TemplateLiteral": - lines.push(`${indent(d)}TemplateLiteral {`); - lines.push( - `${indent(d + 1)}quasis: [${value.quasis.map((q) => JSON.stringify(q.raw)).join(", ")}]` - ); - lines.push( - `${indent(d + 1)}subexprs: [${value.subexprs.map((s) => `$${s.identifier.id}`).join(", ")}]` - ); - lines.push(`${indent(d)}}`); - break; - case "RegExpLiteral": - lines.push(`${indent(d)}RegExpLiteral {`); - lines.push(`${indent(d + 1)}pattern: ${value.pattern}`); - lines.push(`${indent(d + 1)}flags: ${value.flags}`); - lines.push(`${indent(d)}}`); - break; - case "MetaProperty": - lines.push(`${indent(d)}MetaProperty {`); - lines.push(`${indent(d + 1)}meta: ${value.meta}`); - lines.push(`${indent(d + 1)}property: ${value.property}`); - lines.push(`${indent(d)}}`); - break; - case "Await": - lines.push(`${indent(d)}Await {`); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "GetIterator": - lines.push(`${indent(d)}GetIterator {`); - lines.push( - `${indent(d + 1)}collection: $${value.collection.identifier.id}` - ); - lines.push(`${indent(d)}}`); - break; - case "IteratorNext": - lines.push(`${indent(d)}IteratorNext {`); - lines.push( - `${indent(d + 1)}iterator: $${value.iterator.identifier.id}` - ); - lines.push( - `${indent(d + 1)}collection: $${value.collection.identifier.id}` - ); - lines.push(`${indent(d)}}`); - break; - case "NextPropertyOf": - lines.push(`${indent(d)}NextPropertyOf {`); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "PostfixUpdate": - lines.push(`${indent(d)}PostfixUpdate {`); - lines.push(`${indent(d + 1)}lvalue: $${value.lvalue.identifier.id}`); - lines.push(`${indent(d + 1)}operation: ${value.operation}`); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "PrefixUpdate": - lines.push(`${indent(d)}PrefixUpdate {`); - lines.push(`${indent(d + 1)}lvalue: $${value.lvalue.identifier.id}`); - lines.push(`${indent(d + 1)}operation: ${value.operation}`); - lines.push(`${indent(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${indent(d)}}`); - break; - case "Debugger": - lines.push(`${indent(d)}Debugger {}`); - break; - case "StartMemoize": - lines.push(`${indent(d)}StartMemoize {`); - lines.push(`${indent(d + 1)}manualMemoId: ${value.manualMemoId}`); - lines.push(`${indent(d + 1)}deps: ${value.deps != null ? `[${value.deps.length} deps]` : "null"}`); - lines.push(`${indent(d)}}`); - break; - case "FinishMemoize": - lines.push(`${indent(d)}FinishMemoize {`); - lines.push(`${indent(d + 1)}manualMemoId: ${value.manualMemoId}`); - lines.push(`${indent(d + 1)}decl: $${value.decl.identifier.id}`); - lines.push(`${indent(d + 1)}pruned: ${value.pruned === true}`); - lines.push(`${indent(d)}}`); - break; - case "UnsupportedNode": - lines.push(`${indent(d)}UnsupportedNode {`); - lines.push( - `${indent(d + 1)}type: ${value.node != null ? value.node.type : "unknown"}` - ); - lines.push(`${indent(d)}}`); - break; - // Reactive-only value types that may appear: - case "LogicalExpression": - lines.push(`${indent(d)}LogicalExpression {`); - lines.push(`${indent(d + 1)}operator: ${value.operator}`); - lines.push(`${indent(d)}}`); - break; - case "ConditionalExpression": - lines.push(`${indent(d)}ConditionalExpression {}`); - break; - case "SequenceExpression": - lines.push(`${indent(d)}SequenceExpression {}`); - break; - case "OptionalExpression": - lines.push(`${indent(d)}OptionalExpression {`); - lines.push(`${indent(d + 1)}optional: ${value.optional}`); - lines.push(`${indent(d)}}`); - break; - default: - lines.push(`${indent(d)}${kind} {}`); - break; - } - return lines.join("\n"); -} - -// --------------------------------------------------------------------------- -// Instruction printing -// --------------------------------------------------------------------------- - -function printInstruction(instr, depth) { - const lines = []; - lines.push(`${indent(depth)}[${instr.id}] Instruction {`); - const d = depth + 1; - lines.push(`${indent(d)}id: ${instr.id}`); - // lvalue - lines.push(`${indent(d)}lvalue:`); - lines.push(printPlaceInline(instr.lvalue, d + 1)); - // value - lines.push(`${indent(d)}value:`); - lines.push(printInstructionValueFields(instr.value, d + 1)); - // effects - if (instr.effects != null) { - lines.push(`${indent(d)}effects: [${instr.effects.length} effects]`); - } else { - lines.push(`${indent(d)}effects: null`); - } - lines.push(`${indent(d)}loc: ${formatLoc(instr.loc)}`); - lines.push(`${indent(depth)}}`); - return lines.join("\n"); -} - -// --------------------------------------------------------------------------- -// Terminal printing -// --------------------------------------------------------------------------- - -function printTerminal(terminal, depth) { - const lines = []; - const d = depth; - const kind = terminal.kind; - lines.push(`${indent(d)}${terminalName(kind)} {`); - lines.push(`${indent(d + 1)}id: ${terminal.id}`); - - switch (kind) { - case "if": - lines.push(`${indent(d + 1)}test:`); - lines.push(printPlaceInline(terminal.test, d + 2)); - lines.push(`${indent(d + 1)}consequent: bb${terminal.consequent}`); - lines.push(`${indent(d + 1)}alternate: bb${terminal.alternate}`); - lines.push( - `${indent(d + 1)}fallthrough: ${terminal.fallthrough != null ? `bb${terminal.fallthrough}` : "null"}` - ); - break; - case "branch": - lines.push(`${indent(d + 1)}test:`); - lines.push(printPlaceInline(terminal.test, d + 2)); - lines.push(`${indent(d + 1)}consequent: bb${terminal.consequent}`); - lines.push(`${indent(d + 1)}alternate: bb${terminal.alternate}`); - lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`); - break; - case "return": - lines.push(`${indent(d + 1)}returnVariant: ${terminal.returnVariant}`); - lines.push(`${indent(d + 1)}value:`); - lines.push(printPlaceInline(terminal.value, d + 2)); - if (terminal.effects != null) { - lines.push( - `${indent(d + 1)}effects: [${terminal.effects.length} effects]` - ); - } else { - lines.push(`${indent(d + 1)}effects: null`); - } - break; - case "throw": - lines.push(`${indent(d + 1)}value:`); - lines.push(printPlaceInline(terminal.value, d + 2)); - break; - case "goto": - lines.push(`${indent(d + 1)}block: bb${terminal.block}`); - lines.push(`${indent(d + 1)}variant: ${terminal.variant}`); - break; - case "switch": - lines.push(`${indent(d + 1)}test:`); - lines.push(printPlaceInline(terminal.test, d + 2)); - lines.push(`${indent(d + 1)}cases:`); - for (const c of terminal.cases) { - if (c.test != null) { - lines.push(`${indent(d + 2)}case $${c.test.identifier.id}: bb${c.block}`); - } else { - lines.push(`${indent(d + 2)}default: bb${c.block}`); - } - } - lines.push( - `${indent(d + 1)}fallthrough: ${terminal.fallthrough != null ? `bb${terminal.fallthrough}` : "null"}` - ); - break; - case "do-while": - lines.push(`${indent(d + 1)}loop: bb${terminal.loop}`); - lines.push(`${indent(d + 1)}test: bb${terminal.test}`); - lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`); - break; - case "while": - lines.push(`${indent(d + 1)}test: bb${terminal.test}`); - lines.push( - `${indent(d + 1)}loop: ${terminal.loop != null ? `bb${terminal.loop}` : "null"}` - ); - lines.push( - `${indent(d + 1)}fallthrough: ${terminal.fallthrough != null ? `bb${terminal.fallthrough}` : "null"}` - ); - break; - case "for": - lines.push(`${indent(d + 1)}init: bb${terminal.init}`); - lines.push(`${indent(d + 1)}test: bb${terminal.test}`); - lines.push( - `${indent(d + 1)}update: ${terminal.update != null ? `bb${terminal.update}` : "null"}` - ); - lines.push(`${indent(d + 1)}loop: bb${terminal.loop}`); - lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`); - break; - case "for-of": - lines.push(`${indent(d + 1)}init: bb${terminal.init}`); - lines.push(`${indent(d + 1)}test: bb${terminal.test}`); - lines.push(`${indent(d + 1)}loop: bb${terminal.loop}`); - lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`); - break; - case "for-in": - lines.push(`${indent(d + 1)}init: bb${terminal.init}`); - lines.push(`${indent(d + 1)}loop: bb${terminal.loop}`); - lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`); - break; - case "logical": - lines.push(`${indent(d + 1)}operator: ${terminal.operator}`); - lines.push(`${indent(d + 1)}test: bb${terminal.test}`); - lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`); - break; - case "ternary": - lines.push(`${indent(d + 1)}test: bb${terminal.test}`); - lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`); - break; - case "optional": - lines.push(`${indent(d + 1)}optional: ${terminal.optional}`); - lines.push(`${indent(d + 1)}test: bb${terminal.test}`); - lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`); - break; - case "label": - lines.push(`${indent(d + 1)}block: bb${terminal.block}`); - lines.push( - `${indent(d + 1)}fallthrough: ${terminal.fallthrough != null ? `bb${terminal.fallthrough}` : "null"}` - ); - break; - case "sequence": - lines.push(`${indent(d + 1)}block: bb${terminal.block}`); - lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`); - break; - case "maybe-throw": - lines.push(`${indent(d + 1)}continuation: bb${terminal.continuation}`); - lines.push( - `${indent(d + 1)}handler: ${terminal.handler != null ? `bb${terminal.handler}` : "null"}` - ); - if (terminal.effects != null) { - lines.push( - `${indent(d + 1)}effects: [${terminal.effects.length} effects]` - ); - } else { - lines.push(`${indent(d + 1)}effects: null`); - } - break; - case "try": - lines.push(`${indent(d + 1)}block: bb${terminal.block}`); - if (terminal.handlerBinding != null) { - lines.push(`${indent(d + 1)}handlerBinding:`); - lines.push(printPlaceInline(terminal.handlerBinding, d + 2)); - } else { - lines.push(`${indent(d + 1)}handlerBinding: null`); - } - lines.push(`${indent(d + 1)}handler: bb${terminal.handler}`); - lines.push( - `${indent(d + 1)}fallthrough: ${terminal.fallthrough != null ? `bb${terminal.fallthrough}` : "null"}` - ); - break; - case "scope": - lines.push(`${indent(d + 1)}scope: @${terminal.scope.id}`); - lines.push(`${indent(d + 1)}block: bb${terminal.block}`); - lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`); - break; - case "pruned-scope": - lines.push(`${indent(d + 1)}scope: @${terminal.scope.id}`); - lines.push(`${indent(d + 1)}block: bb${terminal.block}`); - lines.push(`${indent(d + 1)}fallthrough: bb${terminal.fallthrough}`); - break; - case "unreachable": - break; - case "unsupported": - break; - default: - break; - } - - lines.push(`${indent(d + 1)}loc: ${formatLoc(terminal.loc)}`); - lines.push(`${indent(d)}}`); - return lines.join("\n"); -} - -function terminalName(kind) { - const names = { - if: "If", - branch: "Branch", - return: "Return", - throw: "Throw", - goto: "Goto", - switch: "Switch", - "do-while": "DoWhile", - while: "While", - for: "For", - "for-of": "ForOf", - "for-in": "ForIn", - logical: "Logical", - ternary: "Ternary", - optional: "Optional", - label: "Label", - sequence: "Sequence", - "maybe-throw": "MaybeThrow", - try: "Try", - scope: "Scope", - "pruned-scope": "PrunedScope", - unreachable: "Unreachable", - unsupported: "Unsupported", - }; - return names[kind] ?? kind; -} - -// --------------------------------------------------------------------------- -// Phi printing -// --------------------------------------------------------------------------- - -function printPhi(phi, depth) { - const lines = []; - lines.push(`${indent(depth)}Phi {`); - lines.push(`${indent(depth + 1)}place: $${phi.place.identifier.id}`); - lines.push(`${indent(depth + 1)}operands:`); - // phi.operands is a Map<BlockId, Place> - const sortedOperands = [...phi.operands].sort((a, b) => a[0] - b[0]); - for (const [blockId, place] of sortedOperands) { - lines.push( - `${indent(depth + 2)}bb${blockId}: $${place.identifier.id}` - ); - } - lines.push(`${indent(depth)}}`); - return lines.join("\n"); -} - -// --------------------------------------------------------------------------- -// Main function printer -// --------------------------------------------------------------------------- - -function printHIRFunction(fn, functionIndex, outlinedCollector) { - const lines = []; - const d0 = 0; - const d1 = 1; - const d2 = 2; - - lines.push(`${indent(d0)}Function #${functionIndex}:`); - - // id - lines.push( - `${indent(d1)}id: ${fn.id != null ? JSON.stringify(fn.id) : "null"}` - ); - - // params - lines.push(`${indent(d1)}params:`); - for (let i = 0; i < fn.params.length; i++) { - const param = fn.params[i]; - if (param.kind === "Identifier") { - lines.push(`${indent(d2)}[${i}]`); - lines.push(printPlaceInline(param, d2 + 1)); - } else { - // Spread - lines.push(`${indent(d2)}[${i}] ...`); - lines.push(printPlaceInline(param.place, d2 + 1)); - } - } - - // returns - lines.push(`${indent(d1)}returns:`); - lines.push(printPlaceInline(fn.returns, d2)); - - // returnTypeAnnotation - if (fn.returnTypeAnnotation != null) { - lines.push(`${indent(d1)}returnTypeAnnotation: ${JSON.stringify(fn.returnTypeAnnotation)}`); - } else { - lines.push(`${indent(d1)}returnTypeAnnotation: null`); - } - - // context - if (fn.context.length > 0) { - lines.push(`${indent(d1)}context:`); - for (const ctx of fn.context) { - lines.push(printPlaceInline(ctx, d2)); - } - } else { - lines.push(`${indent(d1)}context: []`); - } - - // directives - if (fn.directives.length > 0) { - lines.push( - `${indent(d1)}directives: [${fn.directives.map((d) => JSON.stringify(d)).join(", ")}]` - ); - } else { - lines.push(`${indent(d1)}directives: []`); - } - - // generator / async - lines.push(`${indent(d1)}generator: ${fn.generator}`); - lines.push(`${indent(d1)}async: ${fn.async}`); - - // aliasingEffects - if (fn.aliasingEffects != null) { - lines.push( - `${indent(d1)}aliasingEffects: [${fn.aliasingEffects.length} effects]` - ); - } else { - lines.push(`${indent(d1)}aliasingEffects: null`); - } - - // Collect all identifiers from the function - const identifiers = new Map(); - collectIdentifiers(fn, identifiers); - - lines.push(""); - lines.push(`${indent(d1)}Identifiers:`); - const sortedIds = [...identifiers.entries()].sort((a, b) => a[0] - b[0]); - for (const [, identifier] of sortedIds) { - lines.push(printIdentifierEntry(identifier, d2)); - } - - // Blocks (in order from body.blocks, which is RPO) - lines.push(""); - lines.push(`${indent(d1)}Blocks:`); - for (const [blockId, block] of fn.body.blocks) { - lines.push(`${indent(d2)}bb${blockId} (${block.kind}):`); - const d3 = d2 + 1; - - // preds - const preds = [...block.preds].sort((a, b) => a - b); - lines.push(`${indent(d3)}preds: [${preds.map((p) => `bb${p}`).join(", ")}]`); - - // phis - if (block.phis.size > 0) { - lines.push(`${indent(d3)}phis:`); - for (const phi of block.phis) { - lines.push(printPhi(phi, d3 + 1)); - } - } else { - lines.push(`${indent(d3)}phis: []`); - } - - // instructions - lines.push(`${indent(d3)}instructions:`); - for (const instr of block.instructions) { - lines.push(printInstruction(instr, d3 + 1)); - // Collect outlined functions - if ( - instr.value.kind === "FunctionExpression" || - instr.value.kind === "ObjectMethod" - ) { - outlinedCollector.push(instr.value.loweredFunc.func); - } - } - - // terminal - lines.push(`${indent(d3)}terminal:`); - lines.push(printTerminal(block.terminal, d3 + 1)); - } - - return lines.join("\n"); -} - -// --------------------------------------------------------------------------- -// Identifier collection -// --------------------------------------------------------------------------- - -function collectIdentifiers(fn, map) { - // From params - for (const param of fn.params) { - if (param.kind === "Identifier") { - addIdentifier(map, param.identifier); - } else { - addIdentifier(map, param.place.identifier); - } - } - - // returns - addIdentifier(map, fn.returns.identifier); - - // context - for (const ctx of fn.context) { - addIdentifier(map, ctx.identifier); - } - - // From blocks - for (const [, block] of fn.body.blocks) { - // phis - for (const phi of block.phis) { - addIdentifier(map, phi.place.identifier); - for (const [, place] of phi.operands) { - addIdentifier(map, place.identifier); - } - } - - // instructions - for (const instr of block.instructions) { - addIdentifier(map, instr.lvalue.identifier); - collectIdentifiersFromValue(instr.value, map); - } - - // terminal - collectIdentifiersFromTerminal(block.terminal, map); - } -} - -function addIdentifier(map, identifier) { - if (!map.has(identifier.id)) { - map.set(identifier.id, identifier); - } -} - -function collectIdentifiersFromPlace(place, map) { - if (place != null) { - addIdentifier(map, place.identifier); - } -} - -function collectIdentifiersFromValue(value, map) { - if (value == null) return; - switch (value.kind) { - case "LoadLocal": - case "LoadContext": - collectIdentifiersFromPlace(value.place, map); - break; - case "DeclareLocal": - case "DeclareContext": - collectIdentifiersFromPlace(value.lvalue.place, map); - break; - case "StoreLocal": - case "StoreContext": - collectIdentifiersFromPlace(value.lvalue.place, map); - collectIdentifiersFromPlace(value.value, map); - break; - case "Destructure": - collectIdentifiersFromPattern(value.lvalue.pattern, map); - collectIdentifiersFromPlace(value.value, map); - break; - case "BinaryExpression": - collectIdentifiersFromPlace(value.left, map); - collectIdentifiersFromPlace(value.right, map); - break; - case "UnaryExpression": - collectIdentifiersFromPlace(value.value, map); - break; - case "CallExpression": - case "NewExpression": - collectIdentifiersFromPlace(value.callee, map); - for (const arg of value.args) { - if (arg.kind === "Identifier") collectIdentifiersFromPlace(arg, map); - else if (arg.kind === "Spread") - collectIdentifiersFromPlace(arg.place, map); - } - break; - case "MethodCall": - collectIdentifiersFromPlace(value.receiver, map); - collectIdentifiersFromPlace(value.property, map); - for (const arg of value.args) { - if (arg.kind === "Identifier") collectIdentifiersFromPlace(arg, map); - else if (arg.kind === "Spread") - collectIdentifiersFromPlace(arg.place, map); - } - break; - case "ObjectExpression": - if (value.properties != null) { - for (const prop of value.properties) { - if (prop.kind === "ObjectProperty") { - collectIdentifiersFromPlace(prop.place, map); - if (prop.key.kind === "computed") - collectIdentifiersFromPlace(prop.key.name, map); - } else { - collectIdentifiersFromPlace(prop.place, map); - } - } - } - break; - case "ArrayExpression": - for (const el of value.elements) { - if (el.kind === "Identifier") collectIdentifiersFromPlace(el, map); - else if (el.kind === "Spread") - collectIdentifiersFromPlace(el.place, map); - } - break; - case "PropertyLoad": - case "PropertyDelete": - collectIdentifiersFromPlace(value.object, map); - break; - case "PropertyStore": - collectIdentifiersFromPlace(value.object, map); - collectIdentifiersFromPlace(value.value, map); - break; - case "ComputedLoad": - case "ComputedDelete": - collectIdentifiersFromPlace(value.object, map); - collectIdentifiersFromPlace(value.property, map); - break; - case "ComputedStore": - collectIdentifiersFromPlace(value.object, map); - collectIdentifiersFromPlace(value.property, map); - collectIdentifiersFromPlace(value.value, map); - break; - case "StoreGlobal": - collectIdentifiersFromPlace(value.value, map); - break; - case "TypeCastExpression": - collectIdentifiersFromPlace(value.value, map); - break; - case "JsxExpression": - if (value.tag.kind === "Identifier") - collectIdentifiersFromPlace(value.tag, map); - for (const attr of value.props) { - if (attr.kind === "JsxAttribute") - collectIdentifiersFromPlace(attr.place, map); - else collectIdentifiersFromPlace(attr.argument, map); - } - if (value.children != null) { - for (const child of value.children) - collectIdentifiersFromPlace(child, map); - } - break; - case "JsxFragment": - for (const child of value.children) - collectIdentifiersFromPlace(child, map); - break; - case "FunctionExpression": - case "ObjectMethod": - // context of lowered func - for (const ctx of value.loweredFunc.func.context) { - collectIdentifiersFromPlace(ctx, map); - } - break; - case "TaggedTemplateExpression": - collectIdentifiersFromPlace(value.tag, map); - break; - case "TemplateLiteral": - for (const s of value.subexprs) collectIdentifiersFromPlace(s, map); - break; - case "Await": - collectIdentifiersFromPlace(value.value, map); - break; - case "GetIterator": - collectIdentifiersFromPlace(value.collection, map); - break; - case "IteratorNext": - collectIdentifiersFromPlace(value.iterator, map); - collectIdentifiersFromPlace(value.collection, map); - break; - case "NextPropertyOf": - collectIdentifiersFromPlace(value.value, map); - break; - case "PostfixUpdate": - case "PrefixUpdate": - collectIdentifiersFromPlace(value.lvalue, map); - collectIdentifiersFromPlace(value.value, map); - break; - case "FinishMemoize": - collectIdentifiersFromPlace(value.decl, map); - break; - case "StartMemoize": - if (value.deps != null) { - for (const dep of value.deps) { - if (dep.root.kind === "NamedLocal") { - collectIdentifiersFromPlace(dep.root.value, map); - } - } - } - break; - default: - break; - } -} - -function collectIdentifiersFromPattern(pattern, map) { - switch (pattern.kind) { - case "ArrayPattern": - for (const item of pattern.items) { - if (item.kind === "Identifier") collectIdentifiersFromPlace(item, map); - else if (item.kind === "Spread") - collectIdentifiersFromPlace(item.place, map); - } - break; - case "ObjectPattern": - for (const prop of pattern.properties) { - if (prop.kind === "ObjectProperty") { - collectIdentifiersFromPlace(prop.place, map); - if (prop.key.kind === "computed") - collectIdentifiersFromPlace(prop.key.name, map); - } else { - collectIdentifiersFromPlace(prop.place, map); - } - } - break; - } -} - -function collectIdentifiersFromTerminal(terminal, map) { - switch (terminal.kind) { - case "if": - case "branch": - collectIdentifiersFromPlace(terminal.test, map); - break; - case "return": - collectIdentifiersFromPlace(terminal.value, map); - break; - case "throw": - collectIdentifiersFromPlace(terminal.value, map); - break; - case "switch": - collectIdentifiersFromPlace(terminal.test, map); - for (const c of terminal.cases) { - if (c.test != null) collectIdentifiersFromPlace(c.test, map); - } - break; - case "try": - if (terminal.handlerBinding != null) - collectIdentifiersFromPlace(terminal.handlerBinding, map); - break; - default: - break; - } -} diff --git a/compiler/scripts/debug-print-reactive.mjs b/compiler/scripts/debug-print-reactive.mjs deleted file mode 100644 index 18f2fc0fa1fd..000000000000 --- a/compiler/scripts/debug-print-reactive.mjs +++ /dev/null @@ -1,1015 +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. - */ - -/** - * Debug ReactiveFunction printer for the Rust port testing infrastructure. - * - * Custom printer that walks the ReactiveFunction tree structure and prints - * every field of every scope, instruction, terminal, and reactive value node. - * - * This does NOT delegate to printReactiveFunctionWithOutlined() — it is a - * standalone walker that produces a detailed, deterministic representation - * suitable for cross-compiler comparison between the TS and Rust implementations. - * - * @param {Function} _printReactiveFunctionWithOutlined - Unused (kept for API compat) - * @param {object} reactiveFunction - The ReactiveFunction to print - * @returns {string} The debug representation - */ -export function debugPrintReactive( - _printReactiveFunctionWithOutlined, - reactiveFunction -) { - const outlined = []; - const result = printReactiveFunction(reactiveFunction, 0, outlined); - const parts = [result]; - for (let i = 0; i < outlined.length; i++) { - parts.push(printReactiveFunction(outlined[i], i + 1, outlined)); - } - return parts.join("\n\n"); -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -function ind(depth) { - return " ".repeat(depth); -} - -function formatLoc(loc) { - if (loc == null || typeof loc === "symbol") { - return "generated"; - } - return `${loc.start.line}:${loc.start.column}-${loc.end.line}:${loc.end.column}`; -} - -function formatEffect(effect) { - return String(effect); -} - -function formatType(type) { - if (type == null) return "Type"; - switch (type.kind) { - case "Type": - return "Type"; - case "Primitive": - return "Primitive"; - case "Object": - return type.shapeId != null ? `Object<${type.shapeId}>` : "Object"; - case "Function": { - const ret = formatType(type.return); - const base = - type.shapeId != null ? `Function<${type.shapeId}>` : "Function"; - return ret !== "Type" ? `${base}():${ret}` : base; - } - case "Poly": - return "Poly"; - case "Phi": { - const ops = type.operands.map(formatType).join(", "); - return `Phi(${ops})`; - } - case "Property": { - const objType = formatType(type.objectType); - return `Property(${objType}.${type.objectName})`; - } - case "ObjectMethod": - return "ObjectMethod"; - default: - return "Type"; - } -} - -function formatIdentifierName(name) { - if (name == null) return "null"; - if (typeof name === "object" && name.value != null) { - return JSON.stringify(name.value); - } - return JSON.stringify(String(name)); -} - -function formatMutableRange(range) { - if (range == null) return "[0:0]"; - return `[${range.start}:${range.end}]`; -} - -function formatScopeId(scope) { - if (scope == null) return "null"; - return `@${scope.id}`; -} - -function formatDeclarationId(id) { - if (id == null) return "null"; - return String(id); -} - -// --------------------------------------------------------------------------- -// Place printing -// --------------------------------------------------------------------------- - -function printPlaceInline(place, depth) { - const id = place.identifier; - return [ - `${ind(depth)}Place {`, - `${ind(depth + 1)}identifier: $${id.id}`, - `${ind(depth + 1)}effect: ${formatEffect(place.effect)}`, - `${ind(depth + 1)}reactive: ${place.reactive}`, - `${ind(depth + 1)}loc: ${formatLoc(place.loc)}`, - `${ind(depth)}}`, - ].join("\n"); -} - -// --------------------------------------------------------------------------- -// Object property key -// --------------------------------------------------------------------------- - -function printObjectPropertyKey(key) { - switch (key.kind) { - case "identifier": - return key.name; - case "string": - return `"${key.name}"`; - case "computed": - return `[$${key.name.identifier.id}]`; - case "number": - return String(key.name); - default: - return String(key.name ?? key.kind); - } -} - -function printPlaceOrSpread(ps) { - if (ps.kind === "Identifier") return `$${ps.identifier.id}`; - if (ps.kind === "Spread") return `...$${ps.place.identifier.id}`; - return "<hole>"; -} - -function printPattern(pattern) { - switch (pattern.kind) { - case "ArrayPattern": - return `[${pattern.items.map((item) => (item.kind === "Hole" ? "<hole>" : item.kind === "Spread" ? `...$${item.place.identifier.id}` : `$${item.identifier.id}`)).join(", ")}]`; - case "ObjectPattern": - return `{${pattern.properties.map((p) => p.kind === "Spread" ? `...$${p.place.identifier.id}` : `${printObjectPropertyKey(p.key)}: $${p.place.identifier.id}`).join(", ")}}`; - default: - return String(pattern); - } -} - -// --------------------------------------------------------------------------- -// InstructionValue printing (shared with HIR printer) -// --------------------------------------------------------------------------- - -function printInstructionValueFields(value, depth) { - const d = depth; - const lines = []; - const kind = value.kind; - - switch (kind) { - case "LoadLocal": - lines.push(`${ind(d)}LoadLocal {`); - lines.push(`${ind(d + 1)}place: $${value.place.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "LoadContext": - lines.push(`${ind(d)}LoadContext {`); - lines.push(`${ind(d + 1)}place: $${value.place.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "DeclareLocal": - lines.push(`${ind(d)}DeclareLocal {`); - lines.push(`${ind(d + 1)}lvalue.kind: ${value.lvalue.kind}`); - lines.push( - `${ind(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}` - ); - lines.push(`${ind(d)}}`); - break; - case "DeclareContext": - lines.push(`${ind(d)}DeclareContext {`); - lines.push(`${ind(d + 1)}lvalue.kind: ${value.lvalue.kind}`); - lines.push( - `${ind(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}` - ); - lines.push(`${ind(d)}}`); - break; - case "StoreLocal": - lines.push(`${ind(d)}StoreLocal {`); - lines.push(`${ind(d + 1)}lvalue.kind: ${value.lvalue.kind}`); - lines.push( - `${ind(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}` - ); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "StoreContext": - lines.push(`${ind(d)}StoreContext {`); - lines.push(`${ind(d + 1)}lvalue.kind: ${value.lvalue.kind}`); - lines.push( - `${ind(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}` - ); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "Destructure": - lines.push(`${ind(d)}Destructure {`); - lines.push(`${ind(d + 1)}lvalue.kind: ${value.lvalue.kind}`); - lines.push( - `${ind(d + 1)}lvalue.pattern: ${printPattern(value.lvalue.pattern)}` - ); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "Primitive": - lines.push(`${ind(d)}Primitive {`); - lines.push( - `${ind(d + 1)}value: ${value.value === undefined ? "undefined" : JSON.stringify(value.value)}` - ); - lines.push(`${ind(d)}}`); - break; - case "JSXText": - lines.push(`${ind(d)}JSXText {`); - lines.push(`${ind(d + 1)}value: ${JSON.stringify(value.value)}`); - lines.push(`${ind(d)}}`); - break; - case "BinaryExpression": - lines.push(`${ind(d)}BinaryExpression {`); - lines.push(`${ind(d + 1)}operator: ${value.operator}`); - lines.push(`${ind(d + 1)}left: $${value.left.identifier.id}`); - lines.push(`${ind(d + 1)}right: $${value.right.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "UnaryExpression": - lines.push(`${ind(d)}UnaryExpression {`); - lines.push(`${ind(d + 1)}operator: ${value.operator}`); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "CallExpression": - lines.push(`${ind(d)}CallExpression {`); - lines.push(`${ind(d + 1)}callee: $${value.callee.identifier.id}`); - lines.push( - `${ind(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]` - ); - lines.push(`${ind(d)}}`); - break; - case "MethodCall": - lines.push(`${ind(d)}MethodCall {`); - lines.push(`${ind(d + 1)}receiver: $${value.receiver.identifier.id}`); - lines.push(`${ind(d + 1)}property: $${value.property.identifier.id}`); - lines.push( - `${ind(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]` - ); - lines.push(`${ind(d)}}`); - break; - case "NewExpression": - lines.push(`${ind(d)}NewExpression {`); - lines.push(`${ind(d + 1)}callee: $${value.callee.identifier.id}`); - lines.push( - `${ind(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]` - ); - lines.push(`${ind(d)}}`); - break; - case "ObjectExpression": - lines.push(`${ind(d)}ObjectExpression {`); - if (value.properties != null) { - lines.push(`${ind(d + 1)}properties:`); - for (const prop of value.properties) { - if (prop.kind === "ObjectProperty") { - lines.push( - `${ind(d + 2)}${printObjectPropertyKey(prop.key)}: $${prop.place.identifier.id}` - ); - } else { - lines.push(`${ind(d + 2)}...$${prop.place.identifier.id}`); - } - } - } else { - lines.push(`${ind(d + 1)}properties: null`); - } - lines.push(`${ind(d)}}`); - break; - case "ArrayExpression": - lines.push(`${ind(d)}ArrayExpression {`); - lines.push( - `${ind(d + 1)}elements: [${value.elements.map((e) => (e.kind === "Hole" ? "<hole>" : e.kind === "Spread" ? `...$${e.place.identifier.id}` : `$${e.identifier.id}`)).join(", ")}]` - ); - lines.push(`${ind(d)}}`); - break; - case "PropertyLoad": - lines.push(`${ind(d)}PropertyLoad {`); - lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`); - lines.push(`${ind(d + 1)}property: ${value.property}`); - lines.push(`${ind(d)}}`); - break; - case "PropertyStore": - lines.push(`${ind(d)}PropertyStore {`); - lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`); - lines.push(`${ind(d + 1)}property: ${value.property}`); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "PropertyDelete": - lines.push(`${ind(d)}PropertyDelete {`); - lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`); - lines.push(`${ind(d + 1)}property: ${value.property}`); - lines.push(`${ind(d)}}`); - break; - case "ComputedLoad": - lines.push(`${ind(d)}ComputedLoad {`); - lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`); - lines.push(`${ind(d + 1)}property: $${value.property.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "ComputedStore": - lines.push(`${ind(d)}ComputedStore {`); - lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`); - lines.push(`${ind(d + 1)}property: $${value.property.identifier.id}`); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "ComputedDelete": - lines.push(`${ind(d)}ComputedDelete {`); - lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`); - lines.push(`${ind(d + 1)}property: $${value.property.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "LoadGlobal": { - lines.push(`${ind(d)}LoadGlobal {`); - const b = value.binding; - lines.push(`${ind(d + 1)}binding.kind: ${b.kind}`); - lines.push(`${ind(d + 1)}binding.name: ${b.name}`); - if (b.module != null) { - lines.push(`${ind(d + 1)}binding.module: ${b.module}`); - } - if (b.imported != null) { - lines.push(`${ind(d + 1)}binding.imported: ${b.imported}`); - } - lines.push(`${ind(d)}}`); - break; - } - case "StoreGlobal": - lines.push(`${ind(d)}StoreGlobal {`); - lines.push(`${ind(d + 1)}name: ${value.name}`); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "TypeCastExpression": - lines.push(`${ind(d)}TypeCastExpression {`); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d + 1)}type: ${formatType(value.type)}`); - lines.push(`${ind(d)}}`); - break; - case "JsxExpression": { - lines.push(`${ind(d)}JsxExpression {`); - if (value.tag.kind === "Identifier") { - lines.push(`${ind(d + 1)}tag: $${value.tag.identifier.id}`); - } else { - lines.push(`${ind(d + 1)}tag: "${value.tag.name}"`); - } - lines.push(`${ind(d + 1)}props:`); - for (const attr of value.props) { - if (attr.kind === "JsxAttribute") { - lines.push( - `${ind(d + 2)}${attr.name}: $${attr.place.identifier.id}` - ); - } else { - lines.push(`${ind(d + 2)}...$${attr.argument.identifier.id}`); - } - } - if (value.children != null) { - lines.push( - `${ind(d + 1)}children: [${value.children.map((c) => `$${c.identifier.id}`).join(", ")}]` - ); - } else { - lines.push(`${ind(d + 1)}children: null`); - } - lines.push(`${ind(d)}}`); - break; - } - case "JsxFragment": - lines.push(`${ind(d)}JsxFragment {`); - lines.push( - `${ind(d + 1)}children: [${value.children.map((c) => `$${c.identifier.id}`).join(", ")}]` - ); - lines.push(`${ind(d)}}`); - break; - case "FunctionExpression": - case "ObjectMethod": { - const label = - kind === "FunctionExpression" ? "FunctionExpression" : "ObjectMethod"; - lines.push(`${ind(d)}${label} {`); - if (kind === "FunctionExpression") { - lines.push( - `${ind(d + 1)}name: ${value.name != null ? JSON.stringify(value.name) : "null"}` - ); - } - lines.push( - `${ind(d + 1)}loweredFunc.id: ${value.loweredFunc.func.id ?? "null"}` - ); - const ctx = value.loweredFunc.func.context; - lines.push( - `${ind(d + 1)}context: [${ctx.map((c) => `$${c.identifier.id}`).join(", ")}]` - ); - const ae = value.loweredFunc.func.aliasingEffects; - lines.push( - `${ind(d + 1)}aliasingEffects: ${ae != null ? `[${ae.length} effects]` : "null"}` - ); - lines.push(`${ind(d)}}`); - break; - } - case "TaggedTemplateExpression": - lines.push(`${ind(d)}TaggedTemplateExpression {`); - lines.push(`${ind(d + 1)}tag: $${value.tag.identifier.id}`); - lines.push( - `${ind(d + 1)}value.raw: ${JSON.stringify(value.value.raw)}` - ); - lines.push(`${ind(d)}}`); - break; - case "TemplateLiteral": - lines.push(`${ind(d)}TemplateLiteral {`); - lines.push( - `${ind(d + 1)}quasis: [${value.quasis.map((q) => JSON.stringify(q.raw)).join(", ")}]` - ); - lines.push( - `${ind(d + 1)}subexprs: [${value.subexprs.map((s) => `$${s.identifier.id}`).join(", ")}]` - ); - lines.push(`${ind(d)}}`); - break; - case "RegExpLiteral": - lines.push(`${ind(d)}RegExpLiteral {`); - lines.push(`${ind(d + 1)}pattern: ${value.pattern}`); - lines.push(`${ind(d + 1)}flags: ${value.flags}`); - lines.push(`${ind(d)}}`); - break; - case "MetaProperty": - lines.push(`${ind(d)}MetaProperty {`); - lines.push(`${ind(d + 1)}meta: ${value.meta}`); - lines.push(`${ind(d + 1)}property: ${value.property}`); - lines.push(`${ind(d)}}`); - break; - case "Await": - lines.push(`${ind(d)}Await {`); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "GetIterator": - lines.push(`${ind(d)}GetIterator {`); - lines.push( - `${ind(d + 1)}collection: $${value.collection.identifier.id}` - ); - lines.push(`${ind(d)}}`); - break; - case "IteratorNext": - lines.push(`${ind(d)}IteratorNext {`); - lines.push(`${ind(d + 1)}iterator: $${value.iterator.identifier.id}`); - lines.push( - `${ind(d + 1)}collection: $${value.collection.identifier.id}` - ); - lines.push(`${ind(d)}}`); - break; - case "NextPropertyOf": - lines.push(`${ind(d)}NextPropertyOf {`); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "PostfixUpdate": - lines.push(`${ind(d)}PostfixUpdate {`); - lines.push(`${ind(d + 1)}lvalue: $${value.lvalue.identifier.id}`); - lines.push(`${ind(d + 1)}operation: ${value.operation}`); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "PrefixUpdate": - lines.push(`${ind(d)}PrefixUpdate {`); - lines.push(`${ind(d + 1)}lvalue: $${value.lvalue.identifier.id}`); - lines.push(`${ind(d + 1)}operation: ${value.operation}`); - lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`); - lines.push(`${ind(d)}}`); - break; - case "Debugger": - lines.push(`${ind(d)}Debugger {}`); - break; - case "StartMemoize": - lines.push(`${ind(d)}StartMemoize {`); - lines.push(`${ind(d + 1)}manualMemoId: ${value.manualMemoId}`); - lines.push( - `${ind(d + 1)}deps: ${value.deps != null ? `[${value.deps.length} deps]` : "null"}` - ); - lines.push(`${ind(d)}}`); - break; - case "FinishMemoize": - lines.push(`${ind(d)}FinishMemoize {`); - lines.push(`${ind(d + 1)}manualMemoId: ${value.manualMemoId}`); - lines.push(`${ind(d + 1)}decl: $${value.decl.identifier.id}`); - lines.push(`${ind(d + 1)}pruned: ${value.pruned === true}`); - lines.push(`${ind(d)}}`); - break; - case "UnsupportedNode": - lines.push(`${ind(d)}UnsupportedNode {`); - lines.push( - `${ind(d + 1)}type: ${value.node != null ? value.node.type : "unknown"}` - ); - lines.push(`${ind(d)}}`); - break; - default: - lines.push(`${ind(d)}${kind} {}`); - break; - } - return lines.join("\n"); -} - -// --------------------------------------------------------------------------- -// Reactive value printing (tree-structured values) -// --------------------------------------------------------------------------- - -function printReactiveValue(value, depth, outlinedCollector) { - const d = depth; - const lines = []; - - switch (value.kind) { - case "LogicalExpression": - lines.push(`${ind(d)}LogicalExpression {`); - lines.push(`${ind(d + 1)}operator: ${value.operator}`); - lines.push(`${ind(d + 1)}left:`); - lines.push(printReactiveValue(value.left, d + 2, outlinedCollector)); - lines.push(`${ind(d + 1)}right:`); - lines.push(printReactiveValue(value.right, d + 2, outlinedCollector)); - lines.push(`${ind(d + 1)}loc: ${formatLoc(value.loc)}`); - lines.push(`${ind(d)}}`); - break; - case "ConditionalExpression": - lines.push(`${ind(d)}ConditionalExpression {`); - lines.push(`${ind(d + 1)}test:`); - lines.push(printReactiveValue(value.test, d + 2, outlinedCollector)); - lines.push(`${ind(d + 1)}consequent:`); - lines.push( - printReactiveValue(value.consequent, d + 2, outlinedCollector) - ); - lines.push(`${ind(d + 1)}alternate:`); - lines.push( - printReactiveValue(value.alternate, d + 2, outlinedCollector) - ); - lines.push(`${ind(d + 1)}loc: ${formatLoc(value.loc)}`); - lines.push(`${ind(d)}}`); - break; - case "SequenceExpression": - lines.push(`${ind(d)}SequenceExpression {`); - lines.push(`${ind(d + 1)}id: ${value.id}`); - lines.push(`${ind(d + 1)}instructions:`); - for (const instr of value.instructions) { - lines.push(printReactiveInstruction(instr, d + 2, outlinedCollector)); - } - lines.push(`${ind(d + 1)}value:`); - lines.push(printReactiveValue(value.value, d + 2, outlinedCollector)); - lines.push(`${ind(d + 1)}loc: ${formatLoc(value.loc)}`); - lines.push(`${ind(d)}}`); - break; - case "OptionalExpression": - lines.push(`${ind(d)}OptionalExpression {`); - lines.push(`${ind(d + 1)}id: ${value.id}`); - lines.push(`${ind(d + 1)}optional: ${value.optional}`); - lines.push(`${ind(d + 1)}value:`); - lines.push(printReactiveValue(value.value, d + 2, outlinedCollector)); - lines.push(`${ind(d + 1)}loc: ${formatLoc(value.loc)}`); - lines.push(`${ind(d)}}`); - break; - default: - // Plain InstructionValue - lines.push(printInstructionValueFields(value, d)); - break; - } - return lines.join("\n"); -} - -// --------------------------------------------------------------------------- -// Reactive instruction printing -// --------------------------------------------------------------------------- - -function printReactiveInstruction(instr, depth, outlinedCollector) { - const lines = []; - lines.push(`${ind(depth)}[${instr.id}] ReactiveInstruction {`); - const d = depth + 1; - lines.push(`${ind(d)}id: ${instr.id}`); - // lvalue - if (instr.lvalue != null) { - lines.push(`${ind(d)}lvalue:`); - lines.push(printPlaceInline(instr.lvalue, d + 1)); - } else { - lines.push(`${ind(d)}lvalue: null`); - } - // value - lines.push(`${ind(d)}value:`); - lines.push(printReactiveValue(instr.value, d + 1, outlinedCollector)); - // Collect outlined functions - collectOutlinedFromValue(instr.value, outlinedCollector); - // effects - if (instr.effects != null) { - lines.push(`${ind(d)}effects: [${instr.effects.length} effects]`); - } else { - lines.push(`${ind(d)}effects: null`); - } - lines.push(`${ind(d)}loc: ${formatLoc(instr.loc)}`); - lines.push(`${ind(depth)}}`); - return lines.join("\n"); -} - -// --------------------------------------------------------------------------- -// Reactive scope printing -// --------------------------------------------------------------------------- - -function printReactiveScopeDetails(scope, depth) { - const lines = []; - const d = depth; - lines.push(`${ind(d)}scope @${scope.id} {`); - lines.push(`${ind(d + 1)}id: ${scope.id}`); - lines.push( - `${ind(d + 1)}range: ${formatMutableRange(scope.range)}` - ); - // dependencies - const deps = [...scope.dependencies]; - lines.push(`${ind(d + 1)}dependencies: [${deps.length}]`); - for (const dep of deps) { - const path = dep.path - .map((p) => `${p.optional ? "?." : "."}${p.property}`) - .join(""); - lines.push( - `${ind(d + 2)}$${dep.identifier.id}${path} (reactive=${dep.reactive})` - ); - } - // declarations - const decls = [...scope.declarations].sort((a, b) => a[0] - b[0]); - lines.push(`${ind(d + 1)}declarations: [${decls.length}]`); - for (const [id, decl] of decls) { - lines.push(`${ind(d + 2)}$${id}: $${decl.identifier.id}`); - } - // reassignments - const reassigns = [...scope.reassignments]; - lines.push(`${ind(d + 1)}reassignments: [${reassigns.length}]`); - for (const ident of reassigns) { - lines.push(`${ind(d + 2)}$${ident.id}`); - } - // earlyReturnValue - if (scope.earlyReturnValue != null) { - lines.push(`${ind(d + 1)}earlyReturnValue:`); - lines.push( - `${ind(d + 2)}value: $${scope.earlyReturnValue.value.id}` - ); - lines.push( - `${ind(d + 2)}label: bb${scope.earlyReturnValue.label}` - ); - } else { - lines.push(`${ind(d + 1)}earlyReturnValue: null`); - } - // merged - const merged = [...scope.merged]; - if (merged.length > 0) { - lines.push( - `${ind(d + 1)}merged: [${merged.map((m) => `@${m}`).join(", ")}]` - ); - } else { - lines.push(`${ind(d + 1)}merged: []`); - } - lines.push(`${ind(d + 1)}loc: ${formatLoc(scope.loc)}`); - lines.push(`${ind(d)}}`); - return lines.join("\n"); -} - -// --------------------------------------------------------------------------- -// Reactive terminal printing -// --------------------------------------------------------------------------- - -function printReactiveTerminal(terminal, depth, outlinedCollector) { - const lines = []; - const d = depth; - const kind = terminal.kind; - - lines.push(`${ind(d)}${reactiveTerminalName(kind)} {`); - lines.push(`${ind(d + 1)}id: ${terminal.id}`); - - switch (kind) { - case "break": - lines.push(`${ind(d + 1)}target: bb${terminal.target}`); - lines.push(`${ind(d + 1)}targetKind: ${terminal.targetKind}`); - break; - case "continue": - lines.push(`${ind(d + 1)}target: bb${terminal.target}`); - lines.push(`${ind(d + 1)}targetKind: ${terminal.targetKind}`); - break; - case "return": - lines.push(`${ind(d + 1)}value:`); - lines.push(printPlaceInline(terminal.value, d + 2)); - break; - case "throw": - lines.push(`${ind(d + 1)}value:`); - lines.push(printPlaceInline(terminal.value, d + 2)); - break; - case "if": - lines.push(`${ind(d + 1)}test:`); - lines.push(printPlaceInline(terminal.test, d + 2)); - lines.push(`${ind(d + 1)}consequent:`); - lines.push( - printReactiveBlock(terminal.consequent, d + 2, outlinedCollector) - ); - if (terminal.alternate != null) { - lines.push(`${ind(d + 1)}alternate:`); - lines.push( - printReactiveBlock(terminal.alternate, d + 2, outlinedCollector) - ); - } else { - lines.push(`${ind(d + 1)}alternate: null`); - } - break; - case "switch": - lines.push(`${ind(d + 1)}test:`); - lines.push(printPlaceInline(terminal.test, d + 2)); - lines.push(`${ind(d + 1)}cases:`); - for (const c of terminal.cases) { - if (c.test != null) { - lines.push(`${ind(d + 2)}case $${c.test.identifier.id}:`); - } else { - lines.push(`${ind(d + 2)}default:`); - } - if (c.block != null) { - lines.push(printReactiveBlock(c.block, d + 3, outlinedCollector)); - } else { - lines.push(`${ind(d + 3)}(empty)`); - } - } - break; - case "do-while": - lines.push(`${ind(d + 1)}loop:`); - lines.push( - printReactiveBlock(terminal.loop, d + 2, outlinedCollector) - ); - lines.push(`${ind(d + 1)}test:`); - lines.push( - printReactiveValue(terminal.test, d + 2, outlinedCollector) - ); - break; - case "while": - lines.push(`${ind(d + 1)}test:`); - lines.push( - printReactiveValue(terminal.test, d + 2, outlinedCollector) - ); - lines.push(`${ind(d + 1)}loop:`); - lines.push( - printReactiveBlock(terminal.loop, d + 2, outlinedCollector) - ); - break; - case "for": - lines.push(`${ind(d + 1)}init:`); - lines.push( - printReactiveValue(terminal.init, d + 2, outlinedCollector) - ); - lines.push(`${ind(d + 1)}test:`); - lines.push( - printReactiveValue(terminal.test, d + 2, outlinedCollector) - ); - if (terminal.update != null) { - lines.push(`${ind(d + 1)}update:`); - lines.push( - printReactiveValue(terminal.update, d + 2, outlinedCollector) - ); - } else { - lines.push(`${ind(d + 1)}update: null`); - } - lines.push(`${ind(d + 1)}loop:`); - lines.push( - printReactiveBlock(terminal.loop, d + 2, outlinedCollector) - ); - break; - case "for-of": - lines.push(`${ind(d + 1)}init:`); - lines.push( - printReactiveValue(terminal.init, d + 2, outlinedCollector) - ); - lines.push(`${ind(d + 1)}test:`); - lines.push( - printReactiveValue(terminal.test, d + 2, outlinedCollector) - ); - lines.push(`${ind(d + 1)}loop:`); - lines.push( - printReactiveBlock(terminal.loop, d + 2, outlinedCollector) - ); - break; - case "for-in": - lines.push(`${ind(d + 1)}init:`); - lines.push( - printReactiveValue(terminal.init, d + 2, outlinedCollector) - ); - lines.push(`${ind(d + 1)}loop:`); - lines.push( - printReactiveBlock(terminal.loop, d + 2, outlinedCollector) - ); - break; - case "label": - lines.push(`${ind(d + 1)}block:`); - lines.push( - printReactiveBlock(terminal.block, d + 2, outlinedCollector) - ); - break; - case "try": - lines.push(`${ind(d + 1)}block:`); - lines.push( - printReactiveBlock(terminal.block, d + 2, outlinedCollector) - ); - if (terminal.handlerBinding != null) { - lines.push(`${ind(d + 1)}handlerBinding:`); - lines.push(printPlaceInline(terminal.handlerBinding, d + 2)); - } else { - lines.push(`${ind(d + 1)}handlerBinding: null`); - } - lines.push(`${ind(d + 1)}handler:`); - lines.push( - printReactiveBlock(terminal.handler, d + 2, outlinedCollector) - ); - break; - default: - break; - } - - lines.push(`${ind(d + 1)}loc: ${formatLoc(terminal.loc)}`); - lines.push(`${ind(d)}}`); - return lines.join("\n"); -} - -function reactiveTerminalName(kind) { - const names = { - break: "Break", - continue: "Continue", - return: "Return", - throw: "Throw", - if: "If", - switch: "Switch", - "do-while": "DoWhile", - while: "While", - for: "For", - "for-of": "ForOf", - "for-in": "ForIn", - label: "Label", - try: "Try", - }; - return names[kind] ?? kind; -} - -// --------------------------------------------------------------------------- -// Reactive block printing (array of ReactiveStatements) -// --------------------------------------------------------------------------- - -function printReactiveBlock(block, depth, outlinedCollector) { - if (block == null || block.length === 0) { - return `${ind(depth)}(empty block)`; - } - const lines = []; - for (const stmt of block) { - lines.push(printReactiveStatement(stmt, depth, outlinedCollector)); - } - return lines.join("\n"); -} - -function printReactiveStatement(stmt, depth, outlinedCollector) { - const lines = []; - const d = depth; - - switch (stmt.kind) { - case "instruction": - lines.push( - printReactiveInstruction(stmt.instruction, d, outlinedCollector) - ); - break; - case "scope": - lines.push(`${ind(d)}ReactiveScopeBlock {`); - lines.push(printReactiveScopeDetails(stmt.scope, d + 1)); - lines.push(`${ind(d + 1)}instructions:`); - lines.push( - printReactiveBlock(stmt.instructions, d + 2, outlinedCollector) - ); - lines.push(`${ind(d)}}`); - break; - case "pruned-scope": - lines.push(`${ind(d)}PrunedReactiveScopeBlock {`); - lines.push(printReactiveScopeDetails(stmt.scope, d + 1)); - lines.push(`${ind(d + 1)}instructions:`); - lines.push( - printReactiveBlock(stmt.instructions, d + 2, outlinedCollector) - ); - lines.push(`${ind(d)}}`); - break; - case "terminal": - if (stmt.label != null) { - lines.push( - `${ind(d)}label bb${stmt.label.id} (implicit=${stmt.label.implicit}):` - ); - } - lines.push( - printReactiveTerminal(stmt.terminal, d, outlinedCollector) - ); - break; - default: - lines.push(`${ind(d)}Unknown statement kind: ${stmt.kind}`); - break; - } - - return lines.join("\n"); -} - -// --------------------------------------------------------------------------- -// Collect outlined functions from reactive values -// --------------------------------------------------------------------------- - -function collectOutlinedFromValue(value, collector) { - if (value == null) return; - if ( - value.kind === "FunctionExpression" || - value.kind === "ObjectMethod" - ) { - // The loweredFunc in reactive context points to an HIRFunction - // which in turn has a reactive body after BuildReactiveFunction. - // But outlined functions are collected from env, so we just track - // them for the main function printer. - } - // For reactive compound values, recurse - if (value.kind === "SequenceExpression") { - for (const instr of value.instructions) { - collectOutlinedFromValue(instr.value, collector); - } - collectOutlinedFromValue(value.value, collector); - } else if (value.kind === "LogicalExpression") { - collectOutlinedFromValue(value.left, collector); - collectOutlinedFromValue(value.right, collector); - } else if (value.kind === "ConditionalExpression") { - collectOutlinedFromValue(value.test, collector); - collectOutlinedFromValue(value.consequent, collector); - collectOutlinedFromValue(value.alternate, collector); - } else if (value.kind === "OptionalExpression") { - collectOutlinedFromValue(value.value, collector); - } -} - -// --------------------------------------------------------------------------- -// Main function printer -// --------------------------------------------------------------------------- - -function printReactiveFunction(fn, functionIndex, outlinedCollector) { - const lines = []; - const d0 = 0; - const d1 = 1; - const d2 = 2; - - lines.push(`${ind(d0)}ReactiveFunction #${functionIndex}:`); - - // id - lines.push( - `${ind(d1)}id: ${fn.id != null ? JSON.stringify(fn.id) : "null"}` - ); - - // nameHint - lines.push( - `${ind(d1)}nameHint: ${fn.nameHint != null ? JSON.stringify(fn.nameHint) : "null"}` - ); - - // params - lines.push(`${ind(d1)}params:`); - for (let i = 0; i < fn.params.length; i++) { - const param = fn.params[i]; - if (param.kind === "Identifier") { - lines.push(`${ind(d2)}[${i}]`); - lines.push(printPlaceInline(param, d2 + 1)); - } else { - lines.push(`${ind(d2)}[${i}] ...`); - lines.push(printPlaceInline(param.place, d2 + 1)); - } - } - - // generator / async - lines.push(`${ind(d1)}generator: ${fn.generator}`); - lines.push(`${ind(d1)}async: ${fn.async}`); - - // directives - if (fn.directives.length > 0) { - lines.push( - `${ind(d1)}directives: [${fn.directives.map((d) => JSON.stringify(d)).join(", ")}]` - ); - } else { - lines.push(`${ind(d1)}directives: []`); - } - - // loc - lines.push(`${ind(d1)}loc: ${formatLoc(fn.loc)}`); - - // body - lines.push(""); - lines.push(`${ind(d1)}body:`); - lines.push(printReactiveBlock(fn.body, d2, outlinedCollector)); - - // Outlined functions from env - if (fn.env != null && typeof fn.env.getOutlinedFunctions === "function") { - const outlinedFns = fn.env.getOutlinedFunctions(); - for (const outlined of outlinedFns) { - outlinedCollector.push(outlined.fn); - } - } - - return lines.join("\n"); -} diff --git a/compiler/scripts/profile-rust-port.sh b/compiler/scripts/profile-rust-port.sh deleted file mode 100755 index 089c029dc0d7..000000000000 --- a/compiler/scripts/profile-rust-port.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/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. - -# Thin wrapper for the profiling script. -# -# Usage: bash compiler/scripts/profile-rust-port.sh [flags] -# Flags: --release, --json, --limit N - -set -eo pipefail - -REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" - -exec npx tsx "$REPO_ROOT/compiler/scripts/profile-rust-port.ts" "$@" diff --git a/compiler/scripts/profile-rust-port.ts b/compiler/scripts/profile-rust-port.ts deleted file mode 100644 index 7206a7dd6533..000000000000 --- a/compiler/scripts/profile-rust-port.ts +++ /dev/null @@ -1,640 +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. - */ - -/** - * Performance profiling script for Rust vs JS React Compiler. - * - * Runs both compilers on all fixtures without debug logging, - * collects fine-grained timing data at every stage, and reports - * aggregate performance breakdowns. - * - * Usage: npx tsx compiler/scripts/profile-rust-port.ts [flags] - * - * Flags: - * --release Build and use release-mode Rust binary - * --json Output JSON instead of formatted tables - * --limit N Max fixtures to profile (default: all) - */ - -import * as babel from '@babel/core'; -import {execSync} from 'child_process'; -import fs from 'fs'; -import path from 'path'; - -import {parseConfigPragmaForTests} from '../packages/babel-plugin-react-compiler/src/Utils/TestUtils'; - -const REPO_ROOT = path.resolve(__dirname, '../..'); - -// --- Parse flags --- -const rawArgs = process.argv.slice(2); -const releaseMode = rawArgs.includes('--release'); -const jsonMode = rawArgs.includes('--json'); -const limitIdx = rawArgs.indexOf('--limit'); -const limitArg = limitIdx >= 0 ? parseInt(rawArgs[limitIdx + 1], 10) : 0; - -// --- ANSI colors --- -const useColor = !jsonMode; -const BOLD = useColor ? '\x1b[1m' : ''; -const DIM = useColor ? '\x1b[2m' : ''; -const RED = useColor ? '\x1b[0;31m' : ''; -const GREEN = useColor ? '\x1b[0;32m' : ''; -const YELLOW = useColor ? '\x1b[0;33m' : ''; -const CYAN = useColor ? '\x1b[0;36m' : ''; -const RESET = useColor ? '\x1b[0m' : ''; - -// --- Build native module --- -const NATIVE_DIR = path.join( - REPO_ROOT, - 'compiler/packages/babel-plugin-react-compiler-rust/native', -); -const NATIVE_NODE_PATH = path.join(NATIVE_DIR, 'index.node'); - -if (!jsonMode) { - console.log( - `Building Rust native module (${releaseMode ? 'release' : 'debug'})...`, - ); -} - -// Profiling needs symbol names; override the release profile's strip=true. -const cargoBuildArgs = releaseMode - ? "--release --config 'profile.release.strip=false' -p react_compiler_napi" - : '-p react_compiler_napi'; - -try { - execSync(`~/.cargo/bin/cargo build ${cargoBuildArgs}`, { - cwd: path.join(REPO_ROOT, 'compiler/crates'), - stdio: jsonMode ? ['inherit', 'pipe', 'inherit'] : 'inherit', - shell: true, - }); -} catch { - console.error('ERROR: Failed to build Rust native module.'); - process.exit(1); -} - -// Copy the built dylib as index.node -const TARGET_DIR = path.join( - REPO_ROOT, - releaseMode ? 'compiler/target/release' : 'compiler/target/debug', -); -const dylib = fs.existsSync( - path.join(TARGET_DIR, 'libreact_compiler_napi.dylib'), -) - ? path.join(TARGET_DIR, 'libreact_compiler_napi.dylib') - : path.join(TARGET_DIR, 'libreact_compiler_napi.so'); - -if (!fs.existsSync(dylib)) { - console.error(`ERROR: Could not find built native module in ${TARGET_DIR}`); - process.exit(1); -} -fs.copyFileSync(dylib, NATIVE_NODE_PATH); - -// --- Load plugins --- -const tsPlugin = require('../packages/babel-plugin-react-compiler/src').default; -const {extractScopeInfo} = - require('../packages/babel-plugin-react-compiler-rust/src/scope') as typeof import('../packages/babel-plugin-react-compiler-rust/src/scope'); -const {resolveOptions} = - require('../packages/babel-plugin-react-compiler-rust/src/options') as typeof import('../packages/babel-plugin-react-compiler-rust/src/options'); -const {compileWithRustProfiled} = - require('../packages/babel-plugin-react-compiler-rust/src/bridge') as typeof import('../packages/babel-plugin-react-compiler-rust/src/bridge'); - -// --- Types --- -interface TimingEntry { - name: string; - duration_us: number; -} - -interface BridgeTiming { - jsStringifyAst_us: number; - jsStringifyScope_us: number; - jsStringifyOptions_us: number; - napiCall_us: number; - jsParseResult_us: number; -} - -interface FixtureProfile { - fixture: string; - sizeBytes: number; - tsTotal_us: number; - rustTotal_us: number; - rustScopeExtraction_us: number; - rustBridge: BridgeTiming; - rustPasses: TimingEntry[]; -} - -// --- Discover fixtures --- -function discoverFixtures(rootPath: string): string[] { - const results: string[] = []; - function walk(dir: string): void { - for (const entry of fs.readdirSync(dir, {withFileTypes: true})) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - walk(fullPath); - } else if ( - /\.(js|jsx|ts|tsx)$/.test(entry.name) && - !entry.name.endsWith('.expect.md') - ) { - results.push(fullPath); - } - } - } - walk(rootPath); - results.sort(); - return results; -} - -// --- Compile fixture with TS compiler (no debug logging) --- -function compileWithTS(fixturePath: string): number { - const source = fs.readFileSync(fixturePath, 'utf8'); - const firstLine = source.substring(0, source.indexOf('\n')); - const pragmaOpts = parseConfigPragmaForTests(firstLine, { - compilationMode: 'all', - }); - - const isFlow = firstLine.includes('@flow'); - const isScript = firstLine.includes('@script'); - const parserPlugins: string[] = isFlow - ? ['flow', 'jsx'] - : ['typescript', 'jsx']; - - const start = performance.now(); - try { - babel.transformSync(source, { - filename: fixturePath, - sourceType: isScript ? 'script' : 'module', - parserOpts: {plugins: parserPlugins}, - plugins: [ - [ - tsPlugin, - { - ...pragmaOpts, - compilationMode: 'all' as const, - panicThreshold: 'all_errors' as const, - }, - ], - ], - configFile: false, - babelrc: false, - }); - } catch { - // Ignore errors - we still measure timing - } - const end = performance.now(); - return Math.round((end - start) * 1000); // microseconds -} - -// --- Compile fixture with Rust compiler (profiled) --- -function compileWithRustProfile(fixturePath: string): { - total_us: number; - scopeExtraction_us: number; - bridge: BridgeTiming; - passes: TimingEntry[]; -} { - const source = fs.readFileSync(fixturePath, 'utf8'); - const firstLine = source.substring(0, source.indexOf('\n')); - const pragmaOpts = parseConfigPragmaForTests(firstLine, { - compilationMode: 'all', - }); - - const isFlow = firstLine.includes('@flow'); - const isScript = firstLine.includes('@script'); - const parserPlugins: string[] = isFlow - ? ['flow', 'jsx'] - : ['typescript', 'jsx']; - - // Parse the AST via Babel (same as the real plugin) - const parseResult = babel.transformSync(source, { - filename: fixturePath, - sourceType: isScript ? 'script' : 'module', - parserOpts: {plugins: parserPlugins}, - plugins: [ - // Use a minimal plugin that captures the AST and scope info - function capturePlugin(_api: typeof babel): babel.PluginObj { - return { - name: 'capture', - visitor: { - Program: { - enter(prog, pass): void { - // Resolve options - const opts = resolveOptions( - { - ...pragmaOpts, - compilationMode: 'all', - panicThreshold: 'all_errors', - }, - pass.file, - fixturePath, - pass.file.ast, - ); - - // Extract scope info (timed) - const scopeStart = performance.now(); - let scopeInfo; - try { - scopeInfo = extractScopeInfo(prog); - } catch { - // Store failed result - (pass as any).__profileResult = { - total_us: Math.round( - (performance.now() - scopeStart) * 1000, - ), - scopeExtraction_us: Math.round( - (performance.now() - scopeStart) * 1000, - ), - bridge: { - jsStringifyAst_us: 0, - jsStringifyScope_us: 0, - jsStringifyOptions_us: 0, - napiCall_us: 0, - jsParseResult_us: 0, - }, - passes: [], - }; - return; - } - const scopeEnd = performance.now(); - - const totalStart = performance.now(); - try { - const profiled = compileWithRustProfiled( - pass.file.ast, - scopeInfo, - opts, - pass.file.code ?? null, - ); - const totalEnd = performance.now(); - - (pass as any).__profileResult = { - total_us: Math.round((totalEnd - totalStart) * 1000), - scopeExtraction_us: Math.round( - (scopeEnd - scopeStart) * 1000, - ), - bridge: profiled.bridgeTiming, - passes: profiled.rustTiming, - }; - } catch { - const totalEnd = performance.now(); - (pass as any).__profileResult = { - total_us: Math.round((totalEnd - totalStart) * 1000), - scopeExtraction_us: Math.round( - (scopeEnd - scopeStart) * 1000, - ), - bridge: { - jsStringifyAst_us: 0, - jsStringifyScope_us: 0, - jsStringifyOptions_us: 0, - napiCall_us: 0, - jsParseResult_us: 0, - }, - passes: [], - }; - } - prog.skip(); - }, - }, - }, - }; - }, - ], - configFile: false, - babelrc: false, - }); - - // Extract the profile result stored by the plugin - const result = (parseResult as any)?.metadata?.__profileResult ?? - (parseResult as any)?.__profileResult ?? { - total_us: 0, - scopeExtraction_us: 0, - bridge: { - jsStringifyAst_us: 0, - jsStringifyScope_us: 0, - jsStringifyOptions_us: 0, - napiCall_us: 0, - jsParseResult_us: 0, - }, - passes: [], - }; - - return result; -} - -// --- Compile fixture with Rust compiler (simpler approach using direct API) --- -function compileWithRustDirect(fixturePath: string): { - total_us: number; - scopeExtraction_us: number; - bridge: BridgeTiming; - passes: TimingEntry[]; -} { - const source = fs.readFileSync(fixturePath, 'utf8'); - const firstLine = source.substring(0, source.indexOf('\n')); - const pragmaOpts = parseConfigPragmaForTests(firstLine, { - compilationMode: 'all', - }); - - const isFlow = firstLine.includes('@flow'); - const isScript = firstLine.includes('@script'); - const parserPlugins: string[] = isFlow - ? ['flow', 'jsx'] - : ['typescript', 'jsx']; - - // Parse the AST via Babel - let ast: babel.types.File | null = null; - let scopeInfo: any = null; - let opts: any = null; - let scopeExtraction_us = 0; - - try { - babel.transformSync(source, { - filename: fixturePath, - sourceType: isScript ? 'script' : 'module', - parserOpts: {plugins: parserPlugins}, - plugins: [ - function capturePlugin(_api: typeof babel): babel.PluginObj { - return { - name: 'capture-for-profile', - visitor: { - Program: { - enter(prog, pass): void { - ast = pass.file.ast; - opts = resolveOptions( - { - ...pragmaOpts, - compilationMode: 'all', - panicThreshold: 'all_errors', - }, - pass.file, - fixturePath, - pass.file.ast, - ); - - const scopeStart = performance.now(); - try { - scopeInfo = extractScopeInfo(prog); - } catch { - scopeInfo = null; - } - scopeExtraction_us = Math.round( - (performance.now() - scopeStart) * 1000, - ); - prog.skip(); - }, - }, - }, - }; - }, - ], - configFile: false, - babelrc: false, - }); - } catch { - // Parse error or other babel failure - skip this fixture - } - - if (ast == null || scopeInfo == null || opts == null) { - return { - total_us: 0, - scopeExtraction_us, - bridge: { - jsStringifyAst_us: 0, - jsStringifyScope_us: 0, - jsStringifyOptions_us: 0, - napiCall_us: 0, - jsParseResult_us: 0, - }, - passes: [], - }; - } - - const totalStart = performance.now(); - try { - const profiled = compileWithRustProfiled(ast, scopeInfo, opts, source); - const totalEnd = performance.now(); - - return { - total_us: Math.round((totalEnd - totalStart) * 1000), - scopeExtraction_us, - bridge: profiled.bridgeTiming, - passes: profiled.rustTiming, - }; - } catch { - const totalEnd = performance.now(); - return { - total_us: Math.round((totalEnd - totalStart) * 1000), - scopeExtraction_us, - bridge: { - jsStringifyAst_us: 0, - jsStringifyScope_us: 0, - jsStringifyOptions_us: 0, - napiCall_us: 0, - jsParseResult_us: 0, - }, - passes: [], - }; - } -} - -// --- Main --- -const DEFAULT_FIXTURES_DIR = path.join( - REPO_ROOT, - 'compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler', -); - -let fixtures = discoverFixtures(DEFAULT_FIXTURES_DIR); -if (limitArg > 0) { - fixtures = fixtures.slice(0, limitArg); -} - -if (fixtures.length === 0) { - console.error('No fixtures found.'); - process.exit(1); -} - -if (!jsonMode) { - console.log(`\nProfiling ${BOLD}${fixtures.length}${RESET} fixtures...`); -} - -// --- Warmup pass --- -if (!jsonMode) { - console.log(`${DIM}Warmup pass (results discarded)...${RESET}`); -} -for (const fixturePath of fixtures) { - compileWithTS(fixturePath); - compileWithRustDirect(fixturePath); -} - -// --- Profile pass --- -if (!jsonMode) { - console.log(`Profiling...`); -} - -const profiles: FixtureProfile[] = []; - -for (const fixturePath of fixtures) { - const relPath = path.relative(REPO_ROOT, fixturePath); - const sizeBytes = fs.statSync(fixturePath).size; - - const tsTotal_us = compileWithTS(fixturePath); - const rustResult = compileWithRustDirect(fixturePath); - - profiles.push({ - fixture: relPath, - sizeBytes, - tsTotal_us, - rustTotal_us: rustResult.scopeExtraction_us + rustResult.total_us, - rustScopeExtraction_us: rustResult.scopeExtraction_us, - rustBridge: rustResult.bridge, - rustPasses: rustResult.passes, - }); -} - -// --- Aggregation --- -const totalTS = profiles.reduce((sum, p) => sum + p.tsTotal_us, 0); -const totalRust = profiles.reduce((sum, p) => sum + p.rustTotal_us, 0); -const ratio = totalRust / totalTS; - -// Aggregate pass timing -const passAggregates = new Map<string, {total_us: number; values: number[]}>(); - -function addPassTiming(name: string, duration_us: number): void { - let agg = passAggregates.get(name); - if (!agg) { - agg = {total_us: 0, values: []}; - passAggregates.set(name, agg); - } - agg.total_us += duration_us; - agg.values.push(duration_us); -} - -for (const profile of profiles) { - // Bridge phases - addPassTiming('JS: extractScopeInfo', profile.rustScopeExtraction_us); - addPassTiming('JS: JSON.stringify AST', profile.rustBridge.jsStringifyAst_us); - addPassTiming( - 'JS: JSON.stringify scope', - profile.rustBridge.jsStringifyScope_us, - ); - addPassTiming( - 'JS: JSON.stringify options', - profile.rustBridge.jsStringifyOptions_us, - ); - addPassTiming('JS: JSON.parse result', profile.rustBridge.jsParseResult_us); - - // Rust passes - for (const pass of profile.rustPasses) { - addPassTiming(`Rust: ${pass.name}`, pass.duration_us); - } -} - -function percentile(values: number[], p: number): number { - const sorted = [...values].sort((a, b) => a - b); - const idx = Math.ceil((p / 100) * sorted.length) - 1; - return sorted[Math.max(0, idx)]; -} - -// --- Output --- -if (jsonMode) { - const output = { - build: releaseMode ? 'release' : 'debug', - fixtureCount: fixtures.length, - totalTS_us: totalTS, - totalRust_us: totalRust, - ratio: Math.round(ratio * 100) / 100, - passAggregates: Object.fromEntries( - [...passAggregates.entries()].map(([name, agg]) => [ - name, - { - total_us: agg.total_us, - avg_us: Math.round(agg.total_us / agg.values.length), - p95_us: percentile(agg.values, 95), - count: agg.values.length, - }, - ]), - ), - fixtures: profiles, - }; - console.log(JSON.stringify(output, null, 2)); -} else { - console.log(''); - console.log(`${BOLD}=== Summary ===${RESET}`); - console.log( - `Build: ${CYAN}${releaseMode ? 'release' : 'debug'}${RESET} | Fixtures: ${BOLD}${fixtures.length}${RESET} | Warmup: done`, - ); - console.log(''); - - const tsMs = (totalTS / 1000).toFixed(1); - const rustMs = (totalRust / 1000).toFixed(1); - const ratioStr = ratio.toFixed(2); - const ratioColor = ratio <= 1.0 ? GREEN : ratio <= 1.5 ? YELLOW : RED; - console.log( - `Total: TS ${BOLD}${tsMs}ms${RESET} | Rust ${BOLD}${rustMs}ms${RESET} | Ratio ${ratioColor}${ratioStr}x${RESET}`, - ); - console.log(''); - - // --- Pass breakdown table --- - console.log(`${BOLD}=== Rust Time Breakdown (aggregate) ===${RESET}`); - - // Sort by total time descending - const sortedPasses = [...passAggregates.entries()].sort( - (a, b) => b[1].total_us - a[1].total_us, - ); - - const header = `${'Phase'.padEnd(50)} ${'Total(ms)'.padStart(10)} ${'%'.padStart(6)} ${'Avg(us)'.padStart(9)} ${'P95(us)'.padStart(9)}`; - console.log(`${DIM}${header}${RESET}`); - - for (const [name, agg] of sortedPasses) { - const totalMs = (agg.total_us / 1000).toFixed(1); - const pct = ((agg.total_us / totalRust) * 100).toFixed(1); - const avg = Math.round(agg.total_us / agg.values.length); - const p95 = percentile(agg.values, 95); - - console.log( - `${name.padEnd(50)} ${totalMs.padStart(10)} ${(pct + '%').padStart(6)} ${String(avg).padStart(9)} ${String(p95).padStart(9)}`, - ); - } - console.log(''); - - // --- Top 20 slowest fixtures --- - console.log(`${BOLD}=== Top 20 Slowest Fixtures (Rust) ===${RESET}`); - const sortedFixtures = [...profiles].sort( - (a, b) => b.rustTotal_us - a.rustTotal_us, - ); - const topN = sortedFixtures.slice(0, 20); - - const fHeader = `${'Fixture'.padEnd(55)} ${'Size'.padStart(6)} ${'TS(ms)'.padStart(8)} ${'Rust(ms)'.padStart(9)} ${'Ratio'.padStart(7)} ${'Bottleneck'.padStart(20)}`; - console.log(`${DIM}${fHeader}${RESET}`); - - for (const p of topN) { - const shortName = - p.fixture.length > 54 ? '...' + p.fixture.slice(-51) : p.fixture; - const sizeStr = - p.sizeBytes > 1024 - ? (p.sizeBytes / 1024).toFixed(0) + 'K' - : String(p.sizeBytes); - const tsMsStr = (p.tsTotal_us / 1000).toFixed(2); - const rustMsStr = (p.rustTotal_us / 1000).toFixed(2); - const fixtureRatio = p.tsTotal_us > 0 ? p.rustTotal_us / p.tsTotal_us : 0; - const ratioStr = fixtureRatio.toFixed(1) + 'x'; - const ratioColor = - fixtureRatio <= 1.0 ? GREEN : fixtureRatio <= 1.5 ? YELLOW : RED; - - // Find bottleneck pass - let bottleneck = ''; - if (p.rustPasses.length > 0) { - const sorted = [...p.rustPasses].sort( - (a, b) => b.duration_us - a.duration_us, - ); - const top = sorted[0]; - const pct = ((top.duration_us / p.rustTotal_us) * 100).toFixed(0); - bottleneck = `${top.name} (${pct}%)`; - } - const bottleneckStr = - bottleneck.length > 19 ? bottleneck.slice(0, 19) + '…' : bottleneck; - - console.log( - `${shortName.padEnd(55)} ${sizeStr.padStart(6)} ${tsMsStr.padStart(8)} ${rustMsStr.padStart(9)} ${ratioColor}${ratioStr.padStart(7)}${RESET} ${bottleneckStr.padStart(20)}`, - ); - } -} diff --git a/compiler/scripts/test-babel-ast.sh b/compiler/scripts/test-babel-ast.sh deleted file mode 100755 index f2f23ff2c244..000000000000 --- a/compiler/scripts/test-babel-ast.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -set -e - -REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" - -FIXTURE_SRC_DIR="$1" -if [ -z "$FIXTURE_SRC_DIR" ]; then - # Default: the compiler's own test fixtures - FIXTURE_SRC_DIR="$REPO_ROOT/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures" -fi - -# Parse source files into JSON in a temp directory -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -echo "Parsing fixtures from $FIXTURE_SRC_DIR..." -node "$REPO_ROOT/compiler/scripts/babel-ast-to-json.mjs" "$FIXTURE_SRC_DIR" "$TMPDIR" - -# Bump the default 8 MiB Rust thread stack to 32 MiB. The round_trip test -# walks deeply-nested Babel AST fixtures via recursive serde Visitor; some -# fixtures exceed the default stack on Linux CI runners. RUST_MIN_STACK only -# affects threads spawned via std::thread (which is how the libtest harness -# runs each test), so this is enough without changing the test sources. -export RUST_MIN_STACK=33554432 - -echo "Running round-trip test..." -cd "$REPO_ROOT/compiler/crates" -FIXTURE_JSON_DIR="$TMPDIR" ~/.cargo/bin/cargo test -p react_compiler_ast --test round_trip -- --nocapture - -echo "Running scope resolution test..." -FIXTURE_JSON_DIR="$TMPDIR" ~/.cargo/bin/cargo test -p react_compiler_ast --test scope_resolution -- --nocapture diff --git a/compiler/scripts/test-e2e.sh b/compiler/scripts/test-e2e.sh deleted file mode 100755 index 5f23de648a45..000000000000 --- a/compiler/scripts/test-e2e.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/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. - -# End-to-end test runner comparing the Rust compiler (Babel/NAPI bridge) -# against the TS reference plugin. -# -# Usage: bash compiler/scripts/test-e2e.sh [fixtures-path] [--variant babel] [--limit N] [--no-color] - -set -eo pipefail - -REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" - -exec npx tsx "$REPO_ROOT/compiler/scripts/test-e2e.ts" "$@" diff --git a/compiler/scripts/test-e2e.ts b/compiler/scripts/test-e2e.ts deleted file mode 100644 index ddccb45f18a8..000000000000 --- a/compiler/scripts/test-e2e.ts +++ /dev/null @@ -1,558 +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. - */ - -/** - * End-to-end test script comparing the Rust compiler against the TS reference. - * - * Runs fixtures through: - * - TS baseline (Babel plugin, in-process) — the reference output - * - babel variant: Rust via Babel plugin (in-process via NAPI) — the - * production path - * - * This complements `yarn snap --rust` by independently comparing the NAPI - * bridge's code output AND its logged events against the TS plugin. - * - * Usage: npx tsx compiler/scripts/test-e2e.ts [fixtures-path] [--variant babel] [--limit N] [--no-color] - */ - -import * as babel from '@babel/core'; -import generate from '@babel/generator'; -import {execSync} from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import prettier from 'prettier'; - -import {parseConfigPragmaForTests} from '../packages/babel-plugin-react-compiler/src/Utils/TestUtils'; - -const REPO_ROOT = path.resolve(__dirname, '../..'); - -// --- Parse flags --- -const rawArgs = process.argv.slice(2); -const noColor = rawArgs.includes('--no-color') || !!process.env.NO_COLOR; -const variantIdx = rawArgs.indexOf('--variant'); -const variantArg = - variantIdx >= 0 ? (rawArgs[variantIdx + 1] as 'babel') : null; -const limitIdx = rawArgs.indexOf('--limit'); -const limitArg = limitIdx >= 0 ? parseInt(rawArgs[limitIdx + 1], 10) : 50; - -// Extract positional args (strip flags and flag values) -const skipIndices = new Set<number>(); -for (const flag of ['--no-color']) { - const idx = rawArgs.indexOf(flag); - if (idx >= 0) skipIndices.add(idx); -} -for (const flag of ['--variant', '--limit']) { - const idx = rawArgs.indexOf(flag); - if (idx >= 0) { - skipIndices.add(idx); - skipIndices.add(idx + 1); - } -} -const positional = rawArgs.filter((_a, i) => !skipIndices.has(i)); - -// --- ANSI colors --- -const useColor = !noColor; -const RED = useColor ? '\x1b[0;31m' : ''; -const GREEN = useColor ? '\x1b[0;32m' : ''; -const YELLOW = useColor ? '\x1b[0;33m' : ''; -const BOLD = useColor ? '\x1b[1m' : ''; -const DIM = useColor ? '\x1b[2m' : ''; -const RESET = useColor ? '\x1b[0m' : ''; - -// --- Fixtures --- -const DEFAULT_FIXTURES_DIR = path.join( - REPO_ROOT, - 'compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler', -); - -const fixturesPath = positional[0] - ? path.resolve(positional[0]) - : DEFAULT_FIXTURES_DIR; - -function discoverFixtures(rootPath: string): string[] { - const stat = fs.statSync(rootPath); - if (stat.isFile()) { - return [rootPath]; - } - - const results: string[] = []; - function walk(dir: string): void { - for (const entry of fs.readdirSync(dir, {withFileTypes: true})) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - walk(fullPath); - } else if ( - /\.(js|jsx|ts|tsx)$/.test(entry.name) && - !entry.name.endsWith('.expect.md') - ) { - results.push(fullPath); - } - } - } - walk(rootPath); - results.sort(); - return results; -} - -// --- Build --- -console.log('Building Rust native module...'); -try { - execSync('~/.cargo/bin/cargo build -p react_compiler_napi', { - cwd: path.join(REPO_ROOT, 'compiler/crates'), - stdio: ['inherit', 'pipe', 'pipe'], - shell: true, - }); -} catch (e: any) { - // Show stderr on build failure (includes errors + warnings) - if (e.stderr) { - process.stderr.write(e.stderr); - } - console.error(`${RED}ERROR: Failed to build Rust crates.${RESET}`); - process.exit(1); -} - -// Copy the built dylib as index.node -const NATIVE_DIR = path.join( - REPO_ROOT, - 'compiler/packages/babel-plugin-react-compiler-rust/native', -); -const NATIVE_NODE_PATH = path.join(NATIVE_DIR, 'index.node'); -const TARGET_DIR = path.join(REPO_ROOT, 'compiler/target/debug'); -const dylib = fs.existsSync( - path.join(TARGET_DIR, 'libreact_compiler_napi.dylib'), -) - ? path.join(TARGET_DIR, 'libreact_compiler_napi.dylib') - : path.join(TARGET_DIR, 'libreact_compiler_napi.so'); - -if (!fs.existsSync(dylib)) { - console.error( - `${RED}ERROR: Could not find built native module in ${TARGET_DIR}${RESET}`, - ); - process.exit(1); -} -fs.copyFileSync(dylib, NATIVE_NODE_PATH); - -// --- Load plugins --- -const tsPlugin = require('../packages/babel-plugin-react-compiler/src').default; -const rustPlugin = - require('../packages/babel-plugin-react-compiler-rust/src').default; - -// --- Normalize code for comparison --- -// Reparse with Babel and regenerate with compact:true to erase all -// whitespace/formatting differences, then Prettier for readable output. -async function formatCode(code: string, isFlow: boolean): Promise<string> { - try { - const parserPlugins: string[] = isFlow - ? ['flow', 'jsx'] - : ['typescript', 'jsx']; - const ast = babel.parseSync(code, { - sourceType: 'module', - parserOpts: {plugins: parserPlugins}, - configFile: false, - babelrc: false, - }); - if (!ast) return code; - const compact = generate(ast, {compact: true}).code; - return await prettier.format(compact, { - semi: true, - parser: isFlow ? 'flow' : 'babel-ts', - }); - } catch { - return code; - } -} - -// --- Compile via Babel plugin --- -type CompileResult = { - code: string | null; - error: string | null; - events: Array<Record<string, unknown>>; -}; - -function compileBabel( - plugin: any, - fixturePath: string, - source: string, - firstLine: string, -): CompileResult { - const isFlow = firstLine.includes('@flow'); - const isScript = firstLine.includes('@script'); - const parserPlugins: string[] = isFlow - ? ['flow', 'jsx'] - : ['typescript', 'jsx']; - - const pragmaOpts = parseConfigPragmaForTests(firstLine, { - compilationMode: 'all', - }); - - const events: Array<Record<string, unknown>> = []; - const pluginOptions = { - ...pragmaOpts, - compilationMode: 'all' as const, - panicThreshold: 'all_errors' as const, - logger: { - logEvent(_filename: string | null, event: Record<string, unknown>): void { - events.push(event); - }, - debugLogIRs(): void {}, - }, - }; - - try { - const result = babel.transformSync(source, { - filename: fixturePath, - sourceType: isScript ? 'script' : 'module', - parserOpts: {plugins: parserPlugins}, - plugins: [[plugin, pluginOptions]], - configFile: false, - babelrc: false, - }); - return {code: result?.code ?? null, error: null, events}; - } catch (e) { - return { - code: null, - error: e instanceof Error ? e.message : String(e), - events, - }; - } -} - -// --- Event normalization --- -// Strip identifierName (Babel-specific SourceLocation property), sort -// keys for stable comparison, then JSON.stringify. Both the TS plugin and -// the Rust NAPI bridge emit 0-based columns/indices, so no positional -// adjustment is needed. -const STRIP_KEYS = new Set(['identifierName', 'fnLoc']); - -function sortAndStrip(obj: unknown): unknown { - if (obj === null || typeof obj !== 'object') return obj; - if (Array.isArray(obj)) return obj.map(sortAndStrip); - const sorted: Record<string, unknown> = {}; - for (const key of Object.keys(obj as Record<string, unknown>).sort()) { - if (STRIP_KEYS.has(key)) continue; - sorted[key] = sortAndStrip((obj as Record<string, unknown>)[key]); - } - return sorted; -} - -function stripPipelineErrorStack( - events: Array<Record<string, unknown>>, -): Array<Record<string, unknown>> { - return events.map(event => { - if (event.kind !== 'PipelineError') return event; - const data = event.data; - if (typeof data !== 'string') return event; - // Strip JS stack trace: keep only the message (before first "\n at ") - const idx = data.indexOf('\n at '); - return {...event, data: idx >= 0 ? data.substring(0, idx) : data}; - }); -} - -function normalizeEvents(events: Array<Record<string, unknown>>): string { - return JSON.stringify(sortAndStrip(stripPipelineErrorStack(events)), null, 2); -} - -// --- Simple unified diff --- -function unifiedDiff( - expected: string, - actual: string, - leftLabel: string, - rightLabel: string, -): string { - const expectedLines = expected.split('\n'); - const actualLines = actual.split('\n'); - const lines: string[] = []; - lines.push(`${RED}--- ${leftLabel}${RESET}`); - lines.push(`${GREEN}+++ ${rightLabel}${RESET}`); - - const maxLen = Math.max(expectedLines.length, actualLines.length); - let contextStart = -1; - for (let i = 0; i < maxLen; i++) { - const eLine = i < expectedLines.length ? expectedLines[i] : undefined; - const aLine = i < actualLines.length ? actualLines[i] : undefined; - if (eLine === aLine) continue; - if (contextStart !== i) { - lines.push(`${YELLOW}@@ line ${i + 1} @@${RESET}`); - } - contextStart = i + 1; - if (eLine !== undefined && aLine !== undefined) { - lines.push(`${RED}-${eLine}${RESET}`); - lines.push(`${GREEN}+${aLine}${RESET}`); - } else if (eLine !== undefined) { - lines.push(`${RED}-${eLine}${RESET}`); - } else if (aLine !== undefined) { - lines.push(`${GREEN}+${aLine}${RESET}`); - } - } - return lines.join('\n'); -} - -// --- Main --- -type Variant = 'babel'; -const ALL_VARIANTS: Variant[] = ['babel']; -const variants: Variant[] = variantArg ? [variantArg] : ALL_VARIANTS; - -const fixtures = discoverFixtures(fixturesPath); -if (fixtures.length === 0) { - console.error('No fixtures found at', fixturesPath); - process.exit(1); -} - -interface VariantStats { - passed: number; - failed: number; - codePassed: number; - codeFailed: number; - eventsPassed: number; - eventsFailed: number; - failures: Array<{fixture: string; detail: string}>; - failedFixtures: string[]; -} - -function makeStats(): VariantStats { - return { - passed: 0, - failed: 0, - codePassed: 0, - codeFailed: 0, - eventsPassed: 0, - eventsFailed: 0, - failures: [], - failedFixtures: [], - }; -} - -// --- Progress helper --- -function writeProgress(msg: string): void { - if (process.stderr.isTTY) { - process.stderr.write(`\r\x1b[K${msg}`); - } -} - -function clearProgress(): void { - if (process.stderr.isTTY) { - process.stderr.write('\r\x1b[K'); - } -} - -// --- Pre-compute TS baselines (shared across variants) --- -interface FixtureInfo { - fixturePath: string; - relPath: string; - source: string; - firstLine: string; - isFlow: boolean; -} - -async function runVariant( - variant: Variant, - fixtureInfos: FixtureInfo[], - tsBaselines: Map<string, string>, - tsRawEvents: Map<string, Array<Record<string, unknown>>>, - s: VariantStats, -): Promise<void> { - for (let i = 0; i < fixtureInfos.length; i++) { - const {fixturePath, relPath, source, firstLine, isFlow} = fixtureInfos[i]; - const tsCode = tsBaselines.get(fixturePath)!; - const tsEvents = normalizeEvents(tsRawEvents.get(fixturePath)!); - - writeProgress( - ` ${variant}: ${i + 1}/${fixtureInfos.length} (${s.passed} passed, ${ - s.failed - } failed)`, - ); - - const variantResult = compileBabel( - rustPlugin, - fixturePath, - source, - firstLine, - ); - - const variantCode = await formatCode(variantResult.code ?? '', isFlow); - const variantEvents = normalizeEvents(variantResult.events); - - // When both TS and the variant error (produce empty/no output), count as pass. - const tsErrored = tsCode.trim() === ''; - const variantErrored = - variantCode.trim() === '' || variantResult.error != null; - - const codeMatch = tsCode === variantCode || (tsErrored && variantErrored); - const eventsMatch = tsEvents === variantEvents; - - // When code doesn't match due to TS error + variant passthrough, check - // if the variant output is just uncompiled source (no memoization). - let codePassthrough = false; - if (!codeMatch && tsErrored && variantCode.trim() !== '') { - const variantHasMemoization = - variantCode.includes('_c(') || variantCode.includes('useMemoCache'); - if (!variantHasMemoization) { - codePassthrough = true; - } - } - - const codeOk = codeMatch || codePassthrough; - if (codeOk) { - s.codePassed++; - } else { - s.codeFailed++; - } - if (eventsMatch) { - s.eventsPassed++; - } else { - s.eventsFailed++; - } - - if (codeOk && eventsMatch) { - s.passed++; - } else { - s.failed++; - s.failedFixtures.push(relPath); - if (limitArg === 0 || s.failures.length < limitArg) { - const details: string[] = []; - if (!codeOk) { - details.push(unifiedDiff(tsCode, variantCode, 'TypeScript', variant)); - } - if (!eventsMatch) { - details.push( - unifiedDiff( - tsEvents, - variantEvents, - 'TS events', - variant + ' events', - ), - ); - } - s.failures.push({ - fixture: relPath, - detail: details.join('\n\n'), - }); - } - } - } - clearProgress(); -} - -(async () => { - const stats = new Map<Variant, VariantStats>(); - for (const v of variants) { - stats.set(v, makeStats()); - } - - if (variantArg) { - console.log( - `Testing ${BOLD}${fixtures.length}${RESET} fixtures: TS baseline vs ${BOLD}${variantArg}${RESET}`, - ); - } else { - console.log( - `Testing ${BOLD}${fixtures.length}${RESET} fixtures across all variants`, - ); - } - console.log(''); - - // Pre-compute fixture info and TS baselines - const fixtureInfos: FixtureInfo[] = []; - const tsBaselines = new Map<string, string>(); - const tsRawEvents = new Map<string, Array<Record<string, unknown>>>(); - - console.log('Computing TS baselines...'); - for (let i = 0; i < fixtures.length; i++) { - const fixturePath = fixtures[i]; - const relPath = path.relative(REPO_ROOT, fixturePath); - const source = fs.readFileSync(fixturePath, 'utf8'); - const firstLine = source.substring(0, source.indexOf('\n')); - const isFlow = firstLine.includes('@flow'); - - writeProgress(` baseline: ${i + 1}/${fixtures.length}`); - - const tsResult = compileBabel(tsPlugin, fixturePath, source, firstLine); - const tsCode = await formatCode(tsResult.code ?? '', isFlow); - - fixtureInfos.push({fixturePath, relPath, source, firstLine, isFlow}); - tsBaselines.set(fixturePath, tsCode); - tsRawEvents.set(fixturePath, tsResult.events); - } - clearProgress(); - console.log(`Computed ${fixtures.length} baselines.`); - console.log(''); - - // Run each variant - for (const variant of variants) { - console.log(`Running ${BOLD}${variant}${RESET} variant...`); - await runVariant( - variant, - fixtureInfos, - tsBaselines, - tsRawEvents, - stats.get(variant)!, - ); - const s = stats.get(variant)!; - console.log(` ${s.passed} passed, ${s.failed} failed`); - } - console.log(''); - - // --- Output --- - if (variantArg) { - // Single variant mode: show diffs - const s = stats.get(variantArg)!; - const total = fixtures.length; - const summaryColor = s.failed === 0 ? GREEN : RED; - const summary = - `Code: ${s.codePassed}/${total} passed ` + - `Events: ${s.eventsPassed}/${total} passed ` + - `Total: ${s.passed}/${total} passed`; - console.log(`${summaryColor}${summary}${RESET}`); - console.log(''); - - for (const failure of s.failures) { - console.log(`${RED}FAIL${RESET} ${failure.fixture}`); - console.log(failure.detail); - console.log(''); - } - - if (s.failures.length < s.failed) { - console.log( - `${DIM} (showing first ${s.failures.length} of ${s.failed} failures)${RESET}`, - ); - } - - console.log('---'); - console.log(`${summaryColor}${summary}${RESET}`); - } else { - // Summary table mode - const total = fixtures.length; - - function fmtCell(passed: number, total: number): string { - const pct = ((passed / total) * 100).toFixed(1); - return `${passed}/${total} (${pct}%)`; - } - - // Table header - const colW = 22; - const hdr = - `${'Variant'.padEnd(10)} ` + - `${'Code'.padEnd(colW)} ` + - `${'Events'.padEnd(colW)} ` + - `${'Total'.padEnd(colW)}`; - console.log(`${BOLD}${hdr}${RESET}`); - - for (const variant of ALL_VARIANTS) { - const s = stats.get(variant)!; - const line = - `${variant.padEnd(10)} ` + - `${fmtCell(s.codePassed, total).padEnd(colW)} ` + - `${fmtCell(s.eventsPassed, total).padEnd(colW)} ` + - `${fmtCell(s.passed, total)}`; - const color = s.failed === 0 ? GREEN : s.passed === 0 ? RED : YELLOW; - console.log(`${color}${line}${RESET}`); - } - } - - // Exit with failure if any variant has failures - const anyFailed = [...stats.values()].some(s => s.failed > 0); - process.exit(anyFailed ? 1 : 0); -})(); diff --git a/compiler/scripts/test-internal-files.sh b/compiler/scripts/test-internal-files.sh deleted file mode 100755 index 032be8c1584b..000000000000 --- a/compiler/scripts/test-internal-files.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/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. - -# Compare TS and Rust React Compiler output on external production files. -# -# Usage: bash compiler/scripts/test-internal-files.sh <config-path> <source-root> [flags] -# Flags: --limit N, --pattern PAT, --project NAME, --dry-run, --no-color - -set -eo pipefail - -REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" - -exec npx tsx "$REPO_ROOT/compiler/scripts/test-internal-files.ts" "$@" diff --git a/compiler/scripts/test-internal-files.ts b/compiler/scripts/test-internal-files.ts deleted file mode 100644 index 74e7a5910759..000000000000 --- a/compiler/scripts/test-internal-files.ts +++ /dev/null @@ -1,724 +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. - */ - -/** - * Compare TS and Rust React Compiler output on external production files. - * - * Given a configuration module and a source root directory, compiles files - * using the exact plugin options from the configuration, then compares code - * output and error events between the TS and Rust compilers. Files with - * differences can be copied as test fixtures for further investigation. - * - * Usage: - * npx tsx compiler/scripts/test-internal-files.ts <config-path> <source-root> [flags] - * - * Arguments: - * <config-path> Path to the compiler configuration module (JS file that - * exports getForgetConfiguration and config) - * <source-root> Root directory from which the config's source prefixes - * are resolved - * - * Flags: - * --limit N Max files to process (default: 0 = all) - * --pattern PAT Filter file paths (substring match) - * --project NAME Filter by project name from config - * --dry-run Compare only, skip fixture creation - * --no-color Disable ANSI color codes - */ - -import * as babel from '@babel/core'; -import hermesParserPlugin from 'babel-plugin-syntax-hermes-parser'; -import {execSync} from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import prettier from 'prettier'; - -const REPO_ROOT = path.resolve(__dirname, '../..'); -const FIXTURE_DIR = path.join( - REPO_ROOT, - 'compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/internal', -); - -// --- Parse flags and positional args --- -const rawArgs = process.argv.slice(2); -const noColor = rawArgs.includes('--no-color') || !!process.env.NO_COLOR; -const dryRun = rawArgs.includes('--dry-run'); -const limitIdx = rawArgs.indexOf('--limit'); -const limitArg = limitIdx >= 0 ? parseInt(rawArgs[limitIdx + 1], 10) : 0; -const patternIdx = rawArgs.indexOf('--pattern'); -const patternArg = patternIdx >= 0 ? rawArgs[patternIdx + 1] : null; -const projectIdx = rawArgs.indexOf('--project'); -const projectArg = projectIdx >= 0 ? rawArgs[projectIdx + 1] : null; - -// Collect flag indices to exclude from positional args -const flagValueIndices = new Set<number>(); -if (limitIdx >= 0) flagValueIndices.add(limitIdx + 1); -if (patternIdx >= 0) flagValueIndices.add(patternIdx + 1); -if (projectIdx >= 0) flagValueIndices.add(projectIdx + 1); -const positional = rawArgs.filter( - (a, i) => !a.startsWith('--') && !flagValueIndices.has(i), -); - -if (positional.length < 2) { - console.error( - 'Usage: npx tsx compiler/scripts/test-internal-files.ts <config-path> <source-root> [flags]', - ); - console.error(''); - console.error('Arguments:'); - console.error(' <config-path> Path to the compiler configuration module'); - console.error( - ' <source-root> Root directory for resolving config source prefixes', - ); - console.error(''); - console.error('Flags:'); - console.error(' --limit N Max files to process'); - console.error(' --pattern PAT Filter file paths (substring match)'); - console.error(' --project NAME Filter by project name from config'); - console.error(' --dry-run Compare only, skip fixture creation'); - console.error(' --no-color Disable ANSI color codes'); - process.exit(1); -} - -const configPath = path.resolve(positional[0]); -const sourceRoot = path.resolve(positional[1]); - -// --- ANSI colors --- -const RED = noColor ? '' : '\x1b[0;31m'; -const GREEN = noColor ? '' : '\x1b[0;32m'; -const YELLOW = noColor ? '' : '\x1b[0;33m'; -const BOLD = noColor ? '' : '\x1b[1m'; -const DIM = noColor ? '' : '\x1b[2m'; -const RESET = noColor ? '' : '\x1b[0m'; - -// --- Load config --- -if (!fs.existsSync(configPath)) { - console.error(`${RED}ERROR: Could not find config at ${configPath}${RESET}`); - process.exit(1); -} -if (!fs.existsSync(sourceRoot) || !fs.statSync(sourceRoot).isDirectory()) { - console.error( - `${RED}ERROR: Source root is not a valid directory: ${sourceRoot}${RESET}`, - ); - process.exit(1); -} -const forgetConfig = require(configPath); -const {getForgetConfiguration, config: rawConfig} = forgetConfig; - -// --- Build native module --- -const NATIVE_DIR = path.join( - REPO_ROOT, - 'compiler/packages/babel-plugin-react-compiler-rust/native', -); -const NATIVE_NODE_PATH = path.join(NATIVE_DIR, 'index.node'); - -console.log('Building Rust native module...'); -try { - execSync('~/.cargo/bin/cargo build -p react_compiler_napi', { - cwd: path.join(REPO_ROOT, 'compiler/crates'), - stdio: 'inherit', - shell: true, - }); -} catch { - console.error(`${RED}ERROR: Failed to build Rust native module.${RESET}`); - process.exit(1); -} - -const TARGET_DIR = path.join(REPO_ROOT, 'compiler/target/debug'); -const dylib = fs.existsSync( - path.join(TARGET_DIR, 'libreact_compiler_napi.dylib'), -) - ? path.join(TARGET_DIR, 'libreact_compiler_napi.dylib') - : path.join(TARGET_DIR, 'libreact_compiler_napi.so'); - -if (!fs.existsSync(dylib)) { - console.error( - `${RED}ERROR: Could not find built native module in ${TARGET_DIR}${RESET}`, - ); - process.exit(1); -} -fs.copyFileSync(dylib, NATIVE_NODE_PATH); - -// --- Load plugins --- -const tsPlugin = require('../packages/babel-plugin-react-compiler/src').default; -const rustPlugin = - require('../packages/babel-plugin-react-compiler-rust/src').default; - -// --- Types --- -interface LoggerEvent { - kind: string; - fnName?: string | null; - fnLoc?: unknown; - reason?: string; - detail?: { - reason?: string; - severity?: string; - category?: string; - description?: string; - }; - data?: string; -} - -interface CompileResult { - code: string | null; - events: LoggerEvent[]; - error: string | null; -} - -interface FileEntry { - filePath: string; - projectName: string; - projectConfig: Record<string, unknown>; -} - -// --- File discovery --- -// Additional directory exclusions beyond what the config handles -const EXTRA_EXCLUDES = [ - '__server_snapshot_tests__', - '__e2e_tests__', - '__perf_tests__', - '__integration_tests__', -]; - -function walkDir(dir: string, callback: (filePath: string) => void): void { - let entries: fs.Dirent[]; - try { - entries = fs.readdirSync(dir, {withFileTypes: true}); - } catch { - return; - } - for (const entry of entries) { - if (EXTRA_EXCLUDES.includes(entry.name)) continue; - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - walkDir(fullPath, callback); - } else if (/\.(js|jsx)$/.test(entry.name)) { - callback(fullPath); - } - } -} - -function discoverFiles(): FileEntry[] { - console.log('Discovering files...'); - const results: FileEntry[] = []; - const seen = new Set<string>(); - - for (const [prefix, projectName] of Object.entries( - rawConfig.sources as Record<string, string>, - )) { - if (projectArg && projectName !== projectArg) continue; - - // Resolve prefix to absolute path relative to source root - const absPrefix = path.join(sourceRoot, prefix); - if (!fs.existsSync(absPrefix)) continue; - - const stat = fs.statSync(absPrefix); - if (!stat.isDirectory()) continue; - - walkDir(absPrefix, filePath => { - if (seen.has(filePath)) return; - seen.add(filePath); - - if (patternArg && !filePath.includes(patternArg)) return; - - const config = getForgetConfiguration(filePath); - if (config == null || !config.enable) return; - - results.push({filePath, projectName, projectConfig: config}); - }); - - if (limitArg > 0 && results.length >= limitArg) break; - } - - if (limitArg > 0) { - return results.slice(0, limitArg); - } - return results; -} - -// --- Build plugin options from production config --- -function makePluginOptions( - projectConfig: Record<string, unknown>, - logger: {logEvent: (filename: string | null, event: LoggerEvent) => void}, -): Record<string, unknown> { - return { - compilationMode: projectConfig.compilationMode, - panicThreshold: 'none', - environment: projectConfig.environment, - target: - projectConfig.compilerVersion === 'experimental' - ? projectConfig.target - : projectConfig.target, - gating: null, - flowSuppressions: projectConfig.flowSuppressions, - enableReanimatedCheck: false, - logger, - sources: null, - }; -} - -// --- Compile a file --- -function compileFile( - mode: 'ts' | 'rust', - filePath: string, - pluginOptions: Record<string, unknown>, -): CompileResult { - const source = fs.readFileSync(filePath, 'utf8'); - const events: LoggerEvent[] = []; - - const logger = { - logEvent(_filename: string | null, event: LoggerEvent): void { - events.push(event); - }, - }; - - const opts = {...pluginOptions, logger}; - const plugin = mode === 'ts' ? tsPlugin : rustPlugin; - - try { - const result = babel.transformSync(source, { - filename: filePath, - sourceType: 'module', - plugins: [hermesParserPlugin, [plugin, opts]], - configFile: false, - babelrc: false, - }); - return {code: result?.code ?? null, events, error: null}; - } catch (e) { - const msg = e instanceof Error ? e.message : String(e); - return {code: null, events, error: msg}; - } -} - -// --- Format events for comparison --- -function formatEvents(events: LoggerEvent[]): string { - return events - .filter(e => - [ - 'CompileError', - 'CompileSkip', - 'CompileSuccess', - 'PipelineError', - ].includes(e.kind), - ) - .map(e => { - if (e.kind === 'CompileSuccess') { - return `[CompileSuccess] ${e.fnName ?? '(anonymous)'}`; - } - if (e.kind === 'CompileError') { - const d = e.detail; - return `[CompileError] ${d?.reason ?? '(no reason)'} (${d?.severity ?? ''}, ${d?.category ?? ''})`; - } - if (e.kind === 'CompileSkip') { - return `[CompileSkip] ${e.reason ?? '(no reason)'}`; - } - return `[${e.kind}] ${e.data ?? ''}`; - }) - .join('\n'); -} - -// --- Simple diff --- -function unifiedDiff(expected: string, actual: string): string { - const expectedLines = expected.split('\n'); - const actualLines = actual.split('\n'); - const lines: string[] = []; - lines.push(`${RED}--- TS${RESET}`); - lines.push(`${GREEN}+++ Rust${RESET}`); - - const maxLen = Math.max(expectedLines.length, actualLines.length); - let contextStart = -1; - for (let i = 0; i < maxLen; i++) { - const eLine = i < expectedLines.length ? expectedLines[i] : undefined; - const aLine = i < actualLines.length ? actualLines[i] : undefined; - if (eLine === aLine) continue; - if (contextStart !== i) { - lines.push(`${YELLOW}@@ line ${i + 1} @@${RESET}`); - } - contextStart = i + 1; - if (eLine !== undefined) lines.push(`${RED}-${eLine}${RESET}`); - if (aLine !== undefined) lines.push(`${GREEN}+${aLine}${RESET}`); - } - return lines.join('\n'); -} - -// --- Format code with prettier --- -async function formatCode(code: string): Promise<string> { - return prettier.format(code, {semi: true, parser: 'flow'}); -} - -// --- Generate pragma line for fixture --- -function generatePragma( - projectConfig: Record<string, unknown>, - isFlow: boolean, -): string { - const parts: string[] = []; - - // The snap tool checks the first line for @flow to determine parser - if (isFlow) { - parts.push('@flow'); - } - - // compilationMode - const mode = projectConfig.compilationMode as string; - if (mode && mode !== 'all') { - parts.push(`@compilationMode:"${mode}"`); - } - - // Environment options - const env = (projectConfig.environment ?? {}) as Record<string, unknown>; - - if (env.enableAssumeHooksFollowRulesOfReact === true) { - parts.push('@enableAssumeHooksFollowRulesOfReact'); - } - if (env.enableTransitivelyFreezeFunctionExpressions === true) { - parts.push('@enableTransitivelyFreezeFunctionExpressions'); - } - if (env.validatePreserveExistingMemoizationGuarantees === true) { - parts.push('@validatePreserveExistingMemoizationGuarantees'); - } - if (env.enableFunctionOutlining === false) { - parts.push('@enableFunctionOutlining:false'); - } - if ( - Array.isArray(env.validateNoCapitalizedCalls) && - env.validateNoCapitalizedCalls.length > 0 - ) { - parts.push( - `@validateNoCapitalizedCalls:${JSON.stringify(env.validateNoCapitalizedCalls)}`, - ); - } - - // target for experimental projects - if (projectConfig.compilerVersion === 'experimental') { - parts.push('@target:"donotuse_meta_internal"'); - } - - return parts.length > 0 ? '// ' + parts.join(' ') : ''; -} - -// --- Create fixture file and baseline .expect.md --- -async function createFixture( - filePath: string, - projectName: string, - projectConfig: Record<string, unknown>, - tsCode: string | null, - tsError: string | null, -): Promise<string> { - fs.mkdirSync(FIXTURE_DIR, {recursive: true}); - - const basename = path.basename(filePath); - const ext = path.extname(basename); - const stem = basename.slice(0, -ext.length); - let fixtureName = `${projectName}--${basename}`; - - // Deduplicate - let counter = 0; - while (fs.existsSync(path.join(FIXTURE_DIR, fixtureName))) { - counter++; - fixtureName = `${projectName}--${stem}-${counter}${ext}`; - } - - const source = fs.readFileSync(filePath, 'utf8'); - const headerBlock = source.substring(0, source.indexOf('*/') + 2 || 200); - const isFlow = headerBlock.includes('@flow'); - const pragma = generatePragma(projectConfig, isFlow); - - const content = pragma ? `${pragma}\n${source}` : source; - - const fixturePath = path.join(FIXTURE_DIR, fixtureName); - fs.writeFileSync(fixturePath, content, 'utf8'); - - // Generate .expect.md baseline from TS compiler output. - // This bypasses yarn snap -u (which fails due to sprout evaluator - // not being able to resolve internal module imports). - // Format matches snap's writeOutputToString in reporter.ts. - let formattedCode: string | null = null; - if (tsCode != null) { - try { - formattedCode = await formatCode(tsCode); - } catch { - formattedCode = tsCode; - } - } - - let expectMd = `\n## Input\n\n\`\`\`javascript\n${content}\n\`\`\`\n`; - if (formattedCode != null) { - expectMd += `\n## Code\n\n\`\`\`javascript\n${formattedCode}\`\`\`\n`; - } else { - expectMd += '\n'; - } - if (tsError != null) { - const cleanError = tsError.replace(/^\/.*?:\s/, ''); - expectMd += `\n## Error\n\n\`\`\`\n${cleanError}\n\`\`\`\n \n`; - } - expectMd += ` `; - - const expectPath = fixturePath.replace(/\.[^.]+$/, '.expect.md'); - fs.writeFileSync(expectPath, expectMd, 'utf8'); - - return fixturePath; -} - -// --- Main --- -(async () => { - const files = discoverFiles(); - if (files.length === 0) { - console.error('No files found matching filters.'); - process.exit(1); - } - - console.log( - `\nComparing ${BOLD}${files.length}${RESET} files (TS vs Rust)...\n`, - ); - - // Crash recovery: maintain a skip list of files that segfault the native module. - // On crash, the current file is appended to the skip list. - // On subsequent runs, those files are skipped automatically. - const crashLogPath = path.join( - REPO_ROOT, - 'compiler/.test-internal-files-current', - ); - const skipListPath = path.join( - REPO_ROOT, - 'compiler/.test-internal-skip-list', - ); - const skipSet = new Set<string>(); - try { - const skipData = fs.readFileSync(skipListPath, 'utf8'); - for (const line of skipData.split('\n')) { - const trimmed = line.trim(); - if (trimmed) skipSet.add(trimmed); - } - if (skipSet.size > 0) { - console.log( - `${DIM}Skipping ${skipSet.size} previously-crashing files${RESET}`, - ); - } - } catch {} - - process.on('exit', code => { - if (code === 139 || code === 134 || code === 11) { - // SIGSEGV or SIGABRT — append the culprit to skip list - try { - const crashFile = fs.readFileSync(crashLogPath, 'utf8').trim(); - fs.appendFileSync(skipListPath, crashFile + '\n', 'utf8'); - console.error( - `\n${RED}CRASH (signal ${code}) on file: ${crashFile}${RESET}`, - ); - console.error( - `${YELLOW}File added to skip list. Re-run to continue.${RESET}`, - ); - } catch {} - } - try { - fs.unlinkSync(crashLogPath); - } catch {} - }); - - let processed = 0; - let codeDiffs = 0; - let eventDiffs = 0; - let bothErrored = 0; - let crashes = 0; - const diffFiles: Array<{ - filePath: string; - projectName: string; - projectConfig: Record<string, unknown>; - codeDiff: string | null; - eventDiff: string | null; - tsCode: string | null; - tsError: string | null; - }> = []; - const createdFixtures: string[] = []; - - for (const {filePath, projectName, projectConfig} of files) { - if (skipSet.has(filePath)) continue; - processed++; - - // Write current file for crash identification - fs.writeFileSync(crashLogPath, filePath, 'utf8'); - - // Progress - const relPath = filePath.replace(sourceRoot + '/', ''); - process.stdout.write( - `\r${DIM}[${processed}/${files.length}] ${codeDiffs} code diffs, ${eventDiffs} event diffs, ${crashes} crashes${RESET} `, - ); - - const pluginOpts = makePluginOptions(projectConfig, { - logEvent() {}, - }); - - let tsResult: CompileResult; - let rustResult: CompileResult; - try { - tsResult = compileFile('ts', filePath, pluginOpts); - } catch (e) { - // Unexpected crash in TS compiler - tsResult = { - code: null, - events: [], - error: `TS crash: ${e instanceof Error ? e.message : String(e)}`, - }; - } - try { - rustResult = compileFile('rust', filePath, pluginOpts); - } catch (e) { - // Unexpected crash in Rust compiler - rustResult = { - code: null, - events: [], - error: `Rust crash: ${e instanceof Error ? e.message : String(e)}`, - }; - } - - // Compare code - let hasCodeDiff = false; - let codeDiffDetail: string | null = null; - try { - const tsCode = await formatCode(tsResult.code ?? ''); - const rustCode = await formatCode(rustResult.code ?? ''); - if (tsCode !== rustCode) { - hasCodeDiff = true; - codeDiffDetail = unifiedDiff(tsCode, rustCode); - } - } catch { - // Prettier failed — compare raw - if ((tsResult.code ?? '') !== (rustResult.code ?? '')) { - hasCodeDiff = true; - codeDiffDetail = unifiedDiff( - tsResult.code ?? '', - rustResult.code ?? '', - ); - } - } - - // Compare error events - const tsEvents = formatEvents(tsResult.events); - const rustEvents = formatEvents(rustResult.events); - let hasEventDiff = false; - let eventDiffDetail: string | null = null; - if (tsEvents !== rustEvents) { - hasEventDiff = true; - eventDiffDetail = unifiedDiff(tsEvents, rustEvents); - } - - // Track errors - if (tsResult.error && rustResult.error) { - bothErrored++; - } - - if (hasCodeDiff || hasEventDiff) { - if (hasCodeDiff) codeDiffs++; - if (hasEventDiff) eventDiffs++; - diffFiles.push({ - filePath, - projectName, - projectConfig, - codeDiff: codeDiffDetail, - eventDiff: eventDiffDetail, - tsCode: tsResult.code, - tsError: tsResult.error, - }); - } - } - - // Clear progress line - process.stdout.write('\r' + ' '.repeat(80) + '\r'); - - // --- Report diffs --- - if (diffFiles.length > 0) { - console.log(`\n${BOLD}--- Differences ---${RESET}\n`); - const showLimit = 50; - const toShow = diffFiles.slice(0, showLimit); - - for (const entry of toShow) { - const relPath = entry.filePath.replace(sourceRoot + '/', ''); - console.log( - `${RED}DIFF${RESET} ${relPath} ${DIM}(${entry.projectName})${RESET}`, - ); - if (entry.codeDiff) { - console.log(entry.codeDiff); - } - if (entry.eventDiff) { - console.log(`${YELLOW}Event diff:${RESET}`); - console.log(entry.eventDiff); - } - console.log(''); - } - if (diffFiles.length > showLimit) { - console.log( - `${DIM}(showing first ${showLimit} of ${diffFiles.length} diffs)${RESET}`, - ); - } - } - - // --- Create fixtures --- - if (!dryRun && diffFiles.length > 0) { - console.log(`\n${BOLD}--- Creating fixtures ---${RESET}\n`); - - // Only create fixtures for files with code diffs (not event-only diffs) - const codeDiffFiles = diffFiles.filter(f => f.codeDiff != null); - - for (const entry of codeDiffFiles) { - const fixturePath = await createFixture( - entry.filePath, - entry.projectName, - entry.projectConfig, - entry.tsCode, - entry.tsError, - ); - createdFixtures.push(fixturePath); - const fixtureName = path.basename(fixturePath); - console.log(` ${GREEN}+${RESET} ${fixtureName}`); - } - - if (createdFixtures.length > 0) { - console.log( - `\nCreated ${BOLD}${createdFixtures.length}${RESET} fixtures in internal/`, - ); - - // --- Verification --- - console.log(`\n${BOLD}--- Verifying fixtures ---${RESET}\n`); - - // Verify with Rust compiler (baselines already written as .expect.md) - console.log('\nVerifying with yarn snap --rust ...'); - try { - execSync("yarn snap --rust -p 'internal/*'", { - cwd: path.join(REPO_ROOT, 'compiler'), - stdio: 'inherit', - }); - // If snap --rust passes, all fixtures matched (unexpected) - console.log( - `${YELLOW}WARNING: yarn snap --rust passed — fixtures may not reproduce differences.${RESET}`, - ); - } catch { - // Expected: snap --rust should fail for differing fixtures - console.log( - `${GREEN}yarn snap --rust failed as expected — fixtures reproduce differences.${RESET}`, - ); - } - - console.log( - `\n${RED}${BOLD}DO NOT COMMIT${RESET}${RED} the fixture files in internal/${RESET}`, - ); - } - } - - // --- Summary --- - console.log(`\n${BOLD}--- Summary ---${RESET}`); - console.log(`Processed: ${processed}`); - console.log(`Code diffs: ${codeDiffs}`); - console.log( - `Event-only diffs: ${eventDiffs - codeDiffs > 0 ? eventDiffs - codeDiffs : 0}`, - ); - console.log(`Both errored: ${bothErrored}`); - if (crashes > 0) console.log(`Crashes (skipped): ${crashes}`); - if (createdFixtures.length > 0) { - console.log(`Fixtures created: ${createdFixtures.length}`); - } - - process.exit(codeDiffs > 0 ? 1 : 0); -})(); diff --git a/compiler/scripts/test-rust-port.sh b/compiler/scripts/test-rust-port.sh deleted file mode 100755 index 710479ad9100..000000000000 --- a/compiler/scripts/test-rust-port.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/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. - -# Thin wrapper that delegates to the TS test script. -# The TS script handles building the native module itself. -# -# Usage: bash compiler/scripts/test-rust-port.sh [<pass>] [<fixtures-path>] [flags] -# Flags: --no-color, --json, --failures, --limit N, --mode MODE - -set -eo pipefail - -REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" - -exec npx tsx "$REPO_ROOT/compiler/scripts/test-rust-port.ts" "$@" diff --git a/compiler/scripts/test-rust-port.ts b/compiler/scripts/test-rust-port.ts deleted file mode 100644 index 096eb8aae877..000000000000 --- a/compiler/scripts/test-rust-port.ts +++ /dev/null @@ -1,830 +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. - */ - -/** - * Unified Babel plugin-based test script for comparing TS and Rust compilers. - * - * Runs both compilers through their real Babel plugins, captures debug log - * entries via the logger API, and diffs output for a specific pass. - * - * Usage: npx tsx compiler/scripts/test-rust-port.ts [<pass>] [<fixtures-path>] [flags] - * - * Flags: - * --no-color Disable ANSI color codes (also respects NO_COLOR env var) - * --json Output a single JSON object to stdout (machine-readable) - * --failures Print only failing fixture paths, one per line - * --limit N Max failures to display with diffs (default: 50, 0 = all) - * --mode MODE Compilation mode (default: use implementation default) - */ - -import * as babel from '@babel/core'; -import hermesParserPlugin from 'babel-plugin-syntax-hermes-parser'; -import {execSync} from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import prettier from 'prettier'; - -import {parseConfigPragmaForTests} from '../packages/babel-plugin-react-compiler/src/Utils/TestUtils'; -import {printDebugHIR} from '../packages/babel-plugin-react-compiler/src/HIR/DebugPrintHIR'; -import {printDebugReactiveFunction} from '../packages/babel-plugin-react-compiler/src/HIR/DebugPrintReactiveFunction'; -import type {CompilerPipelineValue} from '../packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline'; - -const REPO_ROOT = path.resolve(__dirname, '../..'); - -// --- Parse flags --- -const rawArgs = process.argv.slice(2); -const noColor = rawArgs.includes('--no-color') || !!process.env.NO_COLOR; -const jsonMode = rawArgs.includes('--json'); -const failuresMode = rawArgs.includes('--failures'); -const limitIdx = rawArgs.indexOf('--limit'); -const limitArg = limitIdx >= 0 ? parseInt(rawArgs[limitIdx + 1], 10) : 50; -const modeIdx = rawArgs.indexOf('--mode'); -const compilationModeArg: string | null = - modeIdx >= 0 ? rawArgs[modeIdx + 1] : null; - -// Extract positional args (strip flags and flag values) -const flagValueIndices = new Set<number>(); -if (limitIdx >= 0) flagValueIndices.add(limitIdx + 1); -if (modeIdx >= 0) flagValueIndices.add(modeIdx + 1); -const positional = rawArgs.filter( - (a, i) => !a.startsWith('--') && !flagValueIndices.has(i), -); - -// --- ANSI colors --- -const useColor = !noColor && !jsonMode && !failuresMode; -const RED = useColor ? '\x1b[0;31m' : ''; -const GREEN = useColor ? '\x1b[0;32m' : ''; -const YELLOW = useColor ? '\x1b[0;33m' : ''; -const BOLD = useColor ? '\x1b[1m' : ''; -const DIM = useColor ? '\x1b[2m' : ''; -const RESET = useColor ? '\x1b[0m' : ''; - -// --- Ordered pass list (derived from pipeline.rs DebugLogEntry calls) --- -function derivePassOrder(): string[] { - const pipelinePath = path.join( - REPO_ROOT, - 'compiler/crates/react_compiler/src/entrypoint/pipeline.rs', - ); - const content = fs.readFileSync(pipelinePath, 'utf8'); - const matches = [...content.matchAll(/DebugLogEntry::new\("([^"]+)"/g)]; - return matches.map(m => m[1]); -} - -const PASS_ORDER = derivePassOrder(); - -// --- Detect last ported pass from pipeline.rs --- -function detectLastPortedPass(): string { - if (PASS_ORDER.length === 0) { - throw new Error('No ported passes found in pipeline.rs'); - } - return PASS_ORDER[PASS_ORDER.length - 1]; -} - -// --- Parse args --- -const [passArgRaw, fixturesPathArg] = positional; - -let passArg: string; -if (passArgRaw) { - passArg = passArgRaw; -} else { - passArg = detectLastPortedPass(); - if (!jsonMode && !failuresMode) { - console.log( - `No pass argument given, auto-detected last ported pass: ${BOLD}${passArg}${RESET}`, - ); - } -} -const DEFAULT_FIXTURES_DIR = path.join( - REPO_ROOT, - 'compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler', -); - -const fixturesPath = fixturesPathArg - ? path.resolve(fixturesPathArg) - : DEFAULT_FIXTURES_DIR; - -// --- Build native module --- -const NATIVE_DIR = path.join( - REPO_ROOT, - 'compiler/packages/babel-plugin-react-compiler-rust/native', -); -const NATIVE_NODE_PATH = path.join(NATIVE_DIR, 'index.node'); - -if (!jsonMode && !failuresMode) { - console.log('Building Rust native module...'); -} -try { - execSync('~/.cargo/bin/cargo build -p react_compiler_napi', { - cwd: path.join(REPO_ROOT, 'compiler/crates'), - stdio: - jsonMode || failuresMode ? ['inherit', 'pipe', 'inherit'] : 'inherit', - shell: true, - }); -} catch { - console.error(`${RED}ERROR: Failed to build Rust native module.${RESET}`); - process.exit(1); -} - -// Copy the built dylib as index.node (Node requires .node extension for native addons) -const TARGET_DIR = path.join(REPO_ROOT, 'compiler/target/debug'); -const dylib = fs.existsSync( - path.join(TARGET_DIR, 'libreact_compiler_napi.dylib'), -) - ? path.join(TARGET_DIR, 'libreact_compiler_napi.dylib') - : path.join(TARGET_DIR, 'libreact_compiler_napi.so'); - -if (!fs.existsSync(dylib)) { - console.error( - `${RED}ERROR: Could not find built native module in ${TARGET_DIR}${RESET}`, - ); - process.exit(1); -} -fs.copyFileSync(dylib, NATIVE_NODE_PATH); - -// --- Load plugins --- -const tsPlugin = require('../packages/babel-plugin-react-compiler/src').default; -const rustPlugin = - require('../packages/babel-plugin-react-compiler-rust/src').default; - -// --- Types --- -interface LogEntry { - kind: 'entry'; - name: string; - value: string; -} - -interface LogEvent { - kind: 'event'; - eventKind: string; - fnName: string | null; - detail: string; -} - -type LogItem = LogEntry | LogEvent; - -interface CompileOutput { - log: LogItem[]; - code: string | null; - error: string | null; -} - -type CompileMode = 'ts' | 'rust'; - -// --- Discover fixtures --- -function discoverFixtures(rootPath: string): string[] { - const stat = fs.statSync(rootPath); - if (stat.isFile()) { - return [rootPath]; - } - - const results: string[] = []; - function walk(dir: string): void { - for (const entry of fs.readdirSync(dir, {withFileTypes: true})) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - walk(fullPath); - } else if ( - /\.(js|jsx|ts|tsx)$/.test(entry.name) && - !entry.name.endsWith('.expect.md') - ) { - results.push(fullPath); - } - } - } - walk(rootPath); - results.sort(); - return results; -} - -// --- Format a source location for comparison --- -function formatLoc(loc: unknown): string { - if (loc == null) return '(generated)'; - if (typeof loc === 'symbol') return '(generated)'; - const l = loc as Record<string, unknown>; - const start = l.start as Record<string, unknown> | undefined; - const end = l.end as Record<string, unknown> | undefined; - if (start && end) { - return `${start.line}:${start.column}-${end.line}:${end.column}`; - } - return String(loc); -} - -// --- Compile a fixture through a Babel plugin and capture debug entries --- -function compileFixture(mode: CompileMode, fixturePath: string): CompileOutput { - const source = fs.readFileSync(fixturePath, 'utf8'); - const firstLine = source.substring(0, source.indexOf('\n')); - - // Parse pragma config - const pragmaOpts = parseConfigPragmaForTests(firstLine, { - compilationMode: 'all', - }); - - // Capture debug entries and logger events in order, stopping after the target pass - const log: LogItem[] = []; - let reachedTarget = false; - - const logger = { - logEvent(_filename: string | null, event: Record<string, unknown>): void { - if (reachedTarget) return; - const kind = event.kind as string; - if ( - kind === 'CompileError' || - kind === 'CompileSkip' || - // Skip CompileUnexpectedThrow: this is a TS-only artifact logged when a pass - // throws instead of recording errors. The Rust port uses Result-based error - // propagation, so this event is never emitted and should be excluded from comparison. - // kind === 'CompileUnexpectedThrow' || - kind === 'PipelineError' - ) { - const fnName = (event.fnName as string | null) ?? null; - let detail: string; - if (kind === 'CompileError') { - const d = event.detail as Record<string, unknown> | undefined; - if (d) { - const lines = [ - `reason: ${d.reason ?? '(none)'}`, - `severity: ${d.severity ?? '(none)'}`, - `category: ${d.category ?? '(none)'}`, - ]; - if (d.description) { - lines.push(`description: ${d.description}`); - } - // CompilerDiagnostic stores details in this.options.details (no getter), - // while Rust JSON has details as a direct field. Check both paths. - const opts = (d as Record<string, unknown>).options as - | Record<string, unknown> - | undefined; - const details = (opts?.details ?? d.details) as - | Array<Record<string, unknown>> - | undefined; - if (details && details.length > 0) { - for (const item of details) { - if (item.kind === 'error') { - lines.push( - ` error: ${formatLoc(item.loc)}${item.message ? ': ' + item.message : ''}`, - ); - } else if (item.kind === 'hint') { - lines.push(` hint: ${item.message ?? ''}`); - } - } - } - // Legacy CompilerErrorDetail has loc directly - if (d.loc && !details) { - lines.push(`loc: ${formatLoc(d.loc)}`); - } - detail = lines.join('\n '); - } else { - detail = '(no detail)'; - } - } else if (kind === 'CompileSkip') { - detail = (event.reason as string) ?? '(no reason)'; - } else { - detail = (event.data as string) ?? '(no data)'; - } - log.push({kind: 'event', eventKind: kind, fnName, detail}); - } - }, - debugLogIRs(entry: CompilerPipelineValue): void { - if (reachedTarget) return; - if (entry.name === 'EnvironmentConfig') return; - if (entry.kind === 'hir') { - // TS pipeline emits HIR objects — convert to debug string - log.push({ - kind: 'entry', - name: entry.name, - value: printDebugHIR(entry.value), - }); - } else if (entry.kind === 'debug') { - // Rust pipeline (and TS EnvironmentConfig) emits pre-formatted strings - log.push({ - kind: 'entry', - name: entry.name, - value: entry.value, - }); - } else if (entry.kind === 'reactive') { - log.push({ - kind: 'entry', - name: entry.name, - value: printDebugReactiveFunction(entry.value), - }); - } else if (entry.kind === 'ast' && entry.name === passArg) { - throw new Error( - `TODO: test-rust-port does not yet support '${entry.kind}' log entries ` + - `(pass "${entry.name}"). Extend the debugLogIRs handler to support this kind.`, - ); - } - if (entry.name === passArg) { - reachedTarget = true; - } - }, - }; - - // Determine parser plugins — scan the leading comment block for pragmas - // since @flow is typically on line 3-4 inside a doc comment, not the first line. - const headerBlock = source.substring(0, source.indexOf('*/') + 2 || 200); - const isFlow = headerBlock.includes('@flow'); - const isScript = firstLine.includes('@script'); - - const plugin = mode === 'ts' ? tsPlugin : rustPlugin; - - const pluginOptions = { - ...pragmaOpts, - ...(compilationModeArg != null - ? {compilationMode: compilationModeArg} - : {}), - panicThreshold: 'all_errors' as const, - logger, - }; - - // For Flow files, use hermes-parser which supports component syntax. - // For TypeScript files, use @babel/parser with typescript+jsx plugins. - const babelPlugins: Array<babel.PluginItem> = isFlow - ? [hermesParserPlugin, [plugin, pluginOptions]] - : [[plugin, pluginOptions]]; - - let error: string | null = null; - let code: string | null = null; - try { - const result = babel.transformSync(source, { - filename: fixturePath, - sourceType: isScript ? 'script' : 'module', - ...(isFlow ? {} : {parserOpts: {plugins: ['typescript', 'jsx']}}), - plugins: babelPlugins, - configFile: false, - babelrc: false, - }); - code = result?.code ?? null; - } catch (e) { - error = e instanceof Error ? e.message : String(e); - } - - return {log, code, error}; -} - -// --- Format a single log item as comparable string --- -function formatLogItem(item: LogItem): string { - if (item.kind === 'entry') { - return `## ${item.name}\n${item.value}`; - } else { - return `[${item.eventKind}]${item.fnName ? ' ' + item.fnName : ''}: ${item.detail}`; - } -} - -// --- Format log items as comparable string --- -function formatLog(log: LogItem[]): string { - return log.map(formatLogItem).join('\n'); -} - -// --- Normalize opaque IDs --- -// Type IDs and Identifier IDs are opaque identifiers whose absolute values -// differ between TS and Rust due to differences in allocation order. -// We normalize by remapping each unique ID to a sequential index. -function normalizeIds(text: string): string { - // ID maps are reset at function boundaries (## HIR) because TS uses a global - // type counter while Rust creates a fresh Environment per function, so raw IDs - // from different functions may collide in Rust but never in TS. - let typeMap = new Map<string, number>(); - let nextTypeId = 0; - let idMap = new Map<string, number>(); - let nextIdId = 0; - let declMap = new Map<string, number>(); - let nextDeclId = 0; - let generatedMap = new Map<string, number>(); - let nextGeneratedId = 0; - let blockMap = new Map<string, number>(); - let nextBlockId = 0; - let isFirstHIR = true; - - // Process line-by-line so we can reset maps at function boundaries - const lines = text.split('\n'); - const result = lines.map(line => { - // Reset all maps when a new function's compilation starts (## HIR header). - // The first HIR entry doesn't need a reset since maps are already empty. - if (line === '## HIR') { - if (!isFirstHIR) { - typeMap = new Map(); - nextTypeId = 0; - idMap = new Map(); - nextIdId = 0; - declMap = new Map(); - nextDeclId = 0; - generatedMap = new Map(); - nextGeneratedId = 0; - blockMap = new Map(); - nextBlockId = 0; - } - isFirstHIR = false; - } - - return ( - line - // Normalize block IDs (bb0, bb1, ...) — these are auto-incrementing counters - // that may differ between TS and Rust due to different block allocation counts - // in earlier passes (lowering, IIFE inlining, etc.). - .replace(/\bbb(\d+)\b/g, (_match, num) => { - const key = `bb:${num}`; - if (!blockMap.has(key)) { - blockMap.set(key, nextBlockId++); - } - return `bb${blockMap.get(key)}`; - }) - // Normalize <generated_N> shape IDs — these are auto-incrementing counters - // that may differ between TS and Rust due to allocation ordering. - .replace(/<generated_(\d+)>/g, (_match, num) => { - const key = `generated:${num}`; - if (!generatedMap.has(key)) { - generatedMap.set(key, nextGeneratedId++); - } - return `<generated_${generatedMap.get(key)}>`; - }) - .replace(/Type\(\d+\)/g, match => { - if (!typeMap.has(match)) { - typeMap.set(match, nextTypeId++); - } - return `Type(${typeMap.get(match)})`; - }) - .replace(/((?:id|declarationId): )(\d+)/g, (_match, prefix, num) => { - if (prefix === 'id: ') { - const key = `id:${num}`; - if (!idMap.has(key)) { - idMap.set(key, nextIdId++); - } - return `${prefix}${idMap.get(key)}`; - } else { - const key = `decl:${num}`; - if (!declMap.has(key)) { - declMap.set(key, nextDeclId++); - } - return `${prefix}${declMap.get(key)}`; - } - }) - .replace(/Identifier\((\d+)\)/g, (_match, num) => { - const key = `id:${num}`; - if (!idMap.has(key)) { - idMap.set(key, nextIdId++); - } - return `Identifier(${idMap.get(key)})`; - }) - // Normalize printed identifiers like "x$5" in error descriptions. - // The $N suffix is an opaque IdentifierId that may differ between TS and Rust. - .replace(/(\w+)\$(\d+)/g, (_match, name, num) => { - const key = `id:${num}`; - if (!idMap.has(key)) { - idMap.set(key, nextIdId++); - } - return `${name}\$${idMap.get(key)}`; - }) - // Normalize mutableRange: [N:M] values by stripping them entirely. - // In TS, identifier.mutableRange shares a reference with scope.range, - // so modifications to scope.range automatically propagate. In Rust, - // mutableRange is a copy and diverges from scope.range after certain - // passes. Since scope.range is separately displayed and validated, - // mutableRange comparison adds noise without catching real bugs. - .replace(/mutableRange: \[\d+:\d+\]/g, 'mutableRange: [_:_]') - ); - }); - return result.join('\n'); -} - -// --- Simple unified diff --- -function unifiedDiff(expected: string, actual: string): string { - const expectedLines = expected.split('\n'); - const actualLines = actual.split('\n'); - const lines: string[] = []; - lines.push(`${RED}--- TypeScript${RESET}`); - lines.push(`${GREEN}+++ Rust${RESET}`); - - // Simple line-by-line diff (not a real unified diff, but good enough for debugging) - const maxLen = Math.max(expectedLines.length, actualLines.length); - let contextStart = -1; - for (let i = 0; i < maxLen; i++) { - const eLine = i < expectedLines.length ? expectedLines[i] : undefined; - const aLine = i < actualLines.length ? actualLines[i] : undefined; - if (eLine === aLine) { - // matching line — skip (or show as context near diffs) - continue; - } - if (contextStart !== i) { - lines.push(`${YELLOW}@@ line ${i + 1} @@${RESET}`); - } - contextStart = i + 1; - if (eLine !== undefined && aLine !== undefined) { - lines.push(`${RED}-${eLine}${RESET}`); - lines.push(`${GREEN}+${aLine}${RESET}`); - } else if (eLine !== undefined) { - lines.push(`${RED}-${eLine}${RESET}`); - } else if (aLine !== undefined) { - lines.push(`${GREEN}+${aLine}${RESET}`); - } - } - return lines.join('\n'); -} - -// --- Format code with prettier --- -async function formatCode(code: string, isFlow: boolean): Promise<string> { - return prettier.format(code, { - semi: true, - parser: isFlow ? 'flow' : 'babel-ts', - }); -} - -// --- Main --- -const fixtures = discoverFixtures(fixturesPath); -if (fixtures.length === 0) { - console.error('No fixtures found at', fixturesPath); - process.exit(1); -} - -if (!jsonMode && !failuresMode) { - console.log( - `Testing ${BOLD}${fixtures.length}${RESET} fixtures for pass: ${BOLD}${passArg}${RESET}`, - ); - console.log(''); -} - -let passed = 0; -let failed = 0; -let tsHadEntries = false; -const failures: Array<{ - fixture: string; - detail: string; -}> = []; -const failedFixtures: string[] = []; - -// Code comparison tracking -let codePassed = 0; -let codeFailed = 0; -const codeFailures: Array<{ - fixture: string; - detail: string; -}> = []; -const codeFailedFixtures: string[] = []; - -// Per-pass failure tracking for frontier detection -const perPassResults = new Map<string, {passed: number; failed: number}>(); -for (const pass of PASS_ORDER) { - perPassResults.set(pass, {passed: 0, failed: 0}); -} - -// --- Find the earliest diverging pass for a fixture --- -function findDivergencePass(tsLog: LogItem[], rustLog: LogItem[]): string { - const maxLen = Math.max(tsLog.length, rustLog.length); - for (let i = 0; i < maxLen; i++) { - const tsItem = i < tsLog.length ? tsLog[i] : undefined; - const rustItem = i < rustLog.length ? rustLog[i] : undefined; - - if (tsItem === undefined || rustItem === undefined) { - // One log is shorter — attribute to the pass of the last available entry - const item = tsItem ?? rustItem; - if (item && item.kind === 'entry') { - return item.name; - } - // For events, attribute to the preceding entry's pass - for (let j = i - 1; j >= 0; j--) { - const prev = tsLog[j] ?? rustLog[j]; - if (prev && prev.kind === 'entry') return prev.name; - } - // No preceding entry — attribute to first pass - return PASS_ORDER[0]; - } - - const tsFormatted = normalizeIds(formatLogItem(tsItem)); - const rustFormatted = normalizeIds(formatLogItem(rustItem)); - if (tsFormatted !== rustFormatted) { - if (tsItem.kind === 'entry') { - return tsItem.name; - } - // For events, find the most recent entry pass - for (let j = i - 1; j >= 0; j--) { - if (tsLog[j] && tsLog[j].kind === 'entry') { - return (tsLog[j] as LogEntry).name; - } - } - // No preceding entry — attribute to first pass - return PASS_ORDER[0]; - } - } - // No divergence found (shouldn't happen since caller verified logs differ) - return PASS_ORDER[0]; -} - -(async () => { - for (const fixturePath of fixtures) { - const relPath = path.relative(REPO_ROOT, fixturePath); - const ts = compileFixture('ts', fixturePath); - const rust = compileFixture('rust', fixturePath); - - // Check if TS produced any entries for the target pass - if (ts.log.some(item => item.kind === 'entry' && item.name === passArg)) { - tsHadEntries = true; - } - - // Compare the full log (entries + events in order, up to target pass) - const tsFormatted = normalizeIds(formatLog(ts.log)); - const rustFormatted = normalizeIds(formatLog(rust.log)); - - if (tsFormatted === rustFormatted) { - passed++; - // Count as passed for all passes that appeared in the log - const seenPasses = new Set<string>(); - for (const item of ts.log) { - if (item.kind === 'entry') seenPasses.add(item.name); - } - for (const pass of seenPasses) { - const stats = perPassResults.get(pass); - if (stats) stats.passed++; - } - } else { - failed++; - // Find which pass diverged and attribute the failure - const divergePass = findDivergencePass(ts.log, rust.log); - const stats = perPassResults.get(divergePass); - if (stats) stats.failed++; - // Count passes before divergence as passed - const seenPasses: string[] = []; - for (const item of ts.log) { - if (item.kind === 'entry' && item.name !== divergePass) { - seenPasses.push(item.name); - } else if (item.kind === 'entry') { - break; - } - } - for (const pass of seenPasses) { - const stats = perPassResults.get(pass); - if (stats) stats.passed++; - } - - failedFixtures.push(relPath); - if (limitArg === 0 || failures.length < limitArg) { - failures.push({ - fixture: relPath, - detail: unifiedDiff(tsFormatted, rustFormatted), - }); - } - } - - // Compare final code output - const source = fs.readFileSync(fixturePath, 'utf8'); - const headerBlock = source.substring(0, source.indexOf('*/') + 2 || 200); - const isFlow = headerBlock.includes('@flow'); - try { - const tsCode = await formatCode(ts.code ?? '', isFlow); - const rustCode = await formatCode(rust.code ?? '', isFlow); - if (tsCode === rustCode) { - codePassed++; - } else { - codeFailed++; - codeFailedFixtures.push(relPath); - if (limitArg === 0 || codeFailures.length < limitArg) { - codeFailures.push({ - fixture: relPath, - detail: unifiedDiff(tsCode, rustCode), - }); - } - } - } catch { - // If prettier fails, treat as a code mismatch - const tsCode = ts.code ?? ''; - const rustCode = rust.code ?? ''; - if (tsCode === rustCode) { - codePassed++; - } else { - codeFailed++; - codeFailedFixtures.push(relPath); - if (limitArg === 0 || codeFailures.length < limitArg) { - codeFailures.push({ - fixture: relPath, - detail: unifiedDiff(tsCode, rustCode), - }); - } - } - } - } - - // --- Check for invalid pass name --- - if (!tsHadEntries) { - console.error( - `${RED}ERROR: TypeScript compiler produced no log entries for pass "${passArg}" across all fixtures.${RESET}`, - ); - console.error('This likely means the pass name is incorrect.'); - console.error(''); - console.error( - 'Pass names must match exactly as used in Pipeline.ts, e.g.:', - ); - console.error( - ' HIR, PruneMaybeThrows, SSA, InferTypes, AnalyseFunctions, ...', - ); - process.exit(1); - } - - // --- Compute frontier --- - let frontier: string | null = null; - for (const pass of PASS_ORDER) { - const stats = perPassResults.get(pass); - if (stats && stats.failed > 0) { - frontier = pass; - break; - } - } - - // --- Summary --- - const total = fixtures.length; - let frontierStr: string; - if (frontier != null) { - frontierStr = frontier; - } else if (passArgRaw) { - // Explicit pass arg given and it's clean — we can't know the global frontier - frontierStr = `${passArg} passes, rerun without a pass name to find frontier`; - } else { - frontierStr = 'none'; - } - - // --- Per-pass breakdown --- - const perPassParts: string[] = []; - for (const pass of PASS_ORDER) { - const stats = perPassResults.get(pass); - if (stats && (stats.passed > 0 || stats.failed > 0)) { - perPassParts.push( - `${pass} ${stats.passed}/${stats.passed + stats.failed}`, - ); - } - } - - // --- Output --- - if (jsonMode) { - const output = { - pass: passArg, - autoDetected: !passArgRaw, - total, - passed, - failed, - frontier: frontier, - perPass: Object.fromEntries( - [...perPassResults.entries()].filter( - ([_, v]) => v.passed > 0 || v.failed > 0, - ), - ), - failures: failedFixtures, - codePassed, - codeFailed, - codeFailures: codeFailedFixtures, - }; - console.log(JSON.stringify(output)); - } else if (failuresMode) { - for (const f of failedFixtures) { - console.log(f); - } - } else { - const summaryColor = failed === 0 ? GREEN : RED; - const summaryLine = `${summaryColor}Results: ${passed} passed, ${failed} failed (${total} total), frontier: ${frontierStr}${RESET}`; - const codeSummaryColor = codeFailed === 0 ? GREEN : RED; - const codeSummaryLine = `${codeSummaryColor}Code: ${codePassed} passed, ${codeFailed} failed (${total} total)${RESET}`; - - // Print summary first - console.log(summaryLine); - console.log(codeSummaryLine); - if (perPassParts.length > 0) { - console.log(`Per-pass: ${perPassParts.join(', ')}`); - } - console.log(''); - - // --- Show log failures --- - for (const failure of failures) { - console.log(`${RED}FAIL${RESET} ${failure.fixture}`); - console.log(failure.detail); - console.log(''); - } - - // --- Show code failures --- - if (codeFailures.length > 0) { - console.log(`${BOLD}--- Code comparison failures ---${RESET}`); - console.log(''); - for (const failure of codeFailures) { - console.log(`${RED}FAIL (code)${RESET} ${failure.fixture}`); - console.log(failure.detail); - console.log(''); - } - } - - // --- Summary again (so tail -1 works) --- - console.log('---'); - if (failures.length < failed) { - console.log( - `${DIM} (showing first ${failures.length} of ${failed} log failures)${RESET}`, - ); - } - if (codeFailures.length < codeFailed) { - console.log( - `${DIM} (showing first ${codeFailures.length} of ${codeFailed} code failures)${RESET}`, - ); - } - console.log(summaryLine); - console.log(codeSummaryLine); - } - - process.exit(failed > 0 || codeFailed > 0 ? 1 : 0); -})(); diff --git a/compiler/scripts/ts-compile-fixture.ts b/compiler/scripts/ts-compile-fixture.ts deleted file mode 100644 index 1ad81ad425c0..000000000000 --- a/compiler/scripts/ts-compile-fixture.ts +++ /dev/null @@ -1,697 +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. - */ - -/** - * TS test binary for the Rust port testing infrastructure. - * - * Implements the compiler pipeline independently (NOT using compile() or - * runWithEnvironment()), calling each pass function directly in the same - * sequence as the Rust binary. This ensures both sides have exactly matching - * behavior. - * - * Takes a compiler pass name and a fixture path, finds every top-level - * function, runs the pipeline up to the target pass for each, and prints - * a detailed debug representation to stdout. - * - * Usage: npx tsx compiler/scripts/ts-compile-fixture.mjs <pass> <fixture-path> - */ - -import {parse} from '@babel/parser'; -import _traverse from '@babel/traverse'; -const traverse: typeof _traverse = (_traverse as any).default || _traverse; -import * as t from '@babel/types'; -import {type NodePath} from '@babel/traverse'; -import fs from 'fs'; -import path from 'path'; - -// --- Import pass functions directly from compiler source --- -import {lower} from '../packages/babel-plugin-react-compiler/src/HIR/BuildHIR'; -import { - Environment, - type EnvironmentConfig, - type ReactFunctionType, -} from '../packages/babel-plugin-react-compiler/src/HIR/Environment'; -import {findContextIdentifiers} from '../packages/babel-plugin-react-compiler/src/HIR/FindContextIdentifiers'; -import {mergeConsecutiveBlocks} from '../packages/babel-plugin-react-compiler/src/HIR/MergeConsecutiveBlocks'; -import { - assertConsistentIdentifiers, - assertTerminalSuccessorsExist, - assertTerminalPredsExist, -} from '../packages/babel-plugin-react-compiler/src/HIR'; -import {assertValidBlockNesting} from '../packages/babel-plugin-react-compiler/src/HIR/AssertValidBlockNesting'; -import {assertValidMutableRanges} from '../packages/babel-plugin-react-compiler/src/HIR/AssertValidMutableRanges'; -import {pruneUnusedLabelsHIR} from '../packages/babel-plugin-react-compiler/src/HIR/PruneUnusedLabelsHIR'; -import {mergeOverlappingReactiveScopesHIR} from '../packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR'; -import {buildReactiveScopeTerminalsHIR} from '../packages/babel-plugin-react-compiler/src/HIR/BuildReactiveScopeTerminalsHIR'; -import {alignReactiveScopesToBlockScopesHIR} from '../packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR'; -import {flattenReactiveLoopsHIR} from '../packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenReactiveLoopsHIR'; -import {flattenScopesWithHooksOrUseHIR} from '../packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUseHIR'; -import {propagateScopeDependenciesHIR} from '../packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR'; - -import { - pruneMaybeThrows, - constantPropagation, - deadCodeElimination, -} from '../packages/babel-plugin-react-compiler/src/Optimization'; -import {optimizePropsMethodCalls} from '../packages/babel-plugin-react-compiler/src/Optimization/OptimizePropsMethodCalls'; -import {outlineFunctions} from '../packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions'; -import {optimizeForSSR} from '../packages/babel-plugin-react-compiler/src/Optimization/OptimizeForSSR'; - -import { - enterSSA, - eliminateRedundantPhi, - rewriteInstructionKindsBasedOnReassignment, -} from '../packages/babel-plugin-react-compiler/src/SSA'; -import {inferTypes} from '../packages/babel-plugin-react-compiler/src/TypeInference'; - -import { - analyseFunctions, - dropManualMemoization, - inferReactivePlaces, - inlineImmediatelyInvokedFunctionExpressions, -} from '../packages/babel-plugin-react-compiler/src/Inference'; -import {inferMutationAliasingEffects} from '../packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects'; -import {inferMutationAliasingRanges} from '../packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges'; - -import { - buildReactiveFunction, - inferReactiveScopeVariables, - memoizeFbtAndMacroOperandsInSameScope, - promoteUsedTemporaries, - propagateEarlyReturns, - pruneHoistedContexts, - pruneNonEscapingScopes, - pruneNonReactiveDependencies, - pruneUnusedLValues, - pruneUnusedLabels, - pruneUnusedScopes, - mergeReactiveScopesThatInvalidateTogether, - renameVariables, - extractScopeDeclarationsFromDestructuring, - codegenFunction, - alignObjectMethodScopes, -} from '../packages/babel-plugin-react-compiler/src/ReactiveScopes'; -import {alignMethodCallScopes} from '../packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignMethodCallScopes'; -import {pruneAlwaysInvalidatingScopes} from '../packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneAlwaysInvalidatingScopes'; -import {stabilizeBlockIds} from '../packages/babel-plugin-react-compiler/src/ReactiveScopes/StabilizeBlockIds'; - -import {nameAnonymousFunctions} from '../packages/babel-plugin-react-compiler/src/Transform/NameAnonymousFunctions'; - -import { - validateContextVariableLValues, - validateHooksUsage, - validateNoCapitalizedCalls, - validateNoRefAccessInRender, - validateNoSetStateInRender, - validatePreservedManualMemoization, - validateUseMemo, -} from '../packages/babel-plugin-react-compiler/src/Validation'; -import {validateLocalsNotReassignedAfterRender} from '../packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender'; -import {validateNoFreezingKnownMutableFunctions} from '../packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions'; - -import {CompilerError} from '../packages/babel-plugin-react-compiler/src/CompilerError'; -import {type HIRFunction} from '../packages/babel-plugin-react-compiler/src/HIR/HIR'; - -import {parseConfigPragmaForTests} from '../packages/babel-plugin-react-compiler/src/Utils/TestUtils'; -import { - parsePluginOptions, - ProgramContext, -} from '../packages/babel-plugin-react-compiler/src/Entrypoint'; - -import {debugPrintHIR} from './debug-print-hir.mjs'; -import {debugPrintReactive} from './debug-print-reactive.mjs'; -import {debugPrintError} from './debug-print-error.mjs'; - -// --- Arguments --- -const [passArg, fixturePath] = process.argv.slice(2); - -if (!passArg || !fixturePath) { - console.error( - 'Usage: npx tsx compiler/scripts/ts-compile-fixture.mjs <pass> <fixture-path>', - ); - process.exit(1); -} - -// --- Valid pass names (checkpoint names) --- -const VALID_PASSES = new Set([ - 'HIR', - 'PruneMaybeThrows', - 'DropManualMemoization', - 'InlineIIFEs', - 'MergeConsecutiveBlocks', - 'SSA', - 'EliminateRedundantPhi', - 'ConstantPropagation', - 'InferTypes', - 'OptimizePropsMethodCalls', - 'AnalyseFunctions', - 'InferMutationAliasingEffects', - 'OptimizeForSSR', - 'DeadCodeElimination', - 'PruneMaybeThrows2', - 'InferMutationAliasingRanges', - 'InferReactivePlaces', - 'RewriteInstructionKinds', - 'InferReactiveScopeVariables', - 'MemoizeFbtOperands', - 'NameAnonymousFunctions', - 'OutlineFunctions', - 'AlignMethodCallScopes', - 'AlignObjectMethodScopes', - 'PruneUnusedLabelsHIR', - 'AlignReactiveScopesToBlockScopes', - 'MergeOverlappingReactiveScopes', - 'BuildReactiveScopeTerminals', - 'FlattenReactiveLoops', - 'FlattenScopesWithHooksOrUse', - 'PropagateScopeDependencies', - 'BuildReactiveFunction', - 'PruneUnusedLabels', - 'PruneNonEscapingScopes', - 'PruneNonReactiveDependencies', - 'PruneUnusedScopes', - 'MergeReactiveScopesThatInvalidateTogether', - 'PruneAlwaysInvalidatingScopes', - 'PropagateEarlyReturns', - 'PruneUnusedLValues', - 'PromoteUsedTemporaries', - 'ExtractScopeDeclarationsFromDestructuring', - 'StabilizeBlockIds', - 'RenameVariables', - 'PruneHoistedContexts', - 'Codegen', -]); - -if (!VALID_PASSES.has(passArg)) { - console.error(`Unknown pass: ${passArg}`); - console.error(`Valid passes: ${[...VALID_PASSES].join(', ')}`); - process.exit(1); -} - -// --- Read fixture source --- -const source = fs.readFileSync(fixturePath, 'utf8'); -const firstLine = source.substring(0, source.indexOf('\n')); - -// Determine language and source type -const language = firstLine.includes('@flow') ? 'flow' : 'typescript'; -const sourceType = firstLine.includes('@script') ? 'script' : 'module'; - -// --- Parse config pragmas --- -const parsedOpts = parseConfigPragmaForTests(firstLine, { - compilationMode: 'all', -}); -const envConfig: EnvironmentConfig = { - ...parsedOpts.environment, - assertValidMutableRanges: true, -}; - -// --- Parse the fixture --- -const plugins: Array<any> = - language === 'flow' ? ['flow', 'jsx'] : ['typescript', 'jsx']; -const inputAst = parse(source, { - sourceFilename: path.basename(fixturePath), - plugins, - sourceType, - errorRecovery: true, -}); - -// --- Find ALL top-level functions --- -const functionPaths: Array< - NodePath< - t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression - > -> = []; -let programPath: NodePath<t.Program> | null = null; - -traverse(inputAst, { - Program(nodePath: NodePath<t.Program>) { - programPath = nodePath; - }, - 'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression'( - nodePath: NodePath< - t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression - >, - ) { - if (isTopLevelFunction(nodePath)) { - functionPaths.push(nodePath); - nodePath.skip(); - } - }, - ClassDeclaration(nodePath: NodePath<t.ClassDeclaration>) { - nodePath.skip(); - }, - ClassExpression(nodePath: NodePath<t.ClassExpression>) { - nodePath.skip(); - }, -}); - -function isTopLevelFunction(fnPath: NodePath): boolean { - let current = fnPath; - while (current.parentPath) { - const parent = current.parentPath; - if (parent.isProgram()) { - return true; - } - if (parent.isVariableDeclarator()) { - current = parent; - continue; - } - if (parent.isVariableDeclaration()) { - current = parent; - continue; - } - if ( - parent.isExportNamedDeclaration() || - parent.isExportDefaultDeclaration() - ) { - current = parent; - continue; - } - return false; - } - return false; -} - -if (functionPaths.length === 0) { - console.error('No top-level functions found in fixture'); - process.exit(1); -} - -// --- Compile each function --- -const filename = '/' + path.basename(fixturePath); -const allOutputs: string[] = []; - -for (const fnPath of functionPaths) { - const output = compileOneFunction(fnPath); - if (output != null) { - allOutputs.push(output); - } -} - -// --- Write output --- -if (allOutputs.length === 0) { - console.error('No functions produced output'); - process.exit(1); -} -const finalOutput = allOutputs.join('\n---\n'); -process.stdout.write(finalOutput); -if (!finalOutput.endsWith('\n')) { - process.stdout.write('\n'); -} - -// --- Run the pipeline for a single function, mirroring Rust's run_pipeline --- -function compileOneFunction( - fnPath: NodePath< - t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression - >, -): string | null { - const contextIdentifiers = findContextIdentifiers(fnPath); - const env = new Environment( - fnPath.scope, - 'Other' as ReactFunctionType, - 'client', // outputMode - envConfig, - contextIdentifiers, - fnPath, - null, // logger - filename, - source, - new ProgramContext({ - program: programPath!, - opts: parsedOpts, - filename, - code: source, - suppressions: [], - hasModuleScopeOptOut: false, - }), - ); - - const pass = passArg; - - function formatEnvErrors(): string { - return debugPrintError(env.aggregateErrors()); - } - - function printHIR(hir: HIRFunction): string { - return debugPrintHIR(null, hir); - } - - function checkpointHIR(hir: HIRFunction): string { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return printHIR(hir); - } - - try { - // --- HIR Phase --- - const hir = lower(fnPath, env); - if (pass === 'HIR') { - return checkpointHIR(hir); - } - - pruneMaybeThrows(hir); - if (pass === 'PruneMaybeThrows') { - return checkpointHIR(hir); - } - - validateContextVariableLValues(hir); - validateUseMemo(hir); - - if (env.enableDropManualMemoization) { - dropManualMemoization(hir); - } - if (pass === 'DropManualMemoization') { - return checkpointHIR(hir); - } - - inlineImmediatelyInvokedFunctionExpressions(hir); - if (pass === 'InlineIIFEs') { - return checkpointHIR(hir); - } - - mergeConsecutiveBlocks(hir); - if (pass === 'MergeConsecutiveBlocks') { - return checkpointHIR(hir); - } - - assertConsistentIdentifiers(hir); - assertTerminalSuccessorsExist(hir); - - enterSSA(hir); - if (pass === 'SSA') { - return checkpointHIR(hir); - } - - eliminateRedundantPhi(hir); - if (pass === 'EliminateRedundantPhi') { - return checkpointHIR(hir); - } - - assertConsistentIdentifiers(hir); - - constantPropagation(hir); - if (pass === 'ConstantPropagation') { - return checkpointHIR(hir); - } - - inferTypes(hir); - if (pass === 'InferTypes') { - return checkpointHIR(hir); - } - - if (env.enableValidations) { - if (env.config.validateHooksUsage) { - validateHooksUsage(hir); - } - if (env.config.validateNoCapitalizedCalls) { - validateNoCapitalizedCalls(hir); - } - } - - optimizePropsMethodCalls(hir); - if (pass === 'OptimizePropsMethodCalls') { - return checkpointHIR(hir); - } - - analyseFunctions(hir); - if (pass === 'AnalyseFunctions') { - return checkpointHIR(hir); - } - - inferMutationAliasingEffects(hir); - if (pass === 'InferMutationAliasingEffects') { - return checkpointHIR(hir); - } - - if (env.outputMode === 'ssr') { - optimizeForSSR(hir); - } - if (pass === 'OptimizeForSSR') { - return checkpointHIR(hir); - } - - deadCodeElimination(hir); - if (pass === 'DeadCodeElimination') { - return checkpointHIR(hir); - } - - pruneMaybeThrows(hir); - if (pass === 'PruneMaybeThrows2') { - return checkpointHIR(hir); - } - - inferMutationAliasingRanges(hir, {isFunctionExpression: false}); - if (pass === 'InferMutationAliasingRanges') { - return checkpointHIR(hir); - } - - if (env.enableValidations) { - validateLocalsNotReassignedAfterRender(hir); - - if (env.config.assertValidMutableRanges) { - assertValidMutableRanges(hir); - } - - if (env.config.validateRefAccessDuringRender) { - validateNoRefAccessInRender(hir); - } - - if (env.config.validateNoSetStateInRender) { - validateNoSetStateInRender(hir); - } - - validateNoFreezingKnownMutableFunctions(hir); - } - - inferReactivePlaces(hir); - if (pass === 'InferReactivePlaces') { - return checkpointHIR(hir); - } - - rewriteInstructionKindsBasedOnReassignment(hir); - if (pass === 'RewriteInstructionKinds') { - return checkpointHIR(hir); - } - - if (env.enableMemoization) { - inferReactiveScopeVariables(hir); - } - if (pass === 'InferReactiveScopeVariables') { - return checkpointHIR(hir); - } - - const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir); - if (pass === 'MemoizeFbtOperands') { - return checkpointHIR(hir); - } - - if (env.config.enableNameAnonymousFunctions) { - nameAnonymousFunctions(hir); - } - if (pass === 'NameAnonymousFunctions') { - return checkpointHIR(hir); - } - - if (env.config.enableFunctionOutlining) { - outlineFunctions(hir, fbtOperands); - } - if (pass === 'OutlineFunctions') { - return checkpointHIR(hir); - } - - alignMethodCallScopes(hir); - if (pass === 'AlignMethodCallScopes') { - return checkpointHIR(hir); - } - - alignObjectMethodScopes(hir); - if (pass === 'AlignObjectMethodScopes') { - return checkpointHIR(hir); - } - - pruneUnusedLabelsHIR(hir); - if (pass === 'PruneUnusedLabelsHIR') { - return checkpointHIR(hir); - } - - alignReactiveScopesToBlockScopesHIR(hir); - if (pass === 'AlignReactiveScopesToBlockScopes') { - return checkpointHIR(hir); - } - - mergeOverlappingReactiveScopesHIR(hir); - if (pass === 'MergeOverlappingReactiveScopes') { - return checkpointHIR(hir); - } - - assertValidBlockNesting(hir); - - buildReactiveScopeTerminalsHIR(hir); - if (pass === 'BuildReactiveScopeTerminals') { - return checkpointHIR(hir); - } - - assertValidBlockNesting(hir); - - flattenReactiveLoopsHIR(hir); - if (pass === 'FlattenReactiveLoops') { - return checkpointHIR(hir); - } - - flattenScopesWithHooksOrUseHIR(hir); - if (pass === 'FlattenScopesWithHooksOrUse') { - return checkpointHIR(hir); - } - - assertTerminalSuccessorsExist(hir); - assertTerminalPredsExist(hir); - - propagateScopeDependenciesHIR(hir); - if (pass === 'PropagateScopeDependencies') { - return checkpointHIR(hir); - } - - // --- Reactive Phase --- - const reactiveFunction = buildReactiveFunction(hir); - if (pass === 'BuildReactiveFunction') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - pruneUnusedLabels(reactiveFunction); - if (pass === 'PruneUnusedLabels') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - pruneNonEscapingScopes(reactiveFunction); - if (pass === 'PruneNonEscapingScopes') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - pruneNonReactiveDependencies(reactiveFunction); - if (pass === 'PruneNonReactiveDependencies') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - pruneUnusedScopes(reactiveFunction); - if (pass === 'PruneUnusedScopes') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - mergeReactiveScopesThatInvalidateTogether(reactiveFunction); - if (pass === 'MergeReactiveScopesThatInvalidateTogether') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - pruneAlwaysInvalidatingScopes(reactiveFunction); - if (pass === 'PruneAlwaysInvalidatingScopes') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - propagateEarlyReturns(reactiveFunction); - if (pass === 'PropagateEarlyReturns') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - pruneUnusedLValues(reactiveFunction); - if (pass === 'PruneUnusedLValues') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - promoteUsedTemporaries(reactiveFunction); - if (pass === 'PromoteUsedTemporaries') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - extractScopeDeclarationsFromDestructuring(reactiveFunction); - if (pass === 'ExtractScopeDeclarationsFromDestructuring') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - stabilizeBlockIds(reactiveFunction); - if (pass === 'StabilizeBlockIds') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - const uniqueIdentifiers = renameVariables(reactiveFunction); - if (pass === 'RenameVariables') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - pruneHoistedContexts(reactiveFunction); - if (pass === 'PruneHoistedContexts') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return debugPrintReactive(null, reactiveFunction); - } - - if ( - env.config.enablePreserveExistingMemoizationGuarantees || - env.config.validatePreserveExistingMemoizationGuarantees - ) { - validatePreservedManualMemoization(reactiveFunction); - } - - const ast = codegenFunction(reactiveFunction, { - uniqueIdentifiers, - fbtOperands, - }); - if (pass === 'Codegen') { - if (env.hasErrors()) { - return formatEnvErrors(); - } - return '(codegen ast)'; - } - - return null; - } catch (e) { - if (e instanceof CompilerError) { - return debugPrintError(e); - } - throw e; - } -} diff --git a/compiler/yarn.lock b/compiler/yarn.lock index a78afe242ee5..a328b70efa72 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -6398,11 +6398,6 @@ hermes-estree@0.26.0: resolved "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.26.0.tgz" integrity sha512-If1T7lhfXnGlVLbnsmwerNB5cyJm2oIE8TN1UKEq6/OUX1nOGUhjXMpqAwZ1wkkn9Brda0VRyJEWOGT2GgVcAQ== -hermes-estree@0.32.0: - version "0.32.0" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.32.0.tgz#bb7da6613ab8e67e334a1854ea1e209f487d307b" - integrity sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ== - hermes-parser@0.25.1, hermes-parser@^0.25.1: version "0.25.1" resolved "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz" @@ -6417,13 +6412,6 @@ hermes-parser@0.26.0: dependencies: hermes-estree "0.26.0" -hermes-parser@^0.32.0: - version "0.32.0" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.32.0.tgz#7916984ef6fdce62e7415d354cf35392061cd303" - integrity sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw== - dependencies: - hermes-estree "0.32.0" - homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz" @@ -11071,11 +11059,6 @@ typescript-eslint@^8.16.0: "@typescript-eslint/parser" "8.18.1" "@typescript-eslint/utils" "8.18.1" -typescript@^5.0.0: - version "5.9.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" - integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== - typescript@^5.4.3: version "5.4.3" resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz" diff --git a/dangerfile.js b/dangerfile.js index b7ee95d75f88..d60da35b586f 100644 --- a/dangerfile.js +++ b/dangerfile.js @@ -105,7 +105,7 @@ function row(result, baseSha, headSha) { // where the branches differ const upstreamRepo = danger.github.pr.base.repo.full_name; - if (upstreamRepo !== 'react/react') { + if (upstreamRepo !== 'facebook/react') { // Exit unless we're running in the main repo return; } diff --git a/fixtures/flight/src/App.js b/fixtures/flight/src/App.js index 1ecfbbcd9cac..9f8deed495d3 100644 --- a/fixtures/flight/src/App.js +++ b/fixtures/flight/src/App.js @@ -27,7 +27,6 @@ import {like, greet, increment} from './actions.js'; import {getServerState} from './ServerState.js'; import {sdkMethod} from './library.js'; -import FileReader from './FileReader.js'; const promisedText = new Promise(resolve => setTimeout(() => resolve('deferred text'), 50) @@ -244,11 +243,6 @@ export default async function App({prerender, noCache}) { {prerender ? null : ( // TODO: prerender is broken for large content for some reason. <React.Suspense fallback={null}> <LargeContent /> - {/* - This text prop is above the threshold, so in the debug info for - the element we'll see a placeholder instead of the actual value. - */} - <FileReader largeText={'a'.repeat(1000001)} /> </React.Suspense> )} </Container> diff --git a/fixtures/flight/src/FileReader.js b/fixtures/flight/src/FileReader.js deleted file mode 100644 index aeb104f26b45..000000000000 --- a/fixtures/flight/src/FileReader.js +++ /dev/null @@ -1,16 +0,0 @@ -export default async function FileReader() { - // This debug string is below the threshold for debug string length, so its - // value is sent to the client as the awaited value. - await new Promise(resolve => { - setTimeout(() => resolve('o'.repeat(1000000)), 1); - }); - - // This debug string is above the threshold for debug string length, so the - // client receives a placeholder as the awaited value instead of the actual - // string. - await new Promise(resolve => { - setTimeout(() => resolve('x'.repeat(1000001)), 1); - }); - - return <p>FileReader</p>; -} diff --git a/flow-typed/environments/bom.js b/flow-typed/environments/bom.js index b7c721fb2d20..0e83e904cf76 100644 --- a/flow-typed/environments/bom.js +++ b/flow-typed/environments/bom.js @@ -874,7 +874,6 @@ declare class SharedWorker extends EventTarget { declare function importScripts(...urls: Array<string | TrustedScriptURL>): void; declare class WorkerGlobalScope extends EventTarget { - // $FlowFixMe[incompatible-variance] self: this; location: WorkerLocation; navigator: WorkerNavigator; diff --git a/flow-typed/environments/dom.js b/flow-typed/environments/dom.js index 5a556419d485..0ea2d2730a06 100644 --- a/flow-typed/environments/dom.js +++ b/flow-typed/environments/dom.js @@ -1263,7 +1263,6 @@ declare class HTMLCollection<+Elem: Element> { length: number; item(nameOrIndex?: any, optionalIndex?: any): Elem | null; namedItem(name: string): Elem | null; - // $FlowFixMe[incompatible-variance] [index: number | string]: Elem; } diff --git a/flow-typed/environments/node.js b/flow-typed/environments/node.js index acf20bcb1a9d..a3edff20f893 100644 --- a/flow-typed/environments/node.js +++ b/flow-typed/environments/node.js @@ -1908,7 +1908,6 @@ type http$agentOptions = { declare class http$Agent<+SocketT = net$Socket> { constructor(options: http$agentOptions): void; destroy(): void; - // $FlowFixMe[incompatible-variance] freeSockets: {[name: string]: $ReadOnlyArray<SocketT>, ...}; getName(options: { host: string, @@ -1918,9 +1917,7 @@ declare class http$Agent<+SocketT = net$Socket> { }): string; maxFreeSockets: number; maxSockets: number; - // $FlowFixMe[incompatible-variance] requests: {[name: string]: $ReadOnlyArray<http$ClientRequest<SocketT>>, ...}; - // $FlowFixMe[incompatible-variance] sockets: {[name: string]: $ReadOnlyArray<SocketT>, ...}; } diff --git a/flow-typed/environments/streams.js b/flow-typed/environments/streams.js index 6a3aa796e68d..17bfae29e612 100644 --- a/flow-typed/environments/streams.js +++ b/flow-typed/environments/streams.js @@ -42,7 +42,7 @@ declare class ReadableStreamReader { closed: boolean; - cancel(reason: string): Promise<void>; + cancel(reason: string): void; read(): Promise<{ value: ?any, done: boolean, diff --git a/package.json b/package.json index 29abc8c0dafc..c67ae862f5de 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@typescript-eslint/parser": "^6.21.0", "abortcontroller-polyfill": "^1.7.5", "art": "0.10.1", - "babel-plugin-syntax-hermes-parser": "^0.36.1", + "babel-plugin-syntax-hermes-parser": "^0.32.0", "babel-plugin-syntax-trailing-function-commas": "^6.5.0", "busboy": "^1.6.0", "chalk": "^3.0.0", @@ -75,15 +75,15 @@ "eslint-plugin-react-internal": "link:./scripts/eslint-rules", "fbjs-scripts": "^3.0.1", "filesize": "^6.0.1", - "flow-bin": "^0.317.0", - "flow-remove-types": "^2.317.0", + "flow-bin": "^0.279.0", + "flow-remove-types": "^2.279.0", "flow-typed": "^4.1.1", "glob": "^7.1.6", "glob-stream": "^6.1.0", "google-closure-compiler": "^20230206.0.0", "gzip-size": "^5.1.1", - "hermes-eslint": "^0.36.1", - "hermes-parser": "^0.36.1", + "hermes-eslint": "^0.32.0", + "hermes-parser": "^0.32.0", "jest": "^29.4.2", "jest-cli": "^29.4.2", "jest-diff": "^29.4.2", @@ -96,7 +96,6 @@ "ncp": "^2.0.0", "prettier": "^3.3.3", "prettier-2": "npm:prettier@^2", - "prettier-plugin-hermes-parser": "^0.36.1", "pretty-format": "^29.4.1", "prop-types": "^15.6.2", "random-seed": "^0.3.0", diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index fc8a12199444..3e436421b3d3 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -4,7 +4,7 @@ "version": "7.0.0", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/eslint-plugin-react-hooks" }, "files": [ @@ -27,7 +27,7 @@ }, "license": "MIT", "bugs": { - "url": "https://github.com/react/react/issues" + "url": "https://github.com/facebook/react/issues" }, "main": "./index.js", "types": "./index.d.ts", diff --git a/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts index d5b4252f8cdb..b35e62677dfd 100644 --- a/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts +++ b/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts @@ -8,14 +8,14 @@ import type {SourceLocation as BabelSourceLocation} from '@babel/types'; import { - type CompilerSuggestion, + type CompilerDiagnosticOptions, + type CompilerErrorDetailOptions, CompilerSuggestionOperation, LintRules, type LintRule, ErrorSeverity, LintRulePreset, } from 'babel-plugin-react-compiler'; -import type {CompileErrorDetail} from 'babel-plugin-react-compiler/src/Entrypoint'; import {type Linter, type Rule} from 'eslint'; import runReactCompiler, {RunCacheEntry} from './RunReactCompiler'; @@ -23,39 +23,12 @@ function assertExhaustive(_: never, errorMsg: string): never { throw new Error(errorMsg); } -/** - * Get the primary source location from a CompileErrorDetail. - * Handles both the new format (details array) and legacy format (flat loc). - */ -function primaryLocation( - detail: CompileErrorDetail, -): BabelSourceLocation | null { - if (detail.details != null) { - const firstError = detail.details.find(d => d.kind === 'error'); - if (firstError != null) { - return firstError.loc ?? null; - } - } - return detail.loc ?? null; -} - -/** - * Format an error message from a CompileErrorDetail. - */ -function printErrorMessage(detail: CompileErrorDetail): string { - const buffer = [`[ReactCompilerError] ${detail.reason}`]; - if (detail.description != null) { - buffer.push(`\n\n${detail.description}.`); - } - return buffer.join(''); -} - function makeSuggestions( - detail: CompileErrorDetail, + detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, ): Array<Rule.SuggestionReportDescriptor> { const suggest: Array<Rule.SuggestionReportDescriptor> = []; if (Array.isArray(detail.suggestions)) { - for (const suggestion of detail.suggestions as Array<CompilerSuggestion>) { + for (const suggestion of detail.suggestions) { switch (suggestion.op) { case CompilerSuggestionOperation.InsertBefore: suggest.push({ @@ -142,8 +115,8 @@ function makeRule(rule: LintRule): Rule.RuleModule { if (event.kind === 'CompileError') { const detail = event.detail; if (detail.category === rule.category) { - const loc = primaryLocation(detail); - if (loc == null) { + const loc = detail.primaryLocation(); + if (loc == null || typeof loc === 'symbol') { continue; } if ( @@ -160,9 +133,11 @@ function makeRule(rule: LintRule): Rule.RuleModule { * we should deduplicate them with a "reported" set */ context.report({ - message: printErrorMessage(detail), + message: detail.printErrorMessage(result.sourceCode, { + eslint: true, + }), loc, - suggest: makeSuggestions(detail), + suggest: makeSuggestions(detail.options), }); } } diff --git a/packages/internal-test-utils/internalAct.js b/packages/internal-test-utils/internalAct.js index dba6d9f0139d..c2c4afb21ade 100644 --- a/packages/internal-test-utils/internalAct.js +++ b/packages/internal-test-utils/internalAct.js @@ -163,7 +163,7 @@ export async function act<T>(scope: () => Thenable<T>): Thenable<T> { throw thrownError; } - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-return] return result; } finally { const depth = actingUpdatesScopeDepth; @@ -285,7 +285,7 @@ export async function serverAct<T>(scope: () => Thenable<T>): Thenable<T> { throw thrownError; } - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-return] return result; } finally { if (typeof process === 'object') { diff --git a/packages/jest-react/package.json b/packages/jest-react/package.json index 10a7ad9bbb9c..a5d6878294e7 100644 --- a/packages/jest-react/package.json +++ b/packages/jest-react/package.json @@ -5,7 +5,7 @@ "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/jest-react" }, "keywords": [ @@ -15,7 +15,7 @@ ], "license": "MIT", "bugs": { - "url": "https://github.com/react/react/issues" + "url": "https://github.com/facebook/react/issues" }, "homepage": "https://react.dev/", "peerDependencies": { diff --git a/packages/react-art/package.json b/packages/react-art/package.json index 9be3f720b08d..daeb4ad2950b 100644 --- a/packages/react-art/package.json +++ b/packages/react-art/package.json @@ -5,7 +5,7 @@ "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-art" }, "keywords": [ @@ -18,7 +18,7 @@ ], "license": "MIT", "bugs": { - "url": "https://github.com/react/react/issues" + "url": "https://github.com/facebook/react/issues" }, "homepage": "https://react.dev/", "dependencies": { diff --git a/packages/react-cache/package.json b/packages/react-cache/package.json index 4ae3222c9f62..e0f8c0913ca0 100644 --- a/packages/react-cache/package.json +++ b/packages/react-cache/package.json @@ -5,7 +5,7 @@ "version": "2.0.0-alpha.0", "repository": { "type" : "git", - "url" : "https://github.com/react/react.git", + "url" : "https://github.com/facebook/react.git", "directory": "packages/react-cache" }, "files": [ diff --git a/packages/react-cache/src/LRU.js b/packages/react-cache/src/LRU.js index 087503853e71..bf4e4d4c6de8 100644 --- a/packages/react-cache/src/LRU.js +++ b/packages/react-cache/src/LRU.js @@ -56,21 +56,21 @@ export function createLRU<T>(limit: number): LRU<T> { function deleteLeastRecentlyUsedEntries(targetSize: number) { // Delete entries from the cache, starting from the end of the list. if (first !== null) { - const resolvedFirst: Entry<T> = first as any; + const resolvedFirst: Entry<T> = (first: any); let last: null | Entry<T> = resolvedFirst.previous; while (size > targetSize && last !== null) { const onDelete = last.onDelete; const previous = last.previous; - last.onDelete = null as any; + last.onDelete = (null: any); // Remove from the list - last.previous = last.next = null as any; + last.previous = last.next = (null: any); if (last === first) { // Reached the head of the list. first = last = null; } else { - (first as any).previous = previous; - previous.next = first as any; + (first: any).previous = previous; + previous.next = (first: any); last = previous; } @@ -88,8 +88,8 @@ export function createLRU<T>(limit: number): LRU<T> { const entry = { value, onDelete, - next: null as any, - previous: null as any, + next: (null: any), + previous: (null: any), }; if (first === null) { entry.previous = entry.next = entry; @@ -115,10 +115,9 @@ export function createLRU<T>(limit: number): LRU<T> { function access(entry: Entry<T>): T { const next = entry.next; - // $FlowFixMe[invalid-compare] if (next !== null) { // Entry already cached - const resolvedFirst: Entry<T> = first as any; + const resolvedFirst: Entry<T> = (first: any); if (first !== entry) { // Remove from current position const previous = entry.previous; diff --git a/packages/react-cache/src/ReactCacheOld.js b/packages/react-cache/src/ReactCacheOld.js index 5485505af7e2..7ea17286679d 100644 --- a/packages/react-cache/src/ReactCacheOld.js +++ b/packages/react-cache/src/ReactCacheOld.js @@ -107,14 +107,14 @@ function accessResult<I, K, V>( thenable.then( value => { if (newResult.status === Pending) { - const resolvedResult: ResolvedResult<V> = newResult as any; + const resolvedResult: ResolvedResult<V> = (newResult: any); resolvedResult.status = Resolved; resolvedResult.value = value; } }, error => { if (newResult.status === Pending) { - const rejectedResult: RejectedResult = newResult as any; + const rejectedResult: RejectedResult = (newResult: any); rejectedResult.status = Rejected; rejectedResult.value = error; } @@ -128,7 +128,7 @@ function accessResult<I, K, V>( entriesForResource.set(key, newEntry); return newResult; } else { - return lru.access(entry) as any; + return (lru.access(entry): any); } } @@ -147,7 +147,7 @@ export function unstable_createResource<I, K: string | number, V>( maybeHashInput?: I => K, ): Resource<I, V> { const hashInput: I => K = - maybeHashInput !== undefined ? maybeHashInput : (identityHashFn as any); + maybeHashInput !== undefined ? maybeHashInput : (identityHashFn: any); const resource = { read(input: I): V { @@ -171,7 +171,7 @@ export function unstable_createResource<I, K: string | number, V>( } default: // Should be unreachable - return undefined as any; + return (undefined: any); } }, diff --git a/packages/react-client/package.json b/packages/react-client/package.json index 27f1975f83d2..82c7d855ad1c 100644 --- a/packages/react-client/package.json +++ b/packages/react-client/package.json @@ -7,7 +7,7 @@ "react" ], "homepage": "https://react.dev/", - "bugs": "https://github.com/react/react/issues", + "bugs": "https://github.com/facebook/react/issues", "license": "MIT", "files": [ "LICENSE", @@ -17,7 +17,7 @@ ], "repository": { "type" : "git", - "url" : "https://github.com/react/react.git", + "url" : "https://github.com/facebook/react.git", "directory": "packages/react-client" }, "engines": { diff --git a/packages/react-client/src/ReactClientConsoleConfigBrowser.js b/packages/react-client/src/ReactClientConsoleConfigBrowser.js index 3a8625177ea2..f67e4afa0c46 100644 --- a/packages/react-client/src/ReactClientConsoleConfigBrowser.js +++ b/packages/react-client/src/ReactClientConsoleConfigBrowser.js @@ -35,7 +35,7 @@ export function bindToConsole( case 'groupEnd': case 'table': { // These methods cannot be colorized because they don't take a formatting string. - // $FlowFixMe[incompatible-type] + // $FlowFixMe return bind.apply(console[methodName], [console].concat(args)); // eslint-disable-line react-internal/no-production-logging } case 'assert': { @@ -68,7 +68,6 @@ export function bindToConsole( // The "this" binding in the "bind"; newArgs.unshift(console); - // $FlowFixMe[incompatible-type] - // $FlowFixMe[invalid-computed-prop] + // $FlowFixMe return bind.apply(console[methodName], newArgs); // eslint-disable-line react-internal/no-production-logging } diff --git a/packages/react-client/src/ReactClientConsoleConfigPlain.js b/packages/react-client/src/ReactClientConsoleConfigPlain.js index 3841007b4924..ee4c87ca6133 100644 --- a/packages/react-client/src/ReactClientConsoleConfigPlain.js +++ b/packages/react-client/src/ReactClientConsoleConfigPlain.js @@ -25,7 +25,7 @@ export function bindToConsole( case 'groupEnd': case 'table': { // These methods cannot be colorized because they don't take a formatting string. - // $FlowFixMe[incompatible-type] + // $FlowFixMe return bind.apply(console[methodName], [console].concat(args)); // eslint-disable-line react-internal/no-production-logging } case 'assert': { @@ -49,7 +49,6 @@ export function bindToConsole( // The "this" binding in the "bind"; newArgs.unshift(console); - // $FlowFixMe[incompatible-type] - // $FlowFixMe[invalid-computed-prop] + // $FlowFixMe return bind.apply(console[methodName], newArgs); // eslint-disable-line react-internal/no-production-logging } diff --git a/packages/react-client/src/ReactClientConsoleConfigServer.js b/packages/react-client/src/ReactClientConsoleConfigServer.js index 6663549fbd70..6e69ef12a3ce 100644 --- a/packages/react-client/src/ReactClientConsoleConfigServer.js +++ b/packages/react-client/src/ReactClientConsoleConfigServer.js @@ -36,7 +36,7 @@ export function bindToConsole( case 'groupEnd': case 'table': { // These methods cannot be colorized because they don't take a formatting string. - // $FlowFixMe[incompatible-type] + // $FlowFixMe return bind.apply(console[methodName], [console].concat(args)); // eslint-disable-line react-internal/no-production-logging } case 'assert': { @@ -69,7 +69,6 @@ export function bindToConsole( // The "this" binding in the "bind"; newArgs.unshift(console); - // $FlowFixMe[incompatible-type] - // $FlowFixMe[invalid-computed-prop] + // $FlowFixMe return bind.apply(console[methodName], newArgs); // eslint-disable-line react-internal/no-production-logging } diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 7ac416bbe6b6..f67d99ba71de 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -116,7 +116,7 @@ import type {SharedStateClient} from 'react/src/ReactSharedInternalsClient'; // client both in the RSC environment, in the SSR environments as well as the // browser client. We should probably have a separate RSC build. This is DEV // only though. -const ReactSharedInteralsServer: void | SharedStateServer = (React as any) +const ReactSharedInteralsServer: void | SharedStateServer = (React: any) .__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; const ReactSharedInternals: SharedStateServer | SharedStateClient = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE || @@ -254,7 +254,7 @@ function ReactPromise(status: any, value: any, reason: any) { } } // We subclass Promise.prototype so that we get other methods like .catch -ReactPromise.prototype = Object.create(Promise.prototype) as any; +ReactPromise.prototype = (Object.create(Promise.prototype): any); // TODO: This doesn't return a new Promise chain unlike the real .then ReactPromise.prototype.then = function <T>( this: SomeChunk<T>, @@ -281,12 +281,12 @@ ReactPromise.prototype.then = function <T>( const rejectCallback = reject; const wrapperPromise: Promise<T> = new Promise((res, rej) => { resolve = value => { - // $FlowFixMe[prop-missing] + // $FlowFixMe wrapperPromise._debugInfo = this._debugInfo; res(value); }; reject = reason => { - // $FlowFixMe[prop-missing] + // $FlowFixMe wrapperPromise._debugInfo = this._debugInfo; rej(reason); }; @@ -304,15 +304,15 @@ ReactPromise.prototype.then = function <T>( case BLOCKED: if (typeof resolve === 'function') { if (chunk.value === null) { - chunk.value = [] as Array<InitializationReference | (T => mixed)>; + chunk.value = ([]: Array<InitializationReference | (T => mixed)>); } chunk.value.push(resolve); } if (typeof reject === 'function') { if (chunk.reason === null) { - chunk.reason = [] as Array< + chunk.reason = ([]: Array< InitializationReference | (mixed => mixed), - >; + >); } chunk.reason.push(reject); } @@ -394,7 +394,7 @@ function unwrapWeakResponse(weakResponse: WeakResponse): Response { } return response; } else { - return weakResponse as any; // In prod we just use the real Response directly. + return (weakResponse: any); // In prod we just use the real Response directly. } } @@ -402,7 +402,7 @@ function getWeakResponse(response: Response): WeakResponse { if (__DEV__) { return response._weakResponse; } else { - return response as any; // In prod we just use the real Response directly. + return (response: any); // In prod we just use the real Response directly. } } @@ -437,7 +437,7 @@ function readChunk<T>(chunk: SomeChunk<T>): T { case BLOCKED: case HALTED: // eslint-disable-next-line no-throw-literal - throw chunk as any as Thenable<T>; + throw ((chunk: any): Thenable<T>); default: throw chunk.reason; } @@ -446,7 +446,7 @@ function readChunk<T>(chunk: SomeChunk<T>): T { export function getRoot<T>(weakResponse: WeakResponse): Thenable<T> { const response = unwrapWeakResponse(weakResponse); const chunk = getChunk(response, 0); - return chunk as any; + return (chunk: any); } function createPendingChunk<T>(response: Response): PendingChunk<T> { @@ -499,7 +499,6 @@ function filterDebugInfo( response: Response, value: {_debugInfo: ReactDebugInfo, ...}, ) { - // $FlowFixMe[invalid-compare] if (response._debugEndTime === null) { // No end time was defined, so we keep all debug info entries. return; @@ -547,7 +546,7 @@ function moveDebugInfoFromChunkToInnerValue<T>( debugInfo, ); } else if (!Object.isFrozen(resolvedValue)) { - Object.defineProperty(resolvedValue as any, '_debugInfo', { + Object.defineProperty((resolvedValue: any), '_debugInfo', { configurable: false, enumerable: false, writable: true, @@ -665,9 +664,9 @@ function wakeChunkIfInitialized<T>( } } // The status might have changed after fulfilling the reference. - switch ((chunk as SomeChunk<T>).status) { + switch ((chunk: SomeChunk<T>).status) { case INITIALIZED: - const initializedChunk: InitializedChunk<T> = chunk as any; + const initializedChunk: InitializedChunk<T> = (chunk: any); wakeChunk( response, resolveListeners, @@ -721,9 +720,9 @@ function triggerErrorOnChunk<T>( if (chunk.status !== PENDING && chunk.status !== BLOCKED) { // If we get more data to an already resolved ID, we assume that it's // a stream chunk since any other row shouldn't have more than one entry. - const streamChunk: InitializedStreamChunk<any> = chunk as any; + const streamChunk: InitializedStreamChunk<any> = (chunk: any); const controller = streamChunk.reason; - // $FlowFixMe[incompatible-type]: The error method should accept mixed. + // $FlowFixMe[incompatible-call]: The error method should accept mixed. controller.error(error); return; } @@ -736,7 +735,7 @@ function triggerErrorOnChunk<T>( const prevHandler = initializingHandler; const prevChunk = initializingChunk; initializingHandler = null; - const cyclicChunk: BlockedChunk<T> = chunk as any; + const cyclicChunk: BlockedChunk<T> = (chunk: any); cyclicChunk.status = BLOCKED; cyclicChunk.value = null; cyclicChunk.reason = null; @@ -761,7 +760,7 @@ function triggerErrorOnChunk<T>( } } - const erroredChunk: ErroredChunk<T> = chunk as any; + const erroredChunk: ErroredChunk<T> = (chunk: any); erroredChunk.status = ERRORED; erroredChunk.reason = error; if (listeners !== null) { @@ -861,7 +860,7 @@ function resolveModelChunk<T>( if (chunk.status !== PENDING) { // If we get more data to an already resolved ID, we assume that it's // a stream chunk since any other row shouldn't have more than one entry. - const streamChunk: InitializedStreamChunk<any> = chunk as any; + const streamChunk: InitializedStreamChunk<any> = (chunk: any); const controller = streamChunk.reason; controller.enqueueModel(value); return; @@ -869,7 +868,7 @@ function resolveModelChunk<T>( releasePendingChunk(response, chunk); const resolveListeners = chunk.value; const rejectListeners = chunk.reason; - const resolvedChunk: ResolvedModelChunk<T> = chunk as any; + const resolvedChunk: ResolvedModelChunk<T> = (chunk: any); resolvedChunk.status = RESOLVED_MODEL; resolvedChunk.value = value; resolvedChunk.reason = response; @@ -895,7 +894,7 @@ function resolveModuleChunk<T>( releasePendingChunk(response, chunk); const resolveListeners = chunk.value; const rejectListeners = chunk.reason; - const resolvedChunk: ResolvedModuleChunk<T> = chunk as any; + const resolvedChunk: ResolvedModuleChunk<T> = (chunk: any); resolvedChunk.status = RESOLVED_MODULE; resolvedChunk.value = value; resolvedChunk.reason = null; @@ -960,7 +959,7 @@ function initializeDebugChunk( } // Initializing the model for the first time. initializeModelChunk(debugChunk); - const initializedChunk = debugChunk as any as SomeChunk<any>; + const initializedChunk = ((debugChunk: any): SomeChunk<any>); switch (initializedChunk.status) { case INITIALIZED: { debugInfo[idx] = initializeDebugInfo( @@ -1028,7 +1027,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void { // We go to the BLOCKED state until we've fully resolved this. // We do this before parsing in case we try to initialize the same chunk // while parsing the model. Such as in a cyclic reference. - const cyclicChunk: BlockedChunk<T> = chunk as any; + const cyclicChunk: BlockedChunk<T> = (chunk: any); cyclicChunk.status = BLOCKED; cyclicChunk.value = null; cyclicChunk.reason = null; @@ -1075,7 +1074,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void { return; } } - const initializedChunk: InitializedChunk<T> = chunk as any; + const initializedChunk: InitializedChunk<T> = (chunk: any); initializedChunk.status = INITIALIZED; initializedChunk.value = value; initializedChunk.reason = null; @@ -1084,7 +1083,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void { processChunkDebugInfo(response, initializedChunk, value); } } catch (error) { - const erroredChunk: ErroredChunk<T> = chunk as any; + const erroredChunk: ErroredChunk<T> = (chunk: any); erroredChunk.status = ERRORED; erroredChunk.reason = error; } finally { @@ -1098,12 +1097,12 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void { function initializeModuleChunk<T>(chunk: ResolvedModuleChunk<T>): void { try { const value: T = requireModule(chunk.value); - const initializedChunk: InitializedChunk<T> = chunk as any; + const initializedChunk: InitializedChunk<T> = (chunk: any); initializedChunk.status = INITIALIZED; initializedChunk.value = value; initializedChunk.reason = null; } catch (error) { - const erroredChunk: ErroredChunk<T> = chunk as any; + const erroredChunk: ErroredChunk<T> = (chunk: any); erroredChunk.status = ERRORED; erroredChunk.reason = error; } @@ -1240,7 +1239,7 @@ function initializeElement( element._debugStack = normalizedStackTrace; let task: null | ConsoleTask = null; if (supportsCreateTask && stack !== null) { - const createTaskFn = (console as any).createTask.bind( + const createTaskFn = (console: any).createTask.bind( console, getTaskName(element.type), ); @@ -1322,19 +1321,19 @@ function createElement( let element: any; if (__DEV__) { // `ref` is non-enumerable in dev - element = { + element = ({ $$typeof: REACT_ELEMENT_TYPE, type, key, props, _owner: owner === undefined ? null : owner, - } as any; + }: any); Object.defineProperty(element, 'ref', { enumerable: false, get: nullRefGetter, }); } else { - element = { + element = ({ // This tag allows us to uniquely identify this as a React Element $$typeof: REACT_ELEMENT_TYPE, @@ -1342,16 +1341,16 @@ function createElement( key, ref: null, props, - } as any; + }: any); } if (__DEV__) { // We don't really need to add any of these but keeping them for good measure. // Unfortunately, _store is enumerable in jest matchers so for equality to // work, I need to keep it or make _store non-enumerable in the other file. - element._store = {} as { + element._store = ({}: { validated?: number, - }; + }); Object.defineProperty(element._store, 'validated', { configurable: false, enumerable: false, @@ -1463,7 +1462,7 @@ function getChunk(response: Response, id: number): SomeChunk<any> { // For partial streams, chunks accessed after close should be HALTED // (never resolve). chunk = createPendingChunk(response); - const haltedChunk: HaltedChunk<any> = chunk as any; + const haltedChunk: HaltedChunk<any> = (chunk: any); haltedChunk.status = HALTED; haltedChunk.value = null; haltedChunk.reason = null; @@ -1673,7 +1672,7 @@ function fulfillReference( return; } const resolveListeners = chunk.value; - const initializedChunk: InitializedChunk<any> = chunk as any; + const initializedChunk: InitializedChunk<any> = (chunk: any); initializedChunk.status = INITIALIZED; initializedChunk.value = handler.value; initializedChunk.reason = handler.reason; // Used by streaming chunks @@ -1762,7 +1761,7 @@ function waitForReference<T>( // If it's still pending that suggests that it was referencing an object in the debug // channel, but no debug channel was wired up so it's missing. In this case we can just // drop the debug info instead of halting the whole stream. - return null as any; + return (null: any); } } @@ -1805,7 +1804,7 @@ function waitForReference<T>( } // Return a place holder value for now. - return null as any; + return (null: any); } function loadServerReference<A: Iterable<any>, T>( @@ -1840,7 +1839,7 @@ function loadServerReference<A: Iterable<any>, T>( let promise: null | Thenable<any> = preloadModule(serverReference); if (!promise) { if (!metaData.bound) { - const resolvedValue = requireModule(serverReference) as any; + const resolvedValue = (requireModule(serverReference): any); registerBoundServerReference( resolvedValue, metaData.id, @@ -1871,11 +1870,11 @@ function loadServerReference<A: Iterable<any>, T>( } function fulfill(): void { - let resolvedValue = requireModule(serverReference) as any; + let resolvedValue = (requireModule(serverReference): any); if (metaData.bound) { // This promise is coming from us and should have initilialized by now. - const boundArgs: Array<any> = (metaData.bound as any).value.slice(0); + const boundArgs: Array<any> = (metaData.bound: any).value.slice(0); boundArgs.unshift(null); // this resolvedValue = resolvedValue.bind.apply(resolvedValue, boundArgs); } @@ -1927,7 +1926,7 @@ function loadServerReference<A: Iterable<any>, T>( return; } const resolveListeners = chunk.value; - const initializedChunk: InitializedChunk<T> = chunk as any; + const initializedChunk: InitializedChunk<T> = (chunk: any); initializedChunk.status = INITIALIZED; initializedChunk.value = handler.value; initializedChunk.reason = null; @@ -1990,7 +1989,7 @@ function loadServerReference<A: Iterable<any>, T>( promise.then(fulfill, reject); // Return a place holder value for now. - return null as any; + return (null: any); } function resolveLazy(value: any): mixed { @@ -2025,7 +2024,7 @@ function transferReferencedDebugInfo( for (let i = 0; i < referencedDebugInfo.length; ++i) { const debugInfoEntry = referencedDebugInfo[i]; if (debugInfoEntry.name != null) { - debugInfoEntry as ReactComponentInfo; + (debugInfoEntry: ReactComponentInfo); // We're not transferring Component info since we use Component info // in Debug info to fill in gaps between Fibers for the parent stack. } else { @@ -2112,7 +2111,7 @@ function getOutlinedModel<T>( errored: false, }; } - return null as any; + return (null: any); } default: { // This is an error. Instead of erroring directly, we're going to encode this on @@ -2131,7 +2130,7 @@ function getOutlinedModel<T>( errored: true, }; } - return null as any; + return (null: any); } } } @@ -2209,7 +2208,7 @@ function getOutlinedModel<T>( errored: false, }; } - return null as any; + return (null: any); } default: // This is an error. Instead of erroring directly, we're going to encode this on @@ -2229,7 +2228,7 @@ function getOutlinedModel<T>( }; } // Placeholder - return null as any; + return (null: any); } } @@ -2651,7 +2650,7 @@ function parseModelTuple( response: Response, value: {+[key: string]: JSONValue} | $ReadOnlyArray<JSONValue>, ): any { - const tuple: [mixed, mixed, mixed, mixed] = value as any; + const tuple: [mixed, mixed, mixed, mixed] = (value: any); if (tuple[0] === REACT_ELEMENT_TYPE) { // TODO: Consider having React just directly accept these arrays as elements. @@ -2661,9 +2660,9 @@ function parseModelTuple( tuple[1], tuple[2], tuple[3], - __DEV__ ? (tuple as any)[4] : null, - __DEV__ ? (tuple as any)[5] : null, - __DEV__ ? (tuple as any)[6] : 0, + __DEV__ ? (tuple: any)[4] : null, + __DEV__ ? (tuple: any)[5] : null, + __DEV__ ? (tuple: any)[6] : 0, ); } return value; @@ -2730,7 +2729,7 @@ function ResponseInstance( ReactSharedInteralsServer === undefined || ReactSharedInteralsServer.A === null ? null - : (ReactSharedInteralsServer.A.getOwner() as any); + : (ReactSharedInteralsServer.A.getOwner(): any); this._debugRootOwner = rootOwner; this._debugRootStack = @@ -2746,7 +2745,7 @@ function ResponseInstance( // elements created by the server. We use the "use server" string to indicate that // this is where we enter the server from the client. // TODO: Make this string configurable. - this._debugRootTask = (console as any).createTask( + this._debugRootTask = (console: any).createTask( '"use ' + rootEnv.toLowerCase() + '"', ); } @@ -2850,19 +2849,19 @@ export function createStreamState( weakResponse: WeakResponse, // DEV-only streamDebugValue: mixed, // DEV-only ): StreamState { - const streamState: StreamState = { + const streamState: StreamState = (({ _rowState: 0, _rowID: 0, _rowTag: 0, _rowLength: 0, _buffer: [], - } as Omit<StreamState, '_debugInfo' | '_debugTargetChunkSize'> as any; + }: Omit<StreamState, '_debugInfo' | '_debugTargetChunkSize'>): any); if (__DEV__ && enableAsyncDebugInfo) { const response = unwrapWeakResponse(weakResponse); // Create an entry for the I/O to load the stream itself. const debugValuePromise = Promise.resolve(streamDebugValue); - (debugValuePromise as any).status = 'fulfilled'; - (debugValuePromise as any).value = streamDebugValue; + (debugValuePromise: any).status = 'fulfilled'; + (debugValuePromise: any).value = streamDebugValue; streamState._debugInfo = { name: 'rsc stream', start: response._debugStartTime, @@ -2896,7 +2895,7 @@ function incrementChunkDebugInfo( const debugInfo: ReactIOInfo = streamState._debugInfo; const endTime = performance.now(); const previousEndTime = debugInfo.end; - const newByteLength = (debugInfo.byteSize as any as number) + chunkLength; + const newByteLength = ((debugInfo.byteSize: any): number) + chunkLength; if ( newByteLength > streamState._debugTargetChunkSize || endTime > previousEndTime + 10 @@ -2942,7 +2941,7 @@ function addAsyncInfo(chunk: SomeChunk<any>, asyncInfo: ReactAsyncInfo): void { } else if (!Object.isFrozen(value)) { // TODO: Debug info is dropped for frozen elements. See the TODO in // moveDebugInfoFromChunkToInnerValue. - Object.defineProperty(value as any, '_debugInfo', { + Object.defineProperty((value: any), '_debugInfo', { configurable: false, enumerable: false, writable: true, @@ -2988,7 +2987,7 @@ function resolveDebugHalt(response: Response, id: number): void { return; } releasePendingChunk(response, chunk); - const haltedChunk: HaltedChunk<any> = chunk as any; + const haltedChunk: HaltedChunk<any> = (chunk: any); haltedChunk.status = HALTED; haltedChunk.value = null; haltedChunk.reason = null; @@ -3030,7 +3029,7 @@ function resolveText( if (chunk && chunk.status !== PENDING) { // If we get more data to an already resolved ID, we assume that it's // a stream chunk since any other row shouldn't have more than one entry. - const streamChunk: InitializedStreamChunk<any> = chunk as any; + const streamChunk: InitializedStreamChunk<any> = (chunk: any); const controller = streamChunk.reason; controller.enqueueValue(text); return; @@ -3056,7 +3055,7 @@ function resolveBuffer( if (chunk && chunk.status !== PENDING) { // If we get more data to an already resolved ID, we assume that it's // a stream chunk since any other row shouldn't have more than one entry. - const streamChunk: InitializedStreamChunk<any> = chunk as any; + const streamChunk: InitializedStreamChunk<any> = (chunk: any); const controller = streamChunk.reason; controller.enqueueValue(buffer); return; @@ -3109,7 +3108,7 @@ function resolveModule( releasePendingChunk(response, chunk); // This can't actually happen because we don't have any forward // references to modules. - blockedChunk = chunk as any; + blockedChunk = (chunk: any); blockedChunk.status = BLOCKED; } if (__DEV__) { @@ -3171,7 +3170,7 @@ function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>( const prevHandler = initializingHandler; const prevChunk = initializingChunk; initializingHandler = null; - const cyclicChunk: BlockedChunk<T> = chunk as any; + const cyclicChunk: BlockedChunk<T> = (chunk: any); cyclicChunk.status = BLOCKED; cyclicChunk.value = null; cyclicChunk.reason = null; @@ -3198,12 +3197,12 @@ function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>( } } - const resolvedChunk: InitializedStreamChunk<T> = chunk as any; + const resolvedChunk: InitializedStreamChunk<T> = (chunk: any); resolvedChunk.status = INITIALIZED; resolvedChunk.value = stream; resolvedChunk.reason = controller; if (resolveListeners !== null) { - wakeChunk(response, resolveListeners, chunk.value, chunk as any); + wakeChunk(response, resolveListeners, chunk.value, (chunk: any)); } else { if (__DEV__) { processChunkDebugInfo(response, resolvedChunk, stream); @@ -3217,7 +3216,7 @@ function startReadableStream<T>( type: void | 'bytes', streamState: StreamState, ): void { - let controller: ReadableStreamController = null as any; + let controller: ReadableStreamController = (null: any); let closed = false; const stream = new ReadableStream({ type: type, @@ -3252,7 +3251,7 @@ function startReadableStream<T>( } else { chunk.then( v => controller.enqueue(v), - e => controller.error(e as any), + e => controller.error((e: any)), ); previousBlockedChunk = chunk; } @@ -3262,7 +3261,7 @@ function startReadableStream<T>( const chunk: SomeChunk<T> = createPendingChunk(response); chunk.then( v => controller.enqueue(v), - e => controller.error(e as any), + e => controller.error((e: any)), ); previousBlockedChunk = chunk; blockedChunk.then(function () { @@ -3295,13 +3294,13 @@ function startReadableStream<T>( } closed = true; if (previousBlockedChunk === null) { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] controller.error(error); } else { const blockedChunk = previousBlockedChunk; // We shouldn't get any more enqueues after this so we can set it back to null. previousBlockedChunk = null; - blockedChunk.then(() => controller.error(error as any)); + blockedChunk.then(() => controller.error((error: any))); } }, }; @@ -3323,7 +3322,7 @@ function createIterator<T>( // TODO: The iterator could inherit the AsyncIterator prototype which is not exposed as // a global but exists as a prototype of an AsyncGenerator. However, it's not needed // to satisfy the iterable protocol. - (iterator as any)[ASYNC_ITERATOR] = asyncIterator; + (iterator: any)[ASYNC_ITERATOR] = asyncIterator; return iterator; } @@ -3345,13 +3344,13 @@ function startAsyncIterable<T>( false, ); } else { - const chunk: PendingChunk<IteratorResult<T, T>> = buffer[ + const chunk: PendingChunk<IteratorResult<T, T>> = (buffer[ nextWriteIndex - ] as any; + ]: any); const resolveListeners = chunk.value; const rejectListeners = chunk.reason; const initializedChunk: InitializedChunk<IteratorResult<T, T>> = - chunk as any; + (chunk: any); initializedChunk.status = INITIALIZED; initializedChunk.value = {done: false, value: value}; initializedChunk.reason = null; @@ -3428,7 +3427,7 @@ function startAsyncIterable<T>( }, }; - const iterable: $AsyncIterable<T, T, void> = {} as any; + const iterable: $AsyncIterable<T, T, void> = ({}: any); // $FlowFixMe[cannot-write] iterable[ASYNC_ITERATOR] = (): $AsyncIterator<T, T, void> => { let nextReadIndex = 0; @@ -3484,7 +3483,7 @@ function stopStream( response._weakResponse.response = null; } } - const streamChunk: InitializedStreamChunk<any> = chunk as any; + const streamChunk: InitializedStreamChunk<any> = (chunk: any); const controller = streamChunk.reason; controller.close(row === '' ? '"$undefined"' : row); } @@ -3527,13 +3526,12 @@ function resolveErrorDev( let error; const errorOptions = // We don't serialize Error.cause in prod so we never need to deserialize - // $FlowFixMe[constant-condition] __DEV__ && 'cause' in errorInfo ? { cause: reviveModel( response, - // $FlowFixMe[incompatible-type] -- Flow thinks `cause` in `cause?: JSONValue` can be undefined after `in` check. - errorInfo.cause as JSONValue, + // $FlowFixMe[incompatible-cast] -- Flow thinks `cause` in `cause?: JSONValue` can be undefined after `in` check. + (errorInfo.cause: JSONValue), errorInfo, 'cause', ), @@ -3546,8 +3544,8 @@ function resolveErrorDev( __DEV__ && isAggregateError ? reviveModel( response, - // $FlowFixMe[incompatible-type] - errorInfo.errors as JSONValue, + // $FlowFixMe[incompatible-cast] + (errorInfo.errors: JSONValue), errorInfo, 'errors', ) @@ -3597,8 +3595,8 @@ function resolveErrorDev( error = ownerTask.run(callStack); } - (error as any).name = name; - (error as any).environmentName = env; + (error: any).name = name; + (error: any).environmentName = env; return error; } @@ -3617,8 +3615,8 @@ function resolveErrorModel( } else { error = resolveErrorProd(response); } - (error as any).digest = errorInfo.digest; - const errorWithDigest: ErrorWithDigest = error as any; + (error: any).digest = errorInfo.digest; + const errorWithDigest: ErrorWithDigest = (error: any); if (!chunk) { const newChunk: ErroredChunk<any> = createErrorChunk( response, @@ -3645,12 +3643,12 @@ function resolveHint<Code: HintCode>( dispatchHint(code, hintModel); } -const supportsCreateTask = __DEV__ && !!(console as any).createTask; +const supportsCreateTask = __DEV__ && !!(console: any).createTask; type FakeFunction<T> = (() => T) => T; const fakeFunctionCache: Map<string, FakeFunction<any>> = __DEV__ ? new Map() - : (null as any); + : (null: any); let fakeFunctionIdx = 0; function createFakeFunction<T>( @@ -3882,7 +3880,7 @@ function getRootTask( // If the root most owner component is itself in a different environment than the requested // environment then we create an extra task to indicate that we're transitioning into it. // Like if one environment just requests another environment. - const createTaskFn = (console as any).createTask.bind( + const createTaskFn = (console: any).createTask.bind( console, '"use ' + childEnvironmentName.toLowerCase() + '"', ); @@ -3935,10 +3933,10 @@ function initializeFakeTask( ? '"use ' + env.toLowerCase() + '"' : // Some unfortunate pattern matching to refine the type. debugInfo.key !== undefined - ? getServerComponentTaskName(debugInfo as any as ReactComponentInfo) + ? getServerComponentTaskName(((debugInfo: any): ReactComponentInfo)) : debugInfo.name !== undefined - ? getIOInfoTaskName(debugInfo as any as ReactIOInfo) - : getAsyncInfoTaskName(debugInfo as any as ReactAsyncInfo); + ? getIOInfoTaskName(((debugInfo: any): ReactIOInfo)) + : getAsyncInfoTaskName(((debugInfo: any): ReactAsyncInfo)); // $FlowFixMe[cannot-write]: We consider this part of initialization. return (debugInfo.debugTask = buildFakeTask( response, @@ -3958,7 +3956,7 @@ function buildFakeTask( env: string, useEnclosingLine: boolean, ): ConsoleTask { - const createTaskFn = (console as any).createTask.bind(console, taskName); + const createTaskFn = (console: any).createTask.bind(console, taskName); const callStack = buildFakeCallStack( response, stack, @@ -4003,8 +4001,8 @@ const createFakeJSXCallStackInDEV: ( ? // We use this technique to trick minifiers to preserve the function name. (createFakeJSXCallStack.react_stack_bottom_frame.bind( createFakeJSXCallStack, - ) as any) - : (null as any); + ): any) + : (null: any); /** @noinline */ function fakeJSXCallSite() { @@ -4061,7 +4059,7 @@ function initializeDebugInfo( } if (debugInfo.owner == null && response._debugRootOwner != null) { const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo = - // $FlowFixMe[incompatible-type]: By narrowing `owner` to `null`, we narrowed `debugInfo` to `ReactComponentInfo` + // $FlowFixMe: By narrowing `owner` to `null`, we narrowed `debugInfo` to `ReactComponentInfo` debugInfo; // $FlowFixMe[cannot-write] componentInfoOrAsyncInfo.owner = response._debugRootOwner; @@ -4120,7 +4118,7 @@ function resolveDebugModel( initializeDebugChunk(response, parentChunk); if ( __DEV__ && - (debugChunk as any as SomeChunk<any>).status === BLOCKED && + ((debugChunk: any): SomeChunk<any>).status === BLOCKED && (response._debugChannel === undefined || !response._debugChannel.hasReadable) ) { @@ -4167,7 +4165,7 @@ const replayConsoleWithCallStack = { const prevStack = ReactSharedInternals.getCurrentStack; ReactSharedInternals.getCurrentStack = getCurrentStackInDEV; currentOwnerInDEV = - owner === null ? (response._debugRootOwner as any) : owner; + owner === null ? (response._debugRootOwner: any) : owner; try { const callStack = buildFakeCallStack( @@ -4205,8 +4203,8 @@ const replayConsoleWithCallStackInDEV: ( ? // We use this technique to trick minifiers to preserve the function name. (replayConsoleWithCallStack.react_stack_bottom_frame.bind( replayConsoleWithCallStack, - ) as any) - : (null as any); + ): any) + : (null: any); type ConsoleEntry = [ string, @@ -4290,7 +4288,7 @@ function initializeIOInfo(response: Response, ioInfo: ReactIOInfo): void { const env = response._rootEnvironmentName; const promise = ioInfo.value; if (promise) { - const thenable: Thenable<mixed> = promise as any; + const thenable: Thenable<mixed> = (promise: any); switch (thenable.status) { case INITIALIZED: logIOInfo(ioInfo, env, thenable.value); @@ -4413,7 +4411,7 @@ function logComponentInfo( childrenEndTime: number, isLastComponent: boolean, ): void { - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. if ( isLastComponent && root.status === ERRORED && @@ -4592,7 +4590,7 @@ function flushComponentPerformance( if (componentEndTime > childrenEndTime) { childrenEndTime = componentEndTime; } - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. const componentInfo: ReactComponentInfo = candidateInfo; logComponentInfo( response, @@ -4616,12 +4614,12 @@ function flushComponentPerformance( if (endTime > childrenEndTime) { childrenEndTime = endTime; } - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. const asyncInfo: ReactAsyncInfo = candidateInfo; const env = response._rootEnvironmentName; const promise = asyncInfo.awaited.value; if (promise) { - const thenable: Thenable<mixed> = promise as any; + const thenable: Thenable<mixed> = (promise: any); switch (thenable.status) { case INITIALIZED: logComponentAwait( @@ -4679,7 +4677,7 @@ function flushComponentPerformance( if (componentEndTime > childrenEndTime) { childrenEndTime = componentEndTime; } - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. const componentInfo: ReactComponentInfo = candidateInfo; const env = response._rootEnvironmentName; logComponentAborted( @@ -4889,7 +4887,7 @@ function processFullStringRow( return; } case 72 /* "H" */: { - const code: HintCode = row[0] as any; + const code: HintCode = (row[0]: any); resolveHint(response, code, row.slice(1)); return; } @@ -5323,9 +5321,8 @@ function reviveModel( } if (isArray(value)) { for (let i = 0; i < value.length; i++) { - (value as any)[i] = reviveModel(response, value[i], value, '' + i); + (value: any)[i] = reviveModel(response, value[i], value, '' + i); } - // $FlowFixMe[invalid-compare] if (value[0] === REACT_ELEMENT_TYPE) { // React element tuple return parseModelTuple(response, value); @@ -5335,13 +5332,13 @@ function reviveModel( // Plain object for (const k in value) { if (k === __PROTO__) { - delete (value as any)[k]; + delete (value: any)[k]; } else { - const walked = reviveModel(response, (value as any)[k], value, k); + const walked = reviveModel(response, (value: any)[k], value, k); if (walked !== undefined) { - (value as any)[k] = walked; + (value: any)[k] = walked; } else { - delete (value as any)[k]; + delete (value: any)[k]; } } } @@ -5366,7 +5363,7 @@ export function close(weakResponse: WeakResponse): void { // Clear listeners to release closures and transition to HALTED. // Future .then() calls on HALTED chunks are no-ops. releasePendingChunk(response, chunk); - const haltedChunk: HaltedChunk<any> = chunk as any; + const haltedChunk: HaltedChunk<any> = (chunk: any); haltedChunk.status = HALTED; haltedChunk.value = null; haltedChunk.reason = null; diff --git a/packages/react-client/src/ReactFlightClientStreamConfigNode.js b/packages/react-client/src/ReactFlightClientStreamConfigNode.js index 094bc4f94cb6..f544759ccce1 100644 --- a/packages/react-client/src/ReactFlightClientStreamConfigNode.js +++ b/packages/react-client/src/ReactFlightClientStreamConfigNode.js @@ -21,7 +21,6 @@ export function readPartialStringChunk( decoder: StringDecoder, buffer: Uint8Array, ): string { - // $FlowFixMe[incompatible-type] return decoder.decode(buffer, decoderOptions); } diff --git a/packages/react-client/src/ReactFlightReplyClient.js b/packages/react-client/src/ReactFlightReplyClient.js index 0a4b0edce8e4..9eaef573be4a 100644 --- a/packages/react-client/src/ReactFlightReplyClient.js +++ b/packages/react-client/src/ReactFlightReplyClient.js @@ -415,7 +415,7 @@ export function processReply( } if (typeof value === 'object') { - switch ((value as any).$$typeof) { + switch ((value: any).$$typeof) { case REACT_ELEMENT_TYPE: { if (temporaryReferences !== undefined && key.indexOf(':') === -1) { // TODO: If the property name contains a colon, we don't dedupe. Escape instead. @@ -445,7 +445,7 @@ export function processReply( } case REACT_LAZY_TYPE: { // Resolve lazy as if it wasn't here. In the future this will be encoded as a Promise. - const lazy: LazyComponent<any, any> = value as any; + const lazy: LazyComponent<any, any> = (value: any); const payload = lazy._payload; const init = lazy._init; if (formData === null) { @@ -472,7 +472,7 @@ export function processReply( // Suspended pendingParts++; const lazyId = nextPartId++; - const thenable: Thenable<any> = x as any; + const thenable: Thenable<any> = (x: any); const retry = function () { // While the first promise resolved, its value isn't necessarily what we'll // resolve into because we might suspend again. @@ -529,7 +529,7 @@ export function processReply( const promiseId = nextPartId++; const promiseReference = serializePromiseID(promiseId); writtenObjects.set(value, promiseReference); - const thenable: Thenable<any> = value as any; + const thenable: Thenable<any> = (value: any); thenable.then( partValue => { try { @@ -584,7 +584,7 @@ export function processReply( } if (isArray(value)) { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-return] return value; } // TODO: Should we the Object.prototype.toString.call() to test for cross-realm objects? @@ -603,7 +603,7 @@ export function processReply( const prefix = formFieldPrefix + '_' + refId + '_'; // $FlowFixMe[prop-missing]: FormData has forEach. value.forEach((originalValue: string | File, originalKey: string) => { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] data.append(prefix + originalKey, originalValue); }); return serializeFormDataReference(refId); @@ -697,12 +697,11 @@ export function processReply( const iteratorFn = getIteratorFn(value); if (iteratorFn) { const iterator = iteratorFn.call(value); - // $FlowFixMe[invalid-compare] if (iterator === value) { // Iterator, not Iterable const iteratorId = nextPartId++; const partJSON = serializeModel( - Array.from(iterator as any), + Array.from((iterator: any)), iteratorId, ); if (formData === null) { @@ -711,7 +710,7 @@ export function processReply( formData.append(formFieldPrefix + iteratorId, partJSON); return serializeIteratorID(iteratorId); } - return Array.from(iterator as any); + return Array.from((iterator: any)); } // TODO: ReadableStream is not available in old Node. Remove the typeof check later. @@ -721,14 +720,13 @@ export function processReply( ) { return serializeReadableStream(value); } - const getAsyncIterator: void | (() => $AsyncIterator<any, any, any>) = ( - value as any - )[ASYNC_ITERATOR]; + const getAsyncIterator: void | (() => $AsyncIterator<any, any, any>) = + (value: any)[ASYNC_ITERATOR]; if (typeof getAsyncIterator === 'function') { // We treat AsyncIterables as a Fragment and as such we might need to key them. return serializeAsyncIterable( - value as any, - getAsyncIterator.call(value as any), + (value: any), + getAsyncIterator.call((value: any)), ); } @@ -750,7 +748,7 @@ export function processReply( return serializeTemporaryReferenceMarker(); } if (__DEV__) { - if ((value as any).$$typeof === REACT_CONTEXT_TYPE) { + if ((value: any).$$typeof === REACT_CONTEXT_TYPE) { console.error( 'React Context Providers cannot be passed to Server Functions from the Client.%s', describeObjectForErrorMessage(parent, key), @@ -889,7 +887,7 @@ export function processReply( } } modelRoot = model; - // $FlowFixMe[incompatible-type] it's not going to be undefined because we'll encode it. + // $FlowFixMe[incompatible-return] it's not going to be undefined because we'll encode it. return JSON.stringify(model, resolveToJSON); } @@ -915,7 +913,7 @@ export function processReply( // Otherwise, we use FormData to let us stream in the result. formData.set(formFieldPrefix + '0', json); if (pendingParts === 0) { - // $FlowFixMe[incompatible-type] this has already been refined. + // $FlowFixMe[incompatible-call] this has already been refined. resolve(formData); } } @@ -946,13 +944,13 @@ function encodeFormData(reference: any): Thenable<FormData> { data.append('0', body); body = data; } - const fulfilled: FulfilledThenable<FormData> = thenable as any; + const fulfilled: FulfilledThenable<FormData> = (thenable: any); fulfilled.status = 'fulfilled'; fulfilled.value = body; resolve(body); }, e => { - const rejected: RejectedThenable<FormData> = thenable as any; + const rejected: RejectedThenable<FormData> = (thenable: any); rejected.status = 'rejected'; rejected.reason = e; reject(e); @@ -994,7 +992,7 @@ function defaultEncodeFormAction( const prefixedData = new FormData(); // $FlowFixMe[prop-missing] encodedFormData.forEach((value: string | File, key: string) => { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] prefixedData.append('$ACTION_' + identifierPrefix + ':' + key, value); }); data = prefixedData; @@ -1024,8 +1022,7 @@ function customEncodeFormAction( 'This is a bug in React.', ); } - let boundPromise: Promise<Array<any>> = referenceClosure.bound as any; - // $FlowFixMe[invalid-compare] + let boundPromise: Promise<Array<any>> = (referenceClosure.bound: any); if (boundPromise === null) { boundPromise = Promise.resolve([]); } @@ -1073,18 +1070,18 @@ function isSignatureEqual( // Only instrument the thenable if the status if not defined. } else { const pendingThenable: PendingThenable<Array<any>> = - boundPromise as any; + (boundPromise: any); pendingThenable.status = 'pending'; pendingThenable.then( (boundArgs: Array<any>) => { const fulfilledThenable: FulfilledThenable<Array<any>> = - boundPromise as any; + (boundPromise: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = boundArgs; }, (error: mixed) => { const rejectedThenable: RejectedThenable<number> = - boundPromise as any; + (boundPromise: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; }, @@ -1205,7 +1202,6 @@ export function registerBoundServerReference<T: Function>( // Expose encoder for use by SSR, as well as a special bind that can be used to // keep server capabilities. - // $FlowFixMe[constant-condition] if (usedWithSSR) { // Only expose this in builds that would actually use it. Not needed in the browser. const $$FORM_ACTION = @@ -1221,7 +1217,7 @@ export function registerBoundServerReference<T: Function>( encodeFormAction, ); }; - Object.defineProperties(reference as any, { + Object.defineProperties((reference: any), { $$FORM_ACTION: {value: $$FORM_ACTION}, $$IS_SIGNATURE_EQUAL: {value: isSignatureEqual}, bind: {value: bind}, @@ -1246,7 +1242,7 @@ function bind(this: Function): Function { const referenceClosure = knownServerReferences.get(this); if (!referenceClosure) { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] return FunctionBind.apply(this, arguments); } @@ -1267,7 +1263,7 @@ function bind(this: Function): Function { const args = ArraySlice.call(arguments, 1); let boundPromise = null; if (referenceClosure.bound !== null) { - boundPromise = Promise.resolve(referenceClosure.bound as any).then( + boundPromise = Promise.resolve((referenceClosure.bound: any)).then( boundArgs => boundArgs.concat(args), ); } else { @@ -1282,10 +1278,9 @@ function bind(this: Function): Function { // Expose encoder for use by SSR, as well as a special bind that can be used to // keep server capabilities. - // $FlowFixMe[constant-condition] if (usedWithSSR) { // Only expose this in builds that would actually use it. Not needed on the client. - Object.defineProperties(newFn as any, { + Object.defineProperties((newFn: any), { $$FORM_ACTION: {value: this.$$FORM_ACTION}, $$IS_SIGNATURE_EQUAL: {value: isSignatureEqual}, bind: {value: bind}, @@ -1327,7 +1322,7 @@ export function createBoundServerReference<A: Iterable<any>, T>( } // Since this is a fake Promise whose .then doesn't chain, we have to wrap it. // TODO: Remove the wrapper once that's fixed. - return (Promise.resolve(p) as any as Promise<Array<any>>).then( + return ((Promise.resolve(p): any): Promise<Array<any>>).then( function (boundArgs) { return callServer(id, boundArgs.concat(args)); }, diff --git a/packages/react-client/src/ReactFlightTemporaryReferences.js b/packages/react-client/src/ReactFlightTemporaryReferences.js index c259942f9aa0..5ad92b3a1063 100644 --- a/packages/react-client/src/ReactFlightTemporaryReferences.js +++ b/packages/react-client/src/ReactFlightTemporaryReferences.js @@ -27,5 +27,5 @@ export function readTemporaryReference<T>( set: TemporaryReferenceSet, reference: string, ): T { - return set.get(reference) as any; + return (set.get(reference): any); } diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index 95633283b2c7..8736b0585f1b 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -3743,59 +3743,6 @@ describe('ReactFlight', () => { expect(cyclic2.cycle).toBe(cyclic2); }); - // @gate __DEV__ - it('replays logs with large strings replaced by a placeholder', async () => { - // This string exceeds the threshold for debug string length. Reconstructing - // a multi-megabyte string on the client when replaying the log would block - // the main thread for too long, so we omit it and send a placeholder - // instead. - const largeString = 'x'.repeat(1000001); - - function ServerComponent() { - console.log('large string:', largeString); - return null; - } - - function App() { - return ReactServer.createElement(ServerComponent); - } - - // These tests are specifically testing console.log. - // Assign to `mockConsoleLog` so we can still inspect it when `console.log` - // is overridden by the test modules. The original function will be restored - // after this test finishes by `jest.restoreAllMocks()`. - const mockConsoleLog = spyOnDevAndProd(console, 'log').mockImplementation( - () => {}, - ); - - // Reset the modules so that we get a new overridden console on top of the - // one installed by expect. This ensures that we still emit console.error - // calls. - jest.resetModules(); - jest.mock('react', () => require('react/react.react-server')); - ReactServer = require('react'); - ReactNoopFlightServer = require('react-noop-renderer/flight-server'); - const transport = ReactNoopFlightServer.render({ - root: ReactServer.createElement(App), - }); - - // The server logged the actual string synchronously while rendering. - expect(mockConsoleLog).toHaveBeenCalledTimes(1); - expect(mockConsoleLog.mock.calls[0][1]).toBe(largeString); - mockConsoleLog.mockClear(); - mockConsoleLog.mockImplementation(() => {}); - - await ReactNoopFlightClient.read(transport); - - // The replayed log received a placeholder instead of the actual string. - expect(mockConsoleLog).toHaveBeenCalledTimes(1); - expect(mockConsoleLog.mock.calls[0][0]).toBe('large string:'); - expect(mockConsoleLog.mock.calls[0][1]).toBe( - 'This string of length 1000001 has been omitted by React to avoid ' + - 'sending too much data from the server.', - ); - }); - // @gate !__DEV__ || enableComponentPerformanceTrack it('uses the server component debug info as the element owner in DEV', async () => { function Container({children}) { diff --git a/packages/react-debug-tools/package.json b/packages/react-debug-tools/package.json index 011553625fc3..dcd5e888ff92 100644 --- a/packages/react-debug-tools/package.json +++ b/packages/react-debug-tools/package.json @@ -7,7 +7,7 @@ "react" ], "homepage": "https://react.dev/", - "bugs": "https://github.com/react/react/issues", + "bugs": "https://github.com/facebook/react/issues", "license": "MIT", "files": [ "LICENSE", @@ -18,7 +18,7 @@ "main": "index.js", "repository": { "type" : "git", - "url" : "https://github.com/react/react.git", + "url" : "https://github.com/facebook/react.git", "directory": "packages/react-debug-tools" }, "engines": { diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 585205e02c6e..b4eb6c2b5965 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -74,7 +74,7 @@ function getPrimitiveStackCache(): Map<string, Array<any>> { let readHookLog; try { // Use all hooks here to add them to the hook log. - Dispatcher.useContext({_currentValue: null} as any); + Dispatcher.useContext(({_currentValue: null}: any)); Dispatcher.useState(null); Dispatcher.useReducer((s: mixed, a: mixed) => s, null); Dispatcher.useRef(null); @@ -106,19 +106,23 @@ function getPrimitiveStackCache(): Map<string, Array<any>> { } if (typeof Dispatcher.use === 'function') { // This type check is for Flow only. - Dispatcher.use({ - $$typeof: REACT_CONTEXT_TYPE, - _currentValue: null, - } as any); + Dispatcher.use( + ({ + $$typeof: REACT_CONTEXT_TYPE, + _currentValue: null, + }: any), + ); Dispatcher.use({ then() {}, status: 'fulfilled', value: null, }); try { - Dispatcher.use({ - then() {}, - } as any); + Dispatcher.use( + ({ + then() {}, + }: any), + ); } catch (x) {} } @@ -170,7 +174,7 @@ function readContext<T>(context: ReactContext<T>): T { // For now we don't expose readContext usage in the hooks debugging info. if (hasOwnProperty.call(currentContextDependency, 'memoizedValue')) { // $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency` - value = currentContextDependency.memoizedValue as any as T; + value = ((currentContextDependency.memoizedValue: any): T); // $FlowFixMe[incompatible-use] Flow thinks `hasOwnProperty` mutates `currentContextDependency` currentContextDependency = currentContextDependency.next; @@ -196,7 +200,6 @@ const SuspenseException: mixed = new Error( ); function use<T>(usable: Usable<T>): T { - // $FlowFixMe[invalid-compare] if (usable !== null && typeof usable === 'object') { // $FlowFixMe[method-unbinding] if (typeof usable.then === 'function') { @@ -207,7 +210,7 @@ function use<T>(usable: Usable<T>): T { currentThenableState !== null && currentThenableIndex < currentThenableState.length ? currentThenableState[currentThenableIndex++] - : (usable as any); + : (usable: any); switch (thenable.status) { case 'fulfilled': { @@ -241,7 +244,7 @@ function use<T>(usable: Usable<T>): T { }); throw SuspenseException; } else if (usable.$$typeof === REACT_CONTEXT_TYPE) { - const context: ReactContext<T> = usable as any; + const context: ReactContext<T> = (usable: any); const value = readContext(context); hookLog.push({ @@ -306,7 +309,7 @@ function useReducer<S, I, A>( if (hook !== null) { state = hook.memoizedState; } else { - state = init !== undefined ? init(initialArg) : (initialArg as any as S); + state = init !== undefined ? init(initialArg) : ((initialArg: any): S); } hookLog.push({ displayName: null, @@ -604,7 +607,7 @@ function useFormState<S, P>( // $FlowFixMe[method-unbinding] typeof actionResult.then === 'function' ) { - const thenable: Thenable<Awaited<S>> = actionResult as any; + const thenable: Thenable<Awaited<S>> = (actionResult: any); switch (thenable.status) { case 'fulfilled': { value = thenable.value; @@ -626,7 +629,7 @@ function useFormState<S, P>( value = thenable; } } else { - value = actionResult as any; + value = (actionResult: any); } } else { value = initialState; @@ -647,7 +650,7 @@ function useFormState<S, P>( // value being a Thenable is equivalent to error being not null // i.e. we only reach this point with Awaited<S> - const state = value as any as Awaited<S>; + const state = ((value: any): Awaited<S>); // TODO: support displaying pending value return [state, (payload: P) => {}, false]; @@ -674,7 +677,7 @@ function useActionState<S, P>( // $FlowFixMe[method-unbinding] typeof actionResult.then === 'function' ) { - const thenable: Thenable<Awaited<S>> = actionResult as any; + const thenable: Thenable<Awaited<S>> = (actionResult: any); switch (thenable.status) { case 'fulfilled': { value = thenable.value; @@ -696,7 +699,7 @@ function useActionState<S, P>( value = thenable; } } else { - value = actionResult as any; + value = (actionResult: any); } } else { value = initialState; @@ -717,7 +720,7 @@ function useActionState<S, P>( // value being a Thenable is equivalent to error being not null // i.e. we only reach this point with Awaited<S> - const state = value as any as Awaited<S>; + const state = ((value: any): Awaited<S>); // TODO: support displaying pending value return [state, (payload: P) => {}, false]; @@ -726,11 +729,10 @@ function useActionState<S, P>( function useHostTransitionStatus(): TransitionStatus { const status = readContext<TransitionStatus>( // $FlowFixMe[prop-missing] `readContext` only needs _currentValue - // $FlowFixMe[incompatible-type] - { - // $FlowFixMe[incompatible-type] TODO: Incorrect bottom value without access to Fiber config. + ({ + // $FlowFixMe[incompatible-cast] TODO: Incorrect bottom value without access to Fiber config. _currentValue: null, - } as ReactContext<TransitionStatus>, + }: ReactContext<TransitionStatus>), ); hookLog.push({ @@ -1224,7 +1226,7 @@ export function inspectHooks<Props>( } const rootStack = ancestorStackError === undefined - ? ([] as Array<ParsedStackFrame>) + ? ([]: ParsedStackFrame[]) : ErrorStackParser.parse(ancestorStackError); return buildTree(rootStack, readHookLog); } @@ -1234,9 +1236,9 @@ function setupContexts(contextMap: Map<ReactContext<any>, any>, fiber: Fiber) { while (current) { if (current.tag === ContextProvider) { let context: ReactContext<any> = current.type; - if ((context as any)._context !== undefined) { + if ((context: any)._context !== undefined) { // Support inspection of pre-19+ providers. - context = (context as any)._context; + context = (context: any)._context; } if (!contextMap.has(context)) { // Store the current value that we're going to restore later. @@ -1275,7 +1277,7 @@ function inspectHooksOfForwardRef<Props, Ref>( } const rootStack = ancestorStackError === undefined - ? ([] as Array<ParsedStackFrame>) + ? ([]: ParsedStackFrame[]) : ErrorStackParser.parse(ancestorStackError); return buildTree(rootStack, readHookLog); } @@ -1320,7 +1322,7 @@ export function inspectHooksOfFiber( // Set up the current hook so that we can step through and read the // current state from them. - currentHook = fiber.memoizedState as Hook; + currentHook = (fiber.memoizedState: Hook); currentFiber = fiber; const thenableState = fiber.dependencies && fiber.dependencies._debugThenableState; @@ -1337,17 +1339,15 @@ export function inspectHooksOfFiber( currentContextDependency = dependencies !== null ? dependencies.firstContext : null; } else if (hasOwnProperty.call(currentFiber, 'dependencies_old')) { - const dependencies: Dependencies = (currentFiber as any).dependencies_old; + const dependencies: Dependencies = (currentFiber: any).dependencies_old; currentContextDependency = - // $FlowFixMe[invalid-compare] dependencies !== null ? dependencies.firstContext : null; } else if (hasOwnProperty.call(currentFiber, 'dependencies_new')) { - const dependencies: Dependencies = (currentFiber as any).dependencies_new; + const dependencies: Dependencies = (currentFiber: any).dependencies_new; currentContextDependency = - // $FlowFixMe[invalid-compare] dependencies !== null ? dependencies.firstContext : null; } else if (hasOwnProperty.call(currentFiber, 'contextDependencies')) { - const contextDependencies = (currentFiber as any).contextDependencies; + const contextDependencies = (currentFiber: any).contextDependencies; currentContextDependency = contextDependencies !== null ? contextDependencies.first : null; } else { diff --git a/packages/react-devtools-core/package.json b/packages/react-devtools-core/package.json index 6f0274979a36..54b89e72e8ae 100644 --- a/packages/react-devtools-core/package.json +++ b/packages/react-devtools-core/package.json @@ -6,7 +6,7 @@ "main": "./dist/backend.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-devtools-core" }, "files": [ diff --git a/packages/react-devtools-core/src/backend.js b/packages/react-devtools-core/src/backend.js index 7367e7bd1bc2..075758f78500 100644 --- a/packages/react-devtools-core/src/backend.js +++ b/packages/react-devtools-core/src/backend.js @@ -51,7 +51,6 @@ let savedComponentFilters: Array<ComponentFilter> = getDefaultComponentFilters(); function debug(methodName: string, ...args: Array<mixed>) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `%c[core/backend] %c${methodName}`, @@ -99,7 +98,7 @@ export function connectToDevTools(options: ?ConnectOptions) { useHttps = false, port = 8097, websocket, - resolveRNStyle = null as $FlowFixMe, + resolveRNStyle = (null: $FlowFixMe), retryConnectionDelay = 2000, isAppActive = () => true, onSettingsUpdated, @@ -155,14 +154,12 @@ export function connectToDevTools(options: ?ConnectOptions) { }, send(event: string, payload: any, transferable?: Array<any>) { if (ws.readyState === ws.OPEN) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('wall.send()', event, payload); } ws.send(JSON.stringify({event, payload})); } else { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'wall.send()', @@ -189,7 +186,7 @@ export function connectToDevTools(options: ?ConnectOptions) { ); // TODO (npm-packages) Warn if "isBackendStorageAPISupported" - // $FlowFixMe[incompatible-type] found when upgrading Flow + // $FlowFixMe[incompatible-call] found when upgrading Flow const agent = new Agent(bridge, isProfiling, onReloadAndProfile); if (typeof onReloadAndProfileFlagsReset === 'function') { onReloadAndProfileFlagsReset(); @@ -213,11 +210,10 @@ export function connectToDevTools(options: ?ConnectOptions) { // Setup React Native style editor if the environment supports it. if (resolveRNStyle != null || hook.resolveRNStyle != null) { setupNativeStyleEditor( - // $FlowFixMe[incompatible-type] found when upgrading Flow + // $FlowFixMe[incompatible-call] found when upgrading Flow bridge, agent, - // $FlowFixMe[constant-condition] - (resolveRNStyle || hook.resolveRNStyle) as any as ResolveNativeStyle, + ((resolveRNStyle || hook.resolveRNStyle: any): ResolveNativeStyle), nativeStyleEditorValidAttributes || hook.nativeStyleEditorValidAttributes || null, @@ -241,34 +237,41 @@ export function connectToDevTools(options: ?ConnectOptions) { }; if (!hook.hasOwnProperty('resolveRNStyle')) { - Object.defineProperty(hook, 'resolveRNStyle', { - enumerable: false, - get() { - return lazyResolveRNStyle; - }, - set(value: $FlowFixMe) { - lazyResolveRNStyle = value; - initAfterTick(); - }, - } as Object); + Object.defineProperty( + hook, + 'resolveRNStyle', + ({ + enumerable: false, + get() { + return lazyResolveRNStyle; + }, + set(value: $FlowFixMe) { + lazyResolveRNStyle = value; + initAfterTick(); + }, + }: Object), + ); } if (!hook.hasOwnProperty('nativeStyleEditorValidAttributes')) { - Object.defineProperty(hook, 'nativeStyleEditorValidAttributes', { - enumerable: false, - get() { - return lazyNativeStyleEditorValidAttributes; - }, - set(value: $FlowFixMe) { - lazyNativeStyleEditorValidAttributes = value; - initAfterTick(); - }, - } as Object); + Object.defineProperty( + hook, + 'nativeStyleEditorValidAttributes', + ({ + enumerable: false, + get() { + return lazyNativeStyleEditorValidAttributes; + }, + set(value: $FlowFixMe) { + lazyNativeStyleEditorValidAttributes = value; + initAfterTick(); + }, + }: Object), + ); } } }; function handleClose() { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('WebSocket.onclose'); } @@ -281,7 +284,6 @@ export function connectToDevTools(options: ?ConnectOptions) { } function handleFailed() { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('WebSocket.onerror'); } @@ -294,7 +296,6 @@ export function connectToDevTools(options: ?ConnectOptions) { try { if (typeof event.data === 'string') { data = JSON.parse(event.data); - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('WebSocket.onmessage', data); } @@ -303,7 +304,7 @@ export function connectToDevTools(options: ?ConnectOptions) { } } catch (e) { console.error( - '[React DevTools] Failed to parse JSON: ' + (event.data as any), + '[React DevTools] Failed to parse JSON: ' + (event.data: any), ); return; } diff --git a/packages/react-devtools-core/src/standalone.js b/packages/react-devtools-core/src/standalone.js index 3e2538d47750..81f357751913 100644 --- a/packages/react-devtools-core/src/standalone.js +++ b/packages/react-devtools-core/src/standalone.js @@ -32,7 +32,7 @@ export type StatusTypes = 'server-connected' | 'devtools-connected' | 'error'; export type StatusListener = (message: string, status: StatusTypes) => void; export type OnDisconnectedCallback = () => void; -let node: HTMLElement = null as any as HTMLElement; +let node: HTMLElement = ((null: any): HTMLElement); let nodeWaitingToConnectHTML: string = ''; let projectRoots: Array<string> = []; let statusListener: StatusListener = ( @@ -83,7 +83,6 @@ log.error = (...args: Array<mixed>) => console.error('[React DevTools]', ...args); function debug(methodName: string, ...args: Array<mixed>) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `%c[core/standalone] %c${methodName}`, @@ -112,11 +111,11 @@ function reload() { root = createRoot(node); root.render( createElement(DevTools, { - bridge: bridge as any as FrontendBridge, + bridge: ((bridge: any): FrontendBridge), canViewElementSourceFunction, hookNamesModuleLoaderFunction, showTabBar: true, - store: store as any as Store, + store: ((store: any): Store), warnIfLegacyBackendDetected: true, viewElementSourceFunction, fetchFileWithCaching, @@ -226,7 +225,6 @@ function initialize(socket: WebSocket) { if (typeof event.data === 'string') { data = JSON.parse(event.data); - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('WebSocket.onmessage', data); } @@ -263,11 +261,11 @@ function initialize(socket: WebSocket) { } }, }); - (bridge as any as FrontendBridge).addListener('shutdown', () => { + ((bridge: any): FrontendBridge).addListener('shutdown', () => { socket.close(); }); - // $FlowFixMe[incompatible-type] found when upgrading Flow + // $FlowFixMe[incompatible-call] found when upgrading Flow store = new Store(bridge, { checkBridgeProtocolCompatibility: true, supportsTraceUpdates: true, diff --git a/packages/react-devtools-extensions/src/background/index.js b/packages/react-devtools-extensions/src/background/index.js index 34c337a59e25..0ed719c20b22 100644 --- a/packages/react-devtools-extensions/src/background/index.js +++ b/packages/react-devtools-extensions/src/background/index.js @@ -94,7 +94,7 @@ chrome.runtime.onConnect.addListener(port => { registerTab(tabId); registerProxyPort( port, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] tabId, ); diff --git a/packages/react-devtools-extensions/src/main/evalInInspectedWindow.js b/packages/react-devtools-extensions/src/main/evalInInspectedWindow.js index 073224b511b5..27a339f4e8ae 100644 --- a/packages/react-devtools-extensions/src/main/evalInInspectedWindow.js +++ b/packages/react-devtools-extensions/src/main/evalInInspectedWindow.js @@ -50,7 +50,6 @@ function fallbackEvalInInspectedWindow( }); const timeout = setTimeout(() => { evalRequestCallbacks.delete(requestId); - // $FlowFixMe[constant-condition] if (callback) { callback(null, { code, @@ -65,7 +64,6 @@ function fallbackEvalInInspectedWindow( evalRequestCallbacks.set(requestId, ({result, error}) => { clearTimeout(timeout); evalRequestCallbacks.delete(requestId); - // $FlowFixMe[constant-condition] if (callback) { if (error) { callback(null, { diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js index 1edbbf5e1cd3..0072b6934585 100644 --- a/packages/react-devtools-extensions/src/main/index.js +++ b/packages/react-devtools-extensions/src/main/index.js @@ -453,7 +453,6 @@ function performInTabNavigationCleanup() { // Potentially, if react hasn't loaded yet and user performs in-tab navigation clearReactPollingInstance(); - // $FlowFixMe[invalid-compare] if (store !== null) { // Store profiling data, so it can be used later profilingData = store.profilerStore.profilingData; @@ -486,10 +485,10 @@ function performInTabNavigationCleanup() { // Do not clean mostRecentOverrideTab on purpose, so we remember last opened // React DevTools tab, when user does in-tab navigation - store = null as $FlowFixMe; - bridge = null as $FlowFixMe; - render = null as $FlowFixMe; - root = null as $FlowFixMe; + store = (null: $FlowFixMe); + bridge = (null: $FlowFixMe); + render = (null: $FlowFixMe); + root = (null: $FlowFixMe); } function performFullCleanup() { @@ -511,15 +510,15 @@ function performFullCleanup() { componentsPortalContainer = null; profilerPortalContainer = null; suspensePortalContainer = null; - root = null as $FlowFixMe; + root = (null: $FlowFixMe); mostRecentOverrideTab = null; - store = null as $FlowFixMe; - bridge = null as $FlowFixMe; - render = null as $FlowFixMe; + store = (null: $FlowFixMe); + bridge = (null: $FlowFixMe); + render = (null: $FlowFixMe); port?.disconnect(); - port = null as $FlowFixMe; + port = (null: $FlowFixMe); } function connectExtensionPort(): void { @@ -546,7 +545,7 @@ function connectExtensionPort(): void { // so, when we call `port.disconnect()` from this script, // this should not trigger this callback and port reconnection port.onDisconnect.addListener(() => { - port = null as $FlowFixMe; + port = (null: $FlowFixMe); connectExtensionPort(); }); } @@ -599,9 +598,9 @@ function mountReactDevToolsWhenReactHasLoaded() { ); } -let bridge: FrontendBridge = null as $FlowFixMe; +let bridge: FrontendBridge = (null: $FlowFixMe); let lastSubscribedBridgeListener = null; -let store: Store = null as $FlowFixMe; +let store: Store = (null: $FlowFixMe); let profilingData = null; @@ -617,12 +616,12 @@ let editorPortalContainer = null; let inspectedElementPortalContainer = null; let mostRecentOverrideTab: null | TabID = null; -let render: (overrideTab?: TabID) => void = null as $FlowFixMe; -let root: RootType = null as $FlowFixMe; +let render: (overrideTab?: TabID) => void = (null: $FlowFixMe); +let root: RootType = (null: $FlowFixMe); let currentSelectedSource: null | SourceSelection = null; -let port: ExtensionRuntimePort = null as $FlowFixMe; +let port: ExtensionRuntimePort = (null: $FlowFixMe); // In case when multiple navigation events emitted in a short period of time // This debounced callback primarily used to avoid mounting React DevTools multiple times, which results diff --git a/packages/react-devtools-inline/package.json b/packages/react-devtools-inline/package.json index 0b66393890f6..cc5fc51a6ef6 100644 --- a/packages/react-devtools-inline/package.json +++ b/packages/react-devtools-inline/package.json @@ -6,7 +6,7 @@ "main": "./dist/backend.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-devtools-inline" }, "files": [ diff --git a/packages/react-devtools-inline/src/backend.js b/packages/react-devtools-inline/src/backend.js index 8b0ab3584325..9810d5b39ccf 100644 --- a/packages/react-devtools-inline/src/backend.js +++ b/packages/react-devtools-inline/src/backend.js @@ -109,7 +109,7 @@ export function createBridge(contentWindow: any, wall?: Wall): BackendBridge { }; } - return new Bridge(wall) as BackendBridge; + return (new Bridge(wall): BackendBridge); } export function initialize(contentWindow: any): void { diff --git a/packages/react-devtools-inline/src/frontend.js b/packages/react-devtools-inline/src/frontend.js index 28fe70b1b299..b8d1b2ef4d81 100644 --- a/packages/react-devtools-inline/src/frontend.js +++ b/packages/react-devtools-inline/src/frontend.js @@ -40,7 +40,7 @@ export function createBridge(contentWindow: any, wall?: Wall): FrontendBridge { }; } - return new Bridge(wall) as FrontendBridge; + return (new Bridge(wall): FrontendBridge); } export function initialize( @@ -58,7 +58,7 @@ export function initialize( } // Type refinement. - const frontendBridge = bridge as any as FrontendBridge; + const frontendBridge = ((bridge: any): FrontendBridge); if (store == null) { store = createStore(frontendBridge); diff --git a/packages/react-devtools-shared/src/Logger.js b/packages/react-devtools-shared/src/Logger.js index bd6a47d206e0..0d1324d0d70a 100644 --- a/packages/react-devtools-shared/src/Logger.js +++ b/packages/react-devtools-shared/src/Logger.js @@ -84,7 +84,6 @@ export const logEvent: LogFunction = export const registerEventLogger: (logFunction: LogFunction) => () => void = enableLogger === true ? function registerEventLogger(logFunction: LogFunction): () => void { - // $FlowFixMe[constant-condition] if (enableLogger) { logFunctions.push(logFunction); return function unregisterEventLogger() { diff --git a/packages/react-devtools-shared/src/PerformanceLoggingUtils.js b/packages/react-devtools-shared/src/PerformanceLoggingUtils.js index 991064997cc7..68741a899261 100644 --- a/packages/react-devtools-shared/src/PerformanceLoggingUtils.js +++ b/packages/react-devtools-shared/src/PerformanceLoggingUtils.js @@ -48,13 +48,11 @@ export async function withAsyncPerfMeasurements<TReturn>( onComplete?: number => void, ): Promise<TReturn> { const start = now(); - // $FlowFixMe[constant-condition] if (__PERFORMANCE_PROFILE__) { mark(markName); } const result = await callback(); - // $FlowFixMe[constant-condition] if (__PERFORMANCE_PROFILE__) { measure(markName); } @@ -73,13 +71,11 @@ export function withSyncPerfMeasurements<TReturn>( onComplete?: number => void, ): TReturn { const start = now(); - // $FlowFixMe[constant-condition] if (__PERFORMANCE_PROFILE__) { mark(markName); } const result = callback(); - // $FlowFixMe[constant-condition] if (__PERFORMANCE_PROFILE__) { measure(markName); } @@ -98,13 +94,11 @@ export function withCallbackPerfMeasurements<TReturn>( onComplete?: number => void, ): TReturn { const start = now(); - // $FlowFixMe[constant-condition] if (__PERFORMANCE_PROFILE__) { mark(markName); } const done = () => { - // $FlowFixMe[constant-condition] if (__PERFORMANCE_PROFILE__) { measure(markName); } diff --git a/packages/react-devtools-shared/src/__tests__/console-test.js b/packages/react-devtools-shared/src/__tests__/console-test.js index 8822d1016235..c37285e855a5 100644 --- a/packages/react-devtools-shared/src/__tests__/console-test.js +++ b/packages/react-devtools-shared/src/__tests__/console-test.js @@ -363,7 +363,8 @@ describe('console', () => { it('should double log if hideConsoleLogsInStrictMode is disabled in Strict mode', () => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = false; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = + false; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); @@ -404,7 +405,8 @@ describe('console', () => { it('should not double log if hideConsoleLogsInStrictMode is enabled in Strict mode', () => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = true; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = + true; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); @@ -431,7 +433,8 @@ describe('console', () => { it('should double log from Effects if hideConsoleLogsInStrictMode is disabled in Strict mode', () => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = false; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = + false; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); @@ -478,7 +481,8 @@ describe('console', () => { it('should not double log from Effects if hideConsoleLogsInStrictMode is enabled in Strict mode', () => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = true; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = + true; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); @@ -514,7 +518,8 @@ describe('console', () => { it('should double log from useMemo if hideConsoleLogsInStrictMode is disabled in Strict mode', () => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = false; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = + false; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); @@ -557,7 +562,8 @@ describe('console', () => { it('should not double log from useMemo fns if hideConsoleLogsInStrictMode is enabled in Strict mode', () => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = true; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = + true; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); @@ -586,7 +592,8 @@ describe('console', () => { it('should double log in Strict mode initial render for extension', () => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = false; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = + false; // This simulates a render that happens before React DevTools have finished // their handshake to attach the React DOM renderer functions to DevTools @@ -631,7 +638,8 @@ describe('console', () => { it('should not double log in Strict mode initial render for extension', () => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = true; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = + true; // This simulates a render that happens before React DevTools have finished // their handshake to attach the React DOM renderer functions to DevTools @@ -662,7 +670,8 @@ describe('console', () => { it('should properly dim component stacks during strict mode double log', () => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = true; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = false; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = + false; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); @@ -727,8 +736,10 @@ describe('console', () => { it('should not dim console logs if disableSecondConsoleLogDimmingInStrictMode is enabled', () => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.disableSecondConsoleLogDimmingInStrictMode = true; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = + false; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.disableSecondConsoleLogDimmingInStrictMode = + true; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); @@ -761,8 +772,10 @@ describe('console', () => { it('should dim console logs if disableSecondConsoleLogDimmingInStrictMode is disabled', () => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = false; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.disableSecondConsoleLogDimmingInStrictMode = false; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode = + false; + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.disableSecondConsoleLogDimmingInStrictMode = + false; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); diff --git a/packages/react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor.js b/packages/react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor.js index 8b9db86fcc7a..7a8bfff43bf2 100644 --- a/packages/react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor.js +++ b/packages/react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor.js @@ -97,11 +97,14 @@ function measureStyle( ) { const data = agent.getInstanceAndStyle({id, rendererID}); if (!data || !data.style) { - bridge.send('NativeStyleEditor_styleAndLayout', { - id, - layout: null, - style: null, - } as StyleAndLayout); + bridge.send( + 'NativeStyleEditor_styleAndLayout', + ({ + id, + layout: null, + style: null, + }: StyleAndLayout), + ); return; } @@ -116,11 +119,14 @@ function measureStyle( } if (!instance || typeof instance.measure !== 'function') { - bridge.send('NativeStyleEditor_styleAndLayout', { - id, - layout: null, - style: resolvedStyle || null, - } as StyleAndLayout); + bridge.send( + 'NativeStyleEditor_styleAndLayout', + ({ + id, + layout: null, + style: resolvedStyle || null, + }: StyleAndLayout), + ); return; } @@ -128,11 +134,14 @@ function measureStyle( // RN Android sometimes returns undefined here. Don't send measurements in this case. // https://github.com/jhen0409/react-native-debugger/issues/84#issuecomment-304611817 if (typeof x !== 'number') { - bridge.send('NativeStyleEditor_styleAndLayout', { - id, - layout: null, - style: resolvedStyle || null, - } as StyleAndLayout); + bridge.send( + 'NativeStyleEditor_styleAndLayout', + ({ + id, + layout: null, + style: resolvedStyle || null, + }: StyleAndLayout), + ); return; } const margin = @@ -141,20 +150,23 @@ function measureStyle( const padding = (resolvedStyle != null && resolveBoxStyle('padding', resolvedStyle)) || EMPTY_BOX_STYLE; - bridge.send('NativeStyleEditor_styleAndLayout', { - id, - layout: { - x, - y, - width, - height, - left, - top, - margin, - padding, - }, - style: resolvedStyle || null, - } as StyleAndLayout); + bridge.send( + 'NativeStyleEditor_styleAndLayout', + ({ + id, + layout: { + x, + y, + width, + height, + left, + top, + margin, + padding, + }, + style: resolvedStyle || null, + }: StyleAndLayout), + ); }); } @@ -182,7 +194,7 @@ function renameStyle( const {instance, style} = data; const newStyle = newName - ? {[oldName]: undefined as string | void, [newName]: value} + ? {[oldName]: (undefined: string | void), [newName]: value} : {[oldName]: undefined}; let customStyle; diff --git a/packages/react-devtools-shared/src/backend/StyleX/utils.js b/packages/react-devtools-shared/src/backend/StyleX/utils.js index 1c7769c51d6c..ad137c7184e5 100644 --- a/packages/react-devtools-shared/src/backend/StyleX/utils.js +++ b/packages/react-devtools-shared/src/backend/StyleX/utils.js @@ -82,7 +82,7 @@ function crawlObjectProperties( function getPropertyValueForStyleName(styleName: string): string | null { if (cachedStyleNameToValueMap.has(styleName)) { - return cachedStyleNameToValueMap.get(styleName) as any as string; + return ((cachedStyleNameToValueMap.get(styleName): any): string); } for ( @@ -90,9 +90,9 @@ function getPropertyValueForStyleName(styleName: string): string | null { styleSheetIndex < document.styleSheets.length; styleSheetIndex++ ) { - const styleSheet = document.styleSheets[ + const styleSheet = ((document.styleSheets[ styleSheetIndex - ] as any as CSSStyleSheet; + ]: any): CSSStyleSheet); let rules: CSSRuleList | null = null; // this might throw if CORS rules are enforced https://www.w3.org/TR/cssom-1/#the-cssstylesheet-interface try { @@ -105,7 +105,7 @@ function getPropertyValueForStyleName(styleName: string): string | null { if (!(rules[ruleIndex] instanceof CSSStyleRule)) { continue; } - const rule = rules[ruleIndex] as any as CSSStyleRule; + const rule = ((rules[ruleIndex]: any): CSSStyleRule); const {cssText, selectorText, style} = rule; if (selectorText != null) { diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index be566b4c4d35..18f3e208408b 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -46,7 +46,6 @@ import { } from '../storage'; const debug = (methodName: string, ...args: Array<string>) => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `%cAgent %c${methodName}`, @@ -226,9 +225,9 @@ function mergeRoots( } const leftSuspendedBy: DehydratedData = left.suspendedBy; - const {data, cleaned, unserializable} = right.suspendedBy as DehydratedData; - const leftSuspendedByData = leftSuspendedBy.data as any as Array<mixed>; - const rightSuspendedByData = data as any as Array<mixed>; + const {data, cleaned, unserializable} = (right.suspendedBy: DehydratedData); + const leftSuspendedByData = ((leftSuspendedBy.data: any): Array<mixed>); + const rightSuspendedByData = ((data: any): Array<mixed>); for (let i = 0; i < rightSuspendedByData.length; i++) { leftSuspendedByData.push(rightSuspendedByData[i]); } @@ -464,9 +463,9 @@ export default class Agent extends EventEmitter<{ if (isReactNativeEnvironment() || typeof target.nodeType !== 'number') { // In React Native or non-DOM we simply pick any renderer that has a match. for (const rendererID in this._rendererInterfaces) { - const renderer = this._rendererInterfaces[ - rendererID as any - ] as any as RendererInterface; + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); try { const id = onlySuspenseNodes ? renderer.getSuspenseNodeIDForHostInstance(target) @@ -491,11 +490,11 @@ export default class Agent extends EventEmitter<{ let bestRendererID: number = 0; // Find the nearest ancestor which is mounted by a React. for (const rendererID in this._rendererInterfaces) { - const renderer = this._rendererInterfaces[ - rendererID as any - ] as any as RendererInterface; + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); const nearestNode: null | Element = renderer.getNearestMountedDOMNode( - target as any, + (target: any), ); if (nearestNode !== null) { if (nearestNode === target) { @@ -537,9 +536,9 @@ export default class Agent extends EventEmitter<{ getComponentNameForHostInstance(target: HostInstance): string | null { const match = this.getIDForHostInstance(target); if (match !== null) { - const renderer = this._rendererInterfaces[ - match.rendererID as any - ] as any as RendererInterface; + const renderer = ((this._rendererInterfaces[ + (match.rendererID: any) + ]: any): RendererInterface); return renderer.getDisplayNameForElementID(match.id); } return null; @@ -575,7 +574,7 @@ export default class Agent extends EventEmitter<{ console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`); } else { const owners = renderer.getOwnersList(id); - this._bridge.send('ownersList', {id, owners} as OwnersList); + this._bridge.send('ownersList', ({id, owners}: OwnersList)); } }; @@ -653,9 +652,9 @@ export default class Agent extends EventEmitter<{ } for (const rendererID in this._rendererInterfaces) { - const renderer = this._rendererInterfaces[ - rendererID as any - ] as any as RendererInterface; + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); let path: InspectElementParams['path'] = null; if (suspendedByPathIndex !== null && rendererPath !== null) { const suspendedByPathRendererIndex = @@ -709,14 +708,14 @@ export default class Agent extends EventEmitter<{ mergeRoots(inspectedScreen, inspectedRoots, suspendedByOffset); const dehydratedSuspendedBy: DehydratedData = inspectedRoots.suspendedBy; - const suspendedBy = dehydratedSuspendedBy.data as any as Array<mixed>; + const suspendedBy = ((dehydratedSuspendedBy.data: any): Array<mixed>); suspendedByOffset += suspendedBy.length; found = true; break; case 'no-change': found = true; const rootsSuspendedBy: Array<mixed> = - renderer.getElementAttributeByPath(id, ['suspendedBy']) as any; + (renderer.getElementAttributeByPath(id, ['suspendedBy']): any); suspendedByOffset += rootsSuspendedBy.length; break; case 'not-found': @@ -792,9 +791,9 @@ export default class Agent extends EventEmitter<{ rendererID, suspendedSet, }) => { - const renderer = this._rendererInterfaces[ - rendererID as any - ] as any as RendererInterface; + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); if (renderer.supportsTogglingSuspense) { renderer.overrideSuspenseMilestone(suspendedSet); } @@ -978,9 +977,9 @@ export default class Agent extends EventEmitter<{ setTraceUpdatesEnabled(traceUpdatesEnabled); for (const rendererID in this._rendererInterfaces) { - const renderer = this._rendererInterfaces[ - rendererID as any - ] as any as RendererInterface; + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); renderer.setTraceUpdatesEnabled(traceUpdatesEnabled); } }; @@ -1004,9 +1003,9 @@ export default class Agent extends EventEmitter<{ }) => void = ({recordChangeDescriptions, recordTimeline}) => { this._isProfiling = true; for (const rendererID in this._rendererInterfaces) { - const renderer = this._rendererInterfaces[ - rendererID as any - ] as any as RendererInterface; + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); renderer.startProfiling(recordChangeDescriptions, recordTimeline); } this._bridge.send('profilingStatus', this._isProfiling); @@ -1015,9 +1014,9 @@ export default class Agent extends EventEmitter<{ stopProfiling: () => void = () => { this._isProfiling = false; for (const rendererID in this._rendererInterfaces) { - const renderer = this._rendererInterfaces[ - rendererID as any - ] as any as RendererInterface; + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); renderer.stopProfiling(); } this._bridge.send('profilingStatus', this._isProfiling); @@ -1060,9 +1059,9 @@ export default class Agent extends EventEmitter<{ componentFilters => { for (const rendererIDString in this._rendererInterfaces) { const rendererID = +rendererIDString; - const renderer = this._rendererInterfaces[ - rendererID as any - ] as any as RendererInterface; + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); if (this._lastSelectedRendererID === rendererID) { // Changing component filters will unmount and remount the DevTools tree. // Track the last selection's path so we can restore the selection. @@ -1111,7 +1110,6 @@ export default class Agent extends EventEmitter<{ }; onFastRefreshScheduled: () => void = () => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('onFastRefreshScheduled'); } @@ -1120,7 +1118,6 @@ export default class Agent extends EventEmitter<{ }; onHookOperations: (operations: Array<number>) => void = operations => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'onHookOperations', @@ -1204,7 +1201,7 @@ export default class Agent extends EventEmitter<{ if (path !== null) { sessionStorageSetItem( SESSION_STORAGE_LAST_SELECTION_KEY, - JSON.stringify({rendererID, path} as PersistedSelection), + JSON.stringify(({rendererID, path}: PersistedSelection)), ); } else { sessionStorageRemoveItem(SESSION_STORAGE_LAST_SELECTION_KEY); diff --git a/packages/react-devtools-shared/src/backend/fiber/DevToolsFiberComponentStack.js b/packages/react-devtools-shared/src/backend/fiber/DevToolsFiberComponentStack.js index 4481b7865eed..c938b6736323 100644 --- a/packages/react-devtools-shared/src/backend/fiber/DevToolsFiberComponentStack.js +++ b/packages/react-devtools-shared/src/backend/fiber/DevToolsFiberComponentStack.js @@ -165,7 +165,7 @@ export function getOwnerStackByFiberInDev( if (workInProgress.tag === HostText) { // Text nodes never have an owner/stack because they're not created through JSX. // We use the parent since text nodes are always created through a host parent. - workInProgress = workInProgress.return as any; + workInProgress = (workInProgress.return: any); } // The owner stack of the current fiber will be where it was created, i.e. inside its owner. @@ -197,7 +197,7 @@ export function getOwnerStackByFiberInDev( while (owner) { if (typeof owner.tag === 'number') { - const fiber: Fiber = owner as any; + const fiber: Fiber = (owner: any); owner = fiber._debugOwner; let debugStack: void | null | string | Error = fiber._debugStack; // If we don't actually print the stack if there is no owner of this JSX element. diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 77adb8b4f564..037ce1c5cc3b 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -215,7 +215,7 @@ function createFiberInstance(fiber: Fiber): FiberInstance { // This is used to represent a filtered Fiber but still lets us find its host instance. function createFilteredFiberInstance(fiber: Fiber): FilteredFiberInstance { - return { + return ({ kind: FILTERED_FIBER_INSTANCE, id: 0, parent: null, @@ -227,7 +227,7 @@ function createFilteredFiberInstance(fiber: Fiber): FilteredFiberInstance { suspendedBy: null, suspenseNode: null, data: fiber, - } as any; + }: any); } function createVirtualInstance( @@ -290,14 +290,14 @@ export function getDispatcherRef(renderer: { // We got a legacy dispatcher injected, let's create a wrapper proxy to translate. return { get H() { - return (injectedRef as any).current; + return (injectedRef: any).current; }, set H(value) { - (injectedRef as any).current = value; + (injectedRef: any).current = value; }, }; } - return injectedRef as any; + return (injectedRef: any); } // All environment names we've seen so far. This lets us create a list of filters to apply. @@ -765,7 +765,6 @@ export function attach( parentInstance: null | DevToolsInstance, extraString: string = '', ): void { - // $FlowFixMe[constant-condition] if (__DEBUG__) { const displayName = instance.kind === VIRTUAL_INSTANCE @@ -808,7 +807,6 @@ export function attach( // eslint-disable-next-line no-unused-vars function debugTree(instance: DevToolsInstance, indent: number = 0) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { const name = (instance.kind !== VIRTUAL_INSTANCE @@ -971,7 +969,7 @@ export function attach( currentRoot = rootInstance; unmountInstanceRecursively(rootInstance); rootToFiberInstanceMap.delete(root); - currentRoot = null as any; + currentRoot = (null: any); }); if ( @@ -1040,7 +1038,7 @@ export function attach( currentRoot = newRoot; setRootPseudoKey(currentRoot.id, root.current); mountFiberRecursively(root.current, false); - currentRoot = null as any; + currentRoot = (null: any); }); // We need to write back the new ID for the focused Fiber. @@ -1278,7 +1276,7 @@ export function attach( } // When a mount or update is in progress, this value tracks the root that is being operated on. - let currentRoot: FiberInstance = null as any; + let currentRoot: FiberInstance = (null: any); // Removes a Fiber (and its alternate) from the Maps used to track their id. // This method should always be called when a Fiber is unmounting. @@ -1770,7 +1768,6 @@ export function attach( } idToDevToolsInstanceMap.set(fiberInstance.id, fiberInstance); - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('recordMount()', fiberInstance, parentInstance); } @@ -2081,7 +2078,6 @@ export function attach( const isSuspended = fiber.tag === SuspenseComponent && fiber.memoizedState !== null; - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log('recordSuspenseMount()', suspenseInstance); } @@ -2110,7 +2106,6 @@ export function attach( } function recordUnmount(fiberInstance: FiberInstance): void { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('recordUnmount()', fiberInstance, reconcilingParent); } @@ -2145,7 +2140,6 @@ export function attach( } function recordSuspenseResize(suspenseNode: SuspenseNode): void { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log('recordSuspenseResize()', suspenseNode); } @@ -2173,7 +2167,6 @@ export function attach( } function recordSuspenseSuspenders(suspenseNode: SuspenseNode): void { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log('recordSuspenseSuspenders()', suspenseNode); } @@ -2194,7 +2187,6 @@ export function attach( } function recordSuspenseUnmount(suspenseInstance: SuspenseNode): void { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( 'recordSuspenseUnmount()', @@ -2343,7 +2335,6 @@ export function attach( return; } let node: SuspenseNode = firstChild; - // $FlowFixMe[invalid-compare] while (node !== null) { if (node.suspendedBy.has(ioInfo)) { // We have found a child boundary that depended on the unblocked I/O. @@ -2884,7 +2875,7 @@ export function attach( for (let i = 0; i < debugInfo.length; i++) { const debugEntry = debugInfo[i]; if (debugEntry.awaited) { - const asyncInfo: ReactAsyncInfo = debugEntry as any; + const asyncInfo: ReactAsyncInfo = (debugEntry: any); insertSuspendedBy(asyncInfo); } } @@ -2917,7 +2908,7 @@ export function attach( for (let j = 0; j < debugInfo.length; j++) { const debugEntry = debugInfo[j]; if (debugEntry.awaited) { - const asyncInfo: ReactAsyncInfo = debugEntry as any; + const asyncInfo: ReactAsyncInfo = (debugEntry: any); insertSuspendedBy(asyncInfo); } } @@ -2989,20 +2980,20 @@ export function attach( start = resourceEntry.startTime; end = start + resourceEntry.duration; // $FlowFixMe[prop-missing] - byteSize = (resourceEntry.transferSize as any) || 0; + byteSize = (resourceEntry.transferSize: any) || 0; } } } const value = instance.sheet; const promise = Promise.resolve(value); - (promise as any).status = 'fulfilled'; - (promise as any).value = value; + (promise: any).status = 'fulfilled'; + (promise: any).value = value; const ioInfo: ReactIOInfo = { name: 'stylesheet', start, end, value: promise, - // $FlowFixMe[incompatible-type]: This field doesn't usually take a Fiber but we're only using inside this file. + // $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file. owner: fiber, // Allow linking to the <link> if it's not filtered. }; if (byteSize > 0) { @@ -3011,7 +3002,7 @@ export function attach( } const asyncInfo: ReactAsyncInfo = { awaited: ioInfo, - // $FlowFixMe[incompatible-type]: This field doesn't usually take a Fiber but we're only using inside this file. + // $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file. owner: fiber._debugOwner == null ? null : fiber._debugOwner, debugStack: fiber._debugStack == null ? null : fiber._debugStack, debugTask: fiber._debugTask == null ? null : fiber._debugTask, @@ -3085,9 +3076,9 @@ export function attach( start = resourceEntry.startTime; end = start + resourceEntry.duration; // $FlowFixMe[prop-missing] - fileSize = (resourceEntry.decodedBodySize as any) || 0; + fileSize = (resourceEntry.decodedBodySize: any) || 0; // $FlowFixMe[prop-missing] - byteSize = (resourceEntry.transferSize as any) || 0; + byteSize = (resourceEntry.transferSize: any) || 0; } } } @@ -3111,14 +3102,14 @@ export function attach( value.fileSize = fileSize; } const promise = Promise.resolve(value); - (promise as any).status = 'fulfilled'; - (promise as any).value = value; + (promise: any).status = 'fulfilled'; + (promise: any).value = value; const ioInfo: ReactIOInfo = { name: 'img', start, end, value: promise, - // $FlowFixMe[incompatible-type]: This field doesn't usually take a Fiber but we're only using inside this file. + // $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file. owner: fiber, // Allow linking to the <link> if it's not filtered. }; if (byteSize > 0) { @@ -3127,7 +3118,7 @@ export function attach( } const asyncInfo: ReactAsyncInfo = { awaited: ioInfo, - // $FlowFixMe[incompatible-type]: This field doesn't usually take a Fiber but we're only using inside this file. + // $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file. owner: fiber._debugOwner == null ? null : fiber._debugOwner, debugStack: fiber._debugStack == null ? null : fiber._debugStack, debugTask: fiber._debugTask == null ? null : fiber._debugTask, @@ -3175,7 +3166,7 @@ export function attach( const debugEntry = fiber._debugInfo[i]; if (debugEntry.awaited) { // Async Info - const asyncInfo: ReactAsyncInfo = debugEntry as any; + const asyncInfo: ReactAsyncInfo = (debugEntry: any); if (level === virtualLevel) { // Track any async info between the previous virtual instance up until to this // instance and add it to the parent. This can add the same set multiple times @@ -3189,7 +3180,7 @@ export function attach( continue; } // Scan up until the next Component to see if this component changed environment. - const componentInfo: ReactComponentInfo = debugEntry as any; + const componentInfo: ReactComponentInfo = (debugEntry: any); const secondaryEnv = getSecondaryEnvironmentName(fiber._debugInfo, i); if (componentInfo.env != null) { knownEnvironmentNames.add(componentInfo.env); @@ -3369,7 +3360,6 @@ export function attach( recordSuspenseMount(newSuspenseNode, reconcilingParentSuspenseNode); } insertChild(newInstance); - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('mountFiberRecursively()', newInstance, reconcilingParent); } @@ -3430,7 +3420,6 @@ export function attach( } } insertChild(newInstance); - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('mountFiberRecursively()', newInstance, reconcilingParent); } @@ -3631,7 +3620,6 @@ export function attach( // We use this to simulate unmounting for Suspense trees // when we switch from primary to fallback, or deleting a subtree. function unmountInstanceRecursively(instance: DevToolsInstance) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('unmountInstanceRecursively()', instance, reconcilingParent); } @@ -3667,7 +3655,6 @@ export function attach( isInFocusedActivity = true; } else if ( instance.kind === FIBER_INSTANCE && - // $FlowFixMe[invalid-compare] instance.data !== null && instance.data.tag === ActivityComponent ) { @@ -3789,7 +3776,7 @@ export function attach( // In some cases actualDuration might be 0 for fibers we worked on (particularly if we're using Date.now) // In other cases (e.g. Memo) actualDuration might be greater than 0 even if we "bailed out". const metadata = - currentCommitProfilingMetadata as any as CommitProfilingData; + ((currentCommitProfilingMetadata: any): CommitProfilingData); metadata.durations.push(id, actualDuration, selfDuration); metadata.maxActualDuration = Math.max( metadata.maxActualDuration, @@ -3819,7 +3806,7 @@ export function attach( (fiber.alternate !== null && updaters.has(fiber.alternate))) ) { const metadata = - currentCommitProfilingMetadata as any as CommitProfilingData; + ((currentCommitProfilingMetadata: any): CommitProfilingData); if (metadata.updaters === null) { metadata.updaters = []; } @@ -3884,7 +3871,6 @@ export function attach( function recordResetChildren( parentInstance: FiberInstance | VirtualInstance, ) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { if (parentInstance.firstChild !== null) { debug( @@ -3930,7 +3916,6 @@ export function attach( } function recordResetSuspenseChildren(parentInstance: SuspenseNode) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { if (parentInstance.firstChild !== null) { console.log( @@ -3954,7 +3939,6 @@ export function attach( } pushOperation(SUSPENSE_TREE_OPERATION_REORDER_CHILDREN); // $FlowFixMe[incompatible-call] TODO: Allow filtering SuspenseNode - // $FlowFixMe[incompatible-type] pushOperation(parentInstance.instance.id); pushOperation(numChildren); for (let i = 0; i < nextChildren.length; i++) { @@ -4044,7 +4028,7 @@ export function attach( const debugEntry = nextChild._debugInfo[i]; if (debugEntry.awaited) { // Async Info - const asyncInfo: ReactAsyncInfo = debugEntry as any; + const asyncInfo: ReactAsyncInfo = (debugEntry: any); if (level === virtualLevel) { // Track any async info between the previous virtual instance up until to this // instance and add it to the parent. This can add the same set multiple times @@ -4057,7 +4041,7 @@ export function attach( // Not a Component. Some other Debug Info. continue; } - const componentInfo: ReactComponentInfo = debugEntry as any; + const componentInfo: ReactComponentInfo = (debugEntry: any); const secondaryEnv = getSecondaryEnvironmentName( nextChild._debugInfo, i, @@ -4218,7 +4202,7 @@ export function attach( if (existingInstance !== null) { // Common case. Match in the same parent. const fiberInstance: FiberInstance | FilteredFiberInstance = - existingInstance as any; // Only matches if it's a Fiber. + (existingInstance: any); // Only matches if it's a Fiber. // We keep track if the order of the children matches the previous order. // They are always different referentially, but if the instances line up @@ -4235,7 +4219,7 @@ export function attach( updateFlags |= updateFiberRecursively( fiberInstance, nextChild, - prevChild as any, + (prevChild: any), traceNearestHostComponentUpdate, ); } else if (prevChild !== null && shouldFilterFiber(nextChild)) { @@ -4381,7 +4365,6 @@ export function attach( prevFiber: Fiber, traceNearestHostComponentUpdate: boolean, ): UpdateFlags { - // $FlowFixMe[constant-condition] if (__DEBUG__) { if (fiberInstance !== null) { debug('updateFiberRecursively()', fiberInstance, reconcilingParent); @@ -5054,7 +5037,7 @@ export function attach( flushPendingEvents(currentRoot); - currentRoot = null as any; + currentRoot = (null: any); }); needsToFlushComponentLogs = false; @@ -5169,20 +5152,20 @@ export function attach( if (isProfiling && isProfilingSupported) { if (!shouldBailoutWithPendingOperations()) { - const commitProfilingMetadata = ( - rootToCommitProfilingMetadataMap as any as CommitProfilingMetadataMap - ).get(currentRoot.id); + const commitProfilingMetadata = + ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).get( + currentRoot.id, + ); if (commitProfilingMetadata != null) { commitProfilingMetadata.push( - currentCommitProfilingMetadata as any as CommitProfilingData, + ((currentCommitProfilingMetadata: any): CommitProfilingData), ); } else { - ( - rootToCommitProfilingMetadataMap as any as CommitProfilingMetadataMap - ).set(currentRoot.id, [ - currentCommitProfilingMetadata as any as CommitProfilingData, - ]); + ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).set( + currentRoot.id, + [((currentCommitProfilingMetadata: any): CommitProfilingData)], + ); } } } @@ -5196,7 +5179,7 @@ export function attach( hook.emit('traceUpdates', traceUpdatesForNodes); } - currentRoot = null as any; + currentRoot = (null: any); } function getResourceInstance(fiber: Fiber): HostInstance | null { @@ -5331,7 +5314,7 @@ export function attach( const owner = getUnfilteredOwner(fiber); if (owner != null) { if (typeof owner.tag === 'number') { - return getDisplayNameForFiber(owner as any); + return getDisplayNameForFiber((owner: any)); } else { return owner.name || ''; } @@ -5358,7 +5341,7 @@ export function attach( function getNearestMountedDOMNode(publicInstance: Element): null | Element { let domNode: null | Element = publicInstance; while (domNode && !publicInstanceToDevToolsInstanceMap.has(domNode)) { - // $FlowFixMe[incompatible-type]: In practice this is either null or Element. + // $FlowFixMe: In practice this is either null or Element. domNode = domNode.parentNode; } return domNode; @@ -5371,7 +5354,7 @@ export function attach( if (instance !== undefined) { if (instance.kind === FILTERED_FIBER_INSTANCE) { // A Filtered Fiber Instance will always have a Virtual Instance as a parent. - return (instance.parent as any as VirtualInstance).id; + return ((instance.parent: any): VirtualInstance).id; } return instance.id; } @@ -5406,7 +5389,7 @@ export function attach( ): mixed { if (isMostRecentlyInspectedElement(id)) { return getInObject( - mostRecentlyInspectedElement as any as InspectedElement, + ((mostRecentlyInspectedElement: any): InspectedElement), path, ); } @@ -5532,21 +5515,21 @@ export function attach( return null; } if (typeof owner.tag === 'number') { - const ownerFiber: Fiber = owner as any; // Refined + const ownerFiber: Fiber = (owner: any); // Refined owner = ownerFiber._debugOwner; } else { - const ownerInfo: ReactComponentInfo = owner as any; // Refined + const ownerInfo: ReactComponentInfo = (owner: any); // Refined owner = ownerInfo.owner; } while (owner) { if (typeof owner.tag === 'number') { - const ownerFiber: Fiber = owner as any; // Refined + const ownerFiber: Fiber = (owner: any); // Refined if (!shouldFilterFiber(ownerFiber)) { return ownerFiber; } owner = ownerFiber._debugOwner; } else { - const ownerInfo: ReactComponentInfo = owner as any; // Refined + const ownerInfo: ReactComponentInfo = (owner: any); // Refined if (!shouldFilterVirtual(ownerInfo, null)) { return ownerInfo; } @@ -5572,7 +5555,7 @@ export function attach( // isn't propagated down as the new owner. In that case we might match the alternate // instead. This is a bit hacky but the fastest check since type casting owner to a Fiber // needs a duck type check anyway. - parentInstance.data === (owner as any).alternate + parentInstance.data === (owner: any).alternate ) { if (parentInstance.kind === FILTERED_FIBER_INSTANCE) { return null; @@ -5653,7 +5636,7 @@ export function attach( } let firstInstance: null | DevToolsInstance = null; if (filterByChildInstance === null) { - firstInstance = set.values().next().value as any; + firstInstance = (set.values().next().value: any); } else { // eslint-disable-next-line no-for-of-loops/no-for-of-loops for (const childInstance of set.values()) { @@ -6062,7 +6045,6 @@ export function attach( } const fiber = devtoolsInstance.data; - // $FlowFixMe[invalid-compare] if (fiber !== null) { instance = fiber.stateNode; @@ -6089,7 +6071,7 @@ export function attach( ? inspectRootsRaw(devtoolsInstance.id) : inspectFiberInstanceRaw(devtoolsInstance); } - devtoolsInstance as FilteredFiberInstance; // assert exhaustive + (devtoolsInstance: FilteredFiberInstance); // assert exhaustive throw new Error('Unsupported instance kind'); } @@ -6170,7 +6152,7 @@ export function attach( context = consumerResolvedContext._currentValue || null; // Look for overridden value. - let current = (fiber as any as Fiber).return; + let current = ((fiber: any): Fiber).return; while (current !== null) { const currentType = current.type; const currentTypeSymbol = getTypeSymbol(currentType); @@ -6203,7 +6185,7 @@ export function attach( context = consumerResolvedContext._currentValue || null; // Look for overridden value. - let current = (fiber as any as Fiber).return; + let current = ((fiber: any): Fiber).return; while (current !== null) { const currentType = current.type; const currentTypeSymbol = getTypeSymbol(currentType); @@ -6703,7 +6685,7 @@ export function attach( ): void { if (isMostRecentlyInspectedElement(id)) { const value = getInObject( - mostRecentlyInspectedElement as any as InspectedElement, + ((mostRecentlyInspectedElement: any): InspectedElement), path, ); const key = `$reactTemp${count}`; @@ -6721,7 +6703,7 @@ export function attach( ): ?string { if (isMostRecentlyInspectedElement(id)) { const valueToCopy = getInObject( - mostRecentlyInspectedElement as any as InspectedElement, + ((mostRecentlyInspectedElement: any): InspectedElement), path, ); @@ -6756,7 +6738,7 @@ export function attach( path, value: cleanForBridge( getInObject( - mostRecentlyInspectedElement as any as InspectedElement, + ((mostRecentlyInspectedElement: any): InspectedElement), path, ), createIsPathAllowed(null, secondaryCategory), @@ -7043,7 +7025,6 @@ export function attach( return; } const fiber = devtoolsInstance.data; - // $FlowFixMe[invalid-compare] if (fiber !== null) { const instance = fiber.stateNode; @@ -7071,7 +7052,7 @@ export function attach( break; case 'hooks': if (typeof overrideHookStateDeletePath === 'function') { - overrideHookStateDeletePath(fiber, hookID as any as number, path); + overrideHookStateDeletePath(fiber, ((hookID: any): number), path); } break; case 'props': @@ -7114,7 +7095,6 @@ export function attach( return; } const fiber = devtoolsInstance.data; - // $FlowFixMe[invalid-compare] if (fiber !== null) { const instance = fiber.stateNode; @@ -7145,7 +7125,7 @@ export function attach( if (typeof overrideHookStateRenamePath === 'function') { overrideHookStateRenamePath( fiber, - hookID as any as number, + ((hookID: any): number), oldPath, newPath, ); @@ -7198,7 +7178,6 @@ export function attach( return; } const fiber = devtoolsInstance.data; - // $FlowFixMe[invalid-compare] if (fiber !== null) { const instance = fiber.stateNode; @@ -7227,7 +7206,7 @@ export function attach( break; case 'hooks': if (typeof overrideHookState === 'function') { - overrideHookState(fiber, hookID as any as number, path, value); + overrideHookState(fiber, ((hookID: any): number), path, value); } break; case 'props': @@ -7445,16 +7424,13 @@ export function attach( ); } const rootID = rootInstance.id; - (displayNamesByRootID as any as DisplayNamesByRootID).set( + ((displayNamesByRootID: any): DisplayNamesByRootID).set( rootID, getDisplayNameForRoot(root.current), ); const initialTreeBaseDurations: Array<[number, number]> = []; snapshotTreeBaseDurations(rootInstance, initialTreeBaseDurations); - (initialTreeBaseDurationsMap as any).set( - rootID, - initialTreeBaseDurations, - ); + (initialTreeBaseDurationsMap: any).set(rootID, initialTreeBaseDurations); }); isProfiling = true; @@ -8116,7 +8092,7 @@ export function attach( // but it's at least somewhere within it. if (isError(unresolvedSource)) { return (instance.source = extractLocationFromOwnerStack( - unresolvedSource as any, + (unresolvedSource: any), )); } if (typeof unresolvedSource === 'string') { @@ -8126,7 +8102,7 @@ export function attach( return (instance.source = extractLocationFromComponentStack(lastLine)); } - // $FlowFixMe[incompatible-type]: refined. + // $FlowFixMe: refined. return unresolvedSource; } diff --git a/packages/react-devtools-shared/src/backend/fiber/shared/DevToolsFiberInspection.js b/packages/react-devtools-shared/src/backend/fiber/shared/DevToolsFiberInspection.js index c827753a78fb..58c86f7cac09 100644 --- a/packages/react-devtools-shared/src/backend/fiber/shared/DevToolsFiberInspection.js +++ b/packages/react-devtools-shared/src/backend/fiber/shared/DevToolsFiberInspection.js @@ -21,7 +21,7 @@ export function isError(object: mixed): boolean { export function getFiberFlags(fiber: Fiber): number { // The name of this field changed from "effectTag" to "flags" - return fiber.flags !== undefined ? fiber.flags : (fiber as any).effectTag; + return fiber.flags !== undefined ? fiber.flags : (fiber: any).effectTag; } export function rootSupportsProfiling(root: any): boolean { @@ -61,7 +61,7 @@ export function getSecondaryEnvironmentName( index: number, ): null | string { if (debugInfo != null) { - const componentInfo: ReactComponentInfo = debugInfo[index] as any; + const componentInfo: ReactComponentInfo = (debugInfo[index]: any); for (let i = index + 1; i < debugInfo.length; i++) { const debugEntry = debugInfo[i]; if (typeof debugEntry.env === 'string') { diff --git a/packages/react-devtools-shared/src/backend/fiber/shared/DevToolsFiberInternalReactConstants.js b/packages/react-devtools-shared/src/backend/fiber/shared/DevToolsFiberInternalReactConstants.js index aa0449819f39..2f7f92eefbfd 100644 --- a/packages/react-devtools-shared/src/backend/fiber/shared/DevToolsFiberInternalReactConstants.js +++ b/packages/react-devtools-shared/src/backend/fiber/shared/DevToolsFiberInternalReactConstants.js @@ -99,7 +99,7 @@ export function getInternalReactConstants(version: string): { const SuspenseyImagesMode = 0b0100000; - let ReactTypeOfWork: WorkTagMap = null as any as WorkTagMap; + let ReactTypeOfWork: WorkTagMap = ((null: any): WorkTagMap); // ********************************************************** // The section below is copied from files in React repo. @@ -385,16 +385,12 @@ export function getInternalReactConstants(version: string): { } switch (tag) { - // $FlowFixMe[invalid-compare] case ActivityComponent: return 'Activity'; - // $FlowFixMe[invalid-compare] case CacheComponent: return 'Cache'; case ClassComponent: - // $FlowFixMe[invalid-compare] -- falls through case IncompleteClassComponent: - // $FlowFixMe[invalid-compare] -- falls through case IncompleteFunctionComponent: case FunctionComponent: case IndeterminateComponent: @@ -413,9 +409,7 @@ export function getInternalReactConstants(version: string): { } return null; case HostComponent: - // $FlowFixMe[invalid-compare] -- falls through case HostSingleton: - // $FlowFixMe[invalid-compare] -- falls through case HostHoistable: return type; case HostPortal: @@ -423,15 +417,12 @@ export function getInternalReactConstants(version: string): { return null; case Fragment: return 'Fragment'; - // $FlowFixMe[invalid-compare] case LazyComponent: // This display name will not be user visible. // Once a Lazy component loads its inner component, React replaces the tag and type. // This display name will only show up in console logs when DevTools DEBUG mode is on. return 'Lazy'; - // $FlowFixMe[invalid-compare] case MemoComponent: - // $FlowFixMe[invalid-compare] -- falls through case SimpleMemoComponent: // Display name in React does not use `Memo` as a wrapper but fallback name. return getWrappedDisplayName( @@ -442,27 +433,20 @@ export function getInternalReactConstants(version: string): { ); case SuspenseComponent: return 'Suspense'; - // $FlowFixMe[invalid-compare] case LegacyHiddenComponent: return 'LegacyHidden'; - // $FlowFixMe[invalid-compare] case OffscreenComponent: return 'Offscreen'; - // $FlowFixMe[invalid-compare] case ScopeComponent: return 'Scope'; - // $FlowFixMe[invalid-compare] case SuspenseListComponent: return 'SuspenseList'; case Profiler: return 'Profiler'; - // $FlowFixMe[invalid-compare] case TracingMarkerComponent: return 'TracingMarker'; - // $FlowFixMe[invalid-compare] case ViewTransitionComponent: return 'ViewTransition'; - // $FlowFixMe[invalid-compare] case Throw: // This should really never be visible. return 'Error'; diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index 07562065e712..ccd9cdac3e02 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -146,7 +146,7 @@ export function attach( new WeakMap(); let getElementIDForHostInstance: GetElementIDForHostInstance = - null as any as GetElementIDForHostInstance; + ((null: any): GetElementIDForHostInstance); let findHostInstanceForInternalID: (id: number) => ?HostInstance; let getNearestMountedDOMNode = (node: Element): null | Element => { // Not implemented. @@ -198,7 +198,7 @@ export function attach( internalInstanceToIDMap.set(internalInstance, id); idToInternalInstanceMap.set(id, internalInstance); } - return internalInstanceToIDMap.get(internalInstance) as any as number; + return ((internalInstanceToIDMap.get(internalInstance): any): number); } function areEqualArrays(a: Array<any>, b: Array<any>) { @@ -390,7 +390,6 @@ export function attach( ) { const isRoot = parentID === 0; - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( '%crecordMount()', @@ -473,7 +472,6 @@ export function attach( parentID: number, rootID: number, ) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.group('crawlAndRecordInitialMounts() id:', id); } @@ -487,7 +485,6 @@ export function attach( ); } - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.groupEnd(); } @@ -587,7 +584,6 @@ export function attach( } i += pendingOperations.length; - // $FlowFixMe[constant-condition] if (__DEBUG__) { printOperationsArray(operations); } @@ -832,7 +828,7 @@ export function attach( let owner = element._owner; if (owner) { - owners = [] as Array<SerializedElement>; + owners = ([]: Array<SerializedElement>); while (owner != null) { owners.push({ displayName: getData(owner).displayName || 'Unknown', diff --git a/packages/react-devtools-shared/src/backend/profilingHooks.js b/packages/react-devtools-shared/src/backend/profilingHooks.js index 2a10eb5040f6..a3feb1748801 100644 --- a/packages/react-devtools-shared/src/backend/profilingHooks.js +++ b/packages/react-devtools-shared/src/backend/profilingHooks.js @@ -213,8 +213,8 @@ export function createProfilingHooks({ function markAndClear(markName: string) { // This method won't be called unless these functions are defined, so we can skip the extra typeof check. - (performanceTarget as any as Performance).mark(markName); - (performanceTarget as any as Performance).clearMarks(markName); + ((performanceTarget: any): Performance).mark(markName); + ((performanceTarget: any): Performance).clearMarks(markName); } function recordReactMeasureStarted( @@ -605,7 +605,7 @@ export function createProfilingHooks({ if (!wakeableIDs.has(wakeable)) { wakeableIDs.set(wakeable, wakeableID++); } - return wakeableIDs.get(wakeable) as any as number; + return ((wakeableIDs.get(wakeable): any): number); } function markComponentSuspended( @@ -626,7 +626,7 @@ export function createProfilingHooks({ // frameworks like Relay may also annotate Promises with a displayName, // describing what operation/data the thrown Promise is related to. // When this is available we should pass it along to the Timeline. - const displayName = (wakeable as any).displayName || ''; + const displayName = (wakeable: any).displayName || ''; let suspenseEvent: SuspenseEvent | null = null; // TODO (timeline) Record and cache component stack diff --git a/packages/react-devtools-shared/src/backend/shared/ReactSymbols.js b/packages/react-devtools-shared/src/backend/shared/ReactSymbols.js index db5d05eb8da2..483671b90038 100644 --- a/packages/react-devtools-shared/src/backend/shared/ReactSymbols.js +++ b/packages/react-devtools-shared/src/backend/shared/ReactSymbols.js @@ -75,6 +75,6 @@ export const REACT_MEMO_CACHE_SENTINEL: symbol = Symbol.for( import type {ReactOptimisticKey} from 'shared/ReactTypes'; -export const REACT_OPTIMISTIC_KEY: ReactOptimisticKey = Symbol.for( +export const REACT_OPTIMISTIC_KEY: ReactOptimisticKey = (Symbol.for( 'react.optimistic_key', -) as any; +): any); diff --git a/packages/react-devtools-shared/src/backend/utils/formatWithStyles.js b/packages/react-devtools-shared/src/backend/utils/formatWithStyles.js index 6d92b7dad583..b258141e353f 100644 --- a/packages/react-devtools-shared/src/backend/utils/formatWithStyles.js +++ b/packages/react-devtools-shared/src/backend/utils/formatWithStyles.js @@ -32,7 +32,6 @@ export default function formatWithStyles( ): $ReadOnlyArray<any> { if ( inputArgs === undefined || - // $FlowFixMe[invalid-compare] inputArgs === null || inputArgs.length === 0 || // Matches any of %c but not %%c diff --git a/packages/react-devtools-shared/src/backend/utils/index.js b/packages/react-devtools-shared/src/backend/utils/index.js index 816145fdb19d..fcb8d448a0c1 100644 --- a/packages/react-devtools-shared/src/backend/utils/index.js +++ b/packages/react-devtools-shared/src/backend/utils/index.js @@ -61,7 +61,7 @@ export function copyWithDelete( const updated = isArray(obj) ? obj.slice() : {...obj}; if (index + 1 === path.length) { if (isArray(updated)) { - updated.splice(key as any as number, 1); + updated.splice(((key: any): number), 1); } else { delete updated[key]; } @@ -87,7 +87,7 @@ export function copyWithRename( // $FlowFixMe[incompatible-use] number or string is fine here updated[newKey] = updated[oldKey]; if (isArray(updated)) { - updated.splice(oldKey as any as number, 1); + updated.splice(((oldKey: any): number), 1); } else { delete updated[oldKey]; } @@ -195,7 +195,7 @@ export function formatConsoleArgumentsToSingleString( if (args.length) { const REGEXP = /(%?)(%([jds]))/g; - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] formatted = formatted.replace(REGEXP, (match, escaped, ptn, flag) => { let arg = args.shift(); switch (flag) { diff --git a/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js b/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js index 109c12de37a9..e18c2d7baa1b 100644 --- a/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js +++ b/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js @@ -224,12 +224,12 @@ function collectStackTrace( const enclosingLine: number = // $FlowFixMe[prop-missing] typeof callSite.getEnclosingLineNumber === 'function' - ? (callSite as any).getEnclosingLineNumber() || 0 + ? (callSite: any).getEnclosingLineNumber() || 0 : 0; const enclosingCol: number = // $FlowFixMe[prop-missing] typeof callSite.getEnclosingColumnNumber === 'function' - ? (callSite as any).getEnclosingColumnNumber() || 0 + ? (callSite: any).getEnclosingColumnNumber() || 0 : 0; const isAsync = // $FlowFixMe[prop-missing] diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/Highlighter.js b/packages/react-devtools-shared/src/backend/views/Highlighter/Highlighter.js index 6fdd1ac7dc50..ddcb6f1ef11a 100644 --- a/packages/react-devtools-shared/src/backend/views/Highlighter/Highlighter.js +++ b/packages/react-devtools-shared/src/backend/views/Highlighter/Highlighter.js @@ -75,7 +75,7 @@ export function showOverlay( return isReactNativeEnvironment() ? showOverlayNative(elements, agent) : showOverlayWeb( - elements as $ReadOnlyArray<any>, + (elements: $ReadOnlyArray<any>), componentName, agent, hideAfterTimeout, diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js b/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js index 9384966b24d2..e55bdb4298c4 100644 --- a/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js +++ b/packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js @@ -191,9 +191,9 @@ export default class Overlay { // We can't get the size of text nodes or comment nodes. React as of v15 // heavily uses comment nodes to delimit text. // TODO: We actually can measure text nodes. We should. - const elements: $ReadOnlyArray<HTMLElement> = nodes.filter( + const elements: $ReadOnlyArray<HTMLElement> = (nodes.filter( node => node.nodeType === Node.ELEMENT_NODE, - ) as any; + ): any); while (this.rects.length > elements.length) { const rect = this.rects.pop(); diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js index d0c6484b30b8..03397012b536 100644 --- a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js +++ b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js @@ -434,7 +434,7 @@ export default function setupHighlighter( lastHoveredNode = target; if (target.tagName === 'IFRAME') { - const iframe: HTMLIFrameElement = target as any; + const iframe: HTMLIFrameElement = (target: any); try { if (!iframesListeningTo.has(iframe)) { const window = iframe.contentWindow; @@ -493,9 +493,9 @@ export default function setupHighlighter( function getEventTarget(event: MouseEvent): HTMLElement { if (event.composed) { - return event.composedPath()[0] as any; + return (event.composedPath()[0]: any); } - return event.target as any; + return (event.target: any); } } diff --git a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js index 56b6497d2d1b..b9e2cd906813 100644 --- a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js +++ b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js @@ -48,7 +48,7 @@ function drawWeb(nodeToData: Map<HostInstance, Data>) { } const dpr = window.devicePixelRatio || 1; - const canvasFlow: HTMLCanvasElement = canvas as any as HTMLCanvasElement; + const canvasFlow: HTMLCanvasElement = ((canvas: any): HTMLCanvasElement); canvasFlow.width = window.innerWidth * dpr; canvasFlow.height = window.innerHeight * dpr; canvasFlow.style.width = `${window.innerWidth}px`; @@ -217,7 +217,7 @@ function destroyWeb() { // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API and loses canvas nullability tracking if (canvas.parentNode != null) { - // $FlowFixMe[incompatible-type]: Flow doesn't track that canvas is non-null here + // $FlowFixMe[incompatible-call]: Flow doesn't track that canvas is non-null here canvas.parentNode.removeChild(canvas); } canvas = null; diff --git a/packages/react-devtools-shared/src/backend/views/TraceUpdates/index.js b/packages/react-devtools-shared/src/backend/views/TraceUpdates/index.js index b7efe51337ab..5b274e8a2f60 100644 --- a/packages/react-devtools-shared/src/backend/views/TraceUpdates/index.js +++ b/packages/react-devtools-shared/src/backend/views/TraceUpdates/index.js @@ -47,7 +47,7 @@ export type Data = { const nodeToData: Map<HostInstance, Data> = new Map(); -let agent: Agent = null as any as Agent; +let agent: Agent = ((null: any): Agent); let drawAnimationFrameID: AnimationFrameID | null = null; let isEnabled: boolean = false; let redrawTimeoutID: TimeoutID | null = null; diff --git a/packages/react-devtools-shared/src/backend/views/utils.js b/packages/react-devtools-shared/src/backend/views/utils.js index 55c5a2f14eb3..a73c8094edb9 100644 --- a/packages/react-devtools-shared/src/backend/views/utils.js +++ b/packages/react-devtools-shared/src/backend/views/utils.js @@ -104,7 +104,6 @@ export function getNestedBoundingClientRect( } // $FlowFixMe[incompatible-variance] - // $FlowFixMe[incompatible-type] return mergeRectOffsets(rects); } else { // $FlowFixMe[incompatible-variance] diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js index ae5641232971..c332393d2aa1 100644 --- a/packages/react-devtools-shared/src/backendAPI.js +++ b/packages/react-devtools-shared/src/backendAPI.js @@ -204,7 +204,7 @@ function getPromiseForRequestID<T>( const onInspectedElement = (data: any) => { if (data.responseID === requestID) { cleanup(); - resolve(data as T); + resolve((data: T)); } }; diff --git a/packages/react-devtools-shared/src/bridge.js b/packages/react-devtools-shared/src/bridge.js index a568c22c392d..ba4b2a0f8061 100644 --- a/packages/react-devtools-shared/src/bridge.js +++ b/packages/react-devtools-shared/src/bridge.js @@ -323,7 +323,7 @@ class Bridge< this._wallUnlisten = wall.listen((message: Message) => { if (message && message.event) { - (this as any).emit(message.event, message.payload); + (this: any).emit(message.event, message.payload); } }) || null; @@ -361,13 +361,13 @@ class Bridge< this._messageQueue.push(event, payload); if (!this._scheduledFlush) { this._scheduledFlush = true; - // $FlowFixMe[cannot-resolve-name] + // $FlowFixMe if (typeof devtoolsJestTestScheduler === 'function') { // This exists just for our own jest tests. // They're written in such a way that we can neither mock queueMicrotask // because then we break React DOM and we can't not mock it because then // we can't synchronously flush it. So they need to be rewritten. - // $FlowFixMe[cannot-resolve-name] + // $FlowFixMe devtoolsJestTestScheduler(this._flush); // eslint-disable-line no-undef } else { queueMicrotask(this._flush); diff --git a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-fb.js b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-fb.js index 684ca4d21cd0..5d9c476b3bfd 100644 --- a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-fb.js +++ b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-fb.js @@ -27,4 +27,4 @@ import typeof * as FeatureFlagsType from './DevToolsFeatureFlags.default'; import typeof * as ExportsType from './DevToolsFeatureFlags.core-fb'; // Flow magic to verify the exports of this file match the original version. -null as any as ExportsType as FeatureFlagsType as ExportsType; +((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-oss.js b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-oss.js index d506e05ad43f..88b01afc3ee0 100644 --- a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-oss.js +++ b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-oss.js @@ -27,4 +27,4 @@ import typeof * as FeatureFlagsType from './DevToolsFeatureFlags.default'; import typeof * as ExportsType from './DevToolsFeatureFlags.core-oss'; // Flow magic to verify the exports of this file match the original version. -null as any as ExportsType as FeatureFlagsType as ExportsType; +((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-fb.js b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-fb.js index 2bf6eef6172e..734e7988c858 100644 --- a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-fb.js +++ b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-fb.js @@ -27,4 +27,4 @@ import typeof * as FeatureFlagsType from './DevToolsFeatureFlags.default'; import typeof * as ExportsType from './DevToolsFeatureFlags.extension-fb'; // Flow magic to verify the exports of this file match the original version. -null as any as ExportsType as FeatureFlagsType as ExportsType; +((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-oss.js b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-oss.js index dfafc3262ca4..c9b665a9096e 100644 --- a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-oss.js +++ b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-oss.js @@ -27,4 +27,4 @@ import typeof * as FeatureFlagsType from './DevToolsFeatureFlags.default'; import typeof * as ExportsType from './DevToolsFeatureFlags.extension-oss'; // Flow magic to verify the exports of this file match the original version. -null as any as ExportsType as FeatureFlagsType as ExportsType; +((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.js b/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.js index 5befb9ab57dd..ded5bbb22e6c 100644 --- a/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.js +++ b/packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.js @@ -84,10 +84,10 @@ export default function ContextMenu({ "Can't access context menu element. This is a bug in React DevTools.", ); } - const menu = maybeMenu as HTMLDivElement; + const menu = (maybeMenu: HTMLDivElement); function hideUnlessContains(event: Event) { - if (!menu.contains(event.target as any as Node)) { + if (!menu.contains(((event.target: any): Node))) { hide(); } } diff --git a/packages/react-devtools-shared/src/devtools/ContextMenu/types.js b/packages/react-devtools-shared/src/devtools/ContextMenu/types.js index 6395383bf83f..9fda9be19994 100644 --- a/packages/react-devtools-shared/src/devtools/ContextMenu/types.js +++ b/packages/react-devtools-shared/src/devtools/ContextMenu/types.js @@ -26,7 +26,6 @@ export type ContextMenuHandle = { }; export type ContextMenuComponent = component( - // eslint-disable-next-line no-undef - ref: React.RefSetter<ContextMenuHandle>, + ref: React$RefSetter<ContextMenuHandle>, ); export type ContextMenuRef = {current: ContextMenuHandle | null}; diff --git a/packages/react-devtools-shared/src/devtools/cache.js b/packages/react-devtools-shared/src/devtools/cache.js index 0473e38ee0a0..c927b4d3dbf3 100644 --- a/packages/react-devtools-shared/src/devtools/cache.js +++ b/packages/react-devtools-shared/src/devtools/cache.js @@ -46,10 +46,10 @@ if (typeof React.use === 'function') { return React.use(Context); }; } else if ( - typeof (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === + typeof (React: any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === 'object' ) { - const ReactCurrentDispatcher = (React as any) + const ReactCurrentDispatcher = (React: any) .__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher; readContext = function (Context: ReactContext<null>) { const dispatcher = ReactCurrentDispatcher.current; @@ -79,9 +79,9 @@ const resourceConfigs: Map<Resource<any, any, any>, Config> = new Map(); function getEntriesForResource( resource: any, ): Map<any, any> | WeakMap<any, any> { - let entriesForResource: Map<any, any> | WeakMap<any, any> = entries.get( + let entriesForResource: Map<any, any> | WeakMap<any, any> = ((entries.get( resource, - ) as any as Map<any, any>; + ): any): Map<any, any>); if (entriesForResource === undefined) { const config = resourceConfigs.get(resource); entriesForResource = @@ -103,12 +103,12 @@ function accessResult<Input, Key, Value>( const thenable = fetch(input); thenable.then( value => { - const fulfilledThenable: FulfilledThenable<Value> = thenable as any; + const fulfilledThenable: FulfilledThenable<Value> = (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = value; }, error => { - const rejectedThenable: RejectedThenable<Value> = thenable as any; + const rejectedThenable: RejectedThenable<Value> = (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; }, @@ -171,9 +171,9 @@ export function createResource<Input, Key, Value>( write(key: Key, value: Value): void { const entriesForResource = getEntriesForResource(resource); - const fulfilledThenable: FulfilledThenable<Value> = Promise.resolve( + const fulfilledThenable: FulfilledThenable<Value> = (Promise.resolve( value, - ) as any; + ): any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = value; diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js index 557020620e91..e2da4c218a6c 100644 --- a/packages/react-devtools-shared/src/devtools/store.js +++ b/packages/react-devtools-shared/src/devtools/store.js @@ -96,7 +96,6 @@ class RectRBush extends RBush<Rect> { } const debug = (methodName: string, ...args: Array<string>) => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `%cStore %c${methodName}`, @@ -274,7 +273,6 @@ export default class Store extends EventEmitter<{ constructor(bridge: FrontendBridge, config?: Config) { super(); - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('constructor', 'subscribing to Bridge'); } @@ -1057,7 +1055,6 @@ export default class Store extends EventEmitter<{ ): Array<SuspenseTimelineStep> { const target: Array<SuspenseTimelineStep> = []; const focusedTransitionID = this._focusedTransition; - // $FlowFixMe[invalid-compare] if (focusedTransitionID === null) { return target; } @@ -1313,7 +1310,7 @@ export default class Store extends EventEmitter<{ if (didMutate) { let weightAcrossRoots = 0; this._roots.forEach(rootID => { - const {weight} = this.getElementByID(rootID) as any as Element; + const {weight} = ((this.getElementByID(rootID): any): Element); weightAcrossRoots += weight; }); this._weightAcrossRoots = weightAcrossRoots; @@ -1376,7 +1373,6 @@ export default class Store extends EventEmitter<{ }; onBridgeOperations: (operations: Array<number>) => void = operations => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.groupCollapsed('onBridgeOperations'); debug('onBridgeOperations', operations.join(',')); @@ -1426,7 +1422,7 @@ export default class Store extends EventEmitter<{ switch (operation) { case TREE_OPERATION_ADD: { const id = operations[i + 1]; - const type = operations[i + 2] as any as ElementType; + const type = ((operations[i + 2]: any): ElementType); i += 3; @@ -1439,7 +1435,6 @@ export default class Store extends EventEmitter<{ } if (type === ElementTypeRoot) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('Add', `new root node ${id}`); } @@ -1531,7 +1526,6 @@ export default class Store extends EventEmitter<{ const nameProp = stringTable[namePropStringID]; i++; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'Add', @@ -1617,13 +1611,13 @@ export default class Store extends EventEmitter<{ const element = this._idToElement.get(id); if (element === undefined) { - this._throwAndEmitError( - Error( + if (__DEBUG__) { + console.warn( `Cannot remove node "${id}" because no matching node was found in the Store.`, - ), - ); + ); + } - break; + continue; } i += 1; @@ -1639,7 +1633,6 @@ export default class Store extends EventEmitter<{ let parentElement: ?Element = null; if (parentID === 0) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('Remove', `node ${id} root`); } @@ -1650,20 +1643,19 @@ export default class Store extends EventEmitter<{ haveRootsChanged = true; } else { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('Remove', `node ${id} from parent ${parentID}`); } parentElement = this._idToElement.get(parentID); if (parentElement === undefined) { - this._throwAndEmitError( - Error( + if (__DEBUG__) { + console.warn( `Cannot remove node "${id}" from parent "${parentID}" because no matching node was found in the Store.`, - ), - ); + ); + } - break; + continue; } const index = parentElement.children.indexOf(id); @@ -1729,7 +1721,6 @@ export default class Store extends EventEmitter<{ } i += numChildren; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('Re-order', `Node ${id} children ${children.join(',')}`); } @@ -1785,7 +1776,6 @@ export default class Store extends EventEmitter<{ } } - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'Subtree mode', @@ -1820,7 +1810,7 @@ export default class Store extends EventEmitter<{ const parentID = operations[i + 2]; const nameStringID = operations[i + 3]; const isSuspended = operations[i + 4] === 1; - const numRects = operations[i + 5] as any as number; + const numRects = ((operations[i + 5]: any): number); let name = stringTable[nameStringID]; if (this._idToSuspense.has(id)) { @@ -1868,7 +1858,6 @@ export default class Store extends EventEmitter<{ } } - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('Suspense Add', `node ${id} as child of ${parentID}`); } @@ -1942,12 +1931,10 @@ export default class Store extends EventEmitter<{ let parentSuspense: ?SuspenseNode = null; if (parentID === 0) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('Suspense remove', `node ${id} root`); } } else { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('Suspense Remove', `node ${id} from parent ${parentID}`); } @@ -2018,7 +2005,6 @@ export default class Store extends EventEmitter<{ } i += numChildren; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'Re-order', @@ -2030,8 +2016,8 @@ export default class Store extends EventEmitter<{ break; } case SUSPENSE_TREE_OPERATION_RESIZE: { - const id = operations[i + 1] as any as number; - const numRects = operations[i + 2] as any as number; + const id = ((operations[i + 1]: any): number); + const numRects = ((operations[i + 2]: any): number); i += 3; const suspense = this._idToSuspense.get(id); @@ -2077,7 +2063,6 @@ export default class Store extends EventEmitter<{ suspense.rects = nextRects; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'Resize', @@ -2132,7 +2117,6 @@ export default class Store extends EventEmitter<{ break; } - // $FlowFixMe[constant-condition] if (__DEBUG__) { const previousHasUniqueSuspenders = suspense.hasUniqueSuspenders; debug( @@ -2251,7 +2235,6 @@ export default class Store extends EventEmitter<{ this.emit('suspenseTreeMutated', [removedSuspenseIDs]); } - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log(printStore(this, true)); console.groupEnd(); @@ -2276,7 +2259,7 @@ export default class Store extends EventEmitter<{ if (didCollapse) { let weightAcrossRoots = 0; this._roots.forEach(rootID => { - const {weight} = this.getElementByID(rootID) as any as Element; + const {weight} = ((this.getElementByID(rootID): any): Element); weightAcrossRoots += weight; }); this._weightAcrossRoots = weightAcrossRoots; @@ -2336,7 +2319,6 @@ export default class Store extends EventEmitter<{ } onBridgeShutdown: () => void = () => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('onBridgeShutdown', 'unsubscribing from Bridge'); } diff --git a/packages/react-devtools-shared/src/devtools/utils.js b/packages/react-devtools-shared/src/devtools/utils.js index ba21ad0fe2b3..43d3ea6c837b 100644 --- a/packages/react-devtools-shared/src/devtools/utils.js +++ b/packages/react-devtools-shared/src/devtools/utils.js @@ -156,7 +156,7 @@ export function printStore( } store.roots.forEach(rootID => { - const {weight} = store.getElementByID(rootID) as any as Element; + const {weight} = ((store.getElementByID(rootID): any): Element); const maybeWeightLabel = includeWeight ? ` (${weight})` : ''; // Store does not (yet) expose a way to get errors/warnings per root. diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Components.js b/packages/react-devtools-shared/src/devtools/views/Components/Components.js index c1f14e6984ce..80b6d450331d 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Components.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Components.js @@ -239,4 +239,4 @@ function setResizeCSSVariable( } } -export default portaledContent(Components) as component(); +export default (portaledContent(Components): component()); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Element.js b/packages/react-devtools-shared/src/devtools/views/Components/Element.js index 284640364cb9..da71f86dd797 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Element.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Element.js @@ -89,7 +89,6 @@ export default function Element({data, index, style}: Props): React.Node { // $FlowFixMe[missing-local-annot] const handleClick = ({metaKey, button}) => { - // $FlowFixMe[invalid-compare] if (id !== null && button === 0) { logEvent({ event_name: 'select-element', @@ -104,7 +103,6 @@ export default function Element({data, index, style}: Props): React.Node { const handleMouseEnter = () => { setIsHovered(true); - // $FlowFixMe[invalid-compare] if (id !== null) { onElementMouseEnter(id); } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js index 922b564600a5..f6549fc243e4 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js @@ -57,7 +57,7 @@ type Context = { }; export const InspectedElementContext: ReactContext<Context> = - createContext<Context>(null as any as Context); + createContext<Context>(((null: any): Context)); export type Props = { children: ReactNodeList, diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContextTree.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContextTree.js index 129b3f76bf09..941fa5fe01dd 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContextTree.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContextTree.js @@ -70,18 +70,15 @@ export default function InspectedElementContextTree({ <div className={styles.Header}> {hasLegacyContext ? 'legacy context' : 'context'} </div> - {/* $FlowFixMe[constant-condition] */} {!isEmpty && ( <Button onClick={handleCopy} title="Copy to clipboard"> <ButtonIcon type="copy" /> </Button> )} </div> - {/* $FlowFixMe[constant-condition] */} {isEmpty && <div className={styles.Empty}>None</div>} - {/* $FlowFixMe[constant-condition] */} {!isEmpty && - (entries as any).map(([name, value]) => ( + (entries: any).map(([name, value]) => ( <KeyValue key={name} alphaSort={true} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js index 9798cd1dbc7c..80c9f2f92d0b 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js @@ -234,7 +234,7 @@ function HookView({ // Format data for display to mimic the props/state/context for now. if (type === 'string') { - displayValue = `"${value as any as string}"`; + displayValue = `"${((value: any): string)}"`; } else if (type === 'boolean') { displayValue = value ? 'true' : 'false'; } else if (type === 'number') { @@ -389,6 +389,6 @@ function HookView({ } } -export default React.memo(InspectedElementHooksTree) as component( +export default (React.memo(InspectedElementHooksTree): component( ...props: HookViewProps -); +)); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStateTree.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStateTree.js index f18855da7de3..7987a720ddb0 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStateTree.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStateTree.js @@ -58,18 +58,15 @@ export default function InspectedElementStateTree({ <div> <div className={styles.HeaderRow}> <div className={styles.Header}>state</div> - {/* $FlowFixMe[constant-condition] */} {!isEmpty && ( <Button onClick={handleCopy} title="Copy to clipboard"> <ButtonIcon type="copy" /> </Button> )} </div> - {/* $FlowFixMe[constant-condition] */} {isEmpty && <div className={styles.Empty}>None</div>} - {/* $FlowFixMe[constant-condition] */} {!isEmpty && - (entries as any).map(([name, value]) => ( + (entries: any).map(([name, value]) => ( <KeyValue key={name} alphaSort={true} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js b/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js index f1073bb41512..89342ccffc1f 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js @@ -558,7 +558,6 @@ export default function KeyValue({ } } - // $FlowFixMe[incompatible-type] return children; } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/context.js b/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/context.js index 7c643cbcb7d6..e51719ebe1c2 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/context.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/context.js @@ -26,7 +26,7 @@ import type {StyleAndLayout as StyleAndLayoutFrontend} from './types'; type Context = StyleAndLayoutFrontend | null; const NativeStyleContext: ReactContext<Context> = createContext<Context>( - null as any as Context, + ((null: any): Context), ); NativeStyleContext.displayName = 'NativeStyleContext'; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js b/packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js index 9a89e3148820..060a5711626e 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js @@ -26,7 +26,7 @@ import type {Resource, Thenable} from '../../cache'; type Context = (id: number) => Array<SerializedElement> | null; const OwnersListContext: ReactContext<Context> = createContext<Context>( - null as any as Context, + ((null: any): Context), ); OwnersListContext.displayName = 'OwnersListContext'; @@ -52,15 +52,15 @@ const resource: Resource< | ResolveFn | (( result: Promise<Array<SerializedElement>> | Array<SerializedElement>, - ) => void) = null as any as ResolveFn; + ) => void) = ((null: any): ResolveFn); const promise = new Promise(resolve => { resolveFn = resolve; }); - // $FlowFixMe[incompatible-type] found when upgrading Flow + // $FlowFixMe[incompatible-call] found when upgrading Flow inProgressRequests.set(element, {promise, resolveFn}); - return promise as $FlowFixMe; + return (promise: $FlowFixMe); }, (element: Element) => element, {useWeakMap: true}, @@ -88,12 +88,12 @@ function useChangeOwnerAction(): (nextOwnerID: number) => void { result: | Promise<Array<SerializedElement>> | Array<SerializedElement>, - ) => void) = null as any as ResolveFn; + ) => void) = ((null: any): ResolveFn); const promise = new Promise(resolve => { resolveFn = resolve; }); - // $FlowFixMe[incompatible-type] found when upgrading Flow + // $FlowFixMe[incompatible-call] found when upgrading Flow inProgressRequests.set(element, {promise, resolveFn}); } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Tree.js b/packages/react-devtools-shared/src/devtools/views/Components/Tree.js index be141e74a8cb..502056f936c8 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Tree.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Tree.js @@ -132,7 +132,7 @@ export default function Tree(): React.Node { Math.min(0, elementLeft - viewportLeft) + Math.max(0, elementRight - viewportRight); - // $FlowExpectedError[incompatible-type] Flow doesn't support instant as an option for behavior. + // $FlowExpectedError[incompatible-call] Flow doesn't support instant as an option for behavior. listDOMElement.scrollBy({ left: horizontalDelta, behavior: 'instant', @@ -181,7 +181,7 @@ export default function Tree(): React.Node { Math.min(0, elementLeft - viewportLeft) + Math.max(0, elementRight - viewportRight); - // $FlowExpectedError[incompatible-type] Flow doesn't support instant as an option for behavior. + // $FlowExpectedError[incompatible-call] Flow doesn't support instant as an option for behavior. listDOMElement.scrollBy({ top: verticalDelta, left: horizontalDelta, @@ -215,7 +215,7 @@ export default function Tree(): React.Node { } const handleKeyDown = (event: KeyboardEvent) => { - if ((event as any).target.tagName === 'INPUT' || event.defaultPrevented) { + if ((event: any).target.tagName === 'INPUT' || event.defaultPrevented) { return; } @@ -237,7 +237,6 @@ export default function Tree(): React.Node { : null; if (element !== null) { if (event.altKey) { - // $FlowFixMe[invalid-compare] if (element.ownerID !== null) { dispatch({type: 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE'}); } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js b/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js index 9ef558680fe1..80b1e9dce3e3 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js @@ -149,12 +149,12 @@ type Action = export type DispatcherContext = (action: Action) => void; const TreeStateContext: ReactContext<StateContext> = - createContext<StateContext>(null as any as StateContext); + createContext<StateContext>(((null: any): StateContext)); TreeStateContext.displayName = 'TreeStateContext'; // TODO: `dispatch` is an Action and should be named accordingly. const TreeDispatcherContext: ReactContext<DispatcherContext> = - createContext<DispatcherContext>(null as any as DispatcherContext); + createContext<DispatcherContext>(((null: any): DispatcherContext)); TreeDispatcherContext.displayName = 'TreeDispatcherContext'; type State = { @@ -237,7 +237,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State { case 'SELECT_ELEMENT_AT_INDEX': ownerSubtreeLeafElementID = null; - inspectedElementIndex = (action as ACTION_SELECT_ELEMENT_AT_INDEX) + inspectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX) .payload; break; case 'SELECT_ELEMENT_BY_ID': @@ -247,7 +247,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State { // It might also cause problems if the specified element was inside of a (not yet expanded) subtree. lookupIDForIndex = false; - inspectedElementID = (action as ACTION_SELECT_ELEMENT_BY_ID).payload; + inspectedElementID = (action: ACTION_SELECT_ELEMENT_BY_ID).payload; inspectedElementIndex = inspectedElementID === null ? null @@ -270,7 +270,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State { if (inspectedElementIndex !== null) { const selectedElement = store.getElementAtIndex( - inspectedElementIndex as any as number, + ((inspectedElementIndex: any): number), ); if (selectedElement !== null && selectedElement.parentID !== 0) { const parent = store.getElementByID(selectedElement.parentID); @@ -319,7 +319,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State { } const selectedElement = store.getElementAtIndex( - inspectedElementIndex as any as number, + ((inspectedElementIndex: any): number), ); if (selectedElement !== null && selectedElement.ownerID !== 0) { const ownerIndex = store.getIndexOfElementID( @@ -336,7 +336,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State { if (inspectedElementIndex !== null) { const selectedElement = store.getElementAtIndex( - inspectedElementIndex as any as number, + ((inspectedElementIndex: any): number), ); if (selectedElement !== null && selectedElement.parentID !== 0) { const parentIndex = store.getIndexOfElementID( @@ -362,7 +362,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State { if (inspectedElementIndex !== null) { const selectedElement = store.getElementAtIndex( - inspectedElementIndex as any as number, + ((inspectedElementIndex: any): number), ); if (selectedElement !== null && selectedElement.parentID !== 0) { const parent = store.getElementByID(selectedElement.parentID); @@ -470,7 +470,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State { inspectedElementID = null; } else { inspectedElementID = store.getElementIDAtIndex( - inspectedElementIndex as any as number, + ((inspectedElementIndex: any): number), ); } } @@ -520,16 +520,15 @@ function reduceSearchState(store: Store, state: State, action: Action): State { if (numPrevSearchResults > 0) { didRequestSearch = true; searchIndex = - (searchIndex as any as number) > 0 - ? (searchIndex as any as number) - 1 + ((searchIndex: any): number) > 0 + ? ((searchIndex: any): number) - 1 : numPrevSearchResults - 1; } break; case 'HANDLE_STORE_MUTATION': if (searchText !== '') { - const [addedElementIDs, removedElementIDs] = ( - action as ACTION_HANDLE_STORE_MUTATION - ).payload; + const [addedElementIDs, removedElementIDs] = + (action: ACTION_HANDLE_STORE_MUTATION).payload; removedElementIDs.forEach((parentID, id) => { // Prune this item from the search results. @@ -542,36 +541,33 @@ function reduceSearchState(store: Store, state: State, action: Action): State { // If the results are now empty, also deselect things. if (searchResults.length === 0) { searchIndex = null; - } else if ( - (searchIndex as any as number) >= searchResults.length - ) { + } else if (((searchIndex: any): number) >= searchResults.length) { searchIndex = searchResults.length - 1; } } }); addedElementIDs.forEach(id => { - const element = store.getElementByID(id) as any as Element; + const element = ((store.getElementByID(id): any): Element); // It's possible that multiple tree operations will fire before this action has run. // So it's important to check for elements that may have been added and then removed. - // $FlowFixMe[invalid-compare] if (element !== null) { const {displayName} = element; // Add this item to the search results if it matches. const regExp = createRegExp(searchText); if (displayName !== null && regExp.test(displayName)) { - const newElementIndex = store.getIndexOfElementID( + const newElementIndex = ((store.getIndexOfElementID( id, - ) as any as number; + ): any): number); let foundMatch = false; for (let index = 0; index < searchResults.length; index++) { const resultID = searchResults[index]; if ( newElementIndex < - (store.getIndexOfElementID(resultID) as any as number) + ((store.getIndexOfElementID(resultID): any): number) ) { foundMatch = true; searchResults = searchResults @@ -594,7 +590,7 @@ function reduceSearchState(store: Store, state: State, action: Action): State { case 'SET_SEARCH_TEXT': searchIndex = null; searchResults = []; - searchText = (action as ACTION_SET_SEARCH_TEXT).payload; + searchText = (action: ACTION_SET_SEARCH_TEXT).payload; if (searchText !== '') { const regExp = createRegExp(searchText); @@ -614,7 +610,7 @@ function reduceSearchState(store: Store, state: State, action: Action): State { } } else { searchIndex = Math.min( - prevSearchIndex as any as number, + ((prevSearchIndex: any): number), searchResults.length - 1, ); } @@ -628,7 +624,6 @@ function reduceSearchState(store: Store, state: State, action: Action): State { } if (searchText !== prevSearchText) { - // $FlowFixMe[incompatible-type] const newSearchIndex = searchResults.indexOf(inspectedElementID); if (newSearchIndex === -1) { // Only move the selection if the new query @@ -641,9 +636,9 @@ function reduceSearchState(store: Store, state: State, action: Action): State { } } if (didRequestSearch && searchIndex !== null) { - inspectedElementID = searchResults[searchIndex] as any as number; + inspectedElementID = ((searchResults[searchIndex]: any): number); inspectedElementIndex = store.getIndexOfElementID( - inspectedElementID as any as number, + ((inspectedElementID: any): number), ); } @@ -709,13 +704,13 @@ function reduceOwnersState(store: Store, state: State, action: Action): State { break; case 'SELECT_ELEMENT_AT_INDEX': if (ownerFlatTree !== null) { - inspectedElementIndex = (action as ACTION_SELECT_ELEMENT_AT_INDEX) + inspectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX) .payload; } break; case 'SELECT_ELEMENT_BY_ID': if (ownerFlatTree !== null) { - const payload = (action as ACTION_SELECT_ELEMENT_BY_ID).payload; + const payload = (action: ACTION_SELECT_ELEMENT_BY_ID).payload; if (payload === null) { inspectedElementIndex = null; } else { @@ -726,7 +721,6 @@ function reduceOwnersState(store: Store, state: State, action: Action): State { // If the selected element is outside of the current owners list, // exit the list and select the element in the main tree. // This supports features like toggling Suspense. - // $FlowFixMe[invalid-compare] if (inspectedElementIndex !== null && inspectedElementIndex < 0) { ownerID = null; ownerFlatTree = null; @@ -755,7 +749,7 @@ function reduceOwnersState(store: Store, state: State, action: Action): State { // If the Store doesn't have any owners metadata, don't drill into an empty stack. // This is a confusing user experience. if (store.hasOwnerMetadata) { - ownerID = (action as ACTION_SELECT_OWNER).payload; + ownerID = (action: ACTION_SELECT_OWNER).payload; ownerFlatTree = store.getOwnersListForElement(ownerID); // Always force reset selection to be the top of the new owner tree. diff --git a/packages/react-devtools-shared/src/devtools/views/Components/ViewElementSourceContext.js b/packages/react-devtools-shared/src/devtools/views/Components/ViewElementSourceContext.js index 9974c174fbac..42f6b67723e8 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/ViewElementSourceContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/ViewElementSourceContext.js @@ -22,7 +22,7 @@ export type Context = { }; const ViewElementSourceContext: ReactContext<Context> = createContext<Context>( - null as any as Context, + ((null: any): Context), ); ViewElementSourceContext.displayName = 'ViewElementSourceContext'; diff --git a/packages/react-devtools-shared/src/devtools/views/DevTools.js b/packages/react-devtools-shared/src/devtools/views/DevTools.js index 1e36e43c9139..bf541f667287 100644 --- a/packages/react-devtools-shared/src/devtools/views/DevTools.js +++ b/packages/react-devtools-shared/src/devtools/views/DevTools.js @@ -117,19 +117,19 @@ export type Props = { }; const componentsTab = { - id: 'components' as TabID, + id: ('components': TabID), icon: 'components', label: 'Components', title: 'React Components', }; const profilerTab = { - id: 'profiler' as TabID, + id: ('profiler': TabID), icon: 'profiler', label: 'Profiler', title: 'React Profiler', }; const suspenseTab = { - id: 'suspense' as TabID, + id: ('suspense': TabID), icon: 'suspense', label: 'Suspense', title: 'React Suspense', diff --git a/packages/react-devtools-shared/src/devtools/views/Editor/EditorPane.js b/packages/react-devtools-shared/src/devtools/views/Editor/EditorPane.js index 4930fab9a2e2..8237a59956d1 100644 --- a/packages/react-devtools-shared/src/devtools/views/Editor/EditorPane.js +++ b/packages/react-devtools-shared/src/devtools/views/Editor/EditorPane.js @@ -115,4 +115,4 @@ function EditorPane({selectedSource}: Props) { </div> ); } -export default portaledContent(EditorPane) as component(); +export default (portaledContent(EditorPane): component()); diff --git a/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js index 8ad55976582f..6ae471ec993c 100644 --- a/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js +++ b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js @@ -74,13 +74,13 @@ export function findGitHubIssue(errorMessage: string): GitHubIssue | null { }; const wake = () => { // This assumes they won't throw. - callbacks.forEach(callback => callback((thenable as any).value)); + callbacks.forEach(callback => callback((thenable: any).value)); callbacks.clear(); rejectCallbacks.clear(); }; const wakeRejections = () => { // This assumes they won't throw. - rejectCallbacks.forEach(callback => callback((thenable as any).reason)); + rejectCallbacks.forEach(callback => callback((thenable: any).reason)); rejectCallbacks.clear(); callbacks.clear(); }; @@ -96,20 +96,20 @@ export function findGitHubIssue(errorMessage: string): GitHubIssue | null { if (maybeItem) { const fulfilledThenable: FulfilledThenable<GitHubIssue> = - thenable as any; + (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = maybeItem; wake(); } else { const notFoundThenable: RejectedThenable<GitHubIssue> = - thenable as any; + (thenable: any); notFoundThenable.status = 'rejected'; notFoundThenable.reason = null; wakeRejections(); } }) .catch(error => { - const rejectedThenable: RejectedThenable<GitHubIssue> = thenable as any; + const rejectedThenable: RejectedThenable<GitHubIssue> = (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = null; wakeRejections(); @@ -119,7 +119,7 @@ export function findGitHubIssue(errorMessage: string): GitHubIssue | null { setTimeout(() => { didTimeout = true; - const timedoutThenable: RejectedThenable<GitHubIssue> = thenable as any; + const timedoutThenable: RejectedThenable<GitHubIssue> = (thenable: any); timedoutThenable.status = 'rejected'; timedoutThenable.reason = null; wakeRejections(); diff --git a/packages/react-devtools-shared/src/devtools/views/InspectedElement/InspectedElementPane.js b/packages/react-devtools-shared/src/devtools/views/InspectedElement/InspectedElementPane.js index 6abff0b01327..a47bffda8d7f 100644 --- a/packages/react-devtools-shared/src/devtools/views/InspectedElement/InspectedElementPane.js +++ b/packages/react-devtools-shared/src/devtools/views/InspectedElement/InspectedElementPane.js @@ -32,4 +32,4 @@ function InspectedElementPane() { </SettingsModalContextController> ); } -export default portaledContent(InspectedElementPane) as component(); +export default (portaledContent(InspectedElementPane): component()); diff --git a/packages/react-devtools-shared/src/devtools/views/ModalDialog.js b/packages/react-devtools-shared/src/devtools/views/ModalDialog.js index bb71e67e95f4..a584d9a9e3d1 100644 --- a/packages/react-devtools-shared/src/devtools/views/ModalDialog.js +++ b/packages/react-devtools-shared/src/devtools/views/ModalDialog.js @@ -57,7 +57,7 @@ type ModalDialogContextType = { }; const ModalDialogContext: ReactContext<ModalDialogContextType> = - createContext<ModalDialogContextType>(null as any as ModalDialogContextType); + createContext<ModalDialogContextType>(((null: any): ModalDialogContextType)); ModalDialogContext.displayName = 'ModalDialogContext'; function dialogReducer(state: State, action: Action) { diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraph.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraph.js index 4956516b8788..3774a4d34004 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraph.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraph.js @@ -57,13 +57,13 @@ export default function CommitFlamegraphAutoSizer(_: {}): React.Node { if (selectedCommitIndex !== null) { commitTree = profilingCache.getCommitTree({ commitIndex: selectedCommitIndex, - rootID: rootID as any as number, + rootID: ((rootID: any): number), }); chartData = profilingCache.getFlamegraphChartData({ commitIndex: selectedCommitIndex, commitTree, - rootID: rootID as any as number, + rootID: ((rootID: any): number), }); } @@ -75,8 +75,8 @@ export default function CommitFlamegraphAutoSizer(_: {}): React.Node { // Force Flow types to avoid checking for `null` here because there's no static proof that // by the time this render prop function is called, the values of the `let` variables have not changed. <CommitFlamegraph - chartData={chartData as any as ChartData} - commitTree={commitTree as any as CommitTree} + chartData={((chartData: any): ChartData)} + commitTree={((commitTree: any): CommitTree)} height={height} width={width} /> diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraphListItem.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraphListItem.js index ac846b7be80d..5bfe0a299f68 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraphListItem.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraphListItem.js @@ -132,6 +132,6 @@ function CommitFlamegraphListItem({data, index, style}: Props): React.Node { ); } -export default memo(CommitFlamegraphListItem, areEqual) as component( +export default (memo(CommitFlamegraphListItem, areEqual): component( ...props: Props -); +)); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRanked.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRanked.js index 57ed9983dd77..bc482776f4e8 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRanked.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRanked.js @@ -57,13 +57,13 @@ export default function CommitRankedAutoSizer(_: {}): React.Node { if (selectedCommitIndex !== null) { commitTree = profilingCache.getCommitTree({ commitIndex: selectedCommitIndex, - rootID: rootID as any as number, + rootID: ((rootID: any): number), }); chartData = profilingCache.getRankedChartData({ commitIndex: selectedCommitIndex, commitTree, - rootID: rootID as any as number, + rootID: ((rootID: any): number), }); } @@ -73,8 +73,8 @@ export default function CommitRankedAutoSizer(_: {}): React.Node { <AutoSizer> {({height, width}) => ( <CommitRanked - chartData={chartData as any as ChartData} - commitTree={commitTree as any as CommitTree} + chartData={((chartData: any): ChartData)} + commitTree={((commitTree: any): CommitTree)} height={height} width={width} /> diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRankedListItem.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRankedListItem.js index 99da9e9f0885..707fddf6268c 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRankedListItem.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRankedListItem.js @@ -79,6 +79,6 @@ function CommitRankedListItem({data, index, style}: Props) { ); } -export default memo(CommitRankedListItem, areEqual) as component( +export default (memo(CommitRankedListItem, areEqual): component( ...props: Props -); +)); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js index d2b6eeb5d943..02ecc98ec6d1 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js @@ -37,7 +37,6 @@ import type { } from 'react-devtools-shared/src/devtools/views/Profiler/types'; const debug = (methodName: string, ...args: Array<string>) => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `%cCommitTreeBuilder %c${methodName}`, @@ -63,9 +62,9 @@ export function getCommitTree({ rootToCommitTreeMap.set(rootID, []); } - const commitTrees = rootToCommitTreeMap.get( + const commitTrees = ((rootToCommitTreeMap.get( rootID, - ) as any as Array<CommitTree>; + ): any): Array<CommitTree>); if (commitIndex < commitTrees.length) { return commitTrees[commitIndex]; } @@ -87,7 +86,7 @@ export function getCommitTree({ ); } - let commitTree: CommitTree = null as any as CommitTree; + let commitTree: CommitTree = ((null: any): CommitTree); for (let index = commitTrees.length; index <= commitIndex; index++) { // Commits are generated sequentially and cached. // If this is the very first commit, start with the cached snapshot and apply the first mutation. @@ -102,7 +101,6 @@ export function getCommitTree({ if (operations != null && index < operations.length) { commitTree = updateTree({nodes, rootID}, operations[index]); - // $FlowFixMe[constant-condition] if (__DEBUG__) { __printTree(commitTree); } @@ -113,7 +111,6 @@ export function getCommitTree({ const previousCommitTree = commitTrees[index - 1]; commitTree = updateTree(previousCommitTree, operations[index]); - // $FlowFixMe[constant-condition] if (__DEBUG__) { __printTree(commitTree); } @@ -140,9 +137,9 @@ function recursivelyInitializeTree( hocDisplayNames: node.hocDisplayNames, key: node.key, parentID, - treeBaseDuration: dataForRoot.initialTreeBaseDurations.get( + treeBaseDuration: ((dataForRoot.initialTreeBaseDurations.get( id, - ) as any as number, + ): any): number), type: node.type, compiledWithForget: node.compiledWithForget, }); @@ -175,7 +172,7 @@ function updateTree( }; let i = 2; - let id: number = null as any as number; + let id: number = ((null: any): number); // Reassemble the string table. const stringTable: Array<null | string> = [ @@ -199,8 +196,8 @@ function updateTree( switch (operation) { case TREE_OPERATION_ADD: { - id = operations[i + 1] as any as number; - const type = operations[i + 2] as any as ElementType; + id = ((operations[i + 1]: any): number); + const type = ((operations[i + 2]: any): ElementType); i += 3; @@ -216,7 +213,6 @@ function updateTree( i++; // supportsStrictMode flag i++; // hasOwnerMetadata flag - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('Add', `new root fiber ${id}`); } @@ -235,7 +231,7 @@ function updateTree( nodes.set(id, node); } else { - const parentID = operations[i] as any as number; + const parentID = ((operations[i]: any): number); i++; i++; // ownerID @@ -251,7 +247,6 @@ function updateTree( // skip name prop i++; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'Add', @@ -283,11 +278,11 @@ function updateTree( break; } case TREE_OPERATION_REMOVE: { - const removeLength = operations[i + 1] as any as number; + const removeLength = ((operations[i + 1]: any): number); i += 2; for (let removeIndex = 0; removeIndex < removeLength; removeIndex++) { - id = operations[i] as any as number; + id = ((operations[i]: any): number); i++; if (!nodes.has(id)) { @@ -306,7 +301,6 @@ function updateTree( } else { const parentNode = getClonedNode(parentID); - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('Remove', `fiber ${id} from parent ${parentID}`); } @@ -319,16 +313,15 @@ function updateTree( break; } case TREE_OPERATION_REORDER_CHILDREN: { - id = operations[i + 1] as any as number; - const numChildren = operations[i + 2] as any as number; - const children = operations.slice( + id = ((operations[i + 1]: any): number); + const numChildren = ((operations[i + 2]: any): number); + const children = ((operations.slice( i + 3, i + 3 + numChildren, - ) as any as Array<number>; + ): any): Array<number>); i = i + 3 + numChildren; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('Re-order', `fiber ${id} children ${children.join(',')}`); } @@ -344,7 +337,6 @@ function updateTree( i += 3; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug('Subtree mode', `Subtree with root ${id} set to mode ${mode}`); } @@ -356,7 +348,6 @@ function updateTree( const node = getClonedNode(id); node.treeBaseDuration = operations[i + 2] / 1000; // Convert microseconds back to milliseconds; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'Update', @@ -374,7 +365,6 @@ function updateTree( i += 4; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'Warnings and Errors update', @@ -392,7 +382,6 @@ function updateTree( const numRects = operations[i + 5]; const name = stringTable[nameStringID]; - // $FlowFixMe[constant-condition] if (__DEBUG__) { let rects: string; if (numRects === -1) { @@ -414,23 +403,22 @@ function updateTree( } case SUSPENSE_TREE_OPERATION_REMOVE: { - const removeLength = operations[i + 1] as any as number; + const removeLength = ((operations[i + 1]: any): number); i += 2 + removeLength; break; } case SUSPENSE_TREE_OPERATION_REORDER_CHILDREN: { - const suspenseID = operations[i + 1] as any as number; - const numChildren = operations[i + 2] as any as number; - const children = operations.slice( + const suspenseID = ((operations[i + 1]: any): number); + const numChildren = ((operations[i + 2]: any): number); + const children = ((operations.slice( i + 3, i + 3 + numChildren, - ) as any as Array<number>; + ): any): Array<number>); i = i + 3 + numChildren; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'Suspense re-order', @@ -442,18 +430,17 @@ function updateTree( } case SUSPENSE_TREE_OPERATION_RESIZE: { - const suspenseID = operations[i + 1] as any as number; - const numRects = operations[i + 2] as any as number; + const suspenseID = ((operations[i + 1]: any): number); + const numRects = ((operations[i + 2]: any): number); - // $FlowFixMe[constant-condition] if (__DEBUG__) { if (numRects === -1) { debug('Suspense resize', `suspense ${suspenseID} rects null`); } else { - const rects = operations.slice( + const rects = ((operations.slice( i + 3, i + 3 + numRects * 4, - ) as any as Array<number>; + ): any): Array<number>); debug( 'Suspense resize', `suspense ${suspenseID} rects [${rects.join(',')}]`, @@ -468,7 +455,7 @@ function updateTree( case SUSPENSE_TREE_OPERATION_SUSPENDERS: { i++; - const changeLength = operations[i++] as any as number; + const changeLength = ((operations[i++]: any): number); for (let changeIndex = 0; changeIndex < changeLength; changeIndex++) { const suspenseNodeId = operations[i++]; @@ -477,7 +464,6 @@ function updateTree( const isSuspended = operations[i++] === 1; const environmentNamesLength = operations[i++]; i += environmentNamesLength; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'Suspender changes', @@ -492,7 +478,6 @@ function updateTree( case TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE: { i++; const activitySliceIDChange = operations[i++]; - // $FlowFixMe[constant-condition] if (__DEBUG__) { debug( 'Applied activity slice change', @@ -521,7 +506,6 @@ export function invalidateCommitTrees(): void { // DEBUG const __printTree = (commitTree: CommitTree) => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { const {nodes, rootID} = commitTree; console.group('__printTree()'); @@ -531,7 +515,6 @@ const __printTree = (commitTree: CommitTree) => { const depth = queue.shift(); // $FlowFixMe[incompatible-call] - // $FlowFixMe[incompatible-type] const node = nodes.get(id); if (node == null) { // $FlowFixMe[incompatible-type] @@ -540,7 +523,6 @@ const __printTree = (commitTree: CommitTree) => { console.log( // $FlowFixMe[incompatible-call] - // $FlowFixMe[incompatible-type] `${'•'.repeat(depth)}${node.id}:${node.displayName || ''} ${ node.key ? `key:"${node.key}"` : '' } (${node.treeBaseDuration})`, diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/FlamegraphChartBuilder.js b/packages/react-devtools-shared/src/devtools/views/Profiler/FlamegraphChartBuilder.js index f61806096388..53f9f4965779 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/FlamegraphChartBuilder.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/FlamegraphChartBuilder.js @@ -52,7 +52,7 @@ export function getChartData({ const chartDataKey = `${rootID}-${commitIndex}`; if (cachedChartData.has(chartDataKey)) { - return cachedChartData.get(chartDataKey) as any as ChartData; + return ((cachedChartData.get(chartDataKey): any): ChartData); } const idToDepthMap: Map<number, number> = new Map(); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/HookChangeSummary.js b/packages/react-devtools-shared/src/devtools/views/Profiler/HookChangeSummary.js index af127a9037df..5ce3eec42bc0 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/HookChangeSummary.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/HookChangeSummary.js @@ -27,7 +27,7 @@ import Toggle from '../Toggle'; import type {HooksNode} from 'react-debug-tools/src/ReactDebugHooks'; import type {ChangeDescription} from './types'; -// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about Intl.ListFormat +// $FlowFixMe: Flow doesn't know about Intl.ListFormat const hookListFormatter = new Intl.ListFormat('en', { style: 'long', type: 'conjunction', @@ -133,7 +133,6 @@ const HookChangeSummary: component(...props: Props) = memo( toggleParseHookNames(); }, [toggleParseHookNames, parseHookNames]); - // $FlowFixMe[invalid-compare] const element = fiberID !== null ? store.getElementByID(fiberID) : null; const hookNames = element != null ? getAlreadyLoadedHookNames(element) : null; diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/HoveredFiberInfo.js b/packages/react-devtools-shared/src/devtools/views/Profiler/HoveredFiberInfo.js index 097d2bd7d735..a6dfeffb3e20 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/HoveredFiberInfo.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/HoveredFiberInfo.js @@ -57,7 +57,7 @@ export default function HoveredFiberInfo({fiberData}: Props): React.Node { const commitIndex = commitIndices[i]; if (selectedCommitIndex === commitIndex) { const {fiberActualDurations, fiberSelfDurations} = - profilerStore.getCommitData(rootID as any as number, commitIndex); + profilerStore.getCommitData(((rootID: any): number), commitIndex); const actualDuration = fiberActualDurations.get(id) || 0; const selfDuration = fiberSelfDurations.get(id) || 0; diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js index e9511e091627..6254be06d832 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js @@ -243,4 +243,4 @@ const tabsWithTimeline = [ }, ]; -export default portaledContent(Profiler) as component(); +export default (portaledContent(Profiler): component()); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js index 07d82a54b48c..d593558dbdd1 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js @@ -83,7 +83,7 @@ export type Context = { }; const ProfilerContext: ReactContext<Context> = createContext<Context>( - null as any as Context, + ((null: any): Context), ); ProfilerContext.displayName = 'ProfilerContext'; @@ -228,12 +228,12 @@ function ProfilerContextController({children}: Props): React.Node { // Always check didRecordCommits before using commitData or filteredCommitIndices. const commitData = useMemo(() => { if (!didRecordCommits || rootID === null || profilingData === null) { - return [] as Array<CommitDataFrontend>; + return ([]: Array<CommitDataFrontend>); } const dataForRoot = profilingData.dataForRoots.get(rootID); return dataForRoot ? dataForRoot.commitData - : ([] as Array<CommitDataFrontend>); + : ([]: Array<CommitDataFrontend>); }, [didRecordCommits, rootID, profilingData]); // Commit filtering and navigation diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js index 920785bad653..6bb3e229fbd1 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js @@ -86,7 +86,7 @@ export default function ProfilingImportExportButtons(): React.Node { // TODO (profiling) Handle fileReader errors. const fileReader = new FileReader(); fileReader.addEventListener('load', () => { - const raw = fileReader.result as any as string; + const raw = ((fileReader.result: any): string); const json = JSON.parse(raw); if (!isArray(json) && hasOwnProperty.call(json, 'version')) { @@ -95,7 +95,7 @@ export default function ProfilingImportExportButtons(): React.Node { setFile(null); try { - const profilingDataExport = json as any as ProfilingDataExport; + const profilingDataExport = ((json: any): ProfilingDataExport); profilerStore.profilingData = prepareProfilingDataFrontendFromExport(profilingDataExport); } catch (error) { diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/RankedChartBuilder.js b/packages/react-devtools-shared/src/devtools/views/Profiler/RankedChartBuilder.js index 5cded846bc4c..8cec8c6b924f 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/RankedChartBuilder.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/RankedChartBuilder.js @@ -48,7 +48,7 @@ export function getChartData({ const chartDataKey = `${rootID}-${commitIndex}`; if (cachedChartData.has(chartDataKey)) { - return cachedChartData.get(chartDataKey) as any as ChartData; + return ((cachedChartData.get(chartDataKey): any): ChartData); } let maxSelfDuration = 0; diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js index 9781d3e8901d..d785cfebdacd 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js @@ -67,7 +67,6 @@ export default function SidebarSelectedFiberInfo(): React.Node { const handleKeyDown = event => { switch (event.key) { case 'ArrowUp': - // $FlowFixMe[invalid-compare] if (selectedCommitIndex !== null) { const prevIndex = commitIndices.indexOf(selectedCommitIndex); const nextIndex = @@ -77,7 +76,6 @@ export default function SidebarSelectedFiberInfo(): React.Node { event.preventDefault(); break; case 'ArrowDown': - // $FlowFixMe[invalid-compare] if (selectedCommitIndex !== null) { const prevIndex = commitIndices.indexOf(selectedCommitIndex); const nextIndex = @@ -97,7 +95,7 @@ export default function SidebarSelectedFiberInfo(): React.Node { const commitIndex = commitIndices[i]; const {duration, timestamp} = profilerStore.getCommitData( - rootID as any as number, + ((rootID: any): number), commitIndex, ); @@ -136,7 +134,7 @@ export default function SidebarSelectedFiberInfo(): React.Node { compiledWithForget={node.compiledWithForget} /> )} - <WhatChanged fiberID={selectedFiberID as any as number} /> + <WhatChanged fiberID={((selectedFiberID: any): number)} /> {listItems.length > 0 && ( <div> <label className={styles.Label}>Rendered at: </label> diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitList.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitList.js index 356fa7e8b63a..7b1cbcbdd261 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitList.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitList.js @@ -292,11 +292,7 @@ function List({ itemCount={filteredCommitIndices.length} itemData={itemData} itemSize={itemSize} - ref={ - listRef as any - - /* Flow bug? */ - } + ref={(listRef: any) /* Flow bug? */} width={width}> {SnapshotCommitListItem} </FixedSizeList> diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitListItem.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitListItem.js index 263f50849d22..d9f80f005319 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitListItem.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitListItem.js @@ -96,6 +96,6 @@ function SnapshotCommitListItem({data: itemData, index, style}: Props) { ); } -export default memo(SnapshotCommitListItem, areEqual) as component( +export default (memo(SnapshotCommitListItem, areEqual): component( ...props: Props -); +)); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotSelector.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotSelector.js index 6f1fa3ea43a9..0d470ddb0003 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotSelector.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotSelector.js @@ -32,7 +32,7 @@ export default function SnapshotSelector(_: Props): React.Node { } = useContext(ProfilerContext); const {profilerStore} = useContext(StoreContext); - const {commitData} = profilerStore.getDataForRoot(rootID as any as number); + const {commitData} = profilerStore.getDataForRoot(((rootID: any): number)); const totalDurations: Array<number> = []; const commitTimes: Array<number> = []; diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js b/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js index 46c4f5582f70..124be4286d1a 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js @@ -101,7 +101,6 @@ function getMousePosition( // Position within the nearest position:relative container. let targetContainer = relativeContainer; while (targetContainer.parentElement != null) { - // $FlowFixMe[invalid-compare] if (targetContainer.style.position === 'relative') { break; } else { diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/WhatChanged.js b/packages/react-devtools-shared/src/devtools/views/Profiler/WhatChanged.js index e8fd79e969be..dffcd03350cd 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/WhatChanged.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/WhatChanged.js @@ -37,7 +37,7 @@ export default function WhatChanged({ } const {changeDescriptions} = profilerStore.getCommitData( - rootID as any as number, + ((rootID: any): number), selectedCommitIndex, ); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/useCommitFilteringAndNavigation.js b/packages/react-devtools-shared/src/devtools/views/Profiler/useCommitFilteringAndNavigation.js index b1b685de25a2..d3369b4f9538 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/useCommitFilteringAndNavigation.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/useCommitFilteringAndNavigation.js @@ -60,7 +60,7 @@ export function useCommitFilteringAndNavigation( reduced.push(index); } return reduced; - }, [] as Array<number>); + }, ([]: Array<number>)); }, [commitData], ); diff --git a/packages/react-devtools-shared/src/devtools/views/Settings/ComponentsSettings.js b/packages/react-devtools-shared/src/devtools/views/Settings/ComponentsSettings.js index 81df5b7c9722..9b73e949429b 100644 --- a/packages/react-devtools-shared/src/devtools/views/Settings/ComponentsSettings.js +++ b/packages/react-devtools-shared/src/devtools/views/Settings/ComponentsSettings.js @@ -281,7 +281,7 @@ export default function ComponentsSettings({ if (index >= 0) { if (componentFilter.type === ComponentFilterElementType) { cloned[index] = { - ...(cloned[index] as any as ElementTypeComponentFilter), + ...((cloned[index]: any): ElementTypeComponentFilter), isEnabled, }; } else if ( @@ -289,17 +289,17 @@ export default function ComponentsSettings({ componentFilter.type === ComponentFilterLocation ) { cloned[index] = { - ...(cloned[index] as any as RegExpComponentFilter), + ...((cloned[index]: any): RegExpComponentFilter), isEnabled, }; } else if (componentFilter.type === ComponentFilterHOC) { cloned[index] = { - ...(cloned[index] as any as BooleanComponentFilter), + ...((cloned[index]: any): BooleanComponentFilter), isEnabled, }; } else if (componentFilter.type === ComponentFilterEnvironmentName) { cloned[index] = { - ...(cloned[index] as any as EnvironmentNameComponentFilter), + ...((cloned[index]: any): EnvironmentNameComponentFilter), isEnabled, }; } @@ -404,10 +404,10 @@ export default function ComponentsSettings({ onChange={({currentTarget}) => changeFilterType( componentFilter, - parseInt( + ((parseInt( currentTarget.value, 10, - ) as any as ComponentFilterType, + ): any): ComponentFilterType), ) }> {/* TODO: currently disabled, need find a new way of doing this @@ -445,7 +445,7 @@ export default function ComponentsSettings({ onChange={({currentTarget}) => updateFilterValueElementType( componentFilter, - parseInt(currentTarget.value, 10) as any as ElementType, + ((parseInt(currentTarget.value, 10): any): ElementType), ) }> {isInternalFacebookBuild && ( diff --git a/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js b/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js index 27bd47c349da..c20249e89942 100644 --- a/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js @@ -56,7 +56,7 @@ type Context = { }; const SettingsContext: ReactContext<Context> = createContext<Context>( - null as any as Context, + ((null: any): Context), ); SettingsContext.displayName = 'SettingsContext'; @@ -116,24 +116,24 @@ function SettingsContextController({ const documentElements = useMemo<DocumentElements>(() => { const array: Array<HTMLElement> = [ - document.documentElement as any as HTMLElement, + ((document.documentElement: any): HTMLElement), ]; if (componentsPortalContainer != null) { array.push( - componentsPortalContainer.ownerDocument - .documentElement as any as HTMLElement, + ((componentsPortalContainer.ownerDocument + .documentElement: any): HTMLElement), ); } if (profilerPortalContainer != null) { array.push( - profilerPortalContainer.ownerDocument - .documentElement as any as HTMLElement, + ((profilerPortalContainer.ownerDocument + .documentElement: any): HTMLElement), ); } if (suspensePortalContainer != null) { array.push( - suspensePortalContainer.ownerDocument - .documentElement as any as HTMLElement, + ((suspensePortalContainer.ownerDocument + .documentElement: any): HTMLElement), ); } return array; @@ -218,11 +218,11 @@ export function updateDisplayDensity( ): void { // Sizes and paddings/margins are all rem-based, // so update the root font-size as well when the display preference changes. - const computedStyle = getComputedStyle(document.body as any); + const computedStyle = getComputedStyle((document.body: any)); const fontSize = computedStyle.getPropertyValue( `--${displayDensity}-root-font-size`, ); - const root = document.querySelector(':root') as any as HTMLElement; + const root = ((document.querySelector(':root'): any): HTMLElement); root.style.fontSize = fontSize; } diff --git a/packages/react-devtools-shared/src/devtools/views/Settings/SettingsModalContext.js b/packages/react-devtools-shared/src/devtools/views/Settings/SettingsModalContext.js index 4ba7dd20139f..f8f76053fc12 100644 --- a/packages/react-devtools-shared/src/devtools/views/Settings/SettingsModalContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Settings/SettingsModalContext.js @@ -34,7 +34,7 @@ type Context = { }; const SettingsModalContext: ReactContext<Context> = createContext<Context>( - null as any as Context, + ((null: any): Context), ); SettingsModalContext.displayName = 'SettingsModalContext'; diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js index 0862cd69624f..c542858b3016 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js @@ -189,7 +189,6 @@ function SuspenseBreadcrumbsMenu({ )} onPointerLeave={onItemPointerLeave} type="button"> - {/* $FlowFixMe[invalid-compare] */} {selectedSuspenseNode === null ? 'Unknown' : selectedSuspenseNode.name || 'Unknown'} diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js index b61ba892c33d..69d4767a489f 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js @@ -451,7 +451,7 @@ function SuspenseRectsTransition({id}: {id: Element['id']}): React$Node { }); } -const ViewBox = createContext<Rect>(null as any); +const ViewBox = createContext<Rect>((null: any)); function SuspenseRectsContainer({ scaleRef, @@ -522,7 +522,6 @@ function SuspenseRectsContainer({ }); } - // $FlowFixMe[incompatible-type] const isRootSelected = roots.includes(inspectedElementID); // When we're focusing a Transition, the first timeline step will not be a root. const isRootHovered = activityID === null && hoveredTimelineIndex === 0; diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js index d59a037d38c9..3fdd9fe935a3 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js @@ -645,4 +645,4 @@ function setResizeCSSVariable( } } -export default portaledContent(SuspenseTab) as component(); +export default (portaledContent(SuspenseTab): component()); diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js index 68dc5adb606e..6cb4eb7a487e 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js @@ -97,11 +97,11 @@ export type SuspenseTreeAction = export type SuspenseTreeDispatch = (action: SuspenseTreeAction) => void; const SuspenseTreeStateContext: ReactContext<SuspenseTreeState> = - createContext<SuspenseTreeState>(null as any as SuspenseTreeState); + createContext<SuspenseTreeState>(((null: any): SuspenseTreeState)); SuspenseTreeStateContext.displayName = 'SuspenseTreeStateContext'; const SuspenseTreeDispatcherContext: ReactContext<SuspenseTreeDispatch> = - createContext<SuspenseTreeDispatch>(null as any as SuspenseTreeDispatch); + createContext<SuspenseTreeDispatch>(((null: any): SuspenseTreeDispatch)); SuspenseTreeDispatcherContext.displayName = 'SuspenseTreeDispatcherContext'; type Props = { @@ -168,16 +168,15 @@ function SuspenseTreeContextController({children}: Props): React.Node { } const selectedTimelineStep = - // $FlowFixMe[invalid-compare] state.timeline === null || state.timelineIndex === -1 ? null : state.timeline[state.timelineIndex]; let selectedTimelineID: null | number = null; if (selectedTimelineStep !== null) { selectedTimelineID = selectedTimelineStep.id; - // $FlowFixMe[incompatible-type] + // $FlowFixMe while (removedIDs.has(selectedTimelineID)) { - // $FlowFixMe[incompatible-type] + // $FlowFixMe selectedTimelineID = removedIDs.get(selectedTimelineID); } } @@ -259,9 +258,7 @@ function SuspenseTreeContextController({children}: Props): React.Node { // Try to reconcile the new timeline with the previous index. if ( nextRootID === null && - // $FlowFixMe[invalid-compare] previousTimeline !== null && - // $FlowFixMe[invalid-compare] previousMilestoneIndex !== null ) { const previousMilestoneID = diff --git a/packages/react-devtools-shared/src/devtools/views/TabBar.js b/packages/react-devtools-shared/src/devtools/views/TabBar.js index 4575c0ae8942..a277082ab264 100644 --- a/packages/react-devtools-shared/src/devtools/views/TabBar.js +++ b/packages/react-devtools-shared/src/devtools/views/TabBar.js @@ -41,7 +41,7 @@ export default function TabBar({ type, }: Props): React.Node { if (!tabs.some(tab => tab !== null && tab.id === currentTab)) { - const firstTab = tabs.find(tab => tab !== null) as any as TabInfo; + const firstTab = ((tabs.find(tab => tab !== null): any): TabInfo); selectTab(firstTab.id); } diff --git a/packages/react-devtools-shared/src/devtools/views/context.js b/packages/react-devtools-shared/src/devtools/views/context.js index d2ff088c362e..e04d3433c093 100644 --- a/packages/react-devtools-shared/src/devtools/views/context.js +++ b/packages/react-devtools-shared/src/devtools/views/context.js @@ -15,11 +15,11 @@ import type {ViewAttributeSource} from 'react-devtools-shared/src/devtools/views import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; export const BridgeContext: ReactContext<FrontendBridge> = - createContext<FrontendBridge>(null as any as FrontendBridge); + createContext<FrontendBridge>(((null: any): FrontendBridge)); BridgeContext.displayName = 'BridgeContext'; export const StoreContext: ReactContext<Store> = createContext<Store>( - null as any as Store, + ((null: any): Store), ); StoreContext.displayName = 'StoreContext'; diff --git a/packages/react-devtools-shared/src/devtools/views/hooks.js b/packages/react-devtools-shared/src/devtools/views/hooks.js index a861b3201f64..c5e426e7a622 100644 --- a/packages/react-devtools-shared/src/devtools/views/hooks.js +++ b/packages/react-devtools-shared/src/devtools/views/hooks.js @@ -165,7 +165,7 @@ export function useLocalStorage<T>( console.log(error); } if (typeof initialValue === 'function') { - return (initialValue as any as () => T)(); + return ((initialValue: any): () => T)(); } else { return initialValue; } @@ -188,7 +188,7 @@ export function useLocalStorage<T>( (value: $FlowFixMe) => { try { const valueToStore = - value instanceof Function ? (value as any)(storedValue) : value; + value instanceof Function ? (value: any)(storedValue) : value; localStorageSetItem(key, JSON.stringify(valueToStore)); // Notify listeners that this setting has changed. @@ -243,7 +243,7 @@ export function useModalDismissSignal( const handleRootNodeClick: MouseEventHandler = event => { if ( modalRef.current !== null && - /* $FlowExpectedError[incompatible-type] Instead of dealing with possibly multiple realms + /* $FlowExpectedError[incompatible-call] Instead of dealing with possibly multiple realms and multiple Node references to comply with Flow (e.g. checking with `event.target instanceof Node`) just delegate it to contains call */ !modalRef.current.contains(event.target) diff --git a/packages/react-devtools-shared/src/devtools/views/utils.js b/packages/react-devtools-shared/src/devtools/views/utils.js index 6fefc2fff1db..a2d254bc27d8 100644 --- a/packages/react-devtools-shared/src/devtools/views/utils.js +++ b/packages/react-devtools-shared/src/devtools/views/utils.js @@ -134,7 +134,7 @@ export function serializeDataForCopy(props: Object): string { export function serializeHooksForCopy(hooks: HooksTree | null): string { // $FlowFixMe[not-an-object] "HooksTree is not an object" - const cloned = Object.assign([] as Array<any>, hooks); + const cloned = Object.assign(([]: Array<any>), hooks); const queue = [...cloned]; diff --git a/packages/react-devtools-shared/src/dynamicImportCache.js b/packages/react-devtools-shared/src/dynamicImportCache.js index aa4add28b58b..c4dfa3b7ca05 100644 --- a/packages/react-devtools-shared/src/dynamicImportCache.js +++ b/packages/react-devtools-shared/src/dynamicImportCache.js @@ -52,7 +52,6 @@ function readRecord<T>(record: Thenable<T>): T | null { export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module { let record = moduleLoaderFunctionToModuleMap.get(moduleLoaderFunction); - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `[dynamicImportCache] loadModule("${moduleLoaderFunction.name}")`, @@ -93,7 +92,7 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module { } // This assumes they won't throw. - rejectCallbacks.forEach(callback => callback((thenable as any).reason)); + rejectCallbacks.forEach(callback => callback((thenable: any).reason)); rejectCallbacks.clear(); callbacks.clear(); }; @@ -104,7 +103,6 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module { moduleLoaderFunction().then( module => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `[dynamicImportCache] loadModule("${moduleLoaderFunction.name}") then()`, @@ -115,14 +113,13 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module { return; } - const fulfilledThenable: FulfilledThenable<Module> = thenable as any; + const fulfilledThenable: FulfilledThenable<Module> = (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = module; wake(); }, error => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `[dynamicImportCache] loadModule("${moduleLoaderFunction.name}") catch()`, @@ -135,7 +132,7 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module { console.log(error); - const rejectedThenable: RejectedThenable<Module> = thenable as any; + const rejectedThenable: RejectedThenable<Module> = (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; @@ -145,7 +142,6 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module { // Eventually timeout and stop trying to load the module. let timeoutID: null | TimeoutID = setTimeout(function onTimeout() { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `[dynamicImportCache] loadModule("${moduleLoaderFunction.name}") onTimeout()`, @@ -156,7 +152,7 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module { didTimeout = true; - const rejectedThenable: RejectedThenable<Module> = thenable as any; + const rejectedThenable: RejectedThenable<Module> = (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = null; diff --git a/packages/react-devtools-shared/src/hook.js b/packages/react-devtools-shared/src/hook.js index 5ec953101aa0..f24ae48c3601 100644 --- a/packages/react-devtools-shared/src/hook.js +++ b/packages/react-devtools-shared/src/hook.js @@ -433,7 +433,7 @@ export function installHook( const startStackFrame = openModuleRangesStack.pop(); const stopStackFrame = getTopStackFrameString(error); if (stopStackFrame !== null) { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] moduleRanges.push([startStackFrame, stopStackFrame]); } } @@ -493,7 +493,7 @@ export function installHook( // The backend is what implements a message queue, so it's the only one that injects onErrorOrWarning. if (onErrorOrWarning != null) { onErrorOrWarning( - method as any as 'error' | 'warn', + ((method: any): 'error' | 'warn'), args.slice(), ); } @@ -699,15 +699,19 @@ export function installHook( }); } - Object.defineProperty(target, '__REACT_DEVTOOLS_GLOBAL_HOOK__', { - // This property needs to be configurable for the test environment, - // else we won't be able to delete and recreate it between tests. - configurable: __DEV__, - enumerable: false, - get() { - return hook; - }, - } as Object); + Object.defineProperty( + target, + '__REACT_DEVTOOLS_GLOBAL_HOOK__', + ({ + // This property needs to be configurable for the test environment, + // else we won't be able to delete and recreate it between tests. + configurable: __DEV__, + enumerable: false, + get() { + return hook; + }, + }: Object), + ); return hook; } diff --git a/packages/react-devtools-shared/src/hookNamesCache.js b/packages/react-devtools-shared/src/hookNamesCache.js index 7123318cfb34..5a9d1ba7d212 100644 --- a/packages/react-devtools-shared/src/hookNamesCache.js +++ b/packages/react-devtools-shared/src/hookNamesCache.js @@ -80,7 +80,6 @@ export function loadHookNames( ): HookNames | null { let record = map.get(element); - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.groupCollapsed('loadHookNames() record:'); console.log(record); @@ -115,7 +114,7 @@ export function loadHookNames( } // This assumes they won't throw. - callbacks.forEach(callback => callback((thenable as any).value)); + callbacks.forEach(callback => callback((thenable: any).value)); callbacks.clear(); rejectCallbacks.clear(); }; @@ -125,7 +124,7 @@ export function loadHookNames( timeoutID = null; } // This assumes they won't throw. - rejectCallbacks.forEach(callback => callback((thenable as any).reason)); + rejectCallbacks.forEach(callback => callback((thenable: any).reason)); rejectCallbacks.clear(); callbacks.clear(); }; @@ -152,14 +151,13 @@ export function loadHookNames( return; } - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log('[hookNamesCache] onSuccess() hookNames:', hookNames); } if (hookNames) { const fulfilledThenable: FulfilledThenable<HookNames> = - thenable as any; + (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = hookNames; status = 'success'; @@ -168,7 +166,7 @@ export function loadHookNames( wake(); } else { const notFoundThenable: RejectedThenable<HookNames> = - thenable as any; + (thenable: any); notFoundThenable.status = 'rejected'; notFoundThenable.reason = null; status = 'error'; @@ -182,7 +180,6 @@ export function loadHookNames( return; } - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log('[hookNamesCache] onError()'); } @@ -190,7 +187,7 @@ export function loadHookNames( console.error(error); const rejectedThenable: RejectedThenable<HookNames> = - thenable as any; + (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = null; @@ -202,7 +199,6 @@ export function loadHookNames( // Eventually timeout and stop trying to load names. timeoutID = setTimeout(function onTimeout() { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log('[hookNamesCache] onTimeout()'); } @@ -211,7 +207,7 @@ export function loadHookNames( didTimeout = true; - const timedoutThenable: RejectedThenable<HookNames> = thenable as any; + const timedoutThenable: RejectedThenable<HookNames> = (thenable: any); timedoutThenable.status = 'rejected'; timedoutThenable.reason = null; diff --git a/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js b/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js index a18ad15b50ec..468905bf8716 100644 --- a/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js +++ b/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js @@ -39,9 +39,9 @@ export default function SourceMapConsumer( sourceMapJSON: MixedSourceMap | IndexSourceMapSection, ): SourceMapConsumerType { if (sourceMapJSON.sections != null) { - return IndexedSourceMapConsumer(sourceMapJSON as any as IndexSourceMap); + return IndexedSourceMapConsumer(((sourceMapJSON: any): IndexSourceMap)); } else { - return BasicSourceMapConsumer(sourceMapJSON as any as BasicSourceMap); + return BasicSourceMapConsumer(((sourceMapJSON: any): BasicSourceMap)); } } @@ -124,15 +124,15 @@ function BasicSourceMapConsumer(sourceMapJSON: BasicSourceMap) { return { column, line, - sourceContent: sourceContent as any as string | null, - sourceURL: sourceURL as any as string | null, + sourceContent: ((sourceContent: any): string | null), + sourceURL: ((sourceURL: any): string | null), ignored, }; } - return { + return (({ originalPositionFor, - } as any as SourceMapConsumerType; + }: any): SourceMapConsumerType); } type Section = { @@ -231,7 +231,7 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) { }); } - return { + return (({ originalPositionFor, - } as any as SourceMapConsumerType; + }: any): SourceMapConsumerType); } diff --git a/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js b/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js index 3bfc83dd2547..7c40cabf97fa 100644 --- a/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js +++ b/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js @@ -128,7 +128,6 @@ function extractAndLoadSourceMapJSON( // Deduplicate fetches, since there can be multiple location keys per source map. const dedupedFetchPromises = new Map<string, Promise<$FlowFixMe>>(); - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( 'extractAndLoadSourceMapJSON() load', @@ -141,7 +140,7 @@ function extractAndLoadSourceMapJSON( locationKeyToHookSourceAndMetadata.forEach(hookSourceAndMetadata => { const sourceMapRegex = / ?sourceMappingURL=([^\s'"]+)/gm; const runtimeSourceCode = - hookSourceAndMetadata.runtimeSourceCode as any as string; + ((hookSourceAndMetadata.runtimeSourceCode: any): string); // TODO (named hooks) Search for our custom metadata first. // If it's found, we should use it rather than source maps. @@ -155,7 +154,6 @@ function extractAndLoadSourceMapJSON( ); if (sourceMappingURLMatch == null) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log('extractAndLoadSourceMapJSON() No source map found'); } @@ -175,11 +173,9 @@ function extractAndLoadSourceMapJSON( // Web apps like Code Sandbox embed multiple inline source maps. // In this case, we need to loop through and find the right one. // We may also need to trim any part of this string that isn't based64 encoded data. - const trimmed = ( - sourceMappingURL.match( - /base64,([a-zA-Z0-9+\/=]+)/, - ) as any as Array<string> - )[1]; + const trimmed = ((sourceMappingURL.match( + /base64,([a-zA-Z0-9+\/=]+)/, + ): any): Array<string>)[1]; const decoded = withSyncPerfMeasurements( 'decodeBase64String()', () => decodeBase64String(trimmed), @@ -190,7 +186,6 @@ function extractAndLoadSourceMapJSON( () => JSON.parse(decoded), ); - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.groupCollapsed( 'extractAndLoadSourceMapJSON() Inline source map', @@ -276,7 +271,6 @@ function extractAndLoadSourceMapJSON( error => null, ); - // $FlowFixMe[constant-condition] if (__DEBUG__) { if (!dedupedFetchPromises.has(url)) { console.log( @@ -315,7 +309,6 @@ function fetchFile( ): Promise<string> { return withCallbackPerfMeasurements(`${markName}("${url}")`, done => { return new Promise((resolve, reject) => { - // $FlowFixMe[incompatible-type] fetch(url, FETCH_OPTIONS).then( response => { if (response.ok) { @@ -326,7 +319,6 @@ function fetchFile( resolve(text); }) .catch(error => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `${markName}() Could not read text for url "${url}"`, @@ -336,7 +328,6 @@ function fetchFile( reject(null); }); } else { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log(`${markName}() Got bad response for url "${url}"`); } @@ -345,7 +336,6 @@ function fetchFile( } }, error => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log(`${markName}() Could not fetch file: ${error.message}`); } @@ -381,7 +371,6 @@ export function flattenHooksList(hooksTree: HooksTree): HooksList { flattenHooksListImpl(hooksTree, hooksList); }); - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log('flattenHooksList() hooksList:', hooksList); } @@ -398,7 +387,6 @@ function flattenHooksListImpl( if (isUnnamedBuiltInHook(hook)) { // No need to load source code or do any parsing for unnamed hooks. - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log('flattenHooksListImpl() Skipping unnamed hook', hook); } @@ -433,7 +421,7 @@ function initializeHookSourceAndMetadata( const locationKey = getHookSourceLocationKey(hookSource); if (!locationKeyToHookSourceAndMetadata.has(locationKey)) { // Can't be null because getHookSourceLocationKey() would have thrown - const runtimeSourceURL = hookSource.fileName as any as string; + const runtimeSourceURL = ((hookSource.fileName: any): string); const hookSourceAndMetadata: HookSourceAndMetadata = { hookSource, @@ -479,7 +467,7 @@ function loadSourceFiles( return withAsyncPerfMeasurements( `fetchFileWithCaching("${url}")`, () => { - return (fetchFileWithCaching as any as FetchFileWithCaching)(url); + return ((fetchFileWithCaching: any): FetchFileWithCaching)(url); }, ); }; @@ -495,7 +483,6 @@ function loadSourceFiles( throw Error('Source code too large to parse'); } - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.groupCollapsed( `loadSourceFiles() runtimeSourceURL "${runtimeSourceURL}"`, diff --git a/packages/react-devtools-shared/src/hooks/parseHookNames/parseSourceAndMetadata.js b/packages/react-devtools-shared/src/hooks/parseHookNames/parseSourceAndMetadata.js index 4e01d6b360a9..fde538578572 100644 --- a/packages/react-devtools-shared/src/hooks/parseHookNames/parseSourceAndMetadata.js +++ b/packages/react-devtools-shared/src/hooks/parseHookNames/parseSourceAndMetadata.js @@ -82,7 +82,6 @@ const originalURLToMetadataCache: LRUCache<string, CachedSourceCodeMetadata> = originalSourceURL: string, metadata: CachedSourceCodeMetadata, ) => { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `originalURLToMetadataCache.dispose() Evicting cached metadata for "${originalSourceURL}"`, @@ -129,7 +128,7 @@ function findHookNames( hooksList.map(hook => { // We already guard against a null HookSource in parseHookNames() - const hookSource = hook.hookSource as any as HookSource; + const hookSource = ((hook.hookSource: any): HookSource); const fileName = hookSource.fileName; if (!fileName) { return null; // Should not be reachable. @@ -177,14 +176,13 @@ function findHookNames( getHookName( hook, hookParsedMetadata.originalSourceAST, - hookParsedMetadata.originalSourceCode as any as string, - originalSourceLineNumber as any as number, + ((hookParsedMetadata.originalSourceCode: any): string), + ((originalSourceLineNumber: any): number), originalSourceColumnNumber, ), ); } - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log(`findHookNames() Found name "${name || '-'}"`); } @@ -255,7 +253,7 @@ function parseSourceAST( const {metadataConsumer, sourceMapConsumer} = hookParsedMetadata; const runtimeSourceCode = - hookSourceAndMetadata.runtimeSourceCode as any as string; + ((hookSourceAndMetadata.runtimeSourceCode: any): string); let hasHookMap = false; let originalSourceURL; let originalSourceCode; @@ -303,7 +301,6 @@ function parseSourceAST( hasHookMap = true; } - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `parseSourceAST() mapped line ${lineNumber}->${originalSourceLineNumber} and column ${columnNumber}->${originalSourceColumnNumber}`, @@ -311,7 +308,6 @@ function parseSourceAST( } if (hasHookMap) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `parseSourceAST() Found hookMap and skipping parsing for "${originalSourceURL}"`, @@ -323,7 +319,6 @@ function parseSourceAST( return; } - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `parseSourceAST() Did not find hook map for "${originalSourceURL}"`, @@ -334,7 +329,6 @@ function parseSourceAST( // This may need to change if we switch to async parsing. const sourceMetadata = originalURLToMetadataCache.get(originalSourceURL); if (sourceMetadata != null) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.groupCollapsed( `parseSourceAST() Found cached source metadata for "${originalSourceURL}"`, @@ -365,7 +359,6 @@ function parseSourceAST( ); hookParsedMetadata.originalSourceAST = originalSourceAST; - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.log( `parseSourceAST() Caching source metadata for "${originalSourceURL}"`, @@ -405,7 +398,6 @@ function parseSourceMaps( // we can skip reloading it (and more importantly, re-parsing it). const runtimeMetadata = runtimeURLToMetadataCache.get(runtimeSourceURL); if (runtimeMetadata != null) { - // $FlowFixMe[constant-condition] if (__DEBUG__) { console.groupCollapsed( `parseHookNames() Found cached runtime metadata for file "${runtimeSourceURL}"`, diff --git a/packages/react-devtools-shared/src/hydration.js b/packages/react-devtools-shared/src/hydration.js index d11c6220d4c5..3670514ff4c6 100644 --- a/packages/react-devtools-shared/src/hydration.js +++ b/packages/react-devtools-shared/src/hydration.js @@ -26,15 +26,15 @@ import type { import noop from 'shared/noop'; export const meta = { - inspectable: Symbol('inspectable') as symbol, - inspected: Symbol('inspected') as symbol, - name: Symbol('name') as symbol, - preview_long: Symbol('preview_long') as symbol, - preview_short: Symbol('preview_short') as symbol, - readonly: Symbol('readonly') as symbol, - size: Symbol('size') as symbol, - type: Symbol('type') as symbol, - unserializable: Symbol('unserializable') as symbol, + inspectable: (Symbol('inspectable'): symbol), + inspected: (Symbol('inspected'): symbol), + name: (Symbol('name'): symbol), + preview_long: (Symbol('preview_long'): symbol), + preview_short: (Symbol('preview_short'): symbol), + readonly: (Symbol('readonly'): symbol), + size: (Symbol('size'): symbol), + type: (Symbol('type'): symbol), + unserializable: (Symbol('unserializable'): symbol), }; export type Dehydrated = { diff --git a/packages/react-devtools-shared/src/inspectedElementCache.js b/packages/react-devtools-shared/src/inspectedElementCache.js index 8a51e2543a9f..820baf3380dd 100644 --- a/packages/react-devtools-shared/src/inspectedElementCache.js +++ b/packages/react-devtools-shared/src/inspectedElementCache.js @@ -101,13 +101,13 @@ export function inspectElement( const wake = () => { // This assumes they won't throw. - callbacks.forEach(callback => callback((thenable as any).value)); + callbacks.forEach(callback => callback((thenable: any).value)); callbacks.clear(); rejectCallbacks.clear(); }; const wakeRejections = () => { // This assumes they won't throw. - rejectCallbacks.forEach(callback => callback((thenable as any).reason)); + rejectCallbacks.forEach(callback => callback((thenable: any).reason)); rejectCallbacks.clear(); callbacks.clear(); }; @@ -116,7 +116,7 @@ export function inspectElement( const rendererID = store.getRendererIDForElement(element.id); if (rendererID == null) { const rejectedThenable: RejectedThenable<InspectedElementFrontend> = - thenable as any; + (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = new Error( `Could not inspect element with id "${element.id}". No renderer found.`, @@ -133,7 +133,7 @@ export function inspectElement( InspectedElementResponseType, ]) => { const fulfilledThenable: FulfilledThenable<InspectedElementFrontend> = - thenable as any; + (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = inspectedElement; wake(); @@ -143,7 +143,7 @@ export function inspectElement( console.error(error); const rejectedThenable: RejectedThenable<InspectedElementFrontend> = - thenable as any; + (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; diff --git a/packages/react-devtools-shared/src/inspectedElementMutableSource.js b/packages/react-devtools-shared/src/inspectedElementMutableSource.js index 51612a1bb961..d967109163aa 100644 --- a/packages/react-devtools-shared/src/inspectedElementMutableSource.js +++ b/packages/react-devtools-shared/src/inspectedElementMutableSource.js @@ -88,7 +88,7 @@ export function inspectElement( let inspectedElement; switch (type) { case 'error': { - const {message, stack, errorType} = data as any as InspectElementError; + const {message, stack, errorType} = ((data: any): InspectElementError); // create a different error class for each error type // and keep useful information from backend. @@ -124,7 +124,7 @@ export function inspectElement( throw Error(`Element "${id}" not found`); case 'full-data': - const fullData = data as any as InspectElementFullData; + const fullData = ((data: any): InspectElementFullData); // New data has come in. // We should replace the data in our local mutable copy. @@ -137,13 +137,12 @@ export function inspectElement( return [inspectedElement, type]; case 'hydrated-path': - const hydratedPathData = data as any as InspectElementHydratedPath; + const hydratedPathData = ((data: any): InspectElementHydratedPath); const {value} = hydratedPathData; // A path has been hydrated. // Merge it with the latest copy we have locally and resolve with the merged value. inspectedElement = inspectedElementCache.get(id) || null; - // $FlowFixMe[invalid-compare] if (inspectedElement !== null) { // Clone element inspectedElement = {...inspectedElement}; diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js index 22e4c444ddfa..aa7ba33dc595 100644 --- a/packages/react-devtools-shared/src/utils.js +++ b/packages/react-devtools-shared/src/utils.js @@ -138,7 +138,7 @@ export function getWrappedDisplayName( wrapperName: string, fallbackName?: string, ): string { - const displayName = (outerType as any)?.displayName; + const displayName = (outerType: any)?.displayName; return ( displayName || `${wrapperName}(${getDisplayName(innerType, fallbackName)})` ); @@ -251,8 +251,8 @@ export function printOperationsArray(operations: Array<number>) { switch (operation) { case TREE_OPERATION_ADD: { - const id = operations[i + 1] as any as number; - const type = operations[i + 2] as any as ElementType; + const id = ((operations[i + 1]: any): number); + const type = ((operations[i + 2]: any): ElementType); i += 3; @@ -264,7 +264,7 @@ export function printOperationsArray(operations: Array<number>) { i++; // supportsStrictMode i++; // hasOwnerMetadata } else { - const parentID = operations[i] as any as number; + const parentID = ((operations[i]: any): number); i++; i++; // ownerID @@ -283,11 +283,11 @@ export function printOperationsArray(operations: Array<number>) { break; } case TREE_OPERATION_REMOVE: { - const removeLength = operations[i + 1] as any as number; + const removeLength = ((operations[i + 1]: any): number); i += 2; for (let removeIndex = 0; removeIndex < removeLength; removeIndex++) { - const id = operations[i] as any as number; + const id = ((operations[i]: any): number); i += 1; logs.push(`Remove node ${id}`); @@ -304,8 +304,8 @@ export function printOperationsArray(operations: Array<number>) { break; } case TREE_OPERATION_REORDER_CHILDREN: { - const id = operations[i + 1] as any as number; - const numChildren = operations[i + 2] as any as number; + const id = ((operations[i + 1]: any): number); + const numChildren = ((operations[i + 2]: any): number); i += 3; const children = operations.slice(i, i + numChildren); i += numChildren; @@ -369,11 +369,11 @@ export function printOperationsArray(operations: Array<number>) { break; } case SUSPENSE_TREE_OPERATION_REMOVE: { - const removeLength = operations[i + 1] as any as number; + const removeLength = ((operations[i + 1]: any): number); i += 2; for (let removeIndex = 0; removeIndex < removeLength; removeIndex++) { - const id = operations[i] as any as number; + const id = ((operations[i]: any): number); i += 1; logs.push(`Remove suspense node ${id}`); @@ -382,8 +382,8 @@ export function printOperationsArray(operations: Array<number>) { break; } case SUSPENSE_TREE_OPERATION_REORDER_CHILDREN: { - const id = operations[i + 1] as any as number; - const numChildren = operations[i + 2] as any as number; + const id = ((operations[i + 1]: any): number); + const numChildren = ((operations[i + 2]: any): number); i += 3; const children = operations.slice(i, i + numChildren); i += numChildren; @@ -394,8 +394,8 @@ export function printOperationsArray(operations: Array<number>) { break; } case SUSPENSE_TREE_OPERATION_RESIZE: { - const id = operations[i + 1] as any as number; - const numRects = operations[i + 2] as any as number; + const id = ((operations[i + 1]: any): number); + const numRects = ((operations[i + 2]: any): number); i += 3; if (numRects === -1) { @@ -422,7 +422,7 @@ export function printOperationsArray(operations: Array<number>) { } case SUSPENSE_TREE_OPERATION_SUSPENDERS: { i++; - const changeLength = operations[i++] as any as number; + const changeLength = ((operations[i++]: any): number); for (let changeIndex = 0; changeIndex < changeLength; changeIndex++) { const id = operations[i++]; @@ -597,7 +597,7 @@ export function parseElementDisplayNameFromBackend( } return { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-return] formattedDisplayName: displayName, hocDisplayNames, compiledWithForget: false, @@ -651,7 +651,7 @@ export function deletePathInObject( const parent = getInObject(object, path.slice(0, length - 1)); if (parent) { if (isArray(parent)) { - parent.splice(last as any as number, 1); + parent.splice(((last: any): number), 1); } else { delete parent[last]; } @@ -672,7 +672,7 @@ export function renamePathInObject( const lastNew = newPath[length - 1]; parent[lastNew] = parent[lastOld]; if (isArray(parent)) { - parent.splice(lastOld as any as number, 1); + parent.splice(((lastOld: any): number), 1); } else { delete parent[lastOld]; } @@ -976,7 +976,6 @@ export function formatDataForPreview( case 'html_element': return `<${truncateForDisplay(data.tagName.toLowerCase())} />`; case 'function': - // $FlowFixMe[invalid-compare] if (typeof data.name === 'function' || data.name === '') { return '() => {}'; } diff --git a/packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js b/packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js index 5cfdb11c0d31..f1369896eda0 100644 --- a/packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js +++ b/packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js @@ -15,7 +15,7 @@ function wrapWithHoc(Component: () => any, index: number) { return <Component />; } - const displayName = (Component as any).displayName || Component.name; + const displayName = (Component: any).displayName || Component.name; HOC.displayName = `withHoc${index}(${displayName})`; return HOC; diff --git a/packages/react-devtools-shell/src/app/Iframe/index.js b/packages/react-devtools-shell/src/app/Iframe/index.js index 3f861f7beead..7f3964971cf8 100644 --- a/packages/react-devtools-shell/src/app/Iframe/index.js +++ b/packages/react-devtools-shell/src/app/Iframe/index.js @@ -28,7 +28,6 @@ function Frame(props) { React.useLayoutEffect(function () { const iframe = ref.current; - // $FlowFixMe[constant-condition] if (iframe) { const html = ` <!DOCTYPE html> @@ -58,7 +57,6 @@ function Frame(props) { style={iframeStyle} /> - {/* $FlowFixMe[constant-condition] */} {element ? createPortal(props.children, element) : null} </Fragment> ); diff --git a/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js b/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js index 26af74714037..487c0be4da75 100644 --- a/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js +++ b/packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js @@ -72,7 +72,7 @@ function useDeepHookF() { const ContextA = createContext('A'); const ContextB = createContext('B'); -function FunctionWithHooks(props: any, ref: React.RefSetter<any>) { +function FunctionWithHooks(props: any, ref: React$RefSetter<any>) { const [count, updateCount] = useState(0); // eslint-disable-next-line no-unused-vars const contextValueA = useContext(ContextA); @@ -109,7 +109,7 @@ const MemoWithHooks = memo(FunctionWithHooks); const ForwardRefWithHooks = forwardRef(FunctionWithHooks); function wrapWithHoc( - Component: (props: any, ref: React.RefSetter<any>) => any, + Component: (props: any, ref: React$RefSetter<any>) => any, ) { function Hoc() { return <Component />; @@ -122,8 +122,8 @@ function wrapWithHoc( } const HocWithHooks = wrapWithHoc(FunctionWithHooks); -const Suspendender = React.lazy<() => React.Node>(() => { - return new Promise<{default: () => React.Node, ...}>(resolve => { +const Suspendender = React.lazy(() => { + return new Promise<any>(resolve => { setTimeout(() => { resolve({ default: () => 'Finished!', diff --git a/packages/react-devtools-shell/src/app/ToDoList/ListItem.js b/packages/react-devtools-shell/src/app/ToDoList/ListItem.js index d8dbb721e858..549d15c31ea2 100644 --- a/packages/react-devtools-shell/src/app/ToDoList/ListItem.js +++ b/packages/react-devtools-shell/src/app/ToDoList/ListItem.js @@ -46,4 +46,4 @@ function ListItem({item, removeItem, toggleItem}: Props) { ); } -export default memo(ListItem) as component(...props: Props); +export default (memo(ListItem): component(...props: Props)); diff --git a/packages/react-devtools-shell/src/app/devtools.js b/packages/react-devtools-shell/src/app/devtools.js index dcf9b46afc74..8f3de74cf67c 100644 --- a/packages/react-devtools-shell/src/app/devtools.js +++ b/packages/react-devtools-shell/src/app/devtools.js @@ -14,7 +14,7 @@ import {initDevTools} from 'react-devtools-shared/src/devtools'; // $FlowFixMe[cannot-resolve-name] __webpack_public_path__ = '/dist/'; // eslint-disable-line no-undef -const iframe = document.getElementById('target') as any as HTMLIFrameElement; +const iframe = ((document.getElementById('target'): any): HTMLIFrameElement); const {contentDocument, contentWindow} = iframe; @@ -31,13 +31,13 @@ const DevTools = initializeFrontend(contentWindow); // Otherwise the Store may miss important initial tree op codes. activateBackend(contentWindow); -const container = document.getElementById('devtools') as any as HTMLElement; +const container = ((document.getElementById('devtools'): any): HTMLElement); let isTestAppMounted = true; -const mountButton = document.getElementById( +const mountButton = ((document.getElementById( 'mountButton', -) as any as HTMLButtonElement; +): any): HTMLButtonElement); mountButton.addEventListener('click', function () { if (isTestAppMounted) { if (typeof window.unmountTestApp === 'function') { @@ -86,5 +86,5 @@ function inject(sourcePath: string, callback: () => void) { script.onload = callback; script.src = sourcePath; - (contentDocument.body as any as HTMLBodyElement).appendChild(script); + ((contentDocument.body: any): HTMLBodyElement).appendChild(script); } diff --git a/packages/react-devtools-shell/src/app/index.js b/packages/react-devtools-shell/src/app/index.js index 0207a8934c2f..d23b8ca7a451 100644 --- a/packages/react-devtools-shell/src/app/index.js +++ b/packages/react-devtools-shell/src/app/index.js @@ -54,7 +54,7 @@ const unmountFunctions: Array<() => void | boolean> = []; function createContainer() { const container = document.createElement('div'); - (document.body as any as HTMLBodyElement).appendChild(container); + ((document.body: any): HTMLBodyElement).appendChild(container); return container; } @@ -95,7 +95,7 @@ function mountLegacyApp(App: () => React$Node) { // $FlowFixMe[not-a-function]: These are removed in 19. render(createElement(LegacyRender), container); - // $FlowFixMe[not-a-function]: These are removed in 19. + // $FlowFixMe: These are removed in 19. unmountFunctions.push(() => unmountComponentAtNode(container)); } diff --git a/packages/react-devtools-shell/src/e2e-apps/ListApp.js b/packages/react-devtools-shell/src/e2e-apps/ListApp.js index 58183467696d..9cb17188b0ff 100644 --- a/packages/react-devtools-shell/src/e2e-apps/ListApp.js +++ b/packages/react-devtools-shell/src/e2e-apps/ListApp.js @@ -19,7 +19,7 @@ function List() { const inputRef = useRef(null); const addItem = () => { - const input = inputRef.current as any as HTMLInputElement; + const input = ((inputRef.current: any): HTMLInputElement); const text = input.value; input.value = ''; diff --git a/packages/react-devtools-shell/src/e2e-regression/app-legacy.js b/packages/react-devtools-shell/src/e2e-regression/app-legacy.js index 54adbca81564..19ad2f0f6d95 100644 --- a/packages/react-devtools-shell/src/e2e-regression/app-legacy.js +++ b/packages/react-devtools-shell/src/e2e-regression/app-legacy.js @@ -13,7 +13,7 @@ const version = process.env.E2E_APP_REACT_VERSION; function mountApp(App: () => React$Node) { const container = document.createElement('div'); - (document.body as any as HTMLBodyElement).appendChild(container); + ((document.body: any): HTMLBodyElement).appendChild(container); // $FlowFixMe[prop-missing]: These are removed in 19. ReactDOM.render(<App />, container); diff --git a/packages/react-devtools-shell/src/e2e-regression/app.js b/packages/react-devtools-shell/src/e2e-regression/app.js index 2125369dde96..d0210d323570 100644 --- a/packages/react-devtools-shell/src/e2e-regression/app.js +++ b/packages/react-devtools-shell/src/e2e-regression/app.js @@ -9,7 +9,7 @@ import ListApp from '../e2e-apps/ListApp'; function mountApp(App: () => React$Node) { const container = document.createElement('div'); - (document.body as any as HTMLBodyElement).appendChild(container); + ((document.body: any): HTMLBodyElement).appendChild(container); const root = ReactDOMClient.createRoot(container); root.render(<App />); diff --git a/packages/react-devtools-shell/src/e2e/app.js b/packages/react-devtools-shell/src/e2e/app.js index 265ebf8a7172..155c545e9ce6 100644 --- a/packages/react-devtools-shell/src/e2e/app.js +++ b/packages/react-devtools-shell/src/e2e/app.js @@ -7,7 +7,7 @@ import * as ReactDOMClient from 'react-dom/client'; const container = document.createElement('div'); -(document.body as any as HTMLBodyElement).appendChild(container); +((document.body: any): HTMLBodyElement).appendChild(container); // TODO We may want to parameterize this app // so that it can load things other than just ToDoList. diff --git a/packages/react-devtools-shell/src/perf-regression/app.js b/packages/react-devtools-shell/src/perf-regression/app.js index 4fa784d55233..88308e0fcf34 100644 --- a/packages/react-devtools-shell/src/perf-regression/app.js +++ b/packages/react-devtools-shell/src/perf-regression/app.js @@ -9,7 +9,7 @@ import App from './apps/index'; function mountApp() { const container = document.createElement('div'); - (document.body as any as HTMLBodyElement).appendChild(container); + ((document.body: any): HTMLBodyElement).appendChild(container); const root = createRoot(container); root.render( diff --git a/packages/react-devtools-timeline/src/CanvasPage.js b/packages/react-devtools-timeline/src/CanvasPage.js index 104a92727d52..9e861c2712ad 100644 --- a/packages/react-devtools-timeline/src/CanvasPage.js +++ b/packages/react-devtools-timeline/src/CanvasPage.js @@ -438,7 +438,6 @@ function AutoSizedCanvas({ if (suspenseEventsViewWrapper !== null) { rootView.addSubview(suspenseEventsViewWrapper); } - // $FlowFixMe[invalid-compare] if (reactMeasuresViewWrapper !== null) { rootView.addSubview(reactMeasuresViewWrapper); } diff --git a/packages/react-devtools-timeline/src/EventTooltip.js b/packages/react-devtools-timeline/src/EventTooltip.js index ab0a409d7ceb..3d3698c09f94 100644 --- a/packages/react-devtools-timeline/src/EventTooltip.js +++ b/packages/react-devtools-timeline/src/EventTooltip.js @@ -293,7 +293,7 @@ const TooltipSchedulingEvent = ({ case 'schedule-force-update': lanes = schedulingEvent.lanes; laneLabels = lanes.map( - lane => data.laneToLabelMap.get(lane) as any as string, + lane => ((data.laneToLabelMap.get(lane): any): string), ); break; } @@ -441,7 +441,7 @@ const TooltipReactMeasure = ({ const [startTime, stopTime] = getBatchRange(batchUID, data); const laneLabels = lanes.map( - lane => data.laneToLabelMap.get(lane) as any as string, + lane => ((data.laneToLabelMap.get(lane): any): string), ); return ( diff --git a/packages/react-devtools-timeline/src/Timeline.js b/packages/react-devtools-timeline/src/Timeline.js index 5c427bd9b74c..f209309bb0ab 100644 --- a/packages/react-devtools-timeline/src/Timeline.js +++ b/packages/react-devtools-timeline/src/Timeline.js @@ -57,7 +57,7 @@ export function Timeline(_: {}): React.Node { const [key, setKey] = useState<string>(theme); useLayoutEffect(() => { const pollForTheme = () => { - if (updateColorsToMatchTheme(ref.current as any as HTMLDivElement)) { + if (updateColorsToMatchTheme(((ref.current: any): HTMLDivElement))) { clearInterval(intervalID); setKey(deferredTheme); } diff --git a/packages/react-devtools-timeline/src/TimelineContext.js b/packages/react-devtools-timeline/src/TimelineContext.js index a6f52f5fa9d3..7835158e9895 100644 --- a/packages/react-devtools-timeline/src/TimelineContext.js +++ b/packages/react-devtools-timeline/src/TimelineContext.js @@ -41,7 +41,7 @@ export type Context = { }; const TimelineContext: ReactContext<Context> = createContext<Context>( - null as any as Context, + ((null: any): Context), ); TimelineContext.displayName = 'TimelineContext'; diff --git a/packages/react-devtools-timeline/src/TimelineSearchContext.js b/packages/react-devtools-timeline/src/TimelineSearchContext.js index 2a38dbcfd8c3..604dbf180607 100644 --- a/packages/react-devtools-timeline/src/TimelineSearchContext.js +++ b/packages/react-devtools-timeline/src/TimelineSearchContext.js @@ -125,7 +125,7 @@ export type Context = { }; const TimelineSearchContext: ReactContext<Context> = createContext<Context>( - null as any as Context, + ((null: any): Context), ); TimelineSearchContext.displayName = 'TimelineSearchContext'; diff --git a/packages/react-devtools-timeline/src/content-views/ComponentMeasuresView.js b/packages/react-devtools-timeline/src/content-views/ComponentMeasuresView.js index dc1c68045e32..0e4a5727e20b 100644 --- a/packages/react-devtools-timeline/src/content-views/ComponentMeasuresView.js +++ b/packages/react-devtools-timeline/src/content-views/ComponentMeasuresView.js @@ -113,8 +113,8 @@ export class ComponentMeasuresView extends View { return false; // Too small to render at this zoom level } - let textFillStyle = null as any as string; - let typeLabel = null as any as string; + let textFillStyle = ((null: any): string); + let typeLabel = ((null: any): string); const drawableRect = intersectionOfRects(componentMeasureRect, rect); context.beginPath(); diff --git a/packages/react-devtools-timeline/src/content-views/SnapshotsView.js b/packages/react-devtools-timeline/src/content-views/SnapshotsView.js index 23d61a3b63b2..494cca38c4fc 100644 --- a/packages/react-devtools-timeline/src/content-views/SnapshotsView.js +++ b/packages/react-devtools-timeline/src/content-views/SnapshotsView.js @@ -166,7 +166,7 @@ export class SnapshotsView extends View { imageRect.size.height, ); - // $FlowFixMe[incompatible-type] Flow doesn't know about the 9 argument variant of drawImage() + // $FlowFixMe[incompatible-call] Flow doesn't know about the 9 argument variant of drawImage() context.drawImage( snapshot.image, diff --git a/packages/react-devtools-timeline/src/content-views/SuspenseEventsView.js b/packages/react-devtools-timeline/src/content-views/SuspenseEventsView.js index 3c1fe88e91f7..43df27e8c92b 100644 --- a/packages/react-devtools-timeline/src/content-views/SuspenseEventsView.js +++ b/packages/react-devtools-timeline/src/content-views/SuspenseEventsView.js @@ -122,7 +122,7 @@ export class SuspenseEventsView extends View { baseY += depth * ROW_WITH_BORDER_HEIGHT; - let fillStyle = null as any as string; + let fillStyle = ((null: any): string); if (warning !== null) { fillStyle = showHoverHighlight ? COLORS.WARNING_BACKGROUND_HOVER diff --git a/packages/react-devtools-timeline/src/content-views/UserTimingMarksView.js b/packages/react-devtools-timeline/src/content-views/UserTimingMarksView.js index 8967d8463dfa..3128777a9c07 100644 --- a/packages/react-devtools-timeline/src/content-views/UserTimingMarksView.js +++ b/packages/react-devtools-timeline/src/content-views/UserTimingMarksView.js @@ -106,7 +106,6 @@ export class UserTimingMarksView extends View { ? COLORS.USER_TIMING_HOVER : COLORS.USER_TIMING; - // $FlowFixMe[invalid-compare] if (fillStyle !== null) { const y = baseY + halfSize; diff --git a/packages/react-devtools-timeline/src/content-views/utils/text.js b/packages/react-devtools-timeline/src/content-views/utils/text.js index 30b576c83bed..000a41cb0cdb 100644 --- a/packages/react-devtools-timeline/src/content-views/utils/text.js +++ b/packages/react-devtools-timeline/src/content-views/utils/text.js @@ -24,7 +24,7 @@ export function getTextWidth( cachedTextWidths.set(text, measuredWidth); } - return measuredWidth as any as number; + return ((measuredWidth: any): number); } export function trimText( diff --git a/packages/react-devtools-timeline/src/createDataResourceFromImportedFile.js b/packages/react-devtools-timeline/src/createDataResourceFromImportedFile.js index 852ad542def5..2b8dfcd266a7 100644 --- a/packages/react-devtools-timeline/src/createDataResourceFromImportedFile.js +++ b/packages/react-devtools-timeline/src/createDataResourceFromImportedFile.js @@ -22,9 +22,9 @@ export default function createDataResourceFromImportedFile( return createResource( () => { return new Promise<TimelineData | Error>((resolve, reject) => { - const promise = importFile( + const promise = ((importFile( file, - ) as any as Promise<ImportWorkerOutputData>; + ): any): Promise<ImportWorkerOutputData>); promise.then(data => { switch (data.status) { case 'SUCCESS': diff --git a/packages/react-devtools-timeline/src/import-worker/preprocessData.js b/packages/react-devtools-timeline/src/import-worker/preprocessData.js index 50a3091b8eda..8d45be544d74 100644 --- a/packages/react-devtools-timeline/src/import-worker/preprocessData.js +++ b/packages/react-devtools-timeline/src/import-worker/preprocessData.js @@ -171,11 +171,9 @@ function markWorkStarted( // This array is pre-initialized before processing starts. lanes.forEach(lane => { - ( - currentProfilerData.laneToReactMeasureMap.get( - lane, - ) as any as Array<ReactMeasure> - ).push(measure); + ((currentProfilerData.laneToReactMeasureMap.get( + lane, + ): any): ReactMeasure[]).push(measure); }); } @@ -285,12 +283,10 @@ function processEventDispatch( warning: null, }; - // $FlowFixMe[incompatible-type] profilerData.nativeEvents.push(nativeEvent); // Keep track of curent event in case future ones overlap. // We separate them into different vertical lanes in this case. - // $FlowFixMe[incompatible-type] state.nativeEventStack.push(nativeEvent); } } @@ -364,7 +360,7 @@ function processScreenshot( }; // Delay processing until we've extracted snapshot dimensions. - let resolveFn = null as any as Function; + let resolveFn = ((null: any): Function); state.asyncProcessingPromises.push( new Promise(resolve => { resolveFn = resolve; @@ -553,7 +549,7 @@ function processTimelineEvent( currentProfilerData.thrownErrors.push({ componentName, message, - phase: phase as any as Phase, + phase: ((phase: any): Phase), timestamp: startTime, type: 'thrown-error', }); @@ -588,7 +584,7 @@ function processTimelineEvent( depth, duration: null, id, - phase: phase as any as Phase, + phase: ((phase: any): Phase), promiseName: promiseName || null, resolution: 'unresolved', timestamp: startTime, @@ -630,7 +626,7 @@ function processTimelineEvent( } else if (name.startsWith('--render-start-')) { if (state.nextRenderShouldGenerateNewBatchID) { state.nextRenderShouldGenerateNewBatchID = false; - state.batchUID = state.uidCounter++ as any as BatchUID; + state.batchUID = ((state.uidCounter++: any): BatchUID); } // If this render is the result of a nested update, make a note of it. diff --git a/packages/react-devtools-timeline/src/timelineCache.js b/packages/react-devtools-timeline/src/timelineCache.js index 78b2516d8e3e..ee32e4440f0b 100644 --- a/packages/react-devtools-timeline/src/timelineCache.js +++ b/packages/react-devtools-timeline/src/timelineCache.js @@ -33,7 +33,7 @@ function readRecord<T>(record: Thenable<T>): T | Error { return React.use(record); } catch (x) { if (record.status === 'rejected') { - return record.reason as any; + return (record.reason: any); } throw x; } @@ -41,7 +41,7 @@ function readRecord<T>(record: Thenable<T>): T | Error { if (record.status === 'fulfilled') { return record.value; } else if (record.status === 'rejected') { - return record.reason as any; + return (record.reason: any); } else { throw record; } @@ -69,13 +69,13 @@ export function importFile(file: File): TimelineData | Error { const wake = () => { // This assumes they won't throw. - callbacks.forEach(callback => callback((thenable as any).value)); + callbacks.forEach(callback => callback((thenable: any).value)); callbacks.clear(); rejectCallbacks.clear(); }; const wakeRejections = () => { // This assumes they won't throw. - rejectCallbacks.forEach(callback => callback((thenable as any).reason)); + rejectCallbacks.forEach(callback => callback((thenable: any).reason)); rejectCallbacks.clear(); callbacks.clear(); }; @@ -86,7 +86,7 @@ export function importFile(file: File): TimelineData | Error { switch (data.status) { case 'SUCCESS': const fulfilledThenable: FulfilledThenable<TimelineData> = - thenable as any; + (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = data.processedData; wake(); @@ -94,7 +94,7 @@ export function importFile(file: File): TimelineData | Error { case 'INVALID_PROFILE_ERROR': case 'UNEXPECTED_ERROR': const rejectedThenable: RejectedThenable<TimelineData> = - thenable as any; + (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = data.error; wakeRejections(); diff --git a/packages/react-devtools-timeline/src/utils/getBatchRange.js b/packages/react-devtools-timeline/src/utils/getBatchRange.js index d6f334761bd1..72bf119a0d11 100644 --- a/packages/react-devtools-timeline/src/utils/getBatchRange.js +++ b/packages/react-devtools-timeline/src/utils/getBatchRange.js @@ -26,7 +26,7 @@ function unmemoizedGetBatchRange( throw Error(`Could not find measures with batch UID "${batchUID}"`); } - const lastMeasure = measures[measures.length - 1] as any as ReactMeasure; + const lastMeasure = ((measures[measures.length - 1]: any): ReactMeasure); const stopTime = lastMeasure.timestamp + lastMeasure.duration; if (stopTime < minStartTime) { diff --git a/packages/react-devtools-timeline/src/view-base/VerticalScrollView.js b/packages/react-devtools-timeline/src/view-base/VerticalScrollView.js index d48c6083b865..06550e1f837b 100644 --- a/packages/react-devtools-timeline/src/view-base/VerticalScrollView.js +++ b/packages/react-devtools-timeline/src/view-base/VerticalScrollView.js @@ -249,9 +249,9 @@ export class VerticalScrollView extends View { if ( this._viewState.viewToMutableViewStateMap.has(this._mutableViewStateKey) ) { - this._scrollState = this._viewState.viewToMutableViewStateMap.get( + this._scrollState = ((this._viewState.viewToMutableViewStateMap.get( this._mutableViewStateKey, - ) as any as ScrollState; + ): any): ScrollState); } else { this._viewState.viewToMutableViewStateMap.set( this._mutableViewStateKey, diff --git a/packages/react-devtools-timeline/src/view-base/resizable/ResizableView.js b/packages/react-devtools-timeline/src/view-base/resizable/ResizableView.js index 024ff5056f3e..c66d9af3b503 100644 --- a/packages/react-devtools-timeline/src/view-base/resizable/ResizableView.js +++ b/packages/react-devtools-timeline/src/view-base/resizable/ResizableView.js @@ -107,9 +107,9 @@ export class ResizableView extends View { if ( this._viewState.viewToMutableViewStateMap.has(this._mutableViewStateKey) ) { - this._layoutState = this._viewState.viewToMutableViewStateMap.get( + this._layoutState = ((this._viewState.viewToMutableViewStateMap.get( this._mutableViewStateKey, - ) as any as LayoutState; + ): any): LayoutState); this._updateLayoutStateAndResizeBar(this._layoutState.barOffsetY); } else { diff --git a/packages/react-devtools-timeline/src/view-base/useCanvasInteraction.js b/packages/react-devtools-timeline/src/view-base/useCanvasInteraction.js index 0f2bf0e87c46..59fd89c26f35 100644 --- a/packages/react-devtools-timeline/src/view-base/useCanvasInteraction.js +++ b/packages/react-devtools-timeline/src/view-base/useCanvasInteraction.js @@ -125,7 +125,7 @@ export function useCanvasInteraction( } function localToCanvasCoordinates(localCoordinates: Point): Point { - // $FlowFixMe[incompatible-type] found when upgrading Flow + // $FlowFixMe[incompatible-call] found when upgrading Flow const canvasRect = cacheFirstGetCanvasBoundingRect(canvas); return { x: localCoordinates.x - canvasRect.left, diff --git a/packages/react-devtools/package.json b/packages/react-devtools/package.json index 9a7ee408f628..10a492628338 100644 --- a/packages/react-devtools/package.json +++ b/packages/react-devtools/package.json @@ -5,7 +5,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-devtools" }, "bin": { diff --git a/packages/react-dom-bindings/package.json b/packages/react-dom-bindings/package.json index 8fb19d332a10..f7d91a6dd9e2 100644 --- a/packages/react-dom-bindings/package.json +++ b/packages/react-dom-bindings/package.json @@ -6,7 +6,7 @@ "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-dom-bindings" }, "keywords": [ @@ -14,7 +14,7 @@ ], "license": "MIT", "bugs": { - "url": "https://github.com/react/react/issues" + "url": "https://github.com/facebook/react/issues" }, "homepage": "https://react.dev/", "peerDependencies": { diff --git a/packages/react-dom-bindings/src/client/DOMAccessibilityRoles.js b/packages/react-dom-bindings/src/client/DOMAccessibilityRoles.js index c7f7161f62b2..4c1091f2455c 100644 --- a/packages/react-dom-bindings/src/client/DOMAccessibilityRoles.js +++ b/packages/react-dom-bindings/src/client/DOMAccessibilityRoles.js @@ -80,7 +80,7 @@ function getImplicitRole(element: Element): string | null { } break; case 'INPUT': { - const type = (element as any).type; + const type = (element: any).type; switch (type) { case 'button': case 'image': @@ -111,7 +111,7 @@ function getImplicitRole(element: Element): string | null { } case 'SELECT': - if (element.hasAttribute('multiple') || (element as any).size > 1) { + if (element.hasAttribute('multiple') || (element: any).size > 1) { return 'listbox'; } return 'combobox'; diff --git a/packages/react-dom-bindings/src/client/DOMPropertyOperations.js b/packages/react-dom-bindings/src/client/DOMPropertyOperations.js index 2ba8ca79f5fa..df09445acc55 100644 --- a/packages/react-dom-bindings/src/client/DOMPropertyOperations.js +++ b/packages/react-dom-bindings/src/client/DOMPropertyOperations.js @@ -46,7 +46,7 @@ export function getValueForAttribute( if (__DEV__) { checkAttributeStringCoercion(expected, name); } - if (value === '' + (expected as any)) { + if (value === '' + (expected: any)) { return expected; } return value; @@ -88,7 +88,7 @@ export function getValueForAttributeOnCustomComponent( if (__DEV__) { checkAttributeStringCoercion(expected, name); } - if (value === '' + (expected as any)) { + if (value === '' + (expected: any)) { return expected; } return value; @@ -126,7 +126,7 @@ export function setValueForAttribute( } node.setAttribute( name, - enableTrustedTypesIntegration ? (value as any) : '' + (value as any), + enableTrustedTypesIntegration ? (value: any) : '' + (value: any), ); } } @@ -154,7 +154,7 @@ export function setValueForKnownAttribute( } node.setAttribute( name, - enableTrustedTypesIntegration ? (value as any) : '' + (value as any), + enableTrustedTypesIntegration ? (value: any) : '' + (value: any), ); } @@ -183,7 +183,7 @@ export function setValueForNamespacedAttribute( node.setAttributeNS( namespace, name, - enableTrustedTypesIntegration ? (value as any) : '' + (value as any), + enableTrustedTypesIntegration ? (value: any) : '' + (value: any), ); } @@ -206,22 +206,22 @@ export function setValueForPropertyOnCustomComponent( if (typeof prevValue !== 'function' && prevValue !== null) { // If we previously assigned a non-function type into this node, then // remove it when switching to event listener mode. - if (name in (node as any)) { - (node as any)[name] = null; + if (name in (node: any)) { + (node: any)[name] = null; } else if (node.hasAttribute(name)) { node.removeAttribute(name); } } - // $FlowFixMe[incompatible-type] value can't be casted to EventListener. - node.addEventListener(eventName, value as EventListener, useCapture); + // $FlowFixMe[incompatible-cast] value can't be casted to EventListener. + node.addEventListener(eventName, (value: EventListener), useCapture); return; } } trackHostMutation(); - if (name in (node as any)) { - (node as any)[name] = value; + if (name in (node: any)) { + (node: any)[name] = value; return; } diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index 9ce8253d32a0..80ee96a87a8f 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -250,9 +250,7 @@ function isExpectedViewTransitionName(htmlElement: HTMLElement): boolean { return false; } const expectedVtName = htmlElement.getAttribute('vt-name'); - const actualVtName: string = (htmlElement.style as any)[ - 'view-transition-name' - ]; + const actualVtName: string = (htmlElement.style: any)['view-transition-name']; if (expectedVtName) { return expectedVtName === actualVtName; } else { @@ -274,7 +272,7 @@ function warnForExtraAttributes( // Skip empty style. It's fine. return; } - const htmlElement = domElement as any as HTMLElement; + const htmlElement = ((domElement: any): HTMLElement); const style = htmlElement.style; const isOnlyVTStyles = (style.length === 1 && style[0] === 'view-transition-name') || @@ -328,7 +326,7 @@ function normalizeHTML(parent: Element, html: string) { parent.namespaceURI === MATH_NAMESPACE || parent.namespaceURI === SVG_NAMESPACE ? parent.ownerDocument.createElementNS( - parent.namespaceURI as any, + (parent.namespaceURI: any), parent.tagName, ) : parent.ownerDocument.createElement(parent.tagName); @@ -349,8 +347,7 @@ function normalizeMarkupForTextOrAttribute(markup: mixed): string { if (__DEV__) { checkHtmlStringCoercion(markup); } - const markupString = - typeof markup === 'string' ? markup : '' + (markup as any); + const markupString = typeof markup === 'string' ? markup : '' + (markup: any); return markupString .replace(NORMALIZE_NEWLINES_REGEX, '\n') .replace(NORMALIZE_NULL_AND_REPLACEMENT_REGEX, ''); @@ -467,7 +464,7 @@ function setProp( if (__DEV__) { try { // This should always error. - URL.revokeObjectURL(URL.createObjectURL(value as any)); + URL.revokeObjectURL(URL.createObjectURL((value: any))); if (tag === 'source') { console.error( 'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' + @@ -528,9 +525,9 @@ function setProp( if (__DEV__) { checkAttributeStringCoercion(value, key); } - const sanitizedValue = sanitizeURL( - enableTrustedTypesIntegration ? value : '' + (value as any), - ) as any; + const sanitizedValue = (sanitizeURL( + enableTrustedTypesIntegration ? value : '' + (value: any), + ): any); domElement.setAttribute(key, sanitizedValue); break; } @@ -599,9 +596,9 @@ function setProp( if (__DEV__) { checkAttributeStringCoercion(value, key); } - const sanitizedValue = sanitizeURL( - enableTrustedTypesIntegration ? value : '' + (value as any), - ) as any; + const sanitizedValue = (sanitizeURL( + enableTrustedTypesIntegration ? value : '' + (value: any), + ): any); domElement.setAttribute(key, sanitizedValue); break; } @@ -611,7 +608,7 @@ function setProp( if (__DEV__ && typeof value !== 'function') { warnForInvalidEventListener(key, value); } - trapClickOnNonInteractiveElement(domElement as any as HTMLElement); + trapClickOnNonInteractiveElement(((domElement: any): HTMLElement)); } return; } @@ -661,12 +658,12 @@ function setProp( // Note: `option.selected` is not updated if `select.multiple` is // disabled with `removeAttribute`. We have special logic for handling this. case 'multiple': { - (domElement as any).multiple = + (domElement: any).multiple = value && typeof value !== 'function' && typeof value !== 'symbol'; break; } case 'muted': { - (domElement as any).muted = + (domElement: any).muted = value && typeof value !== 'function' && typeof value !== 'symbol'; break; } @@ -702,9 +699,9 @@ function setProp( if (__DEV__) { checkAttributeStringCoercion(value, key); } - const sanitizedValue = sanitizeURL( - enableTrustedTypesIntegration ? value : '' + (value as any), - ) as any; + const sanitizedValue = (sanitizeURL( + enableTrustedTypesIntegration ? value : '' + (value: any), + ): any); domElement.setAttributeNS(xlinkNamespace, 'xlink:href', sanitizedValue); break; } @@ -732,7 +729,7 @@ function setProp( } domElement.setAttribute( key, - enableTrustedTypesIntegration ? (value as any) : '' + (value as any), + enableTrustedTypesIntegration ? (value: any) : '' + (value: any), ); } else { domElement.removeAttribute(key); @@ -803,7 +800,7 @@ function setProp( if (__DEV__) { checkAttributeStringCoercion(value, key); } - domElement.setAttribute(key, value as any); + domElement.setAttribute(key, (value: any)); } else { domElement.removeAttribute(key); } @@ -819,12 +816,12 @@ function setProp( typeof value !== 'function' && typeof value !== 'symbol' && !isNaN(value) && - (value as any) >= 1 + (value: any) >= 1 ) { if (__DEV__) { checkAttributeStringCoercion(value, key); } - domElement.setAttribute(key, value as any); + domElement.setAttribute(key, (value: any)); } else { domElement.removeAttribute(key); } @@ -842,7 +839,7 @@ function setProp( if (__DEV__) { checkAttributeStringCoercion(value, key); } - domElement.setAttribute(key, value as any); + domElement.setAttribute(key, (value: any)); } else { domElement.removeAttribute(key); } @@ -1058,7 +1055,7 @@ function setPropOnCustomElement( if (__DEV__ && typeof value !== 'function') { warnForInvalidEventListener(key, value); } - trapClickOnNonInteractiveElement(domElement as any as HTMLElement); + trapClickOnNonInteractiveElement(((domElement: any): HTMLElement)); } return; } @@ -1347,7 +1344,7 @@ export function setInitialProperties( switch (propKey) { case 'selected': { // TODO: Remove support for selected on option. - (domElement as any).selected = + (domElement: any).selected = propValue && typeof propValue !== 'function' && typeof propValue !== 'symbol'; @@ -1827,7 +1824,7 @@ export function updateProperties( switch (propKey) { case 'selected': { // TODO: Remove support for selected on option. - (domElement as any).selected = false; + (domElement: any).selected = false; break; } default: { @@ -1850,7 +1847,7 @@ export function updateProperties( trackHostMutation(); } // TODO: Remove support for selected on option. - (domElement as any).selected = + (domElement: any).selected = nextProp && typeof nextProp !== 'function' && typeof nextProp !== 'symbol'; @@ -2024,7 +2021,7 @@ function getStylesObjectFromElement(domElement: Element): { [styleName: string]: string, } { const serverValueInObjectForm: {[prop: string]: string} = {}; - const htmlElement: HTMLElement = domElement as any; + const htmlElement: HTMLElement = (domElement: any); const style = htmlElement.style; for (let i = 0; i < style.length; i++) { const styleName: string = style[i]; @@ -2077,7 +2074,7 @@ function diffHydratedStyles( // Trailing semi-colon means this was regenerated. normalizedServerValue[normalizedServerValue.length - 1] === ';' && // TODO: Should we just ignore any style if the style as been manipulated? - hasViewTransition(domElement as any) + hasViewTransition((domElement: any)) ) { // If this had a view transition we might have applied a view transition // name/class and removed it. If that happens, the style attribute gets @@ -2123,7 +2120,6 @@ function hydrateAttribute( if (__DEV__) { checkAttributeStringCoercion(value, propKey); } - // $FlowFixMe[invalid-compare] if (serverValue === '' + value) { return; } @@ -2188,7 +2184,6 @@ function hydrateOverloadedBooleanAttribute( case 'symbol': return; default: - // $FlowFixMe[invalid-compare] if (value === false) { return; } @@ -2203,7 +2198,6 @@ function hydrateOverloadedBooleanAttribute( case 'symbol': break; case 'boolean': - // $FlowFixMe[invalid-compare] if (value === true && serverValue === '') { return; } @@ -2212,7 +2206,6 @@ function hydrateOverloadedBooleanAttribute( if (__DEV__) { checkAttributeStringCoercion(value, propKey); } - // $FlowFixMe[invalid-compare] if (serverValue === '' + value) { return; } @@ -2253,7 +2246,7 @@ function hydrateBooleanishAttribute( if (__DEV__) { checkAttributeStringCoercion(value, attributeName); } - if (serverValue === '' + (value as any)) { + if (serverValue === '' + (value: any)) { return; } } @@ -2304,7 +2297,6 @@ function hydrateNumericAttribute( if (__DEV__) { checkAttributeStringCoercion(value, propKey); } - // $FlowFixMe[invalid-compare] if (serverValue === '' + value) { return; } @@ -2356,7 +2348,6 @@ function hydratePositiveNumericAttribute( if (__DEV__) { checkAttributeStringCoercion(value, propKey); } - // $FlowFixMe[invalid-compare] if (serverValue === '' + value) { return; } @@ -2542,7 +2533,7 @@ function diffHydratedCustomComponent( continue; default: { // This is a DEV-only path - const hostContextDev: HostContextDev = hostContext as any; + const hostContextDev: HostContextDev = (hostContext: any); const hostContextProd = hostContextDev.context; if ( hostContextProd === HostContextNamespaceNone && @@ -2665,26 +2656,26 @@ function diffHydratedGenericElement( continue; case 'multiple': { extraAttributes.delete(propKey); - const serverValue = (domElement as any).multiple; + const serverValue = (domElement: any).multiple; warnForPropDifference(propKey, serverValue, value, serverDifferences); continue; } case 'muted': { extraAttributes.delete(propKey); - const serverValue = (domElement as any).muted; + const serverValue = (domElement: any).muted; warnForPropDifference(propKey, serverValue, value, serverDifferences); continue; } case 'autoFocus': { extraAttributes.delete('autofocus'); - const serverValue = (domElement as any).autofocus; + const serverValue = (domElement: any).autofocus; warnForPropDifference(propKey, serverValue, value, serverDifferences); continue; } case 'data': if (tag !== 'object') { extraAttributes.delete(propKey); - const serverValue = (domElement as any).getAttribute('data'); + const serverValue = (domElement: any).getAttribute('data'); warnForPropDifference(propKey, serverValue, value, serverDifferences); continue; } @@ -2695,7 +2686,7 @@ function diffHydratedGenericElement( if (tag === 'img' || tag === 'video' || tag === 'audio') { try { // Test if this is a compatible object - URL.revokeObjectURL(URL.createObjectURL(value as any)); + URL.revokeObjectURL(URL.createObjectURL((value: any))); hydrateSrcObjectAttribute( domElement, value, @@ -2710,7 +2701,7 @@ function diffHydratedGenericElement( if (__DEV__) { try { // This should always error. - URL.revokeObjectURL(URL.createObjectURL(value as any)); + URL.revokeObjectURL(URL.createObjectURL((value: any))); if (tag === 'source') { console.error( 'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' + @@ -3076,7 +3067,7 @@ function diffHydratedGenericElement( let isMismatchDueToBadCasing = false; // This is a DEV-only path - const hostContextDev: HostContextDev = hostContext as any; + const hostContextDev: HostContextDev = (hostContext: any); const hostContextProd = hostContextDev.context; if ( @@ -3267,7 +3258,7 @@ export function hydrateProperties( if (props.onClick != null) { // TODO: This cast may not be sound for SVG, MathML or custom elements. - trapClickOnNonInteractiveElement(domElement as any as HTMLElement); + trapClickOnNonInteractiveElement(((domElement: any): HTMLElement)); } return true; diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js b/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js index 71b8630450f0..8438410e03a0 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js @@ -72,22 +72,22 @@ export function detachDeletedInstance(node: Instance): void { if (enableInternalInstanceMap) { internalInstanceMap.delete(node); internalPropsMap.delete(node); - delete (node as any)[internalEventHandlersKey]; - delete (node as any)[internalEventHandlerListenersKey]; - delete (node as any)[internalEventHandlesSetKey]; - delete (node as any)[internalRootNodeResourcesKey]; + delete (node: any)[internalEventHandlersKey]; + delete (node: any)[internalEventHandlerListenersKey]; + delete (node: any)[internalEventHandlesSetKey]; + delete (node: any)[internalRootNodeResourcesKey]; if (__DEV__) { - delete (node as any)[internalInstanceKey]; + delete (node: any)[internalInstanceKey]; } return; } // TODO: This function is only called on host components. I don't think all of // these fields are relevant. - delete (node as any)[internalInstanceKey]; - delete (node as any)[internalPropsKey]; - delete (node as any)[internalEventHandlersKey]; - delete (node as any)[internalEventHandlerListenersKey]; - delete (node as any)[internalEventHandlesSetKey]; + delete (node: any)[internalInstanceKey]; + delete (node: any)[internalPropsKey]; + delete (node: any)[internalEventHandlersKey]; + delete (node: any)[internalEventHandlerListenersKey]; + delete (node: any)[internalEventHandlesSetKey]; } export function precacheFiberNode( @@ -102,11 +102,11 @@ export function precacheFiberNode( if (enableInternalInstanceMap) { internalInstanceMap.set(node, hostInst); if (__DEV__) { - (node as any)[internalInstanceKey] = hostInst; + (node: any)[internalInstanceKey] = hostInst; } return; } - (node as any)[internalInstanceKey] = hostInst; + (node: any)[internalInstanceKey] = hostInst; } export function markContainerAsRoot(hostRoot: Fiber, node: Container): void { @@ -135,9 +135,9 @@ export function isContainerMarkedAsRoot(node: Container): boolean { export function getClosestInstanceFromNode(targetNode: Node): null | Fiber { let targetInst: void | Fiber; if (enableInternalInstanceMap) { - targetInst = internalInstanceMap.get(targetNode as any as InstanceUnion); + targetInst = internalInstanceMap.get(((targetNode: any): InstanceUnion)); } else { - targetInst = (targetNode as any)[internalInstanceKey]; + targetInst = (targetNode: any)[internalInstanceKey]; } if (targetInst) { // Don't return HostRoot, SuspenseComponent or ActivityComponent here. @@ -157,12 +157,12 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber { // If it's not a container, we check if it's an instance. if (enableInternalInstanceMap) { targetInst = - (parentNode as any)[internalContainerInstanceKey] || - internalInstanceMap.get(parentNode as any as InstanceUnion); + (parentNode: any)[internalContainerInstanceKey] || + internalInstanceMap.get(((parentNode: any): InstanceUnion)); } else { targetInst = - (parentNode as any)[internalContainerInstanceKey] || - (parentNode as any)[internalInstanceKey]; + (parentNode: any)[internalContainerInstanceKey] || + (parentNode: any)[internalInstanceKey]; } if (targetInst) { // Since this wasn't the direct target of the event, we might have @@ -229,12 +229,12 @@ export function getInstanceFromNode(node: Node): Fiber | null { let inst: void | null | Fiber; if (enableInternalInstanceMap) { inst = - internalInstanceMap.get(node as any as InstanceUnion) || - (node as any)[internalContainerInstanceKey]; + internalInstanceMap.get(((node: any): InstanceUnion)) || + (node: any)[internalContainerInstanceKey]; } else { inst = - (node as any)[internalInstanceKey] || - (node as any)[internalContainerInstanceKey]; + (node: any)[internalInstanceKey] || + (node: any)[internalContainerInstanceKey]; } if (inst) { const tag = inst.tag; @@ -288,7 +288,7 @@ export function getFiberCurrentPropsFromNode( if (enableInternalInstanceMap) { return internalPropsMap.get(node) || null; } - return (node as any)[internalPropsKey] || null; + return (node: any)[internalPropsKey] || null; } export function updateFiberProps(node: Instance, props: Props): void { @@ -296,15 +296,15 @@ export function updateFiberProps(node: Instance, props: Props): void { internalPropsMap.set(node, props); return; } - (node as any)[internalPropsKey] = props; + (node: any)[internalPropsKey] = props; } export function getEventListenerSet(node: EventTarget): Set<string> { - let elementListenerSet: Set<string> | void = (node as any)[ + let elementListenerSet: Set<string> | void = (node: any)[ internalEventHandlersKey ]; if (elementListenerSet === undefined) { - elementListenerSet = (node as any)[internalEventHandlersKey] = new Set(); + elementListenerSet = (node: any)[internalEventHandlersKey] = new Set(); } return elementListenerSet; } @@ -314,9 +314,9 @@ export function getFiberFromScopeInstance( ): null | Fiber { if (enableScopeAPI) { if (enableInternalInstanceMap) { - return internalInstanceMap.get(scope as any as InstanceUnion) || null; + return internalInstanceMap.get(((scope: any): InstanceUnion)) || null; } - return (scope as any)[internalInstanceKey] || null; + return (scope: any)[internalInstanceKey] || null; } return null; } @@ -325,22 +325,22 @@ export function setEventHandlerListeners( scope: EventTarget | ReactScopeInstance, listeners: Set<ReactDOMEventHandleListener>, ): void { - (scope as any)[internalEventHandlerListenersKey] = listeners; + (scope: any)[internalEventHandlerListenersKey] = listeners; } export function getEventHandlerListeners( scope: EventTarget | ReactScopeInstance, ): null | Set<ReactDOMEventHandleListener> { - return (scope as any)[internalEventHandlerListenersKey] || null; + return (scope: any)[internalEventHandlerListenersKey] || null; } export function addEventHandleToTarget( target: EventTarget | ReactScopeInstance, eventHandle: ReactDOMEventHandle, ): void { - let eventHandles = (target as any)[internalEventHandlesSetKey]; + let eventHandles = (target: any)[internalEventHandlesSetKey]; if (eventHandles === undefined) { - eventHandles = (target as any)[internalEventHandlesSetKey] = new Set(); + eventHandles = (target: any)[internalEventHandlesSetKey] = new Set(); } eventHandles.add(eventHandle); } @@ -349,7 +349,7 @@ export function doesTargetHaveEventHandle( target: EventTarget | ReactScopeInstance, eventHandle: ReactDOMEventHandle, ): boolean { - const eventHandles = (target as any)[internalEventHandlesSetKey]; + const eventHandles = (target: any)[internalEventHandlesSetKey]; if (eventHandles === undefined) { return false; } @@ -357,9 +357,9 @@ export function doesTargetHaveEventHandle( } export function getResourcesFromRoot(root: HoistableRoot): RootResources { - let resources = (root as any)[internalRootNodeResourcesKey]; + let resources = (root: any)[internalRootNodeResourcesKey]; if (!resources) { - resources = (root as any)[internalRootNodeResourcesKey] = { + resources = (root: any)[internalRootNodeResourcesKey] = { hoistableStyles: new Map(), hoistableScripts: new Map(), }; @@ -368,45 +368,45 @@ export function getResourcesFromRoot(root: HoistableRoot): RootResources { } export function isMarkedHoistable(node: Node): boolean { - return !!(node as any)[internalHoistableMarker]; + return !!(node: any)[internalHoistableMarker]; } export function markNodeAsHoistable(node: Node) { - (node as any)[internalHoistableMarker] = true; + (node: any)[internalHoistableMarker] = true; } export function getScrollEndTimer(node: EventTarget): ?TimeoutID { - return (node as any)[internalScrollTimer]; + return (node: any)[internalScrollTimer]; } export function setScrollEndTimer(node: EventTarget, timer: TimeoutID): void { - (node as any)[internalScrollTimer] = timer; + (node: any)[internalScrollTimer] = timer; } export function clearScrollEndTimer(node: EventTarget): void { - (node as any)[internalScrollTimer] = undefined; + (node: any)[internalScrollTimer] = undefined; } export function markNodeAsPendingLoad(node: Node): void { - (node as any)[internalLoadPendingKey] = true; + (node: any)[internalLoadPendingKey] = true; } export function clearPendingLoadOnNode(node: Node): void { - (node as any)[internalLoadPendingKey] = undefined; + (node: any)[internalLoadPendingKey] = undefined; } export function isNodePendingLoad(node: Node): boolean { - return (node as any)[internalLoadPendingKey] === true; + return (node: any)[internalLoadPendingKey] === true; } export function isOwnedInstance(node: Node): boolean { if (enableInternalInstanceMap) { return !!( - (node as any)[internalHoistableMarker] || - internalInstanceMap.has(node as any) + (node: any)[internalHoistableMarker] || + internalInstanceMap.has((node: any)) ); } return !!( - (node as any)[internalHoistableMarker] || (node as any)[internalInstanceKey] + (node: any)[internalHoistableMarker] || (node: any)[internalInstanceKey] ); } diff --git a/packages/react-dom-bindings/src/client/ReactDOMContainer.js b/packages/react-dom-bindings/src/client/ReactDOMContainer.js index bc73db8a00e3..df9bcfb44380 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMContainer.js +++ b/packages/react-dom-bindings/src/client/ReactDOMContainer.js @@ -24,6 +24,6 @@ export function isValidContainer(node: any): boolean { node.nodeType === DOCUMENT_FRAGMENT_NODE || (!disableCommentsAsDOMContainers && node.nodeType === COMMENT_NODE && - (node as any).nodeValue === ' react-mount-point-unstable ')) + (node: any).nodeValue === ' react-mount-point-unstable ')) ); } diff --git a/packages/react-dom-bindings/src/client/ReactDOMEventHandle.js b/packages/react-dom-bindings/src/client/ReactDOMEventHandle.js index abb66721c2d2..875a9c9697c3 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMEventHandle.js +++ b/packages/react-dom-bindings/src/client/ReactDOMEventHandle.js @@ -35,11 +35,11 @@ type EventHandleOptions = { }; function isValidEventTarget(target: EventTarget | ReactScopeInstance): boolean { - return typeof (target as Object).addEventListener === 'function'; + return typeof (target: Object).addEventListener === 'function'; } function isReactScope(target: EventTarget | ReactScopeInstance): boolean { - return typeof (target as Object).getChildContextValues === 'function'; + return typeof (target: Object).getChildContextValues === 'function'; } function createEventHandleListener( @@ -59,12 +59,12 @@ function registerReactDOMEvent( domEventName: DOMEventName, isCapturePhaseListener: boolean, ): void { - if ((target as any).nodeType === ELEMENT_NODE) { + if ((target: any).nodeType === ELEMENT_NODE) { // Do nothing. We already attached all root listeners. } else if (enableScopeAPI && isReactScope(target)) { // Do nothing. We already attached all root listeners. } else if (isValidEventTarget(target)) { - const eventTarget = target as any as EventTarget; + const eventTarget = ((target: any): EventTarget); // These are valid event targets, but they are also // non-managed React nodes. listenToNativeEventForNonManagedEventTarget( @@ -85,7 +85,7 @@ export function createEventHandle( options?: EventHandleOptions, ): ReactDOMEventHandle { if (enableCreateEventHandleAPI) { - const domEventName = type as any as DOMEventName; + const domEventName = ((type: any): DOMEventName); // We cannot support arbitrary native events with eager root listeners // because the eager strategy relies on knowing the whole list ahead of time. @@ -137,7 +137,7 @@ export function createEventHandle( } targetListeners.add(listener); return () => { - (targetListeners as any as Set<ReactDOMEventHandleListener>).delete( + ((targetListeners: any): Set<ReactDOMEventHandleListener>).delete( listener, ); }; @@ -145,5 +145,5 @@ export function createEventHandle( return eventHandle; } - return null as any; + return (null: any); } diff --git a/packages/react-dom-bindings/src/client/ReactDOMInput.js b/packages/react-dom-bindings/src/client/ReactDOMInput.js index d38ae2fe0ae2..b6e665e12883 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMInput.js +++ b/packages/react-dom-bindings/src/client/ReactDOMInput.js @@ -96,7 +96,7 @@ export function updateInput( type: ?string, name: ?string, ) { - const node: HTMLInputElement = element as any; + const node: HTMLInputElement = (element: any); // Temporarily disconnect the input from any radio buttons. // Changing the type or name as the same time as changing the checked value @@ -122,11 +122,10 @@ export function updateInput( if (type === 'number') { if ( // $FlowFixMe[incompatible-type] - // $FlowFixMe[invalid-compare] (value === 0 && node.value === '') || // We explicitly want to coerce to number here if possible. // eslint-disable-next-line - node.value != (value as any) + node.value != (value: any) ) { node.value = toString(getToStringValue(value)); } @@ -214,7 +213,7 @@ export function initInput( name: ?string, isHydrating: boolean, ) { - const node: HTMLInputElement = element as any; + const node: HTMLInputElement = (element: any); if ( type != null && @@ -235,7 +234,7 @@ export function initInput( // default value provided by the browser. See: #12872 if (isButton && (value === undefined || value === null)) { // We track the value just in case it changes type later on. - track(element as any); + track((element: any)); return; } @@ -342,7 +341,7 @@ export function initInput( } node.name = name; } - track(element as any); + track((element: any)); } export function hydrateInput( @@ -352,7 +351,7 @@ export function hydrateInput( checked: ?boolean, defaultChecked: ?boolean, ): void { - const node: HTMLInputElement = element as any; + const node: HTMLInputElement = (element: any); const defaultValueStr = defaultValue != null ? toString(getToStringValue(defaultValue)) : ''; @@ -370,7 +369,7 @@ export function hydrateInput( // Detach .checked from .defaultChecked but leave user input alone node.checked = node.checked; - const changed = trackHydrated(node as any, initialValue, initialChecked); + const changed = trackHydrated((node: any), initialValue, initialChecked); if (changed) { // If the current value is different, that suggests that the user // changed it before hydration. Queue a replay of the change event. @@ -382,7 +381,7 @@ export function hydrateInput( } export function restoreControlledInputState(element: Element, props: Object) { - const rootNode: HTMLInputElement = element as any; + const rootNode: HTMLInputElement = (element: any); updateInput( rootNode, props.value, @@ -398,7 +397,7 @@ export function restoreControlledInputState(element: Element, props: Object) { let queryRoot: Element = rootNode; while (queryRoot.parentNode) { - queryRoot = queryRoot.parentNode as any as Element; + queryRoot = ((queryRoot.parentNode: any): Element); } // If `rootNode.form` was non-null, then we could try `form.elements`, @@ -418,7 +417,7 @@ export function restoreControlledInputState(element: Element, props: Object) { ); for (let i = 0; i < group.length; i++) { - const otherNode = group[i] as any as HTMLInputElement; + const otherNode = ((group[i]: any): HTMLInputElement); if (otherNode === rootNode || otherNode.form !== rootNode.form) { continue; } @@ -453,7 +452,7 @@ export function restoreControlledInputState(element: Element, props: Object) { // If any updateInput() call set .checked to true, an input in this group // (often, `rootNode` itself) may have become unchecked for (let i = 0; i < group.length; i++) { - const otherNode = group[i] as any as HTMLInputElement; + const otherNode = ((group[i]: any): HTMLInputElement); if (otherNode.form !== rootNode.form) { continue; } diff --git a/packages/react-dom-bindings/src/client/ReactDOMSelect.js b/packages/react-dom-bindings/src/client/ReactDOMSelect.js index 6553016fcaee..00136aa8175b 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMSelect.js +++ b/packages/react-dom-bindings/src/client/ReactDOMSelect.js @@ -69,7 +69,7 @@ function updateOptions( const options: HTMLOptionsCollection = node.options; if (multiple) { - const selectedValues = propValue as Array<string>; + const selectedValues = (propValue: Array<string>); const selectedValue: {[string]: boolean} = {}; for (let i = 0; i < selectedValues.length; i++) { // Prefix to avoid chaos with special keys. @@ -149,7 +149,7 @@ export function initSelect( defaultValue: ?string, multiple: ?boolean, ) { - const node: HTMLSelectElement = element as any; + const node: HTMLSelectElement = (element: any); node.multiple = !!multiple; if (value != null) { updateOptions(node, !!multiple, value, false); @@ -164,7 +164,7 @@ export function hydrateSelect( defaultValue: ?string, multiple: ?boolean, ): void { - const node: HTMLSelectElement = element as any; + const node: HTMLSelectElement = (element: any); const options: HTMLOptionsCollection = node.options; const propValue: any = value != null ? value : defaultValue; @@ -172,7 +172,7 @@ export function hydrateSelect( let changed = false; if (multiple) { - const selectedValues = propValue as ?Array<string>; + const selectedValues = (propValue: ?Array<string>); const selectedValue: {[string]: boolean} = {}; if (selectedValues != null) { for (let i = 0; i < selectedValues.length; i++) { @@ -218,7 +218,7 @@ export function updateSelect( multiple: ?boolean, wasMultiple: ?boolean, ) { - const node: HTMLSelectElement = element as any; + const node: HTMLSelectElement = (element: any); if (value != null) { updateOptions(node, !!multiple, value, false); @@ -234,7 +234,7 @@ export function updateSelect( } export function restoreControlledSelectState(element: Element, props: Object) { - const node: HTMLSelectElement = element as any; + const node: HTMLSelectElement = (element: any); const value = props.value; if (value != null) { diff --git a/packages/react-dom-bindings/src/client/ReactDOMSrcObject.js b/packages/react-dom-bindings/src/client/ReactDOMSrcObject.js index f8c25ce5c358..160d634236d2 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMSrcObject.js +++ b/packages/react-dom-bindings/src/client/ReactDOMSrcObject.js @@ -10,7 +10,7 @@ export function setSrcObject(domElement: Element, tag: string, value: any) { // We optimistically create the URL regardless of object type. This lets us // support cross-realms and any type that the browser supports like new types. - const url = URL.createObjectURL(value as any); + const url = URL.createObjectURL((value: any)); const loadEvent = tag === 'img' ? 'load' : 'loadstart'; const cleanUp = () => { // Once the object has started loading, then it's already collected by the diff --git a/packages/react-dom-bindings/src/client/ReactDOMTextarea.js b/packages/react-dom-bindings/src/client/ReactDOMTextarea.js index be6b20806872..bc346b4bce40 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMTextarea.js +++ b/packages/react-dom-bindings/src/client/ReactDOMTextarea.js @@ -66,7 +66,7 @@ export function updateTextarea( value: ?string, defaultValue: ?string, ) { - const node: HTMLTextAreaElement = element as any; + const node: HTMLTextAreaElement = (element: any); if (value != null) { // Cast `value` to a string to ensure the value is set correctly. While // browsers typically do this as necessary, jsdom doesn't. @@ -96,7 +96,7 @@ export function initTextarea( defaultValue: ?string, children: ?string, ) { - const node: HTMLTextAreaElement = element as any; + const node: HTMLTextAreaElement = (element: any); let initialValue = value; @@ -128,7 +128,7 @@ export function initTextarea( } const stringValue = getToStringValue(initialValue); - node.defaultValue = stringValue as any; // This will be toString:ed. + node.defaultValue = (stringValue: any); // This will be toString:ed. // This is in postMount because we need access to the DOM node, which is not // available until after the component has mounted. @@ -138,15 +138,13 @@ export function initTextarea( // initial value. In IE10/IE11 there is a bug where the placeholder attribute // will populate textContent as well. // https://developer.microsoft.com/microsoft-edge/platform/issues/101525/ - // $FlowFixMe[invalid-compare] if (textContent === stringValue) { - // $FlowFixMe[invalid-compare] if (textContent !== '' && textContent !== null) { node.value = textContent; } } - track(element as any); + track((element: any)); } export function hydrateTextarea( @@ -154,7 +152,7 @@ export function hydrateTextarea( value: ?string, defaultValue: ?string, ): void { - const node: HTMLTextAreaElement = element as any; + const node: HTMLTextAreaElement = (element: any); let initialValue = value; if (initialValue == null) { if (defaultValue == null) { @@ -166,7 +164,7 @@ export function hydrateTextarea( // that any change event that fires will trigger onChange on the actual // current value. const stringValue = toString(getToStringValue(initialValue)); - const changed = trackHydrated(node as any, stringValue, false); + const changed = trackHydrated((node: any), stringValue, false); if (changed) { // If the current value is different, that suggests that the user // changed it before hydration. Queue a replay of the change event. diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index d4e4dfde408c..1ae2f663c72c 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -300,7 +300,7 @@ function getOwnerDocumentFromRootContainer( rootContainerElement: Element | Document | DocumentFragment, ): Document { return rootContainerElement.nodeType === DOCUMENT_NODE - ? (rootContainerElement as any) + ? (rootContainerElement: any) : rootContainerElement.ownerDocument; } @@ -314,7 +314,7 @@ export function getRootHostContext( case DOCUMENT_NODE: case DOCUMENT_FRAGMENT_NODE: { type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment'; - const root = (rootContainerInstance as any).documentElement; + const root = (rootContainerInstance: any).documentElement; if (root) { const namespaceURI = root.namespaceURI; context = namespaceURI @@ -398,7 +398,7 @@ export function getChildHostContext( type: string, ): HostContext { if (__DEV__) { - const parentHostContextDev = parentHostContext as any as HostContextDev; + const parentHostContextDev = ((parentHostContext: any): HostContextDev); const context = getChildHostContextProd(parentHostContextDev.context, type); const ancestorInfo = updatedAncestorInfoDev( parentHostContextDev.ancestorInfo, @@ -406,7 +406,7 @@ export function getChildHostContext( ); return {context, ancestorInfo}; } - const parentNamespace = parentHostContext as any as HostContextProd; + const parentNamespace = ((parentHostContext: any): HostContextProd); return getChildHostContextProd(parentNamespace, type); } @@ -432,7 +432,7 @@ export function beforeActiveInstanceBlur(internalInstanceHandle: Object): void { if (enableCreateEventHandleAPI) { ReactBrowserEventEmitterSetEnabled(true); dispatchBeforeDetachedBlur( - (selectionInformation as any).focusedElem, + (selectionInformation: any).focusedElem, internalInstanceHandle, ); ReactBrowserEventEmitterSetEnabled(false); @@ -442,7 +442,7 @@ export function beforeActiveInstanceBlur(internalInstanceHandle: Object): void { export function afterActiveInstanceBlur(): void { if (enableCreateEventHandleAPI) { ReactBrowserEventEmitterSetEnabled(true); - dispatchAfterDetachedBlur((selectionInformation as any).focusedElem); + dispatchAfterDetachedBlur((selectionInformation: any).focusedElem); ReactBrowserEventEmitterSetEnabled(false); } } @@ -534,11 +534,11 @@ export function createInstance( let hostContextProd: HostContextProd; if (__DEV__) { // TODO: take namespace into account when validating. - const hostContextDev: HostContextDev = hostContext as any; + const hostContextDev: HostContextDev = (hostContext: any); validateDOMNesting(type, hostContextDev.ancestorInfo); hostContextProd = hostContextDev.context; } else { - hostContextProd = hostContext as any; + hostContextProd = (hostContext: any); } const ownerDocument = getOwnerDocumentFromRootContainer( @@ -586,7 +586,7 @@ export function createInstance( } div.innerHTML = '<script><' + '/script>'; // This is guaranteed to yield a script element. - const firstChild = div.firstChild as any as HTMLScriptElement; + const firstChild = ((div.firstChild: any): HTMLScriptElement); domElement = div.removeChild(firstChild); break; } @@ -754,7 +754,7 @@ export function createTextInstance( internalInstanceHandle: Object, ): TextInstance { if (__DEV__) { - const hostContextDev = hostContext as any as HostContextDev; + const hostContextDev = ((hostContext: any): HostContextDev); const ancestor = hostContextDev.ancestorInfo.current; if (ancestor != null) { validateTextNesting( @@ -823,9 +823,9 @@ export const warnsIfNotActing = true; // if a component just imports ReactDOM (e.g. for findDOMNode). // Some environments might not have setTimeout or clearTimeout. export const scheduleTimeout: any = - typeof setTimeout === 'function' ? setTimeout : (undefined as any); + typeof setTimeout === 'function' ? setTimeout : (undefined: any); export const cancelTimeout: any = - typeof clearTimeout === 'function' ? clearTimeout : (undefined as any); + typeof clearTimeout === 'function' ? clearTimeout : (undefined: any); export const noTimeout: -1 = -1; const localPromise = typeof Promise === 'function' ? Promise : undefined; const localRequestAnimationFrame = @@ -899,13 +899,11 @@ export function commitMount( case 'select': case 'textarea': if (newProps.autoFocus) { - ( - domElement as any as - | HTMLButtonElement - | HTMLInputElement - | HTMLSelectElement - | HTMLTextAreaElement - ).focus(); + ((domElement: any): + | HTMLButtonElement + | HTMLInputElement + | HTMLSelectElement + | HTMLTextAreaElement).focus(); } return; case 'img': { @@ -919,7 +917,7 @@ export function commitMount( // is already a noop regardless of which properties are assigned. We should revisit if browsers update // this heuristic in the future. if (newProps.src) { - const src = (newProps as any).src; + const src = (newProps: any).src; if (enableSrcObject && typeof src === 'object') { // For object src, we can't just set the src again to the same blob URL because it might have // already revoked if it loaded before this. However, we can create a new blob URL and set that. @@ -935,11 +933,9 @@ export function commitMount( // path. } } - (domElement as any as HTMLImageElement).src = src; + ((domElement: any): HTMLImageElement).src = src; } else if (newProps.srcSet) { - (domElement as any as HTMLImageElement).srcset = ( - newProps as any - ).srcSet; + ((domElement: any): HTMLImageElement).srcset = (newProps: any).srcSet; } return; } @@ -1036,7 +1032,7 @@ export function appendChild( function warnForReactChildrenConflict(container: Container): void { if (__DEV__) { - if ((container as any).__reactWarnedAboutChildrenConflict) { + if ((container: any).__reactWarnedAboutChildrenConflict) { return; } const props = getFiberCurrentPropsFromNode(container); @@ -1047,7 +1043,7 @@ function warnForReactChildrenConflict(container: Container): void { typeof props.children === 'string' || typeof props.children === 'number' ) { - (container as any).__reactWarnedAboutChildrenConflict = true; + (container: any).__reactWarnedAboutChildrenConflict = true; // Run the warning with the Fiber of the container for context of where the children are specified. // We could also maybe use the Portal. The current execution context is the child being added. runWithFiberInDEV(fiber, () => { @@ -1058,7 +1054,7 @@ function warnForReactChildrenConflict(container: Container): void { ); }); } else if (props.dangerouslySetInnerHTML != null) { - (container as any).__reactWarnedAboutChildrenConflict = true; + (container: any).__reactWarnedAboutChildrenConflict = true; runWithFiberInDEV(fiber, () => { console.error( 'Cannot use a ref on a React element as a container to `createRoot` or `createPortal` ' + @@ -1081,12 +1077,12 @@ export function appendChildToContainer( } let parentNode: DocumentFragment | Element; if (container.nodeType === DOCUMENT_NODE) { - parentNode = (container as any).body; + parentNode = (container: any).body; } else if ( !disableCommentsAsDOMContainers && container.nodeType === COMMENT_NODE ) { - parentNode = container.parentNode as any; + parentNode = (container.parentNode: any); if (supportsMoveBefore && child.parentNode !== null) { // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore. parentNode.moveBefore(child, container); @@ -1095,9 +1091,9 @@ export function appendChildToContainer( } return; } else if (container.nodeName === 'HTML') { - parentNode = container.ownerDocument.body as any; + parentNode = (container.ownerDocument.body: any); } else { - parentNode = container as any; + parentNode = (container: any); } if (supportsMoveBefore && child.parentNode !== null) { // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore. @@ -1116,12 +1112,11 @@ export function appendChildToContainer( // https://github.com/facebook/react/issues/11918 const reactRootContainer = container._reactRootContainer; if ( - // $FlowFixMe[invalid-compare] (reactRootContainer === null || reactRootContainer === undefined) && parentNode.onclick === null ) { // TODO: This cast may not be sound for SVG, MathML or custom elements. - trapClickOnNonInteractiveElement(parentNode as any as HTMLElement); + trapClickOnNonInteractiveElement(((parentNode: any): HTMLElement)); } } @@ -1148,16 +1143,16 @@ export function insertInContainerBefore( } let parentNode: DocumentFragment | Element; if (container.nodeType === DOCUMENT_NODE) { - parentNode = (container as any).body; + parentNode = (container: any).body; } else if ( !disableCommentsAsDOMContainers && container.nodeType === COMMENT_NODE ) { - parentNode = container.parentNode as any; + parentNode = (container.parentNode: any); } else if (container.nodeName === 'HTML') { - parentNode = container.ownerDocument.body as any; + parentNode = (container.ownerDocument.body: any); } else { - parentNode = container as any; + parentNode = (container: any); } if (supportsMoveBefore && child.parentNode !== null) { // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore. @@ -1173,7 +1168,7 @@ export function isSingletonScope(type: string): boolean { function createEvent(type: DOMEventName, bubbles: boolean): Event { const event = document.createEvent('Event'); - event.initEvent(type as any as string, bubbles, false); + event.initEvent(((type: any): string), bubbles, false); return event; } @@ -1197,7 +1192,7 @@ function dispatchAfterDetachedBlur(target: HTMLElement): void { const event = createEvent('afterblur', false); // So we know what was detached, make the relatedTarget the // detached target on the "afterblur" event. - (event as any).relatedTarget = target; + (event: any).relatedTarget = target; // Dispatch the event on the document. document.dispatchEvent(event); } @@ -1216,16 +1211,16 @@ export function removeChildFromContainer( ): void { let parentNode: DocumentFragment | Element; if (container.nodeType === DOCUMENT_NODE) { - parentNode = (container as any).body; + parentNode = (container: any).body; } else if ( !disableCommentsAsDOMContainers && container.nodeType === COMMENT_NODE ) { - parentNode = container.parentNode as any; + parentNode = (container.parentNode: any); } else if (container.nodeName === 'HTML') { - parentNode = container.ownerDocument.body as any; + parentNode = (container.ownerDocument.body: any); } else { - parentNode = container as any; + parentNode = (container: any); } parentNode.removeChild(child); } @@ -1243,7 +1238,7 @@ function clearHydrationBoundary( const nextNode = node.nextSibling; parentInstance.removeChild(node); if (nextNode && nextNode.nodeType === COMMENT_NODE) { - const data = (nextNode as any).data as string; + const data = ((nextNode: any).data: string); if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) { if (depth === 0) { parentInstance.removeChild(nextNode); @@ -1265,18 +1260,18 @@ function clearHydrationBoundary( // If a preamble contribution marker is found within the bounds of this boundary, // then it contributed to the html tag and we need to reset it. const ownerDocument = parentInstance.ownerDocument; - const documentElement: Element = ownerDocument.documentElement as any; + const documentElement: Element = (ownerDocument.documentElement: any); releaseSingletonInstance(documentElement); } else if (data === PREAMBLE_CONTRIBUTION_HEAD) { const ownerDocument = parentInstance.ownerDocument; - const head: Element = ownerDocument.head as any; + const head: Element = (ownerDocument.head: any); releaseSingletonInstance(head); // We need to clear the head because this is the only singleton that can have children that // were part of this boundary but are not inside this boundary. clearHead(head); } else if (data === PREAMBLE_CONTRIBUTION_BODY) { const ownerDocument = parentInstance.ownerDocument; - const body: Element = ownerDocument.body as any; + const body: Element = (ownerDocument.body: any); releaseSingletonInstance(body); } } @@ -1308,16 +1303,16 @@ function clearHydrationBoundaryFromContainer( ): void { let parentNode: DocumentFragment | Element; if (container.nodeType === DOCUMENT_NODE) { - parentNode = (container as any).body; + parentNode = (container: any).body; } else if ( !disableCommentsAsDOMContainers && container.nodeType === COMMENT_NODE ) { - parentNode = container.parentNode as any; + parentNode = (container.parentNode: any); } else if (container.nodeName === 'HTML') { - parentNode = container.ownerDocument.body as any; + parentNode = (container.ownerDocument.body: any); } else { - parentNode = container as any; + parentNode = (container: any); } clearHydrationBoundary(parentNode, hydrationInstance); // Retry if any event replaying was blocked on this. @@ -1348,7 +1343,7 @@ function hideOrUnhideDehydratedBoundary( do { const nextNode = node.nextSibling; if (node.nodeType === ELEMENT_NODE) { - const instance = node as any as HTMLElement & {_stashedDisplay?: string}; + const instance = ((node: any): HTMLElement & {_stashedDisplay?: string}); if (isHidden) { instance._stashedDisplay = instance.style.display; instance.style.display = 'none'; @@ -1359,7 +1354,7 @@ function hideOrUnhideDehydratedBoundary( } } } else if (node.nodeType === TEXT_NODE) { - const textNode = node as any as Text & {_stashedText?: string}; + const textNode = ((node: any): Text & {_stashedText?: string}); if (isHidden) { textNode._stashedText = textNode.nodeValue; textNode.nodeValue = ''; @@ -1368,7 +1363,7 @@ function hideOrUnhideDehydratedBoundary( } } if (nextNode && nextNode.nodeType === COMMENT_NODE) { - const data = (nextNode as any).data as string; + const data = ((nextNode: any).data: string); if (data === SUSPENSE_END_DATA) { if (depth === 0) { return; @@ -1399,7 +1394,7 @@ export function hideDehydratedBoundary( export function hideInstance(instance: Instance): void { // TODO: Does this work for all element types? What about MathML? Should we // pass host context to this method? - instance = instance as any as HTMLElement; + instance = ((instance: any): HTMLElement); const style = instance.style; // $FlowFixMe[method-unbinding] if (typeof style.setProperty === 'function') { @@ -1420,11 +1415,10 @@ export function unhideDehydratedBoundary( } export function unhideInstance(instance: Instance, props: Props): void { - instance = instance as any as HTMLElement; + instance = ((instance: any): HTMLElement); const styleProp = props[STYLE]; const display = styleProp !== undefined && - // $FlowFixMe[invalid-compare] styleProp !== null && styleProp.hasOwnProperty('display') ? styleProp.display @@ -1451,7 +1445,7 @@ function warnForBlockInsideInline(instance: HTMLElement) { let node: Node = nextNode; if ( node.nodeType === ELEMENT_NODE && - getComputedStyle(node as any).display === 'block' + getComputedStyle((node: any)).display === 'block' ) { const fiber = getInstanceFromNode(node) || getInstanceFromNode(instance); @@ -1469,7 +1463,7 @@ function warnForBlockInsideInline(instance: HTMLElement) { ); }, instance.tagName, - (node as any).tagName, + (node: any).tagName, ); break; } @@ -1511,7 +1505,7 @@ export function applyViewTransitionName( name: string, className: ?string, ): void { - instance = instance as any as HTMLElement; + instance = ((instance: any): HTMLElement); // If the name isn't valid CSS identifier, base64 encode the name instead. // This doesn't let you select it in custom CSS selectors but it does work in current // browsers. @@ -1531,7 +1525,7 @@ export function applyViewTransitionName( // https://bugs.webkit.org/show_bug.cgi?id=290923 const rects = instance.getClientRects(); if ( - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] countClientRects(rects) === 1 ) { // If the instance has a single client rect, that means that it can be @@ -1560,7 +1554,7 @@ export function restoreViewTransitionName( instance: Instance, props: Props, ): void { - instance = instance as any as HTMLElement; + instance = ((instance: any): HTMLElement); const style = instance.style; const styleProp = props[STYLE]; const viewTransitionName = @@ -1638,7 +1632,7 @@ export function cancelViewTransitionName( if (documentElement !== null) { documentElement.animate( {opacity: [0, 0], pointerEvents: ['none', 'none']}, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] { duration: 0, fill: 'forwards', @@ -1651,7 +1645,7 @@ export function cancelViewTransitionName( export function cancelRootViewTransitionName(rootContainer: Container): void { const documentElement: null | HTMLElement = rootContainer.nodeType === DOCUMENT_NODE - ? (rootContainer as any).documentElement + ? (rootContainer: any).documentElement : rootContainer.ownerDocument.documentElement; if ( @@ -1675,7 +1669,7 @@ export function cancelRootViewTransitionName(rootContainer: Container): void { documentElement.style.viewTransitionName = 'none'; documentElement.animate( {opacity: [0, 0], pointerEvents: ['none', 'none']}, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] { duration: 0, fill: 'forwards', @@ -1692,7 +1686,6 @@ export function cancelRootViewTransitionName(rootContainer: Container): void { documentElement.animate( {width: [0, 0], height: [0, 0]}, // $FlowFixMe[incompatible-call] - // $FlowFixMe[incompatible-type] { duration: 0, fill: 'forwards', @@ -1705,13 +1698,13 @@ export function cancelRootViewTransitionName(rootContainer: Container): void { export function restoreRootViewTransitionName(rootContainer: Container): void { let containerInstance: Instance; if (rootContainer.nodeType === DOCUMENT_NODE) { - containerInstance = (rootContainer as any).body; + containerInstance = (rootContainer: any).body; } else if (rootContainer.nodeName === 'HTML') { - containerInstance = rootContainer.ownerDocument.body as any; + containerInstance = (rootContainer.ownerDocument.body: any); } else { // If the container is not the whole document, then we ideally should probably // clone the whole document outside of the React too. - containerInstance = rootContainer as any; + containerInstance = (rootContainer: any); } if ( !disableCommentsAsDOMContainers && @@ -1789,11 +1782,11 @@ function moveOutOfViewport( // while still letting it paint its "old" state to a snapshot. const transform = getComputedTransform(originalStyle); // Clear the long form properties. - // $FlowFixMe[prop-missing] + // $FlowFixMe element.style.translate = 'none'; - // $FlowFixMe[prop-missing] + // $FlowFixMe element.style.scale = 'none'; - // $FlowFixMe[prop-missing] + // $FlowFixMe element.style.rotate = 'none'; // Apply a translate to move it way out of the viewport. This is applied first // so that it is in the coordinate space of the parent and not after applying @@ -1820,7 +1813,7 @@ export function cloneRootViewTransitionContainer( // the clone so we first clear the name of the root container. const documentElement: null | HTMLElement = rootContainer.nodeType === DOCUMENT_NODE - ? (rootContainer as any).documentElement + ? (rootContainer: any).documentElement : rootContainer.ownerDocument.documentElement; if ( documentElement !== null && @@ -1833,9 +1826,9 @@ export function cloneRootViewTransitionContainer( let containerInstance: HTMLElement; if (rootContainer.nodeType === DOCUMENT_NODE) { - containerInstance = (rootContainer as any).body; + containerInstance = (rootContainer: any).body; } else if (rootContainer.nodeName === 'HTML') { - containerInstance = rootContainer.ownerDocument.body as any; + containerInstance = (rootContainer.ownerDocument.body: any); } else if ( !disableCommentsAsDOMContainers && rootContainer.nodeType === COMMENT_NODE @@ -1846,7 +1839,7 @@ export function cloneRootViewTransitionContainer( } else { // If the container is not the whole document, then we ideally should probably // clone the whole document outside of the React too. - containerInstance = rootContainer as any; + containerInstance = (rootContainer: any); } const containerParent = containerInstance.parentNode; @@ -1876,7 +1869,7 @@ export function cloneRootViewTransitionContainer( if (getComputedStyle(positionedAncestor).position !== 'static') { break; } - // $FlowFixMe[incompatible-type]: This is refined. + // $FlowFixMe: This is refined. positionedAncestor = positionedAncestor.parentNode; } @@ -1949,13 +1942,13 @@ export function removeRootViewTransitionClone( ): void { let containerInstance: Instance; if (rootContainer.nodeType === DOCUMENT_NODE) { - containerInstance = (rootContainer as any).body; + containerInstance = (rootContainer: any).body; } else if (rootContainer.nodeName === 'HTML') { - containerInstance = rootContainer.ownerDocument.body as any; + containerInstance = (rootContainer.ownerDocument.body: any); } else { // If the container is not the whole document, then we ideally should probably // clone the whole document outside of the React too. - containerInstance = rootContainer as any; + containerInstance = (rootContainer: any); } const containerParent = containerInstance.parentNode; if (containerParent === null) { @@ -2147,7 +2140,7 @@ function customizeViewTransitionError( /** @noinline */ function forceLayout(ownerDocument: Document) { // This function exists to trick minifiers to not remove this unused member expression. - return (ownerDocument.documentElement as any).clientHeight; + return (ownerDocument.documentElement: any).clientHeight; } function waitForImageToLoad(this: HTMLImageElement, resolve: () => void) { @@ -2172,7 +2165,7 @@ export function startViewTransition( ): null | RunningViewTransition { const ownerDocument: Document = rootContainer.nodeType === DOCUMENT_NODE - ? (rootContainer as any) + ? (rootContainer: any) : rootContainer.ownerDocument; try { // $FlowFixMe[prop-missing] @@ -2271,15 +2264,14 @@ export function startViewTransition( const viewTransitionAnimations: Array<Animation> = []; const readyCallback = () => { - const documentElement: Element = ownerDocument.documentElement as any; + const documentElement: Element = (ownerDocument.documentElement: any); // Loop through all View Transition Animations. // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-type] const animations = documentElement.getAnimations({subtree: true}); for (let i = 0; i < animations.length; i++) { const animation = animations[i]; - const effect: KeyframeEffect = animation.effect as any; - // $FlowFixMe[prop-missing] + const effect: KeyframeEffect = (animation.effect: any); + // $FlowFixMe const pseudoElement: ?string = effect.pseudoElement; if ( pseudoElement != null && @@ -2321,13 +2313,13 @@ export function startViewTransition( height !== undefined ) { // Replace the keyframes with ones that don't animate the width/height. - // $FlowFixMe[incompatible-type] + // $FlowFixMe effect.setKeyframes(keyframes); // Read back the new animation to see what the underlying width/height of the pseudo-element was. const computedStyle = getComputedStyle( - // $FlowFixMe[incompatible-type] + // $FlowFixMe effect.target, - // $FlowFixMe[prop-missing] + // $FlowFixMe effect.pseudoElement, ); if ( @@ -2342,7 +2334,7 @@ export function startViewTransition( const last = keyframes[keyframes.length - 1]; last.width = width; last.height = height; - // $FlowFixMe[incompatible-type] + // $FlowFixMe effect.setKeyframes(keyframes); } } @@ -2503,9 +2495,10 @@ function animateGesture( if (keyframe.translate == null || keyframe.translate === '') { // TODO: If there's a CSS rule targeting translate on the pseudo element // already we need to merge it. - const elementTranslate: ?string = ( - getComputedStyle(targetElement, pseudoElement) as any - ).translate; + const elementTranslate: ?string = (getComputedStyle( + targetElement, + pseudoElement, + ): any).translate; keyframe.translate = mergeTranslate( elementTranslate, '20000px 20000px', @@ -2543,7 +2536,7 @@ function animateGesture( const reverse = rangeStart > rangeEnd; if (timeline instanceof AnimationTimeline) { // Native Timeline - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] const animation = targetElement.animate(keyframes, { pseudoElement: pseudoElement, // Set the timeline to the current gesture timeline to drive the updates. @@ -2565,7 +2558,7 @@ function animateGesture( viewTransitionAnimations.push(animation); } else { // Custom Timeline - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] const animation = targetElement.animate(keyframes, { pseudoElement: pseudoElement, // We reset all easing functions to linear so that it feels like you @@ -2604,7 +2597,7 @@ export function startGestureTransition( ): null | RunningViewTransition { const ownerDocument: Document = rootContainer.nodeType === DOCUMENT_NODE - ? (rootContainer as any) + ? (rootContainer: any) : rootContainer.ownerDocument; try { // Force layout before we start the Transition. This works around a bug in Safari @@ -2621,10 +2614,9 @@ export function startGestureTransition( const customTimelineCleanup: Array<() => void> = []; // Cleanup Animations started in a CustomTimeline const viewTransitionAnimations: Array<Animation> = []; const readyCallback = () => { - const documentElement: Element = ownerDocument.documentElement as any; + const documentElement: Element = (ownerDocument.documentElement: any); // Loop through all View Transition Animations. // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-type] const animations = documentElement.getAnimations({subtree: true}); // First do a pass to collect all known group and new items so we can look // up if they exist later. @@ -2633,8 +2625,8 @@ export function startGestureTransition( // Collect the longest duration of any view-transition animation including delay. let longestDuration = 0; for (let i = 0; i < animations.length; i++) { - const effect: KeyframeEffect = animations[i].effect as any; - // $FlowFixMe[prop-missing] + const effect: KeyframeEffect = (animations[i].effect: any); + // $FlowFixMe const pseudoElement: ?string = effect.pseudoElement; if (pseudoElement == null) { } else if ( @@ -2668,8 +2660,8 @@ export function startGestureTransition( if (anim.playState !== 'running') { continue; } - const effect: KeyframeEffect = anim.effect as any; - // $FlowFixMe[prop-missing] + const effect: KeyframeEffect = (anim.effect: any); + // $FlowFixMe const pseudoElement: ?string = effect.pseudoElement; if ( pseudoElement != null && @@ -2729,7 +2721,7 @@ export function startGestureTransition( } animateGesture( effect.getKeyframes(), - // $FlowFixMe[incompatible-type]: Always documentElement atm. + // $FlowFixMe: Always documentElement atm. effect.target, pseudoElement, timeline, @@ -2757,7 +2749,7 @@ export function startGestureTransition( const pseudoElementName = '::view-transition-group' + groupName; animateGesture( [{}, {}], - // $FlowFixMe[incompatible-type]: Always documentElement atm. + // $FlowFixMe: Always documentElement atm. effect.target, pseudoElementName, timeline, @@ -2778,7 +2770,6 @@ export function startGestureTransition( // that never stops. This seems to keep all running Animations alive until // we explicitly abort (or something forces the View Transition to cancel). // $FlowFixMe[incompatible-call] - // $FlowFixMe[incompatible-type] const blockingAnim = documentElement.animate([{}, {}], { pseudoElement: '::view-transition', duration: 1, @@ -2885,7 +2876,7 @@ function ViewTransitionPseudoElement( name: string, ) { // TODO: Get the owner document from the root container. - this._scope = document.documentElement as any; + this._scope = (document.documentElement: any); this._selector = '::view-transition-' + pseudo + '(' + name + ')'; } // $FlowFixMe[prop-missing] @@ -2900,9 +2891,8 @@ ViewTransitionPseudoElement.prototype.animate = function ( duration: options, } : Object.assign( - // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-type] - {} as KeyframeAnimationOptions, + (// $FlowFixMe[prop-missing] + {}: KeyframeAnimationOptions), options, ); opts.pseudoElement = this._selector; @@ -2918,7 +2908,6 @@ ViewTransitionPseudoElement.prototype.getAnimations = function ( const selector = this._selector; const animations = scope.getAnimations( // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-type] {subtree: true}, ); const result = []; @@ -2927,7 +2916,7 @@ ViewTransitionPseudoElement.prototype.getAnimations = function ( target?: Element, pseudoElement?: string, ... - } = animations[i].effect as any; + } = (animations[i].effect: any); // TODO: Handle multiple child instances. if ( effect !== null && @@ -2953,10 +2942,10 @@ export function createViewTransitionInstance( ): ViewTransitionInstance { return { name: name, - group: new (ViewTransitionPseudoElement as any)('group', name), - imagePair: new (ViewTransitionPseudoElement as any)('image-pair', name), - old: new (ViewTransitionPseudoElement as any)('old', name), - new: new (ViewTransitionPseudoElement as any)('new', name), + group: new (ViewTransitionPseudoElement: any)('group', name), + imagePair: new (ViewTransitionPseudoElement: any)('image-pair', name), + old: new (ViewTransitionPseudoElement: any)('old', name), + new: new (ViewTransitionPseudoElement: any)('new', name), }; } @@ -3363,8 +3352,8 @@ FragmentInstance.prototype.getRootNode = function ( const parentHostInstance = getInstanceFromHostFiber<Instance>(parentHostFiber); const rootNode = - // $FlowFixMe[incompatible-type] Flow expects Node - parentHostInstance.getRootNode(getRootNodeOptions) as Document | ShadowRoot; + // $FlowFixMe[incompatible-cast] Flow expects Node + (parentHostInstance.getRootNode(getRootNodeOptions): Document | ShadowRoot); return rootNode; }; // $FlowFixMe[prop-missing] @@ -3399,7 +3388,7 @@ FragmentInstance.prototype.compareDocumentPosition = function ( // our best guess is to use the parent of the child instance, rather than // the fiber tree host parent. const parentHostInstanceFromDOM = fiberIsPortaledIntoHost(this._fragmentFiber) - ? (firstNode.parentElement as ?Instance) + ? (firstNode.parentElement: ?Instance) : parentHostInstance; if (parentHostInstanceFromDOM == null) { @@ -3487,7 +3476,6 @@ function validateDocumentPositionWithFiberTree( if (otherFiber === null) { // otherFiber could be null if its the document or body element const ownerDocument = otherNode.ownerDocument; - // $FlowFixMe[invalid-compare] return otherNode === ownerDocument || otherNode === ownerDocument.body; } return isFragmentContainedByFiber(fragmentFiber, otherFiber); @@ -3603,7 +3591,7 @@ function addFragmentHandleToInstance( export function createFragmentInstance( fragmentFiber: Fiber, ): FragmentInstanceType { - const fragmentInstance = new (FragmentInstance as any)(fragmentFiber); + const fragmentInstance = new (FragmentInstance: any)(fragmentFiber); if (enableFragmentRefsInstanceHandles) { traverseFragmentInstance( fragmentFiber, @@ -3628,7 +3616,7 @@ export function commitNewChildToFragmentInstance( if (childInstance.nodeType === TEXT_NODE) { return; } - const instance: InstanceWithFragmentHandles = childInstance as any; + const instance: InstanceWithFragmentHandles = (childInstance: any); const eventListeners = fragmentInstance._eventListeners; if (eventListeners !== null) { for (let i = 0; i < eventListeners.length; i++) { @@ -3653,7 +3641,7 @@ export function deleteChildFromFragmentInstance( if (childInstance.nodeType === TEXT_NODE) { return; } - const instance: InstanceWithFragmentHandles = childInstance as any; + const instance: InstanceWithFragmentHandles = (childInstance: any); const eventListeners = fragmentInstance._eventListeners; if (eventListeners !== null) { for (let i = 0; i < eventListeners.length; i++) { @@ -3699,7 +3687,7 @@ function clearContainerSparingly(container: Node) { case 'HTML': case 'HEAD': case 'BODY': { - const element: Element = node as any; + const element: Element = (node: any); clearContainerSparingly(element); // If these singleton instances had previously been rendered with React they // may still hold on to references to the previous fiber tree. We detatch them @@ -3726,9 +3714,7 @@ function clearContainerSparingly(container: Node) { } // Stylesheet tags are retained because they may likely come from 3rd party scripts and extensions case 'LINK': { - if ( - (node as any as HTMLLinkElement).rel.toLowerCase() === 'stylesheet' - ) { + if (((node: any): HTMLLinkElement).rel.toLowerCase() === 'stylesheet') { continue; } } @@ -3748,7 +3734,7 @@ function clearHead(head: Element): void { nodeName === 'SCRIPT' || nodeName === 'STYLE' || (nodeName === 'LINK' && - (node as any as HTMLLinkElement).rel.toLowerCase() === 'stylesheet') + ((node: any): HTMLLinkElement).rel.toLowerCase() === 'stylesheet') ) { // retain these nodes } else { @@ -3768,7 +3754,7 @@ export function bindInstance( props: Props, internalInstanceHandle: mixed, ) { - precacheFiberNode(internalInstanceHandle as any, instance); + precacheFiberNode((internalInstanceHandle: any), instance); updateFiberProps(instance, props); } @@ -3785,15 +3771,12 @@ export function canHydrateInstance( inRootOrSingleton: boolean, ): null | Instance { while (instance.nodeType === ELEMENT_NODE) { - const element: Element = instance as any; - const anyProps = props as any; + const element: Element = (instance: any); + const anyProps = (props: any); if (element.nodeName.toLowerCase() !== type.toLowerCase()) { if (!inRootOrSingleton) { // Usually we error for mismatched tags. - if ( - element.nodeName === 'INPUT' && - (element as any).type === 'hidden' - ) { + if (element.nodeName === 'INPUT' && (element: any).type === 'hidden') { // If we have extra hidden inputs, we don't mismatch. This allows us to embed // extra form data in the original form. } else { @@ -3803,7 +3786,7 @@ export function canHydrateInstance( // In root or singleton parents we skip past mismatched instances. } else if (!inRootOrSingleton) { // Match - if (type === 'input' && (element as any).type === 'hidden') { + if (type === 'input' && (element: any).type === 'hidden') { if (__DEV__) { checkAttributeStringCoercion(anyProps.name, 'name'); } @@ -3939,7 +3922,7 @@ export function canHydrateTextInstance( if ( instance.nodeType === ELEMENT_NODE && instance.nodeName === 'INPUT' && - (instance as any).type === 'hidden' + (instance: any).type === 'hidden' ) { // If we have extra hidden inputs, we don't mismatch. This allows us to // embed extra form data in the original form. @@ -3953,7 +3936,7 @@ export function canHydrateTextInstance( instance = nextInstance; } // This has now been refined to a text node. - return instance as any as TextInstance; + return ((instance: any): TextInstance); } function canHydrateHydrationBoundary( @@ -3964,7 +3947,7 @@ function canHydrateHydrationBoundary( if ( instance.nodeType === ELEMENT_NODE && instance.nodeName === 'INPUT' && - (instance as any).type === 'hidden' + (instance: any).type === 'hidden' ) { // If we have extra hidden inputs, we don't mismatch. This allows us to // embed extra form data in the original form. @@ -3978,7 +3961,7 @@ function canHydrateHydrationBoundary( instance = nextInstance; } // This has now been refined to a hydration boundary node. - return instance as any; + return (instance: any); } export function canHydrateActivityInstance( @@ -3993,7 +3976,7 @@ export function canHydrateActivityInstance( hydratableInstance !== null && hydratableInstance.data === ACTIVITY_START_DATA ) { - return hydratableInstance as any; + return (hydratableInstance: any); } return null; } @@ -4010,7 +3993,7 @@ export function canHydrateSuspenseInstance( hydratableInstance !== null && hydratableInstance.data !== ACTIVITY_START_DATA ) { - return hydratableInstance as any; + return (hydratableInstance: any); } return null; } @@ -4041,8 +4024,7 @@ export function getSuspenseInstanceFallbackErrorDetails( componentStack?: string, } { const dataset = - instance.nextSibling && - (instance.nextSibling as any as HTMLElement).dataset; + instance.nextSibling && ((instance.nextSibling: any): HTMLElement).dataset; let digest, message, stack, componentStack; if (dataset) { digest = dataset.dgst; @@ -4114,12 +4096,12 @@ export function canHydrateFormStateMarker( } instance = nextInstance; } - const nodeData = (instance as any).data; + const nodeData = (instance: any).data; if ( nodeData === FORM_STATE_IS_MATCHING || nodeData === FORM_STATE_IS_NOT_MATCHING ) { - const markerInstance: FormStateMarkerInstance = instance as any; + const markerInstance: FormStateMarkerInstance = (instance: any); return markerInstance; } return null; @@ -4133,13 +4115,13 @@ export function isFormStateMarkerMatching( function getNextHydratable(node: ?Node) { // Skip non-hydratable nodes. - for (; node != null; node = (node as any as Node).nextSibling) { + for (; node != null; node = ((node: any): Node).nextSibling) { const nodeType = node.nodeType; if (nodeType === ELEMENT_NODE || nodeType === TEXT_NODE) { break; } if (nodeType === COMMENT_NODE) { - const data = (node as any).data; + const data = (node: any).data; if ( data === SUSPENSE_START_DATA || data === SUSPENSE_FALLBACK_START_DATA || @@ -4156,7 +4138,7 @@ function getNextHydratable(node: ?Node) { } } } - return node as any; + return (node: any); } export function getNextHydratableSibling( @@ -4177,13 +4159,13 @@ export function getFirstHydratableChildWithinContainer( let parentElement: Element; switch (parentContainer.nodeType) { case DOCUMENT_NODE: - parentElement = (parentContainer as any).body; + parentElement = (parentContainer: any).body; break; default: { if (parentContainer.nodeName === 'HTML') { - parentElement = (parentContainer as any).ownerDocument.body; + parentElement = (parentContainer: any).ownerDocument.body; } else { - parentElement = parentContainer as any; + parentElement = (parentContainer: any); } } } @@ -4244,7 +4226,7 @@ export function describeHydratableInstanceForDevWarnings( // Reverse engineer a set of props that can print for dev warnings return { type: instance.nodeName.toLowerCase(), - props: getPropsFromElement(instance as any), + props: getPropsFromElement((instance: any)), }; } else if (instance.nodeType === COMMENT_NODE) { if (instance.data === ACTIVITY_START_DATA) { @@ -4269,7 +4251,7 @@ export function validateHydratableInstance( ): boolean { if (__DEV__) { // TODO: take namespace into account when validating. - const hostContextDev: HostContextDev = hostContext as any; + const hostContextDev: HostContextDev = (hostContext: any); return validateDOMNesting(type, hostContextDev.ancestorInfo); } return true; @@ -4305,7 +4287,7 @@ export function validateHydratableTextInstance( hostContext: HostContext, ): boolean { if (__DEV__) { - const hostContextDev = hostContext as any as HostContextDev; + const hostContextDev = ((hostContext: any): HostContextDev); const ancestor = hostContextDev.ancestorInfo.current; if (ancestor != null) { return validateTextNesting( @@ -4368,10 +4350,10 @@ function getNextHydratableInstanceAfterHydrationBoundary( let depth = 0; while (node) { if (node.nodeType === COMMENT_NODE) { - const data = (node as any).data as string; + const data = ((node: any).data: string); if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) { if (depth === 0) { - return getNextHydratableSibling(node as any); + return getNextHydratableSibling((node: any)); } else { depth--; } @@ -4416,7 +4398,7 @@ export function getParentHydrationBoundary( let depth = 0; while (node) { if (node.nodeType === COMMENT_NODE) { - const data = (node as any).data as string; + const data = ((node: any).data: string); if ( data === SUSPENSE_START_DATA || data === SUSPENSE_FALLBACK_START_DATA || @@ -4425,7 +4407,7 @@ export function getParentHydrationBoundary( data === ACTIVITY_START_DATA ) { if (depth === 0) { - return node as any as SuspenseInstance | ActivityInstance; + return ((node: any): SuspenseInstance | ActivityInstance); } else { depth--; } @@ -4481,7 +4463,7 @@ export function findFiberRoot(node: Instance): null | FiberRoot { while (index < stack.length) { const current = stack[index++]; if (isContainerMarkedAsRoot(current)) { - return getInstanceFromNodeDOMTree(current) as any as FiberRoot; + return ((getInstanceFromNodeDOMTree(current): any): FiberRoot); } stack.push(...current.children); } @@ -4541,7 +4523,7 @@ export function setFocusIfFocusable( // // We could compare the node to document.activeElement after focus, // but this would not handle the case where application code managed focus to automatically blur. - const element = node as any as HTMLElement; + const element = ((node: any): HTMLElement); // If this element is already the active element, it's focusable and already // focused. Calling .focus() on it would be a no-op (no focus event fires), @@ -4611,7 +4593,7 @@ export function setupIntersectionObserver( const observer = new IntersectionObserver(handleIntersection, options); targets.forEach(target => { - observer.observe(target as any); + observer.observe((target: any)); }); return { @@ -4621,11 +4603,11 @@ export function setupIntersectionObserver( rect: getBoundingRect(target), ratio: 0, }); - observer.observe(target as any); + observer.observe((target: any)); }, unobserve: target => { rectRatioCache.delete(target); - observer.unobserve(target as any); + observer.unobserve((target: any)); }, }; } @@ -4654,7 +4636,7 @@ export function resolveSingletonInstance( validateDOMNestingDev: boolean, ): Instance { if (__DEV__) { - const hostContextDev = hostContext as any as HostContextDev; + const hostContextDev = ((hostContext: any): HostContextDev); if (validateDOMNestingDev) { validateDOMNesting(type, hostContextDev.ancestorInfo); } @@ -4846,18 +4828,14 @@ export type HoistableRoot = Document | ShadowRoot; // getRootNode is missing from IE and old jsdom versions export function getHoistableRoot(container: Container): HoistableRoot { // $FlowFixMe[method-unbinding] - if (typeof container.getRootNode === 'function') { - const rootNode = container.getRootNode(); - if (rootNode.nodeType === DOCUMENT_NODE) { - return rootNode as any as Document; - } - if (rootNode.nodeType === DOCUMENT_FRAGMENT_NODE) { - return rootNode as any as ShadowRoot; - } - } - return container.nodeType === DOCUMENT_NODE - ? (container as any as Document) - : container.ownerDocument; + return typeof container.getRootNode === 'function' + ? /* $FlowFixMe[incompatible-cast] Flow types this as returning a `Node`, + * but it's either a `Document` or `ShadowRoot`. */ + (container.getRootNode(): Document | ShadowRoot) + : container.nodeType === DOCUMENT_NODE + ? // $FlowFixMe[incompatible-cast] We've constrained this to be a Document which satisfies the return type + (container: Document) + : container.ownerDocument; } function getCurrentResourceRoot(): null | HoistableRoot { @@ -4948,7 +4926,7 @@ function preconnectAs( const instance = ownerDocument.createElement('link'); setInitialProperties(instance, 'link', preconnectProps); markNodeAsHoistable(instance); - (ownerDocument.head as any).appendChild(instance); + (ownerDocument.head: any).appendChild(instance); } } } @@ -5005,7 +4983,7 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { } if (!preloadPropsMap.has(key)) { const preloadProps = Object.assign( - { + ({ rel: 'preload', // There is a bug in Safari where imageSrcSet is not respected on preload links // so we omit the href here if we have imageSrcSet b/c safari will load the wrong image. @@ -5014,7 +4992,7 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { href: as === 'image' && options && options.imageSrcSet ? undefined : href, as, - } as PreloadProps, + }: PreloadProps), options, ); preloadPropsMap.set(key, preloadProps); @@ -5043,7 +5021,7 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { }; } markNodeAsHoistable(instance); - (ownerDocument.head as any).appendChild(instance); + (ownerDocument.head: any).appendChild(instance); } } } @@ -5076,10 +5054,10 @@ function preloadModule(href: string, options?: ?PreloadModuleImplOptions) { if (!preloadPropsMap.has(key)) { const props: PreloadModuleProps = Object.assign( - { + ({ rel: 'modulepreload', href, - } as PreloadModuleProps, + }: PreloadModuleProps), options, ); preloadPropsMap.set(key, props); @@ -5100,7 +5078,7 @@ function preloadModule(href: string, options?: ?PreloadModuleImplOptions) { const instance = ownerDocument.createElement('link'); setInitialProperties(instance, 'link', props); markNodeAsHoistable(instance); - (ownerDocument.head as any).appendChild(instance); + (ownerDocument.head: any).appendChild(instance); } } } @@ -5142,11 +5120,11 @@ function preinitStyle( } else { // Construct a new instance and insert it const stylesheetProps = Object.assign( - { + ({ rel: 'stylesheet', href, 'data-precedence': precedence, - } as StylesheetProps, + }: StylesheetProps), options, ); const preloadProps = preloadPropsMap.get(key); @@ -5157,7 +5135,7 @@ function preinitStyle( markNodeAsHoistable(link); setInitialProperties(link, 'link', stylesheetProps); - (link as any)._p = new Promise((resolve, reject) => { + (link: any)._p = new Promise((resolve, reject) => { link.onload = resolve; link.onerror = reject; }); @@ -5173,14 +5151,12 @@ function preinitStyle( } // Construct a Resource and cache it - // $FlowFixMe[incompatible-type] resource = { type: 'stylesheet', instance, count: 1, state, }; - // $FlowFixMe[incompatible-type] styles.set(key, resource); return; } @@ -5210,10 +5186,10 @@ function preinitScript(src: string, options?: ?PreinitScriptOptions) { if (!instance) { // Construct a new instance and insert it const scriptProps = Object.assign( - { + ({ src, async: true, - } as ScriptProps, + }: ScriptProps), options, ); // Adopt certain preload props @@ -5224,7 +5200,7 @@ function preinitScript(src: string, options?: ?PreinitScriptOptions) { instance = ownerDocument.createElement('script'); markNodeAsHoistable(instance); setInitialProperties(instance, 'link', scriptProps); - (ownerDocument.head as any).appendChild(instance); + (ownerDocument.head: any).appendChild(instance); } // Construct a Resource and cache it @@ -5266,11 +5242,11 @@ function preinitModuleScript( if (!instance) { // Construct a new instance and insert it const scriptProps = Object.assign( - { + ({ src, async: true, type: 'module', - } as ScriptProps, + }: ScriptProps), options, ); // Adopt certain preload props @@ -5281,7 +5257,7 @@ function preinitModuleScript( instance = ownerDocument.createElement('script'); markNodeAsHoistable(instance); setInitialProperties(instance, 'link', scriptProps); - (ownerDocument.head as any).appendChild(instance); + (ownerDocument.head: any).appendChild(instance); } // Construct a Resource and cache it @@ -5368,7 +5344,7 @@ export function getResource( if (!resource) { // We asserted this above but Flow can't figure out that the type satisfies const ownerDocument = getDocumentFromRoot(resourceRoot); - resource = { + resource = ({ type: 'stylesheet', instance: null, count: 0, @@ -5376,13 +5352,13 @@ export function getResource( loading: NotLoaded, preload: null, }, - } as StylesheetResource; + }: StylesheetResource); styles.set(key, resource); const instance = ownerDocument.querySelector( getStylesheetSelectorFromKey(key), ); if (instance) { - const loadingState: ?Promise<mixed> = (instance as any)._p; + const loadingState: ?Promise<mixed> = (instance: any)._p; if (loadingState) { // This instance is inserted as part of a boundary reveal and is not yet // loaded @@ -5592,9 +5568,9 @@ function preloadStylesheet( ); setInitialProperties(instance, 'link', preloadProps); markNodeAsHoistable(instance); - (ownerDocument.head as any).appendChild(instance); + (ownerDocument.head: any).appendChild(instance); } - // $FlowFixMe[incompatible-type] -- if instance is an Element it will also be an HTMLLinkElement + // $FlowFixMe: [incompatible-type] -- if instance is an Element it will also be an HTMLLinkElement state.preload = instance; instance.addEventListener('load', () => (state.loading |= Loaded)); instance.addEventListener('error', () => (state.loading |= Errored)); @@ -5689,8 +5665,8 @@ export function acquireResource( const ownerDocument = getDocumentFromRoot(hoistableRoot); instance = ownerDocument.createElement('link'); markNodeAsHoistable(instance); - const linkInstance: HTMLLinkElement = instance as any; - (linkInstance as any)._p = new Promise((resolve, reject) => { + const linkInstance: HTMLLinkElement = (instance: any); + (linkInstance: any)._p = new Promise((resolve, reject) => { linkInstance.onload = resolve; linkInstance.onerror = reject; }); @@ -5730,7 +5706,7 @@ export function acquireResource( instance = ownerDocument.createElement('script'); markNodeAsHoistable(instance); setInitialProperties(instance, 'link', scriptProps); - (ownerDocument.head as any).appendChild(instance); + (ownerDocument.head: any).appendChild(instance); resource.instance = instance; return instance; @@ -5799,12 +5775,12 @@ function insertStylesheet( // We get the prior from the document so we know it is in the tree. // We also know that links can't be the topmost Node so the parentNode // must exist. - (prior.parentNode as any as Node).insertBefore(instance, prior.nextSibling); + ((prior.parentNode: any): Node).insertBefore(instance, prior.nextSibling); } else { const parent = root.nodeType === DOCUMENT_NODE - ? ((root as any as Document).head as any as Element) - : (root as any as ShadowRoot); + ? ((((root: any): Document).head: any): Element) + : ((root: any): ShadowRoot); parent.insertBefore(instance, parent.firstChild); } } @@ -5855,7 +5831,7 @@ export function hydrateHoistable( instance.hasAttribute('itemprop') ) { instance = ownerDocument.createElement(type); - (ownerDocument.head as any).insertBefore( + (ownerDocument.head: any).insertBefore( instance, ownerDocument.querySelector('head > title'), ); @@ -5893,7 +5869,7 @@ export function hydrateHoistable( } instance = ownerDocument.createElement(type); setInitialProperties(instance, type, props); - (ownerDocument.head as any).appendChild(instance); + (ownerDocument.head: any).appendChild(instance); break; } case 'meta': { @@ -5937,7 +5913,7 @@ export function hydrateHoistable( } instance = ownerDocument.createElement(type); setInitialProperties(instance, type, props); - (ownerDocument.head as any).appendChild(instance); + (ownerDocument.head: any).appendChild(instance); break; } default: @@ -5980,7 +5956,7 @@ function getHydratableHoistableCache( } // Mark this cache as seeded for this type - cache.set(type, null as any); + cache.set(type, (null: any)); const nodes = ownerDocument.getElementsByTagName(type); for (let i = 0; i < nodes.length; i++) { @@ -6010,14 +5986,14 @@ export function mountHoistable( instance: Instance, ): void { const ownerDocument = getDocumentFromRoot(hoistableRoot); - (ownerDocument.head as any).insertBefore( + (ownerDocument.head: any).insertBefore( instance, type === 'title' ? ownerDocument.querySelector('head > title') : null, ); } export function unmountHoistable(instance: Instance): void { - (instance.parentNode as any).removeChild(instance); + (instance.parentNode: any).removeChild(instance); } export function isHostHoistableType( @@ -6028,13 +6004,13 @@ export function isHostHoistableType( let outsideHostContainerContext: boolean; let hostContextProd: HostContextProd; if (__DEV__) { - const hostContextDev: HostContextDev = hostContext as any; + const hostContextDev: HostContextDev = (hostContext: any); // We can only render resources when we are not within the host container context outsideHostContainerContext = !hostContextDev.ancestorInfo.containerTagInScope; hostContextProd = hostContextDev.context; } else { - hostContextProd = hostContext as any; + hostContextProd = (hostContext: any); } // Global opt out of hoisting for anything in SVG Namespace or anything with an itemProp inside an itemScope @@ -6248,7 +6224,7 @@ export function preloadInstance( // If we return true here, we'll still get a suspendInstance call in the // pre-commit phase to determine if we still need to decode the image or // if was dropped from cache. This just avoids rendering Suspense fallback. - return !!(instance as any).complete; + return !!(instance: any).complete; } export function preloadResource(resource: Resource): boolean { @@ -6322,9 +6298,9 @@ export function suspendInstance( // Estimate the byte size that we're about to download based on the width/height // specified in the props. This is best practice to know ahead of time but if it's // unspecified we'll fallback to a guess of 100x100 pixels. - if (!(instance as any).complete) { - state.imgBytes += estimateImageBytes(instance as any); - state.suspenseyImages.push(instance as any); + if (!(instance: any).complete) { + state.imgBytes += estimateImageBytes((instance: any)); + state.suspenseyImages.push((instance: any)); } const ping = onUnsuspendImg.bind(state); // $FlowFixMe[prop-missing] @@ -6361,7 +6337,7 @@ export function suspendResource( // as part of the preamble and therefore synchronously loaded. It could have // errored however which we still do not yet have a means to detect. For now // we assume it is loaded. - const maybeLoadingState: ?Promise<mixed> = (instance as any)._p; + const maybeLoadingState: ?Promise<mixed> = (instance: any)._p; if ( maybeLoadingState !== null && typeof maybeLoadingState === 'object' && @@ -6390,10 +6366,10 @@ export function suspendResource( // Construct and insert a new instance instance = ownerDocument.createElement('link'); markNodeAsHoistable(instance); - const linkInstance: HTMLLinkElement = instance as any; + const linkInstance: HTMLLinkElement = (instance: any); // This Promise is a loading state used by the Fizz runtime. We need this incase there is a race // between this resource being rendered on the client and being rendered with a late completed boundary. - (linkInstance as any)._p = new Promise((resolve, reject) => { + (linkInstance: any)._p = new Promise((resolve, reject) => { linkInstance.onload = resolve; linkInstance.onerror = reject; }); @@ -6577,7 +6553,7 @@ const LAST_PRECEDENCE = null; let precedencesByRoot: Map< HoistableRoot, Map<string | typeof LAST_PRECEDENCE, Instance>, -> = null as any; +> = (null: any); function insertSuspendedStylesheets( state: SuspendedState, @@ -6597,7 +6573,7 @@ function insertSuspendedStylesheets( precedencesByRoot = new Map(); resources.forEach(insertStylesheetIntoRoot, state); - precedencesByRoot = null as any; + precedencesByRoot = (null: any); // We can remove our temporary count and if we're still at zero we can unsuspend. // If we are in the synchronous phase before deciding if the commit should suspend and this @@ -6648,9 +6624,9 @@ function insertStylesheetIntoRoot( } // We only call this after we have constructed an instance so we assume it here - const instance: HTMLLinkElement = resource.instance as any; + const instance: HTMLLinkElement = (resource.instance: any); // We will always have a precedence for stylesheet instances - const precedence: string = instance.getAttribute('data-precedence') as any; + const precedence: string = (instance.getAttribute('data-precedence'): any); const prior = precedences.get(precedence) || last; if (prior === last) { @@ -6664,12 +6640,12 @@ function insertStylesheetIntoRoot( instance.addEventListener('error', onComplete); if (prior) { - (prior.parentNode as any).insertBefore(instance, prior.nextSibling); + (prior.parentNode: any).insertBefore(instance, prior.nextSibling); } else { const parent = root.nodeType === DOCUMENT_NODE - ? ((root as any as Document).head as any as Element) - : (root as any as ShadowRoot); + ? ((((root: any): Document).head: any): Element) + : ((root: any): ShadowRoot); parent.insertBefore(instance, parent.firstChild); } resource.state.loading |= Inserted; @@ -6678,8 +6654,8 @@ function insertStylesheetIntoRoot( export const NotPendingTransition: TransitionStatus = NotPending; export const HostTransitionContext: ReactContext<TransitionStatus> = { $$typeof: REACT_CONTEXT_TYPE, - Provider: null as any, - Consumer: null as any, + Provider: (null: any), + Consumer: (null: any), _currentValue: NotPendingTransition, _currentValue2: NotPendingTransition, _threadCount: 0, diff --git a/packages/react-dom-bindings/src/client/ToStringValue.js b/packages/react-dom-bindings/src/client/ToStringValue.js index bdadfbfc2975..46708f2f7695 100644 --- a/packages/react-dom-bindings/src/client/ToStringValue.js +++ b/packages/react-dom-bindings/src/client/ToStringValue.js @@ -24,7 +24,7 @@ export opaque type ToStringValue = export function toString(value: ToStringValue): string { // The coercion safety check is performed in getToStringValue(). // eslint-disable-next-line react-internal/safe-string-coercion - return '' + (value as any); + return '' + (value: any); } export function getToStringValue(value: mixed): ToStringValue { diff --git a/packages/react-dom-bindings/src/client/estimateBandwidth.js b/packages/react-dom-bindings/src/client/estimateBandwidth.js index 5f484aebd95b..4b143a5b562c 100644 --- a/packages/react-dom-bindings/src/client/estimateBandwidth.js +++ b/packages/react-dom-bindings/src/client/estimateBandwidth.js @@ -98,7 +98,7 @@ export default function estimateBandwidth(): number { // Fallback to the navigator.connection estimate if available // $FlowFixMe[prop-missing] if (navigator.connection) { - // $FlowFixMe[incompatible-use] + // $FlowFixMe const downlink: ?number = navigator.connection.downlink; if (typeof downlink === 'number') { return downlink; diff --git a/packages/react-dom-bindings/src/client/inputValueTracking.js b/packages/react-dom-bindings/src/client/inputValueTracking.js index 59dc49cd123d..e931283e6020 100644 --- a/packages/react-dom-bindings/src/client/inputValueTracking.js +++ b/packages/react-dom-bindings/src/client/inputValueTracking.js @@ -125,7 +125,7 @@ export function track(node: ElementWithValueTracker) { // This is read from the DOM so always safe to coerce. We really shouldn't // be coercing to a string at all. It's just historical. // eslint-disable-next-line react-internal/safe-string-coercion - const initialValue = '' + (node[valueField] as any); + const initialValue = '' + (node[valueField]: any); node._valueTracker = trackValueOnNode(node, valueField, initialValue); } @@ -145,15 +145,16 @@ export function trackHydrated( if (isCheckable(node)) { valueField = 'checked'; // eslint-disable-next-line react-internal/safe-string-coercion - expectedValue = '' + (initialChecked as any); + expectedValue = '' + (initialChecked: any); } else { valueField = 'value'; expectedValue = initialValue; } const currentValue = // eslint-disable-next-line react-internal/safe-string-coercion - '' + // $FlowFixMe[prop-missing] - (node[valueField] as any); + '' + + (// $FlowFixMe[prop-missing] + node[valueField]: any); node._valueTracker = trackValueOnNode(node, valueField, expectedValue); return currentValue !== expectedValue; } diff --git a/packages/react-dom-bindings/src/client/validateDOMNesting.js b/packages/react-dom-bindings/src/client/validateDOMNesting.js index 743303fcc290..47aef9353ba8 100644 --- a/packages/react-dom-bindings/src/client/validateDOMNesting.js +++ b/packages/react-dom-bindings/src/client/validateDOMNesting.js @@ -292,7 +292,7 @@ function updatedAncestorInfoDev( return ancestorInfo; } else { - return null as any; + return (null: any); } } @@ -418,7 +418,6 @@ function isTagValidWithParent( case 'rp': case 'rt': - // $FlowFixMe[incompatible-type] return impliedEndTags.indexOf(parentTag) === -1; case 'caption': diff --git a/packages/react-dom-bindings/src/events/DOMEventProperties.js b/packages/react-dom-bindings/src/events/DOMEventProperties.js index d574f986015f..6b2e80ecd1eb 100644 --- a/packages/react-dom-bindings/src/events/DOMEventProperties.js +++ b/packages/react-dom-bindings/src/events/DOMEventProperties.js @@ -129,8 +129,8 @@ function registerSimpleEvent(domEventName: DOMEventName, reactName: string) { export function registerSimpleEvents() { for (let i = 0; i < simpleEventPluginEvents.length; i++) { - const eventName = simpleEventPluginEvents[i] as any as string; - const domEventName = eventName.toLowerCase() as any as DOMEventName; + const eventName = ((simpleEventPluginEvents[i]: any): string); + const domEventName = ((eventName.toLowerCase(): any): DOMEventName); const capitalizedEvent = eventName[0].toUpperCase() + eventName.slice(1); registerSimpleEvent(domEventName, 'on' + capitalizedEvent); } diff --git a/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js b/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js index 6d615160d8be..e30c4798371d 100644 --- a/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js +++ b/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js @@ -430,8 +430,8 @@ export function listenToNativeEventForNonManagedEventTarget( const listeningMarker = '_reactListening' + Math.random().toString(36).slice(2); export function listenToAllSupportedEvents(rootContainerElement: EventTarget) { - if (!(rootContainerElement as any)[listeningMarker]) { - (rootContainerElement as any)[listeningMarker] = true; + if (!(rootContainerElement: any)[listeningMarker]) { + (rootContainerElement: any)[listeningMarker] = true; allNativeEvents.forEach(domEventName => { // We handle selectionchange separately because it // doesn't bubble and needs to be on the document. @@ -443,15 +443,14 @@ export function listenToAllSupportedEvents(rootContainerElement: EventTarget) { } }); const ownerDocument = - (rootContainerElement as any).nodeType === DOCUMENT_NODE + (rootContainerElement: any).nodeType === DOCUMENT_NODE ? rootContainerElement - : (rootContainerElement as any).ownerDocument; - // $FlowFixMe[invalid-compare] + : (rootContainerElement: any).ownerDocument; if (ownerDocument !== null) { // The selectionchange event also needs deduplication // but it is attached to the document. - if (!(ownerDocument as any)[listeningMarker]) { - (ownerDocument as any)[listeningMarker] = true; + if (!(ownerDocument: any)[listeningMarker]) { + (ownerDocument: any)[listeningMarker] = true; listenToNativeEvent('selectionchange', false, ownerDocument); } } @@ -491,7 +490,7 @@ function addTrappedEventListener( targetContainer = enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport - ? (targetContainer as any).ownerDocument + ? (targetContainer: any).ownerDocument : targetContainer; let unsubscribeListener; @@ -594,7 +593,7 @@ export function dispatchEventForPluginEventSystem( (eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 && (eventSystemFlags & IS_NON_DELEGATED) === 0 ) { - const targetContainerNode = targetContainer as any as Node; + const targetContainerNode = ((targetContainer: any): Node); // If we are using the legacy FB support flag, we // defer the event to the null with a one @@ -751,7 +750,7 @@ export function accumulateSinglePhaseListeners( createDispatchListener( instance, entry.callback, - lastHostComponent as any, + (lastHostComponent: any), ), ); } @@ -789,7 +788,7 @@ export function accumulateSinglePhaseListeners( createDispatchListener( instance, entry.callback, - lastHostComponent as any, + (lastHostComponent: any), ), ); } @@ -926,7 +925,6 @@ function accumulateEnterLeaveListenersForEvent( createDispatchListener(instance, captureListener, currentTarget), ); } - // $FlowFixMe[constant-condition] } else if (!inCapturePhase) { const bubbleListener = getListener(instance, registrationName); if (bubbleListener != null) { diff --git a/packages/react-dom-bindings/src/events/EventRegistry.js b/packages/react-dom-bindings/src/events/EventRegistry.js index be0b73f3de9b..29131c89401f 100644 --- a/packages/react-dom-bindings/src/events/EventRegistry.js +++ b/packages/react-dom-bindings/src/events/EventRegistry.js @@ -33,7 +33,7 @@ export const registrationNameDependencies: { */ export const possibleRegistrationNames: { [lowerCasedName: string]: string, -} = __DEV__ ? {} : (null as any); +} = __DEV__ ? {} : (null: any); // Trust the developer to only use possibleRegistrationNames in __DEV__ export function registerTwoPhaseEvent( diff --git a/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js b/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js index 971257a2ba3a..2763b2fc129a 100644 --- a/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js +++ b/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js @@ -168,13 +168,13 @@ export function clearIfContinuousEvent( break; case 'pointerover': case 'pointerout': { - const pointerId = (nativeEvent as any as PointerEventType).pointerId; + const pointerId = ((nativeEvent: any): PointerEventType).pointerId; queuedPointers.delete(pointerId); break; } case 'gotpointercapture': case 'lostpointercapture': { - const pointerId = (nativeEvent as any as PointerEventType).pointerId; + const pointerId = ((nativeEvent: any): PointerEventType).pointerId; queuedPointerCaptures.delete(pointerId); break; } @@ -216,7 +216,6 @@ function accumulateOrCreateContinuousQueuedReplayableEvent( existingQueuedEvent.eventSystemFlags |= eventSystemFlags; const targetContainers = existingQueuedEvent.targetContainers; if ( - // $FlowFixMe[invalid-compare] targetContainer !== null && targetContainers.indexOf(targetContainer) === -1 ) { @@ -237,7 +236,7 @@ export function queueIfContinuousEvent( // Instead of mutating we could clone the event. switch (domEventName) { case 'focusin': { - const focusEvent = nativeEvent as any as FocusEvent; + const focusEvent = ((nativeEvent: any): FocusEvent); queuedFocus = accumulateOrCreateContinuousQueuedReplayableEvent( queuedFocus, blockedOn, @@ -249,7 +248,7 @@ export function queueIfContinuousEvent( return true; } case 'dragenter': { - const dragEvent = nativeEvent as any as DragEvent; + const dragEvent = ((nativeEvent: any): DragEvent); queuedDrag = accumulateOrCreateContinuousQueuedReplayableEvent( queuedDrag, blockedOn, @@ -261,7 +260,7 @@ export function queueIfContinuousEvent( return true; } case 'mouseover': { - const mouseEvent = nativeEvent as any as MouseEvent; + const mouseEvent = ((nativeEvent: any): MouseEvent); queuedMouse = accumulateOrCreateContinuousQueuedReplayableEvent( queuedMouse, blockedOn, @@ -273,7 +272,7 @@ export function queueIfContinuousEvent( return true; } case 'pointerover': { - const pointerEvent = nativeEvent as any as PointerEventType; + const pointerEvent = ((nativeEvent: any): PointerEventType); const pointerId = pointerEvent.pointerId; queuedPointers.set( pointerId, @@ -289,7 +288,7 @@ export function queueIfContinuousEvent( return true; } case 'gotpointercapture': { - const pointerEvent = nativeEvent as any as PointerEventType; + const pointerEvent = ((nativeEvent: any): PointerEventType); const pointerId = pointerEvent.pointerId; queuedPointerCaptures.set( pointerId, @@ -396,7 +395,7 @@ function attemptReplayContinuousQueuedEvent( const nativeEvent = queuedEvent.nativeEvent; const nativeEventClone = new nativeEvent.constructor( nativeEvent.type, - nativeEvent as any, + (nativeEvent: any), ); setReplayingEvent(nativeEventClone); nativeEvent.target.dispatchEvent(nativeEventClone); @@ -429,7 +428,7 @@ function attemptReplayContinuousQueuedEventInMap( function replayChangeEvent(target: EventTarget): void { // Dispatch a fake "change" event for the input. const element: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement = - target as any; + (target: any); if (element.nodeName === 'INPUT') { if (element.type === 'checkbox' || element.type === 'radio') { // Checkboxes always fire a click event regardless of how the change was made. @@ -610,7 +609,7 @@ export function retryIfBlockedOn( // Check the document if there are any queued form actions. // If there's no ownerDocument, then this is the document. const root = unblocked.ownerDocument || unblocked; - const formReplayingQueue: void | FormReplayingQueue = (root as any) + const formReplayingQueue: void | FormReplayingQueue = (root: any) .$$reactFormReplay; if (formReplayingQueue != null) { for (let i = 0; i < formReplayingQueue.length; i += 3) { @@ -643,7 +642,7 @@ export function retryIfBlockedOn( const submitterProps = getFiberCurrentPropsFromNode(submitter); if (submitterProps) { // The submitter is part of this instance. - action = (submitterProps as any).formAction; + action = (submitterProps: any).formAction; } else { const blockedOn = findInstanceBlockingTarget(target); if (blockedOn !== null) { @@ -654,7 +653,7 @@ export function retryIfBlockedOn( // Except the form isn't. We don't dispatch actions in this scenario. } } else { - action = (formProps as any).action; + action = (formProps: any).action; } if (typeof action === 'function') { formReplayingQueue[i + 1] = action; diff --git a/packages/react-dom-bindings/src/events/SyntheticEvent.js b/packages/react-dom-bindings/src/events/SyntheticEvent.js index 1fa5dafba11b..11ec03091941 100644 --- a/packages/react-dom-bindings/src/events/SyntheticEvent.js +++ b/packages/react-dom-bindings/src/events/SyntheticEvent.js @@ -391,7 +391,7 @@ function getEventKey(nativeEvent: {[propName: string]: mixed}) { // Browser does not implement `key`, polyfill as much of it as we can. if (nativeEvent.type === 'keypress') { const charCode = getEventCharCode( - // $FlowFixMe[incompatible-type] unable to narrow to `KeyboardEvent` + // $FlowFixMe[incompatible-call] unable to narrow to `KeyboardEvent` nativeEvent, ); @@ -463,7 +463,7 @@ const KeyboardEventInterface: EventInterfaceType = { // implemented in any major browser. Only KeyPress has charCode. if (event.type === 'keypress') { return getEventCharCode( - // $FlowFixMe[incompatible-type] unable to narrow to `KeyboardEvent` + // $FlowFixMe[incompatible-call] unable to narrow to `KeyboardEvent` event, ); } @@ -487,7 +487,7 @@ const KeyboardEventInterface: EventInterfaceType = { // type of the event. if (event.type === 'keypress') { return getEventCharCode( - // $FlowFixMe[incompatible-type] unable to narrow to `KeyboardEvent` + // $FlowFixMe[incompatible-call] unable to narrow to `KeyboardEvent` event, ); } diff --git a/packages/react-dom-bindings/src/events/forks/EventListener-www.js b/packages/react-dom-bindings/src/events/forks/EventListener-www.js index 6a087a4ba42e..3684e374b32d 100644 --- a/packages/react-dom-bindings/src/events/forks/EventListener-www.js +++ b/packages/react-dom-bindings/src/events/forks/EventListener-www.js @@ -66,4 +66,4 @@ export function removeEventListener( } // Flow magic to verify the exports of this file match the original version. -null as any as EventListenerType as EventListenerShimType as EventListenerType; +((((null: any): EventListenerType): EventListenerShimType): EventListenerType); diff --git a/packages/react-dom-bindings/src/events/isEventSupported.js b/packages/react-dom-bindings/src/events/isEventSupported.js index dd618f223dca..8a32c135975e 100644 --- a/packages/react-dom-bindings/src/events/isEventSupported.js +++ b/packages/react-dom-bindings/src/events/isEventSupported.js @@ -33,7 +33,7 @@ function isEventSupported(eventNameSuffix: string): boolean { if (!isSupported) { const element = document.createElement('div'); element.setAttribute(eventName, 'return;'); - isSupported = typeof (element as any)[eventName] === 'function'; + isSupported = typeof (element: any)[eventName] === 'function'; } return isSupported; diff --git a/packages/react-dom-bindings/src/events/isTextInputElement.js b/packages/react-dom-bindings/src/events/isTextInputElement.js index 7a28a115418c..ff4171dd987b 100644 --- a/packages/react-dom-bindings/src/events/isTextInputElement.js +++ b/packages/react-dom-bindings/src/events/isTextInputElement.js @@ -32,7 +32,7 @@ function isTextInputElement(elem: ?HTMLElement): boolean { const nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase(); if (nodeName === 'input') { - return !!supportedInputTypes[(elem as any as HTMLInputElement).type]; + return !!supportedInputTypes[((elem: any): HTMLInputElement).type]; } if (nodeName === 'textarea') { diff --git a/packages/react-dom-bindings/src/events/plugins/ChangeEventPlugin.js b/packages/react-dom-bindings/src/events/plugins/ChangeEventPlugin.js index c4ec263bb3d5..fff7d0a1bb8c 100644 --- a/packages/react-dom-bindings/src/events/plugins/ChangeEventPlugin.js +++ b/packages/react-dom-bindings/src/events/plugins/ChangeEventPlugin.js @@ -54,7 +54,7 @@ function createAndAccumulateChangeEvent( target: null | EventTarget, ) { // Flag this event loop as needing state restore. - enqueueStateRestore(target as any as Node); + enqueueStateRestore(((target: any): Node)); const listeners = accumulateTwoPhaseListeners(inst, 'onChange'); if (listeners.length > 0) { const event: ReactSyntheticEvent = new SyntheticEvent( @@ -80,7 +80,7 @@ function shouldUseChangeEvent(elem: Instance | TextInstance) { const nodeName = elem.nodeName && elem.nodeName.toLowerCase(); return ( nodeName === 'select' || - (nodeName === 'input' && (elem as any).type === 'file') + (nodeName === 'input' && (elem: any).type === 'file') ); } @@ -113,7 +113,7 @@ function runEventInBatch(dispatchQueue: DispatchQueue) { function getInstIfValueChanged(targetInst: Object) { const targetNode = getNodeFromInstance(targetInst); - if (updateValueIfChanged(targetNode as any as HTMLInputElement)) { + if (updateValueIfChanged(((targetNode: any): HTMLInputElement))) { return targetInst; } } @@ -150,7 +150,7 @@ function startWatchingForValueChange( ) { activeElement = target; activeElementInst = targetInst; - (activeElement as any).attachEvent('onpropertychange', handlePropertyChange); + (activeElement: any).attachEvent('onpropertychange', handlePropertyChange); } /** @@ -161,7 +161,7 @@ function stopWatchingForValueChange() { if (!activeElement) { return; } - (activeElement as any).detachEvent('onpropertychange', handlePropertyChange); + (activeElement: any).detachEvent('onpropertychange', handlePropertyChange); activeElement = null; activeElementInst = null; } @@ -269,7 +269,7 @@ function handleControlledInputBlur(node: HTMLInputElement, props: any) { const isControlled = props.value != null; if (isControlled) { // If controlled, assign the value attribute to the current value on blur - setDefaultValue(node as any, 'number', (node as any).value); + setDefaultValue((node: any), 'number', (node: any).value); } } } @@ -298,7 +298,7 @@ function extractEvents( let getTargetInstFunc, handleEventFunc; if (shouldUseChangeEvent(targetNode)) { getTargetInstFunc = getTargetInstForChangeEvent; - } else if (isTextInputElement(targetNode as any as HTMLElement)) { + } else if (isTextInputElement(((targetNode: any): HTMLElement))) { if (isInputEventSupported) { getTargetInstFunc = getTargetInstForInputOrChangeEvent; } else { @@ -337,7 +337,7 @@ function extractEvents( // between controlled and uncontrolled, so it doesn't matter and the previous // code was also broken for changes. const props = targetInst.memoizedProps; - handleControlledInputBlur(targetNode as any as HTMLInputElement, props); + handleControlledInputBlur(((targetNode: any): HTMLInputElement), props); } } diff --git a/packages/react-dom-bindings/src/events/plugins/EnterLeaveEventPlugin.js b/packages/react-dom-bindings/src/events/plugins/EnterLeaveEventPlugin.js index 6c329678c9d9..e8f08b55d955 100644 --- a/packages/react-dom-bindings/src/events/plugins/EnterLeaveEventPlugin.js +++ b/packages/react-dom-bindings/src/events/plugins/EnterLeaveEventPlugin.js @@ -65,7 +65,7 @@ function extractEvents( // then it's because we couldn't dispatch against this target previously // so we have to do it now instead. const related = - (nativeEvent as any).relatedTarget || (nativeEvent as any).fromElement; + (nativeEvent: any).relatedTarget || (nativeEvent: any).fromElement; if (related) { // If the related node is managed by React, we can assume that we have // already dispatched the corresponding events during its mouseout. @@ -85,12 +85,12 @@ function extractEvents( let win; // TODO: why is this nullable in the types but we read from it? - if ((nativeEventTarget as any).window === nativeEventTarget) { + if ((nativeEventTarget: any).window === nativeEventTarget) { // `nativeEventTarget` is probably a window object. win = nativeEventTarget; } else { // TODO: Figure out why `ownerDocument` is sometimes undefined in IE8. - const doc = (nativeEventTarget as any).ownerDocument; + const doc = (nativeEventTarget: any).ownerDocument; if (doc) { win = doc.defaultView || doc.parentWindow; } else { @@ -101,9 +101,9 @@ function extractEvents( let from; let to; if (isOutEvent) { - const related = nativeEvent.relatedTarget || (nativeEvent as any).toElement; + const related = nativeEvent.relatedTarget || (nativeEvent: any).toElement; from = targetInst; - to = related ? getClosestInstanceFromNode(related as any) : null; + to = related ? getClosestInstanceFromNode((related: any)) : null; if (to !== null) { const nearestMounted = getNearestMountedFiber(to); const tag = to.tag; @@ -153,7 +153,7 @@ function extractEvents( // We should only process this nativeEvent if we are processing // the first ancestor. Next time, we will ignore the event. - const nativeTargetInst = getClosestInstanceFromNode(nativeEventTarget as any); + const nativeTargetInst = getClosestInstanceFromNode((nativeEventTarget: any)); if (nativeTargetInst === targetInst) { const enterEvent: KnownReactSyntheticEvent = new SyntheticEventCtor( enterEventType, diff --git a/packages/react-dom-bindings/src/events/plugins/FormActionEventPlugin.js b/packages/react-dom-bindings/src/events/plugins/FormActionEventPlugin.js index 6d07362b715c..420d1d118820 100644 --- a/packages/react-dom-bindings/src/events/plugins/FormActionEventPlugin.js +++ b/packages/react-dom-bindings/src/events/plugins/FormActionEventPlugin.js @@ -34,14 +34,14 @@ function coerceFormActionProp( ) { return null; } else if (typeof actionProp === 'function') { - return actionProp as any; + return (actionProp: any); } else { if (__DEV__) { checkAttributeStringCoercion(actionProp, 'action'); } - return sanitizeURL( - enableTrustedTypesIntegration ? actionProp : '' + (actionProp as any), - ) as any; + return (sanitizeURL( + enableTrustedTypesIntegration ? actionProp : '' + (actionProp: any), + ): any); } } @@ -67,20 +67,19 @@ function extractEvents( return; } const formInst = maybeTargetInst; - const form: HTMLFormElement = nativeEventTarget as any; + const form: HTMLFormElement = (nativeEventTarget: any); let action = coerceFormActionProp( - (getFiberCurrentPropsFromNode(form) as any).action, + (getFiberCurrentPropsFromNode(form): any).action, ); - let submitter: null | void | HTMLInputElement | HTMLButtonElement = ( - nativeEvent as any - ).submitter; + let submitter: null | void | HTMLInputElement | HTMLButtonElement = + (nativeEvent: any).submitter; let submitterAction; if (submitter) { const submitterProps = getFiberCurrentPropsFromNode(submitter); submitterAction = submitterProps - ? coerceFormActionProp((submitterProps as any).formAction) + ? coerceFormActionProp((submitterProps: any).formAction) : // The built-in Flow type is ?string, wider than the spec - (submitter.getAttribute('formAction') as any as string | null); + ((submitter.getAttribute('formAction'): any): string | null); if (submitterAction !== null) { // The submitter overrides the form action. action = submitterAction; diff --git a/packages/react-dom-bindings/src/events/plugins/SelectEventPlugin.js b/packages/react-dom-bindings/src/events/plugins/SelectEventPlugin.js index 53fb8cd6cced..ce6114257fa3 100644 --- a/packages/react-dom-bindings/src/events/plugins/SelectEventPlugin.js +++ b/packages/react-dom-bindings/src/events/plugins/SelectEventPlugin.js @@ -162,7 +162,7 @@ function extractEvents( // Track the input node that has focus. case 'focusin': if ( - isTextInputElement(targetNode as any) || + isTextInputElement((targetNode: any)) || targetNode.contentEditable === 'true' ) { activeElement = targetNode; diff --git a/packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js b/packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js index 3265d4fd0823..4873dacdbf43 100644 --- a/packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js +++ b/packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js @@ -76,7 +76,7 @@ function extractEvents( // non-printable. One would expect Tab to be as well (but it isn't). // TODO: Fixed in https://bugzilla.mozilla.org/show_bug.cgi?id=968056. Can // probably remove. - if (getEventCharCode(nativeEvent as any as KeyboardEvent) === 0) { + if (getEventCharCode(((nativeEvent: any): KeyboardEvent)) === 0) { return; } /* falls through */ @@ -184,7 +184,7 @@ function extractEvents( const listeners = accumulateEventHandleNonManagedNodeListeners( // TODO: this cast may not make sense for events like // "focus" where React listens to e.g. "focusin". - reactEventType as any as DOMEventName, + ((reactEventType: any): DOMEventName), targetContainer, inCapturePhase, ); diff --git a/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js b/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js index 81fd2ad3d8a5..49c555f43f81 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js +++ b/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js @@ -243,12 +243,12 @@ function trimOptions< >(options: ?T): ?T { if (options == null) return null; let hasProperties = false; - const trimmed: T = {} as any; + const trimmed: T = ({}: any); for (const key in options) { // $FlowFixMe[invalid-computed-prop] if (options[key] != null) { hasProperties = true; - (trimmed as any)[key] = options[key]; + (trimmed: any)[key] = options[key]; } } return hasProperties ? trimmed : null; diff --git a/packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js b/packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js index 6696dcdf6521..54c576856869 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js +++ b/packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js @@ -75,7 +75,7 @@ export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number { } export function closeWithError(destination: Destination, error: mixed): void { - // $FlowFixMe[incompatible-type]: This is an Error object or the destination accepts other types. + // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types. destination.destroy(error); } diff --git a/packages/react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js b/packages/react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js index b0efbf2816be..8a13babe2b91 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js +++ b/packages/react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js @@ -13,8 +13,8 @@ if (document.body != null) { if (document.readyState === 'loading') { installFizzInstrObserver(document.body); } - // $FlowFixMe[incompatible-type] - handleExistingNodes(document.body as HTMLElement); + // $FlowFixMe[incompatible-cast] + handleExistingNodes((document.body: HTMLElement)); } else { // Document must be loading -- body may not exist yet if the fizz external // runtime is sent in <head> (e.g. as a preinit resource) @@ -25,8 +25,8 @@ if (document.body != null) { if (document.readyState === 'loading') { installFizzInstrObserver(document.body); } - // $FlowFixMe[incompatible-type] - handleExistingNodes(document.body as HTMLElement); + // $FlowFixMe[incompatible-cast] + handleExistingNodes((document.body: HTMLElement)); // We can call disconnect without takeRecord here, // since we only expect a single document.body @@ -69,12 +69,12 @@ function installFizzInstrObserver(target: Node) { } function handleNode(node_: Node) { - // $FlowFixMe[incompatible-type] - if (node_.nodeType !== 1 || !(node_ as HTMLElement).dataset) { + // $FlowFixMe[incompatible-cast] + if (node_.nodeType !== 1 || !(node_: HTMLElement).dataset) { return; } - // $FlowFixMe[incompatible-type] - const node = node_ as HTMLElement; + // $FlowFixMe[incompatible-cast] + const node = (node_: HTMLElement); const dataset = node.dataset; if (dataset['rxi'] != null) { window['$RX']( diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index 6cf6fb530453..691e49e563fd 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js @@ -547,12 +547,12 @@ export function createRenderState( for (let i = 0; i < bootstrapScripts.length; i++) { const scriptConfig = bootstrapScripts[i]; let src, crossOrigin, integrity; - const props: PreloadAsProps = { + const props: PreloadAsProps = ({ rel: 'preload', as: 'script', fetchPriority: 'low', nonce, - } as any; + }: any); if (typeof scriptConfig === 'string') { props.href = src = scriptConfig; } else { @@ -605,11 +605,11 @@ export function createRenderState( for (let i = 0; i < bootstrapModules.length; i++) { const scriptConfig = bootstrapModules[i]; let src, crossOrigin, integrity; - const props: PreloadModuleProps = { + const props: PreloadModuleProps = ({ rel: 'modulepreload', fetchPriority: 'low', nonce: nonceScript, - } as any; + }: any); if (typeof scriptConfig === 'string') { props.href = src = scriptConfig; } else { @@ -1502,15 +1502,15 @@ function pushSrcObjectAttribute( const suspenseCache: WeakMap<Blob, Thenable<string>> = blobCache; let thenable = suspenseCache.get(blob); if (thenable === undefined) { - thenable = readAsDataURL(blob) as any as Thenable<string>; + thenable = ((readAsDataURL(blob): any): Thenable<string>); thenable.then( result => { - (thenable as any).status = 'fulfilled'; - (thenable as any).value = result; + (thenable: any).status = 'fulfilled'; + (thenable: any).value = result; }, error => { - (thenable as any).status = 'rejected'; - (thenable as any).reason = error; + (thenable: any).status = 'rejected'; + (thenable: any).reason = error; }, ); suspenseCache.set(blob, thenable); @@ -1560,7 +1560,6 @@ function pushAttribute( return; } case 'src': { - // $FlowFixMe[invalid-compare] if (enableSrcObject && typeof value === 'object' && value !== null) { if (typeof Blob === 'function' && value instanceof Blob) { pushSrcObjectAttribute(target, value); @@ -1756,7 +1755,7 @@ function pushAttribute( typeof value !== 'function' && typeof value !== 'symbol' && !isNaN(value) && - (value as any) >= 1 + (value: any) >= 1 ) { target.push( attributeSeparator, @@ -2105,11 +2104,11 @@ function flattenOptionChildren(children: mixed): string { let content = ''; // Flatten children and warn if they aren't strings or numbers; // invalid types are ignored. - Children.forEach(children as any, function (child) { + Children.forEach((children: any), function (child) { if (child == null) { return; } - content += child as any; + content += (child: any); if (__DEV__) { if ( !didWarnInvalidOptionChildren && @@ -2935,9 +2934,9 @@ function pushLink( if (!styleQueue) { styleQueue = { precedence: stringToChunk(escapeTextForBrowser(precedence)), - rules: [] as Array<Chunk | PrecomputedChunk>, - hrefs: [] as Array<Chunk | PrecomputedChunk>, - sheets: new Map() as Map<string, StylesheetResource>, + rules: ([]: Array<Chunk | PrecomputedChunk>), + hrefs: ([]: Array<Chunk | PrecomputedChunk>), + sheets: (new Map(): Map<string, StylesheetResource>), }; renderState.styles.set(precedence, styleQueue); } @@ -3143,9 +3142,9 @@ function pushStyle( // to create a StyleQueue. styleQueue = { precedence: stringToChunk(escapeTextForBrowser(precedence)), - rules: [] as Array<Chunk | PrecomputedChunk>, - hrefs: [] as Array<Chunk | PrecomputedChunk>, - sheets: new Map() as Map<string, StylesheetResource>, + rules: ([]: Array<Chunk | PrecomputedChunk>), + hrefs: ([]: Array<Chunk | PrecomputedChunk>), + sheets: (new Map(): Map<string, StylesheetResource>), }; renderState.styles.set(precedence, styleQueue); } @@ -3368,7 +3367,7 @@ function pushImg( // reenter this branch in a second pass for duplicate img hrefs. promotablePreloads.delete(key); - // $FlowFixMe[incompatible-type] - Flow should understand that this is a Resource if the condition was true + // $FlowFixMe - Flow should understand that this is a Resource if the condition was true renderState.highImagePreloads.add(resource); } } else if (!resumableState.imageResources.hasOwnProperty(key)) { @@ -3426,22 +3425,25 @@ function pushImg( headers.highImagePreloads += header; } else { resource = []; - pushLinkImpl(resource, { - rel: 'preload', - as: 'image', - // There is a bug in Safari where imageSrcSet is not respected on preload links - // so we omit the href here if we have imageSrcSet b/c safari will load the wrong image. - // This harms older browers that do not support imageSrcSet by making their preloads not work - // but this population is shrinking fast and is already small so we accept this tradeoff. - href: srcSet ? undefined : src, - imageSrcSet: srcSet, - imageSizes: sizes, - crossOrigin: crossOrigin, - integrity: props.integrity, - type: props.type, - fetchPriority: props.fetchPriority, - referrerPolicy: props.referrerPolicy, - } as PreloadProps); + pushLinkImpl( + resource, + ({ + rel: 'preload', + as: 'image', + // There is a bug in Safari where imageSrcSet is not respected on preload links + // so we omit the href here if we have imageSrcSet b/c safari will load the wrong image. + // This harms older browers that do not support imageSrcSet by making their preloads not work + // but this population is shrinking fast and is already small so we accept this tradeoff. + href: srcSet ? undefined : src, + imageSrcSet: srcSet, + imageSizes: sizes, + crossOrigin: crossOrigin, + integrity: props.integrity, + type: props.type, + fetchPriority: props.fetchPriority, + referrerPolicy: props.referrerPolicy, + }: PreloadProps), + ); if ( props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10 @@ -3561,8 +3563,6 @@ function pushTitle( ' tags to a single string value.', childType, ); - // $FlowFixMe[invalid-compare] - // $FlowFixMe[constant-condition] } else if (child && child.toString === {}.toString) { if (child.$$typeof != null) { console.error( @@ -4019,10 +4019,8 @@ function pushStartCustomElement( typeof propValue !== 'function' && typeof propValue !== 'symbol' ) { - // $FlowFixMe[invalid-compare] if (propValue === false) { continue; - // $FlowFixMe[invalid-compare] } else if (propValue === true) { propValue = ''; } else if (typeof propValue === 'object') { @@ -4614,7 +4612,6 @@ export function writeStartPendingSuspenseBoundary( ): boolean { writeChunk(destination, startPendingSuspenseBoundary1); - // $FlowFixMe[invalid-compare] if (id === null) { throw new Error( 'An ID must have been assigned before we can complete the boundary.', @@ -5273,7 +5270,7 @@ function flushStyleTagsLateForBoundary( if (hrefs.length) { writeChunk( this, - (currentlyFlushingRenderState as any as RenderState).startInlineStyle, + ((currentlyFlushingRenderState: any): RenderState).startInlineStyle, ); writeChunk(this, lateStyleTagResourceOpen1); writeChunk(this, styleQueue.precedence); @@ -5393,7 +5390,7 @@ function flushStylesInPreamble( if (!hasStylesheets || hrefs.length) { writeChunk( this, - (currentlyFlushingRenderState as any as RenderState).startInlineStyle, + ((currentlyFlushingRenderState: any): RenderState).startInlineStyle, ); writeChunk(this, styleTagResourceOpen1); writeChunk(this, styleQueue.precedence); @@ -5761,7 +5758,7 @@ function writeStyleResourceDependencyHrefOnlyInJS( if (__DEV__) { checkAttributeStringCoercion(href, 'href'); } - const coercedHref = '' + (href as any); + const coercedHref = '' + (href: any); writeChunk( destination, stringToChunk(escapeJSObjectForInstructionScripts(coercedHref)), @@ -5775,7 +5772,7 @@ function writeStyleResourceDependencyInJS( props: Object, ) { // eslint-disable-next-line react-internal/safe-string-coercion - const coercedHref = sanitizeURL('' + (href as any)); + const coercedHref = sanitizeURL('' + (href: any)); writeChunk( destination, stringToChunk(escapeJSObjectForInstructionScripts(coercedHref)), @@ -5784,7 +5781,7 @@ function writeStyleResourceDependencyInJS( if (__DEV__) { checkAttributeStringCoercion(precedence, 'precedence'); } - const coercedPrecedence = '' + (precedence as any); + const coercedPrecedence = '' + (precedence: any); writeChunk(destination, arrayInterstitial); writeChunk( destination, @@ -5849,7 +5846,7 @@ function writeStyleResourceAttributeInJS( if (__DEV__) { checkAttributeStringCoercion(value, attributeName); } - attributeValue = '' + (value as any); + attributeValue = '' + (value: any); break; } // Booleans @@ -5867,7 +5864,7 @@ function writeStyleResourceAttributeInJS( if (__DEV__) { checkAttributeStringCoercion(value, attributeName); } - attributeValue = '' + (value as any); + attributeValue = '' + (value: any); break; } default: { @@ -5886,7 +5883,7 @@ function writeStyleResourceAttributeInJS( if (__DEV__) { checkAttributeStringCoercion(value, attributeName); } - attributeValue = '' + (value as any); + attributeValue = '' + (value: any); } } writeChunk(destination, arrayInterstitial); @@ -5955,7 +5952,7 @@ function writeStyleResourceDependencyHrefOnlyInAttr( if (__DEV__) { checkAttributeStringCoercion(href, 'href'); } - const coercedHref = '' + (href as any); + const coercedHref = '' + (href: any); writeChunk( destination, stringToChunk(escapeTextForBrowser(JSON.stringify(coercedHref))), @@ -5969,7 +5966,7 @@ function writeStyleResourceDependencyInAttr( props: Object, ) { // eslint-disable-next-line react-internal/safe-string-coercion - const coercedHref = sanitizeURL('' + (href as any)); + const coercedHref = sanitizeURL('' + (href: any)); writeChunk( destination, stringToChunk(escapeTextForBrowser(JSON.stringify(coercedHref))), @@ -5978,7 +5975,7 @@ function writeStyleResourceDependencyInAttr( if (__DEV__) { checkAttributeStringCoercion(precedence, 'precedence'); } - const coercedPrecedence = '' + (precedence as any); + const coercedPrecedence = '' + (precedence: any); writeChunk(destination, arrayInterstitial); writeChunk( destination, @@ -6043,7 +6040,7 @@ function writeStyleResourceAttributeInAttr( if (__DEV__) { checkAttributeStringCoercion(value, attributeName); } - attributeValue = '' + (value as any); + attributeValue = '' + (value: any); break; } @@ -6063,7 +6060,7 @@ function writeStyleResourceAttributeInAttr( if (__DEV__) { checkAttributeStringCoercion(value, attributeName); } - attributeValue = '' + (value as any); + attributeValue = '' + (value: any); break; } default: { @@ -6082,7 +6079,7 @@ function writeStyleResourceAttributeInAttr( if (__DEV__) { checkAttributeStringCoercion(value, attributeName); } - attributeValue = '' + (value as any); + attributeValue = '' + (value: any); } } writeChunk(destination, arrayInterstitial); @@ -6241,7 +6238,7 @@ function prefetchDNS(href: string) { } else { // Encode as element const resource: Resource = []; - pushLinkImpl(resource, {href, rel: 'dns-prefetch'} as PreconnectProps); + pushLinkImpl(resource, ({href, rel: 'dns-prefetch'}: PreconnectProps)); renderState.preconnects.add(resource); } } @@ -6299,11 +6296,10 @@ function preconnect(href: string, crossOrigin: ?CrossOriginEnum) { headers.preconnects += header; } else { const resource: Resource = []; - pushLinkImpl(resource, { - rel: 'preconnect', - href, - crossOrigin, - } as PreconnectProps); + pushLinkImpl( + resource, + ({rel: 'preconnect', href, crossOrigin}: PreconnectProps), + ); renderState.preconnects.add(resource); } } @@ -6374,11 +6370,11 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { // When we have imageSrcSet the browser probably cannot load the right version from headers // (this should be verified by testing). For now we assume these need to go in the head // as elements even if headers are available. - const resource = [] as Resource; + const resource = ([]: Resource); pushLinkImpl( resource, Object.assign( - { + ({ rel: 'preload', // There is a bug in Safari where imageSrcSet is not respected on preload links // so we omit the href here if we have imageSrcSet b/c safari will load the wrong image. @@ -6386,7 +6382,7 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { // but this population is shrinking fast and is already small so we accept this tradeoff. href: imageSrcSet ? undefined : href, as, - } as PreloadAsProps, + }: PreloadAsProps), options, ), ); @@ -6407,10 +6403,10 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { // we can return if we already have this resource return; } - const resource = [] as Resource; + const resource = ([]: Resource); pushLinkImpl( resource, - Object.assign({rel: 'preload', href, as} as PreloadAsProps, options), + Object.assign(({rel: 'preload', href, as}: PreloadAsProps), options), ); resumableState.styleResources[key] = options && @@ -6428,12 +6424,12 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { // we can return if we already have this resource return; } - const resource = [] as Resource; + const resource = ([]: Resource); renderState.preloads.scripts.set(key, resource); renderState.bulkPreloads.add(resource); pushLinkImpl( resource, - Object.assign({rel: 'preload', href, as} as PreloadAsProps, options), + Object.assign(({rel: 'preload', href, as}: PreloadAsProps), options), ); resumableState.scriptResources[key] = options && @@ -6454,7 +6450,7 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { return; } } else { - resources = {} as ResumableState['unknownResources']['asType']; + resources = ({}: ResumableState['unknownResources']['asType']); resumableState.unknownResources[as] = resources; } resources[key] = PRELOAD_NO_CREDS; @@ -6487,13 +6483,13 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { } else { // We either don't have headers or we are preloading something that does // not warrant elevated priority so we encode as an element. - const resource = [] as Resource; + const resource = ([]: Resource); const props = Object.assign( - { + ({ rel: 'preload', href, as, - } as PreloadAsProps, + }: PreloadAsProps), options, ); pushLinkImpl(resource, props); @@ -6541,7 +6537,7 @@ function preloadModule( // we can return if we already have this resource return; } - resource = [] as Resource; + resource = ([]: Resource); resumableState.moduleScriptResources[key] = options && (typeof options.crossOrigin === 'string' || @@ -6562,10 +6558,10 @@ function preloadModule( return; } } else { - resources = {} as ResumableState['moduleUnknownResources']['asType']; + resources = ({}: ResumableState['moduleUnknownResources']['asType']); resumableState.moduleUnknownResources[as] = resources; } - resource = [] as Resource; + resource = ([]: Resource); resources[key] = PRELOAD_NO_CREDS; } } @@ -6573,10 +6569,10 @@ function preloadModule( pushLinkImpl( resource, Object.assign( - { + ({ rel: 'modulepreload', href, - } as PreloadModuleProps, + }: PreloadModuleProps), options, ), ); @@ -6621,9 +6617,9 @@ function preinitStyle( if (!styleQueue) { styleQueue = { precedence: stringToChunk(escapeTextForBrowser(precedence)), - rules: [] as Array<Chunk | PrecomputedChunk>, - hrefs: [] as Array<Chunk | PrecomputedChunk>, - sheets: new Map() as Map<string, StylesheetResource>, + rules: ([]: Array<Chunk | PrecomputedChunk>), + hrefs: ([]: Array<Chunk | PrecomputedChunk>), + sheets: (new Map(): Map<string, StylesheetResource>), }; renderState.styles.set(precedence, styleQueue); } @@ -6631,11 +6627,11 @@ function preinitStyle( const resource = { state: PENDING, props: Object.assign( - { + ({ rel: 'stylesheet', href, 'data-precedence': precedence, - } as StylesheetProps, + }: StylesheetProps), options, ), }; @@ -6700,10 +6696,10 @@ function preinitScript(src: string, options?: ?PreinitScriptOptions): void { resumableState.scriptResources[key] = EXISTS; const props: ScriptProps = Object.assign( - { + ({ src, async: true, - } as ScriptProps, + }: ScriptProps), options, ); if (resourceState) { @@ -6762,11 +6758,11 @@ function preinitModuleScript( resumableState.moduleScriptResources[key] = EXISTS; const props = Object.assign( - { + ({ src, type: 'module', async: true, - } as ModuleScriptProps, + }: ModuleScriptProps), options, ); if (resourceState) { diff --git a/packages/react-dom-bindings/src/server/escapeTextForBrowser.js b/packages/react-dom-bindings/src/server/escapeTextForBrowser.js index ab16d892dd39..4514bf66831d 100644 --- a/packages/react-dom-bindings/src/server/escapeTextForBrowser.js +++ b/packages/react-dom-bindings/src/server/escapeTextForBrowser.js @@ -114,7 +114,7 @@ function escapeTextForBrowser(text: string | number | boolean): string { // this shortcircuit helps perf for types that we know will never have // special characters, especially given that this function is used often // for numeric dom ids. - return '' + (text as any); + return '' + (text: any); } return escapeHtml(text); } diff --git a/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js b/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js index ab5d0b99b4ea..6978f4884506 100644 --- a/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js +++ b/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js @@ -61,7 +61,7 @@ function resolveDispatcher() { // Will result in a null access error if accessed outside render phase. We // intentionally don't throw our own error because this is in a hot path. // Also helps ensure this is inlined. - return dispatcher as any as Dispatcher; + return ((dispatcher: any): Dispatcher); } export function useFormStatus(): FormStatus { diff --git a/packages/react-dom-bindings/src/shared/sanitizeURL.js b/packages/react-dom-bindings/src/shared/sanitizeURL.js index 568046165642..477bb9f9c921 100644 --- a/packages/react-dom-bindings/src/shared/sanitizeURL.js +++ b/packages/react-dom-bindings/src/shared/sanitizeURL.js @@ -22,7 +22,7 @@ const isJavaScriptProtocol = function sanitizeURL<T>(url: T): T | string { // We should never have symbols here because they get filtered out elsewhere. // eslint-disable-next-line react-internal/safe-string-coercion - if (isJavaScriptProtocol.test('' + (url as any))) { + if (isJavaScriptProtocol.test('' + (url: any))) { // Return a different javascript: url that doesn't cause any side-effects and just // throws if ever visited. // eslint-disable-next-line no-script-url diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index dc7b060a7959..351ed0684e64 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -5,7 +5,7 @@ "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-dom" }, "keywords": [ @@ -13,7 +13,7 @@ ], "license": "MIT", "bugs": { - "url": "https://github.com/react/react/issues" + "url": "https://github.com/facebook/react/issues" }, "homepage": "https://react.dev/", "dependencies": { diff --git a/packages/react-dom/src/ReactDOMFB.js b/packages/react-dom/src/ReactDOMFB.js index f1af3138dfaf..4825c80dfb33 100644 --- a/packages/react-dom/src/ReactDOMFB.js +++ b/packages/react-dom/src/ReactDOMFB.js @@ -12,7 +12,7 @@ import {isEnabled} from 'react-dom-bindings/src/events/ReactDOMEventListener'; import Internals from './ReactDOMSharedInternalsFB'; // For classic WWW builds, include a few internals that are already in use. -Object.assign(Internals as any, { +Object.assign((Internals: any), { ReactBrowserEventEmitter: { isEnabled, }, diff --git a/packages/react-dom/src/ReactDOMSharedInternals.js b/packages/react-dom/src/ReactDOMSharedInternals.js index da42b806f66c..b82535b67a15 100644 --- a/packages/react-dom/src/ReactDOMSharedInternals.js +++ b/packages/react-dom/src/ReactDOMSharedInternals.js @@ -14,7 +14,7 @@ import noop from 'shared/noop'; // This should line up with NoEventPriority from react-reconciler/src/ReactEventPriorities // but we can't depend on the react-reconciler from this isomorphic code. -export const NoEventPriority: EventPriority = 0 as any; +export const NoEventPriority: EventPriority = (0: any); type ReactDOMInternals = { d /* ReactDOMCurrentDispatcher */: HostDispatcher, diff --git a/packages/react-dom/src/ReactDOMSharedInternalsFB.js b/packages/react-dom/src/ReactDOMSharedInternalsFB.js index 1970bf344af4..6c91096c7478 100644 --- a/packages/react-dom/src/ReactDOMSharedInternalsFB.js +++ b/packages/react-dom/src/ReactDOMSharedInternalsFB.js @@ -36,7 +36,7 @@ const DefaultDispatcher: HostDispatcher = { }; const Internals: ReactDOMInternals = { - Events: null as any, + Events: (null: any), d /* ReactDOMCurrentDispatcher */: DefaultDispatcher, p /* currentUpdatePriority */: NoEventPriority, findDOMNode: null, diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 5a96a30a517b..efb7e887cf18 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -3562,129 +3562,6 @@ describe('ReactDOMFizzServer', () => { ); }); - it('reports abort errors for every suspended task when aborting fatals the shell', async () => { - const promise = new Promise(() => {}); - const rendered = []; - function Suspend({label}) { - rendered.push(label); - use(promise); - return null; - } - - function App() { - return ( - <> - <Suspense fallback="Loading..."> - <Suspend label="boundary" /> - </Suspense> - <Suspend label="root one" /> - <Suspend label="root two" /> - </> - ); - } - - const errors = []; - let abort; - await act(() => { - abort = renderToPipeableStream(<App />, { - onError(error) { - errors.push(error.message); - }, - onShellError() {}, - }).abort; - }); - - expect(rendered).toEqual(['boundary', 'root one', 'root two']); - - await act(() => { - abort(new Error('abort reason')); - }); - - expect(errors).toEqual(['abort reason', 'abort reason', 'abort reason']); - }); - - it('uses a rejection reason from a lazy component before the abort finishes', async () => { - let reject; - const Lazy = React.lazy( - () => - new Promise((resolve, rejectPromise) => { - reject = rejectPromise; - }), - ); - const haltedPromise = new Promise(() => {}); - function HaltedWait() { - use(haltedPromise); - return null; - } - - const errors = []; - let abort; - await act(() => { - const controls = renderToPipeableStream( - <> - <Suspense fallback="Loading lazy"> - <Lazy /> - </Suspense> - <Suspense fallback="Loading halted"> - <HaltedWait /> - </Suspense> - </>, - { - onError(error) { - errors.push(error.message); - }, - }, - ); - abort = controls.abort; - controls.pipe(writable); - }); - - await act(() => { - abort(new Error('abort reason')); - reject(new Error('rejected during abort')); - }); - - expect(errors).toEqual(['rejected during abort', 'abort reason']); - }); - - it('does not report a rejection reason after abort has finished', async () => { - let reject; - const promise = new Promise((resolve, rejectPromise) => { - reject = rejectPromise; - }); - function Wait() { - use(promise); - return null; - } - - const errors = []; - let abort; - await act(() => { - const controls = renderToPipeableStream( - <Suspense fallback="Loading"> - <Wait /> - </Suspense>, - { - onError(error) { - errors.push(error.message); - }, - }, - ); - abort = controls.abort; - controls.pipe(writable); - }); - - await act(() => { - abort(new Error('abort reason')); - }); - - await act(() => { - reject(new Error('rejected after abort')); - }); - - expect(errors).toEqual(['abort reason']); - }); - it('warns in dev if you access digest from errorInfo in onRecoverableError', async () => { await act(() => { const {pipe} = renderToPipeableStream( @@ -3926,8 +3803,8 @@ describe('ReactDOMFizzServer', () => { expect(headers).toEqual({ Link: ` -<non-responsive-preload>; rel=preload; as="image"; fetchpriority="high", - <non-responsive-img>; rel=preload; as="image"; fetchpriority="high" +<non-responsive-preload>; rel=preload; as="image"; fetchpriority="high", +<non-responsive-img>; rel=preload; as="image"; fetchpriority="high" ` .replaceAll('\n', '') .trim(), @@ -4126,46 +4003,10 @@ describe('ReactDOMFizzServer', () => { await act(() => pipe(testWritable)); expect(didRender).toBe(false); expect(didFatal).toBe(didFatal); - expect(errors).toEqual(['boom']); - }); - - it('does not report aborts after fatally erroring', async () => { - const promise = new Promise(() => {}); - function AsyncComp() { - React.use(promise); - return 'Async'; - } - - function ErrorComp() { - throw new Error('boom'); - } - - const errors = []; - let abort; - await act(() => { - abort = renderToPipeableStream( - <div> - <Suspense fallback="loading..."> - <AsyncComp /> - </Suspense> - <ErrorComp /> - </div>, - { - onError(error) { - errors.push(error.message); - }, - onShellError() {}, - }, - ).abort; - }); - - expect(errors).toEqual(['boom']); - - await act(() => { - abort(new Error('too late')); - }); - - expect(errors).toEqual(['boom']); + expect(errors).toEqual([ + 'boom', + 'The destination stream errored while writing data.', + ]); }); describe('error escaping', () => { @@ -7145,107 +6986,6 @@ describe('ReactDOMFizzServer', () => { ); }); - it('reports an in-flight root task after another root task fatals while aborting', async () => { - const promise = new Promise(() => {}); - function SuspendedRoot() { - use(promise); - return null; - } - - function Child() { - return 'child'; - } - - const abortRef = {current: null}; - function ComponentThatAborts() { - abortRef.current(new Error('abort reason')); - return <Child />; - } - - const errors = []; - await act(() => { - const {abort} = renderToPipeableStream( - <> - <SuspendedRoot /> - <ComponentThatAborts /> - </>, - { - onError(error) { - errors.push(error.message); - }, - onShellError() {}, - }, - ); - abortRef.current = abort; - }); - - expect(errors).toEqual(['abort reason', 'abort reason']); - }); - - it('reports a root task before rendering a suspended child returned after aborting', async () => { - const promise = new Promise(() => {}); - function SuspendedRoot() { - use(promise); - return null; - } - - function Child() { - use(promise); - return null; - } - - const abortRef = {current: null}; - function ComponentThatAborts() { - abortRef.current(new Error('abort reason')); - return <Child />; - } - - const errors = []; - await act(() => { - const {abort} = renderToPipeableStream( - <> - <SuspendedRoot /> - <ComponentThatAborts /> - </>, - { - onError(error) { - errors.push(error.message); - }, - onShellError() {}, - }, - ); - abortRef.current = abort; - }); - - expect(errors).toEqual(['abort reason', 'abort reason']); - }); - - it('reports a root task that suspends directly after aborting during render', async () => { - const promise = new Promise(() => {}); - const abortRef = {current: null}; - function ComponentThatAbortsAndSuspends() { - abortRef.current(new Error('abort reason')); - use(promise); - return null; - } - - const errors = []; - await act(() => { - const {abort} = renderToPipeableStream( - <ComponentThatAbortsAndSuspends />, - { - onError(error) { - errors.push(error.message); - }, - onShellError() {}, - }, - ); - abortRef.current = abort; - }); - - expect(errors).toEqual(['abort reason']); - }); - it('can abort during render in a lazy initializer for a component', async () => { function Sibling() { return <p>sibling</p>; @@ -8550,11 +8290,11 @@ describe('ReactDOMFizzServer', () => { } expect(thrownError).toBe('boom'); + // TODO there should actually be three errors. One for the pending Suspense, one for the fallback task, and one for the task + // that does the abort itself. At the moment abort will flush queues and if there is no pending tasks will close the request before + // the task which initiated the abort can even be processed. This is a bug but not one that I am fixing with the current change + // so I am asserting the current behavior expect(errors).toEqual([ - { - error: 'boom', - componentStack: componentStack(['Abort', 'body', 'html', 'App']), - }, { error: 'boom', componentStack: componentStack([ @@ -8573,6 +8313,9 @@ describe('ReactDOMFizzServer', () => { 'html', 'App', ]), + // }, { + // error: 'boom', + // componentStack: componentStack(['Abort', 'body', 'html', 'App']) }, ]); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js index 9bd3f641040e..27e74750c456 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js @@ -221,9 +221,7 @@ describe('ReactDOMFizzServerBrowser', () => { ), ); - await serverAct(() => { - controller.abort(); - }); + controller.abort(); const result = await readResult(stream); expect(result).toContain('Loading'); @@ -249,9 +247,7 @@ describe('ReactDOMFizzServerBrowser', () => { ); const theReason = new Error('aborted for reasons'); - await serverAct(() => { - controller.abort(theReason); - }); + controller.abort(theReason); let caughtError = null; try { @@ -368,7 +364,7 @@ describe('ReactDOMFizzServerBrowser', () => { const reader = stream.getReader(); await reader.read(); - await serverAct(() => reader.cancel()); + await reader.cancel(); expect(errors).toEqual([ 'The render was aborted by the server without a reason.', @@ -467,9 +463,7 @@ describe('ReactDOMFizzServerBrowser', () => { }), ); - await serverAct(() => { - controller.abort('foobar'); - }); + controller.abort('foobar'); expect(errors).toEqual(['foobar', 'foobar']); }); @@ -508,9 +502,7 @@ describe('ReactDOMFizzServerBrowser', () => { }), ); - await serverAct(() => { - controller.abort(new Error('uh oh')); - }); + controller.abort(new Error('uh oh')); expect(errors).toEqual(['uh oh', 'uh oh']); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js index ad9678a2b366..98030d43386c 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js @@ -237,37 +237,6 @@ describe('ReactDOMFizzServerNode', () => { expect(reportedShellErrors).toEqual([theError]); }); - it('should not report aborts after the shell has fatally errored', async () => { - const reportedErrors = []; - const reportedShellErrors = []; - const {abort} = ReactDOMFizzServer.renderToPipeableStream( - <div> - <Suspense fallback="Loading"> - <InfiniteSuspend /> - </Suspense> - <Throw /> - </div>, - { - onError(x) { - reportedErrors.push(x); - }, - onShellError(x) { - reportedShellErrors.push(x); - }, - }, - ); - - await jest.runAllTimers(); - - expect(reportedErrors).toEqual([theError]); - expect(reportedShellErrors).toEqual([theError]); - - abort(new Error('too late')); - - expect(reportedErrors).toEqual([theError]); - expect(reportedShellErrors).toEqual([theError]); - }); - it('should error the stream when an error is thrown inside a fallback', async () => { const reportedErrors = []; const reportedShellErrors = []; @@ -294,7 +263,10 @@ describe('ReactDOMFizzServerNode', () => { expect(output.error).toBe(theError); expect(output.result).toBe(''); - expect(reportedErrors).toEqual([theError.message]); + expect(reportedErrors).toEqual([ + theError.message, + 'The destination stream errored while writing data.', + ]); expect(reportedShellErrors).toEqual([theError]); }); @@ -382,7 +354,6 @@ describe('ReactDOMFizzServerNode', () => { expect(isCompleteCalls).toBe(0); abort(new Error('uh oh')); - await jest.runAllTimers(); await completed; @@ -435,46 +406,6 @@ describe('ReactDOMFizzServerNode', () => { expect(isCompleteCalls).toBe(0); }); - it('should report abort errors for every suspended task but fail the shell only once', async () => { - const promise = new Promise(() => {}); - const rendered = []; - function Suspend({label}) { - rendered.push(label); - React.use(promise); - return null; - } - - const errors = []; - const shellErrors = []; - const {abort} = ReactDOMFizzServer.renderToPipeableStream( - <> - <Suspense fallback="Loading..."> - <Suspend label="boundary" /> - </Suspense> - <Suspend label="root one" /> - <Suspend label="root two" /> - </>, - { - onError(error) { - errors.push(error.message); - }, - onShellError(error) { - shellErrors.push(error); - }, - }, - ); - - await jest.runAllTimers(); - expect(rendered).toEqual(['boundary', 'root one', 'root two']); - - const reason = new Error('abort reason'); - abort(reason); - await jest.runAllTimers(); - - expect(shellErrors).toEqual([reason]); - expect(errors).toEqual(['abort reason', 'abort reason', 'abort reason']); - }); - it('should be able to complete by abort when the fallback is also suspended', async () => { let isCompleteCalls = 0; const errors = []; @@ -506,7 +437,6 @@ describe('ReactDOMFizzServerNode', () => { expect(isCompleteCalls).toBe(0); abort(); - await jest.runAllTimers(); await completed; @@ -749,7 +679,6 @@ describe('ReactDOMFizzServerNode', () => { resolve(); await completed; - await jest.runAllTimers(); expect(errors).toEqual([ 'The destination stream errored while writing data.', diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js index 5b0d69f9f35e..30dce64f1049 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js @@ -299,9 +299,7 @@ describe('ReactDOMFizzStaticBrowser', () => { ); }); - await serverAct(() => { - controller.abort(); - }); + controller.abort(); const result = await resultPromise; @@ -331,9 +329,7 @@ describe('ReactDOMFizzStaticBrowser', () => { await jest.runAllTimers(); const theReason = new Error('aborted for reasons'); - await serverAct(() => { - controller.abort(theReason); - }); + controller.abort(theReason); let rejected = false; let prelude; @@ -379,29 +375,6 @@ describe('ReactDOMFizzStaticBrowser', () => { expect(content).toBe(''); }); - it('reports the abort reason if a task suspends after aborting a prerender', async () => { - const promise = new Promise(() => {}); - const errors = []; - const controller = new AbortController(); - function App() { - controller.abort(new Error('abort reason')); - React.use(promise); - return null; - } - - const result = await serverAct(() => - ReactDOMFizzStatic.prerender(<App />, { - signal: controller.signal, - onError(error) { - errors.push(error.message); - }, - }), - ); - - expect(errors).toEqual(['abort reason']); - expect(await readContent(result.prelude)).toBe(''); - }); - it('should resolve an empty prelude if passing an already aborted signal', async () => { const errors = []; const controller = new AbortController(); @@ -474,9 +447,7 @@ describe('ReactDOMFizzStaticBrowser', () => { }); }); - await serverAct(() => { - controller.abort('foobar'); - }); + controller.abort('foobar'); await resultPromise; @@ -518,63 +489,13 @@ describe('ReactDOMFizzStaticBrowser', () => { }); }); - await serverAct(() => { - controller.abort(new Error('uh oh')); - }); + controller.abort(new Error('uh oh')); await resultPromise; expect(errors).toEqual(['uh oh', 'uh oh']); }); - it('uses a rejection reason when an abort listener rejects pending work before the abort finishes', async () => { - let reject; - const rejectedPromise = new Promise((resolve, rejectPromise) => { - reject = rejectPromise; - }); - const haltedPromise = new Promise(() => {}); - function RejectedWait() { - React.use(rejectedPromise); - return null; - } - function HaltedWait() { - React.use(haltedPromise); - return null; - } - - const errors = []; - const controller = new AbortController(); - let resultPromise; - await serverAct(() => { - resultPromise = ReactDOMFizzStatic.prerender( - <> - <Suspense fallback="Loading rejected"> - <RejectedWait /> - </Suspense> - <Suspense fallback="Loading halted"> - <HaltedWait /> - </Suspense> - </>, - { - signal: controller.signal, - onError(error) { - errors.push(error.message); - }, - }, - ); - }); - - controller.signal.addEventListener('abort', () => { - reject(new Error('rejected during abort')); - }); - await serverAct(() => { - controller.abort(new Error('abort reason')); - }); - await resultPromise; - - expect(errors).toEqual(['rejected during abort', 'abort reason']); - }); - it('logs an error if onHeaders throws but continues the prerender', async () => { const errors = []; function onError(error) { @@ -641,9 +562,7 @@ describe('ReactDOMFizzStaticBrowser', () => { }); }); - await serverAct(() => { - controller.abort(); - }); + controller.abort(); const prerendered = await pendingResult; const postponedState = JSON.stringify(prerendered.postponed); @@ -668,9 +587,7 @@ describe('ReactDOMFizzStaticBrowser', () => { ); }); - await serverAct(() => { - controller2.abort(); - }); + controller2.abort(); const prerendered2 = await pendingResult; const postponedState2 = JSON.stringify(prerendered2.postponed); @@ -688,179 +605,6 @@ describe('ReactDOMFizzStaticBrowser', () => { expect(getVisibleChildren(container)).toEqual(<div>Hello</div>); }); - it('can abort while replaying a prerendered tree', async () => { - const promise = new Promise(() => {}); - let prerendering = true; - const resumeController = new AbortController(); - - function AbortDuringReplay({children}) { - if (!prerendering) { - resumeController.abort('resume abort'); - } - return children; - } - - function Wait() { - return prerendering ? React.use(promise) : 'Hello'; - } - - function App() { - return ( - <div> - <AbortDuringReplay> - <Suspense fallback="Loading 1..."> - <Wait /> - </Suspense> - </AbortDuringReplay> - </div> - ); - } - - const controller = new AbortController(); - let pendingResult; - await serverAct(() => { - pendingResult = ReactDOMFizzStatic.prerender(<App />, { - signal: controller.signal, - onError() {}, - }); - }); - await serverAct(() => { - controller.abort('prerender abort'); - }); - const prerendered = await pendingResult; - - prerendering = false; - const errors = []; - await serverAct(() => - ReactDOMFizzServer.resume( - <App />, - JSON.parse(JSON.stringify(prerendered.postponed)), - { - signal: resumeController.signal, - onError(error) { - errors.push(error); - }, - }, - ), - ); - - expect(errors).toEqual(['resume abort']); - }); - - it('can abort and suspend while replaying a prerendered tree', async () => { - const promise = new Promise(() => {}); - let prerendering = true; - const resumeController = new AbortController(); - - function AbortDuringReplay({children}) { - if (!prerendering) { - resumeController.abort('resume abort'); - React.use(promise); - } - return children; - } - - function Wait() { - return React.use(promise); - } - - function App() { - return ( - <div> - <AbortDuringReplay> - <Suspense fallback="Loading..."> - <Wait /> - </Suspense> - </AbortDuringReplay> - </div> - ); - } - - const controller = new AbortController(); - let pendingResult; - await serverAct(() => { - pendingResult = ReactDOMFizzStatic.prerender(<App />, { - signal: controller.signal, - onError() {}, - }); - }); - await serverAct(() => { - controller.abort('prerender abort'); - }); - const prerendered = await pendingResult; - - prerendering = false; - const errors = []; - await serverAct(() => - ReactDOMFizzServer.resume( - <App />, - JSON.parse(JSON.stringify(prerendered.postponed)), - { - signal: resumeController.signal, - onError(error) { - errors.push(error); - }, - }, - ), - ); - - expect(errors).toEqual(['resume abort']); - }); - - it('can abort while rendering a resumed segment', async () => { - const promise = new Promise(() => {}); - let prerendering = true; - const resumeController = new AbortController(); - - function Wait() { - if (prerendering) { - return React.use(promise); - } - resumeController.abort('resume abort'); - return 'Hello'; - } - - function App() { - return ( - <div> - <Suspense fallback="Loading..."> - <Wait /> - </Suspense> - </div> - ); - } - - const controller = new AbortController(); - let pendingResult; - await serverAct(() => { - pendingResult = ReactDOMFizzStatic.prerender(<App />, { - signal: controller.signal, - onError() {}, - }); - }); - await serverAct(() => { - controller.abort('prerender abort'); - }); - const prerendered = await pendingResult; - - prerendering = false; - const errors = []; - await serverAct(() => - ReactDOMFizzServer.resume( - <App />, - JSON.parse(JSON.stringify(prerendered.postponed)), - { - signal: resumeController.signal, - onError(error) { - errors.push(error); - }, - }, - ), - ); - - expect(errors).toEqual(['resume abort']); - }); - it('can prerender a preamble', async () => { const errors = []; @@ -908,9 +652,7 @@ describe('ReactDOMFizzStaticBrowser', () => { }); }); - await serverAct(() => { - controller.abort(); - }); + controller.abort(); const prerendered = await pendingResult; const postponedState = JSON.stringify(prerendered.postponed); @@ -941,9 +683,7 @@ describe('ReactDOMFizzStaticBrowser', () => { ); }); - await serverAct(() => { - controller2.abort(); - }); + controller2.abort(); const prerendered2 = await pendingResult; const postponedState2 = JSON.stringify(prerendered2.postponed); @@ -1006,9 +746,7 @@ describe('ReactDOMFizzStaticBrowser', () => { }); }); - await serverAct(() => { - controller.abort(new Error('boom')); - }); + controller.abort(new Error('boom')); const prerendered = await pendingResult; @@ -1070,9 +808,7 @@ describe('ReactDOMFizzStaticBrowser', () => { }); }); - await serverAct(() => { - controller.abort(); - }); + controller.abort(); const prerendered = await pendingResult; @@ -1150,9 +886,7 @@ describe('ReactDOMFizzStaticBrowser', () => { }); }); - await serverAct(() => { - controller.abort(); - }); + controller.abort(); const prerendered = await pendingResult; const postponedState = JSON.stringify(prerendered.postponed); @@ -1178,9 +912,7 @@ describe('ReactDOMFizzStaticBrowser', () => { ); }); - await serverAct(() => { - controller2.abort(); - }); + controller2.abort(); const prerendered2 = await pendingResult; const postponedState2 = JSON.stringify(prerendered2.postponed); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js index 2c36913fb954..83bc6cb5d306 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js @@ -14,35 +14,6 @@ let React; let ReactDOMFizzStatic; let Suspense; -function normalizeCodeLocInfo(str) { - return ( - str && - str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) { - const dot = name.lastIndexOf('.'); - if (dot !== -1) { - name = name.slice(dot + 1); - } - return ' in ' + name + (/\d/.test(m) ? ' (at **)' : ''); - }) - ); -} - -function ignoreListStack(str) { - if (!str) { - return str; - } - - let ignoreListedStack = ''; - const lines = str.split('\n'); - // eslint-disable-next-line no-for-of-loops/no-for-of-loops - for (const line of lines) { - if (line.indexOf(__filename) !== -1) { - ignoreListedStack += '\n' + line; - } - } - return ignoreListedStack; -} - describe('ReactDOMFizzStaticNode', () => { beforeEach(() => { jest.resetModules(); @@ -245,7 +216,6 @@ describe('ReactDOMFizzStaticNode', () => { await jest.runAllTimers(); controller.abort(); - await jest.runAllTimers(); const result = await resultPromise; @@ -274,7 +244,6 @@ describe('ReactDOMFizzStaticNode', () => { const theReason = new Error('aborted for reasons'); controller.abort(theReason); - await jest.runAllTimers(); let didThrow = false; let prelude; @@ -312,37 +281,12 @@ describe('ReactDOMFizzStaticNode', () => { }, ); - await jest.runAllTimers(); const {prelude} = await streamPromise; const content = await readContent(prelude); expect(errors).toEqual(['This operation was aborted']); expect(content).toBe(''); }); - it('reports the abort reason if a task suspends after aborting a prerender', async () => { - const promise = new Promise(() => {}); - const errors = []; - const controller = new AbortController(); - function App() { - controller.abort(new Error('abort reason')); - React.use(promise); - return null; - } - - const resultPromise = ReactDOMFizzStatic.prerenderToNodeStream(<App />, { - signal: controller.signal, - onError(error) { - errors.push(error.message); - }, - }); - - await jest.runAllTimers(); - const result = await resultPromise; - - expect(errors).toEqual(['abort reason']); - expect(await readContent(result.prelude)).toBe(''); - }); - it('should resolve with an empty prelude if passing an already aborted signal', async () => { const errors = []; const controller = new AbortController(); @@ -365,7 +309,6 @@ describe('ReactDOMFizzStaticNode', () => { // Technically we could still continue rendering the shell but currently the // semantics mean that we also abort any pending CPU work. - await jest.runAllTimers(); let didThrow = false; let prelude; @@ -415,7 +358,6 @@ describe('ReactDOMFizzStaticNode', () => { await jest.runAllTimers(); controller.abort('foobar'); - await jest.runAllTimers(); await resultPromise; @@ -457,115 +399,9 @@ describe('ReactDOMFizzStaticNode', () => { await jest.runAllTimers(); controller.abort(new Error('uh oh')); - await jest.runAllTimers(); await resultPromise; expect(errors).toEqual(['uh oh', 'uh oh']); }); - - it('uses a rejection reason when an abort listener rejects pending work before the abort finishes', async () => { - let reject; - const rejectedPromise = new Promise((resolve, rejectPromise) => { - reject = rejectPromise; - }); - const haltedPromise = new Promise(() => {}); - function RejectedWait() { - React.use(rejectedPromise); - return null; - } - function HaltedWait() { - React.use(haltedPromise); - return null; - } - - const errors = []; - const controller = new AbortController(); - const resultPromise = ReactDOMFizzStatic.prerenderToNodeStream( - <> - <Suspense fallback="Loading rejected"> - <RejectedWait /> - </Suspense> - <Suspense fallback="Loading halted"> - <HaltedWait /> - </Suspense> - </>, - { - signal: controller.signal, - onError(error) { - errors.push(error.message); - }, - }, - ); - - await jest.runAllTimers(); - - controller.signal.addEventListener('abort', () => { - reject(new Error('rejected during abort')); - }); - controller.abort(new Error('abort reason')); - - await Promise.resolve(); - await jest.runAllTimers(); - await resultPromise; - - expect(errors).toEqual(['rejected during abort', 'abort reason']); - }); - - describe('with real timers', () => { - beforeEach(() => { - jest.useRealTimers(); - }); - - afterEach(() => { - jest.useFakeTimers(); - }); - - it('includes the suspended call site when aborting in the same rendering task', async () => { - const promise = new Promise(() => {}); - const controller = new AbortController(); - let caughtError; - let componentStack; - let ownerStack; - - function AbortAndSuspend() { - controller.abort(new Error('abort reason')); - React.use(promise); - return null; - } - - function App() { - return <AbortAndSuspend />; - } - - const {prelude} = await ReactDOMFizzStatic.prerenderToNodeStream( - <App />, - { - signal: controller.signal, - onError(error, errorInfo) { - caughtError = error; - componentStack = errorInfo.componentStack; - ownerStack = __DEV__ ? React.captureOwnerStack() : null; - }, - }, - ); - - expect(caughtError).toEqual( - expect.objectContaining({message: 'abort reason'}), - ); - expect(await readContent(prelude)).toBe(''); - if (__DEV__) { - expect(normalizeCodeLocInfo(componentStack)).toBe( - '\n in AbortAndSuspend (at **)\n in App', - ); - expect(normalizeCodeLocInfo(ignoreListStack(ownerStack))).toBe( - (gate(flags => flags.enableAsyncDebugInfo) - ? '\n in AbortAndSuspend (at **)' - : '') + '\n in App (at **)', - ); - } else { - expect(ownerStack).toBeNull(); - } - }); - }); }); diff --git a/packages/react-dom/src/client/ReactDOMClientFB.js b/packages/react-dom/src/client/ReactDOMClientFB.js index 58254e64ec8a..872a4550aa36 100644 --- a/packages/react-dom/src/client/ReactDOMClientFB.js +++ b/packages/react-dom/src/client/ReactDOMClientFB.js @@ -82,7 +82,7 @@ function createPortal( } // TODO: pass ReactDOM portal implementation as third argument - // $FlowFixMe[incompatible-type] The Flow type is opaque but there's no way to actually create it. + // $FlowFixMe[incompatible-return] The Flow type is opaque but there's no way to actually create it. return createPortalImpl(children, container, null, key); } @@ -100,7 +100,7 @@ function flushSyncFromReconciler<R>(fn: (() => R) | void): R | void { ); } } - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] return flushSyncWithoutWarningIfAlreadyRendering(fn); } diff --git a/packages/react-dom/src/client/ReactDOMDefaultTransitionIndicator.js b/packages/react-dom/src/client/ReactDOMDefaultTransitionIndicator.js index b345480312dc..37661849cad8 100644 --- a/packages/react-dom/src/client/ReactDOMDefaultTransitionIndicator.js +++ b/packages/react-dom/src/client/ReactDOMDefaultTransitionIndicator.js @@ -43,11 +43,11 @@ export function defaultOnDefaultTransitionIndicator(): void | (() => void) { } } - // $FlowFixMe[incompatible-type] + // $FlowFixMe navigation.addEventListener('navigate', handleNavigate); - // $FlowFixMe[incompatible-type] + // $FlowFixMe navigation.addEventListener('navigatesuccess', handleNavigateComplete); - // $FlowFixMe[incompatible-type] + // $FlowFixMe navigation.addEventListener('navigateerror', handleNavigateComplete); function startFakeNavigation() { @@ -76,11 +76,11 @@ export function defaultOnDefaultTransitionIndicator(): void | (() => void) { return function () { isCancelled = true; - // $FlowFixMe[incompatible-type] + // $FlowFixMe navigation.removeEventListener('navigate', handleNavigate); - // $FlowFixMe[incompatible-type] + // $FlowFixMe navigation.removeEventListener('navigatesuccess', handleNavigateComplete); - // $FlowFixMe[incompatible-type] + // $FlowFixMe navigation.removeEventListener('navigateerror', handleNavigateComplete); if (pendingResolve !== null) { pendingResolve(); diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index bb2093f1992e..a32cefe81d5b 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -187,19 +187,17 @@ export function createRoot( let onDefaultTransitionIndicator = defaultOnDefaultTransitionIndicator; let transitionCallbacks = null; - // $FlowFixMe[invalid-compare] if (options !== null && options !== undefined) { if (__DEV__) { - if ((options as any).hydrate) { + if ((options: any).hydrate) { console.warn( 'hydrate through createRoot is deprecated. Use ReactDOMClient.hydrateRoot(container, <App />) instead.', ); } else { if ( typeof options === 'object' && - // $FlowFixMe[invalid-compare] options !== null && - (options as any).$$typeof === REACT_ELEMENT_TYPE + (options: any).$$typeof === REACT_ELEMENT_TYPE ) { console.error( 'You passed a JSX element to createRoot. You probably meant to ' + @@ -253,7 +251,7 @@ export function createRoot( const rootContainerElement: Document | Element | DocumentFragment = !disableCommentsAsDOMContainers && container.nodeType === COMMENT_NODE - ? (container.parentNode as any) + ? (container.parentNode: any) : container; listenToAllSupportedEvents(rootContainerElement); @@ -306,7 +304,6 @@ export function hydrateRoot( let onDefaultTransitionIndicator = defaultOnDefaultTransitionIndicator; let transitionCallbacks = null; let formState = null; - // $FlowFixMe[invalid-compare] if (options !== null && options !== undefined) { if (options.unstable_strictMode === true) { isStrictMode = true; diff --git a/packages/react-dom/src/client/ReactDOMRootFB.js b/packages/react-dom/src/client/ReactDOMRootFB.js index c6a73a35e3e9..3a0205fe799c 100644 --- a/packages/react-dom/src/client/ReactDOMRootFB.js +++ b/packages/react-dom/src/client/ReactDOMRootFB.js @@ -135,11 +135,11 @@ export function createRoot( return createRootImpl( container, assign( - { + ({ onUncaughtError: wwwOnUncaughtError, onCaughtError: wwwOnCaughtError, onDefaultTransitionIndicator: noopOnDefaultTransitionIndicator, - } as any, + }: any), options, ), ); @@ -154,11 +154,11 @@ export function hydrateRoot( container, initialChildren, assign( - { + ({ onUncaughtError: wwwOnUncaughtError, onCaughtError: wwwOnCaughtError, onDefaultTransitionIndicator: noopOnDefaultTransitionIndicator, - } as any, + }: any), options, ), ); @@ -255,7 +255,7 @@ function legacyCreateRootFromDOMContainer( !disableCommentsAsDOMContainers && container.nodeType === COMMENT_NODE ? container.parentNode : container; - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] listenToAllSupportedEvents(rootContainerElement); flushSyncWork(); @@ -292,7 +292,7 @@ function legacyCreateRootFromDOMContainer( !disableCommentsAsDOMContainers && container.nodeType === COMMENT_NODE ? container.parentNode : container; - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] listenToAllSupportedEvents(rootContainerElement); // Initial mount should not be batched. @@ -376,8 +376,8 @@ export function findDOMNode( if (componentOrElement == null) { return null; } - if ((componentOrElement as any).nodeType === ELEMENT_NODE) { - return componentOrElement as any; + if ((componentOrElement: any).nodeType === ELEMENT_NODE) { + return (componentOrElement: any); } if (__DEV__) { return findHostInstanceWithWarning(componentOrElement, 'findDOMNode'); @@ -425,7 +425,6 @@ export function render( } return legacyRenderSubtreeIntoContainer( null, - // $FlowFixMe[incompatible-type] element, container, false, diff --git a/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js b/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js index 51c25c0f765f..75c0768b3248 100644 --- a/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js +++ b/packages/react-dom/src/server/ReactDOMFizzServerBrowser.js @@ -85,7 +85,7 @@ function renderToReadableStream( }); function onShellReady() { - const stream: ReactDOMServerReadableStream = new ReadableStream( + const stream: ReactDOMServerReadableStream = (new ReadableStream( { type: 'bytes', pull: (controller): ?Promise<void> => { @@ -97,9 +97,8 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, - ) as any; + ): any); // TODO: Move to sub-classing ReadableStream. stream.allReady = allReady; resolve(stream); @@ -150,10 +149,10 @@ function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -177,7 +176,7 @@ function resume( }); function onShellReady() { - const stream: ReactDOMServerReadableStream = new ReadableStream( + const stream: ReactDOMServerReadableStream = (new ReadableStream( { type: 'bytes', pull: (controller): ?Promise<void> => { @@ -189,9 +188,8 @@ function resume( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, - ) as any; + ): any); // TODO: Move to sub-classing ReadableStream. stream.allReady = allReady; resolve(stream); @@ -219,10 +217,10 @@ function resume( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-dom/src/server/ReactDOMFizzServerBun.js b/packages/react-dom/src/server/ReactDOMFizzServerBun.js index 2d9c6d1edbcc..90d6ccdad2e5 100644 --- a/packages/react-dom/src/server/ReactDOMFizzServerBun.js +++ b/packages/react-dom/src/server/ReactDOMFizzServerBun.js @@ -74,11 +74,11 @@ function renderToReadableStream( }); function onShellReady() { - const stream: ReactDOMServerReadableStream = new ReadableStream( + const stream: ReactDOMServerReadableStream = (new ReadableStream( { type: 'direct', pull: (controller): ?Promise<void> => { - // $FlowFixMe[incompatible-type] + // $FlowIgnore startFlowing(request, controller); }, cancel: (reason): ?Promise<void> => { @@ -87,9 +87,8 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 2048}, - ) as any; + ): any); // TODO: Move to sub-classing ReadableStream. stream.allReady = allReady; resolve(stream); @@ -140,10 +139,10 @@ function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-dom/src/server/ReactDOMFizzServerEdge.js b/packages/react-dom/src/server/ReactDOMFizzServerEdge.js index 51c25c0f765f..75c0768b3248 100644 --- a/packages/react-dom/src/server/ReactDOMFizzServerEdge.js +++ b/packages/react-dom/src/server/ReactDOMFizzServerEdge.js @@ -85,7 +85,7 @@ function renderToReadableStream( }); function onShellReady() { - const stream: ReactDOMServerReadableStream = new ReadableStream( + const stream: ReactDOMServerReadableStream = (new ReadableStream( { type: 'bytes', pull: (controller): ?Promise<void> => { @@ -97,9 +97,8 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, - ) as any; + ): any); // TODO: Move to sub-classing ReadableStream. stream.allReady = allReady; resolve(stream); @@ -150,10 +149,10 @@ function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -177,7 +176,7 @@ function resume( }); function onShellReady() { - const stream: ReactDOMServerReadableStream = new ReadableStream( + const stream: ReactDOMServerReadableStream = (new ReadableStream( { type: 'bytes', pull: (controller): ?Promise<void> => { @@ -189,9 +188,8 @@ function resume( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, - ) as any; + ): any); // TODO: Move to sub-classing ReadableStream. stream.allReady = allReady; resolve(stream); @@ -219,10 +217,10 @@ function resume( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-dom/src/server/ReactDOMFizzServerNode.js b/packages/react-dom/src/server/ReactDOMFizzServerNode.js index d9261dd0f7ee..c0a3cf1096eb 100644 --- a/packages/react-dom/src/server/ReactDOMFizzServerNode.js +++ b/packages/react-dom/src/server/ReactDOMFizzServerNode.js @@ -170,7 +170,7 @@ function createFakeWritableFromReadableStreamController( ): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk: string | Uint8Array) { if (typeof chunk === 'string') { chunk = textEncoder.encode(chunk); @@ -191,7 +191,7 @@ function createFakeWritableFromReadableStreamController( controller.close(); } }, - } as any; + }: any); } // TODO: Move to sub-classing ReadableStream. @@ -218,7 +218,7 @@ function renderToReadableStream( function onShellReady() { let writable: Writable; - const stream: ReactDOMServerReadableStream = new ReadableStream( + const stream: ReactDOMServerReadableStream = (new ReadableStream( { type: 'bytes', start: (controller): ?Promise<void> => { @@ -234,9 +234,8 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, - ) as any; + ): any); // TODO: Move to sub-classing ReadableStream. stream.allReady = allReady; resolve(stream); @@ -287,10 +286,10 @@ function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -377,7 +376,7 @@ function resume( function onShellReady() { let writable: Writable; - const stream: ReactDOMServerReadableStream = new ReadableStream( + const stream: ReactDOMServerReadableStream = (new ReadableStream( { type: 'bytes', start: (controller): ?Promise<void> => { @@ -393,9 +392,8 @@ function resume( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, - ) as any; + ): any); // TODO: Move to sub-classing ReadableStream. stream.allReady = allReady; resolve(stream); @@ -423,10 +421,10 @@ function resume( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js b/packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js index 313b7f9c49f3..c1024ea3f4d3 100644 --- a/packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js +++ b/packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js @@ -84,7 +84,6 @@ function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); @@ -132,10 +131,10 @@ function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -173,7 +172,6 @@ function resumeAndPrerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); @@ -197,10 +195,10 @@ function resumeAndPrerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-dom/src/server/ReactDOMFizzStaticEdge.js b/packages/react-dom/src/server/ReactDOMFizzStaticEdge.js index 3196b3f7ce23..002c4c51b44f 100644 --- a/packages/react-dom/src/server/ReactDOMFizzStaticEdge.js +++ b/packages/react-dom/src/server/ReactDOMFizzStaticEdge.js @@ -84,7 +84,6 @@ function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); @@ -131,10 +130,10 @@ function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -171,7 +170,6 @@ function resumeAndPrerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); @@ -195,10 +193,10 @@ function resumeAndPrerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-dom/src/server/ReactDOMFizzStaticNode.js b/packages/react-dom/src/server/ReactDOMFizzStaticNode.js index 3f777ae53d9e..73abcadeb7a7 100644 --- a/packages/react-dom/src/server/ReactDOMFizzStaticNode.js +++ b/packages/react-dom/src/server/ReactDOMFizzStaticNode.js @@ -73,7 +73,7 @@ function createFakeWritableFromReadableStreamController( ): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk: string | Uint8Array) { if (typeof chunk === 'string') { chunk = textEncoder.encode(chunk); @@ -94,13 +94,13 @@ function createFakeWritableFromReadableStreamController( controller.close(); } }, - } as any; + }: any); } function createFakeWritableFromReadable(readable: any): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk) { return readable.push(chunk); }, @@ -110,7 +110,7 @@ function createFakeWritableFromReadable(readable: any): Writable { destroy(error) { readable.destroy(error); }, - } as any; + }: any); } function prerenderToNodeStream( @@ -163,10 +163,10 @@ function prerenderToNodeStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -206,7 +206,6 @@ function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); @@ -253,10 +252,10 @@ function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -307,10 +306,10 @@ function resumeAndPrerenderToNodeStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -349,7 +348,6 @@ function resumeAndPrerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); @@ -373,10 +371,10 @@ function resumeAndPrerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-dom/src/shared/ReactDOM.js b/packages/react-dom/src/shared/ReactDOM.js index dc4c73316f6b..f1d48bfacd4d 100644 --- a/packages/react-dom/src/shared/ReactDOM.js +++ b/packages/react-dom/src/shared/ReactDOM.js @@ -63,7 +63,7 @@ function createPortal( } // TODO: pass ReactDOM portal implementation as third argument - // $FlowFixMe[incompatible-type] The Flow type is opaque but there's no way to actually create it. + // $FlowFixMe[incompatible-return] The Flow type is opaque but there's no way to actually create it. return createPortalImpl(children, container, null, key); } diff --git a/packages/react-dom/src/shared/ReactDOMFloat.js b/packages/react-dom/src/shared/ReactDOMFloat.js index 4d916308eb1b..844824fd0667 100644 --- a/packages/react-dom/src/shared/ReactDOMFloat.js +++ b/packages/react-dom/src/shared/ReactDOMFloat.js @@ -114,7 +114,6 @@ export function preload(href: string, options: PreloadOptions) { typeof href === 'string' && // We check existence because we cannot enforce this function is actually called with the stated type typeof options === 'object' && - // $FlowFixMe[invalid-compare] options !== null && typeof options.as === 'string' ) { diff --git a/packages/react-dom/src/test-utils/FizzTestUtils.js b/packages/react-dom/src/test-utils/FizzTestUtils.js index 0d0619c22f80..4d8b6cadbabf 100644 --- a/packages/react-dom/src/test-utils/FizzTestUtils.js +++ b/packages/react-dom/src/test-utils/FizzTestUtils.js @@ -40,7 +40,7 @@ async function insertNodesAndExecuteScripts( lastChild = node; if (node.nodeType === 1) { - const element: Element = node as any; + const element: Element = (node: any); if ( // $FlowFixMe[prop-missing] element.dataset != null && @@ -53,7 +53,7 @@ async function insertNodesAndExecuteScripts( // When we have renderIntoContainer and renderDocument this will be // more enforceable. At the moment you can misconfigure your stream and end up // with instructions that are deep in the document - (ownerDocument.body as any).appendChild(element); + (ownerDocument.body: any).appendChild(element); } else { target.appendChild(element); @@ -95,7 +95,7 @@ async function executeScript(script: Element) { } try { - // $FlowFixMe[unsupported-syntax] + // $FlowFixMe require(scriptSrc); } catch (x) { const event = new window.ErrorEvent('error', {error: x}); diff --git a/packages/react-flight-server-fb/package.json b/packages/react-flight-server-fb/package.json index 014e5c01e656..b802a6abaf85 100644 --- a/packages/react-flight-server-fb/package.json +++ b/packages/react-flight-server-fb/package.json @@ -6,7 +6,7 @@ "react" ], "homepage": "https://react.dev/", - "bugs": "https://github.com/react/react/issues", + "bugs": "https://github.com/facebook/react/issues", "license": "MIT", "files": [ "LICENSE", @@ -21,7 +21,7 @@ }, "repository": { "type" : "git", - "url" : "https://github.com/react/react.git", + "url" : "https://github.com/facebook/react.git", "directory": "packages/react-flight-server-fb" }, "engines": { diff --git a/packages/react-flight-server-fb/src/ReactFlightFBReferences.js b/packages/react-flight-server-fb/src/ReactFlightFBReferences.js index 7ea44328e54e..d639467f6bd3 100644 --- a/packages/react-flight-server-fb/src/ReactFlightFBReferences.js +++ b/packages/react-flight-server-fb/src/ReactFlightFBReferences.js @@ -51,7 +51,7 @@ const FunctionBind = Function.prototype.bind; // $FlowFixMe[method-unbinding] const ArraySlice = Array.prototype.slice; function bind(this: ServerReference<any>): any { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] const newFn = FunctionBind.apply(this, arguments); if (this.$$typeof === SERVER_REFERENCE_TAG) { if (__DEV__) { @@ -67,7 +67,7 @@ function bind(this: ServerReference<any>): any { const $$id = {value: this.$$id}; const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args}; return Object.defineProperties( - newFn as any, + (newFn: any), (__DEV__ ? { $$typeof, @@ -107,7 +107,7 @@ export function registerServerReference<T: Function>( }; const $$bound = {value: null, configurable: true}; return Object.defineProperties( - reference as any, + (reference: any), (__DEV__ ? { $$typeof, diff --git a/packages/react-flight-server-fb/src/ReactServerStreamConfigFB.js b/packages/react-flight-server-fb/src/ReactServerStreamConfigFB.js index 46fa456c53d2..be6f854f1b02 100644 --- a/packages/react-flight-server-fb/src/ReactServerStreamConfigFB.js +++ b/packages/react-flight-server-fb/src/ReactServerStreamConfigFB.js @@ -63,7 +63,7 @@ function writeStringChunk(destination: Destination, stringChunk: string) { if (writtenBytes > 0) { writeToDestination( destination, - (currentView as any as Uint8Array).subarray(0, writtenBytes), + ((currentView: any): Uint8Array).subarray(0, writtenBytes), ); currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; @@ -73,9 +73,9 @@ function writeStringChunk(destination: Destination, stringChunk: string) { return; } - let target: Uint8Array = currentView as any; + let target: Uint8Array = (currentView: any); if (writtenBytes > 0) { - target = (currentView as any as Uint8Array).subarray(writtenBytes); + target = ((currentView: any): Uint8Array).subarray(writtenBytes); } const {read, written} = textEncoder.encodeInto(stringChunk, target); writtenBytes += written; @@ -83,17 +83,17 @@ function writeStringChunk(destination: Destination, stringChunk: string) { if (read < stringChunk.length) { writeToDestination( destination, - (currentView as any).subarray(0, writtenBytes), + (currentView: any).subarray(0, writtenBytes), ); currentView = new Uint8Array(VIEW_SIZE); writtenBytes = textEncoder.encodeInto( stringChunk.slice(read), - currentView as any, + (currentView: any), ).written; } if (writtenBytes === VIEW_SIZE) { - writeToDestination(destination, currentView as any); + writeToDestination(destination, (currentView: any)); currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; } @@ -110,7 +110,7 @@ function writeViewChunk( if (writtenBytes > 0) { writeToDestination( destination, - (currentView as any as Uint8Array).subarray(0, writtenBytes), + ((currentView: any): Uint8Array).subarray(0, writtenBytes), ); currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; @@ -120,28 +120,27 @@ function writeViewChunk( } let bytesToWrite = chunk; - const allowableBytes = - (currentView as any as Uint8Array).length - writtenBytes; + const allowableBytes = ((currentView: any): Uint8Array).length - writtenBytes; if (allowableBytes < bytesToWrite.byteLength) { if (allowableBytes === 0) { - writeToDestination(destination, currentView as any); + writeToDestination(destination, (currentView: any)); } else { - (currentView as any as Uint8Array).set( + ((currentView: any): Uint8Array).set( bytesToWrite.subarray(0, allowableBytes), writtenBytes, ); writtenBytes += allowableBytes; - writeToDestination(destination, currentView as any); + writeToDestination(destination, (currentView: any)); bytesToWrite = bytesToWrite.subarray(allowableBytes); } currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; } - (currentView as any as Uint8Array).set(bytesToWrite, writtenBytes); + ((currentView: any): Uint8Array).set(bytesToWrite, writtenBytes); writtenBytes += bytesToWrite.byteLength; if (writtenBytes === VIEW_SIZE) { - writeToDestination(destination, currentView as any); + writeToDestination(destination, (currentView: any)); currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; } @@ -154,7 +153,7 @@ export function writeChunk( if (typeof chunk === 'string') { writeStringChunk(destination, chunk); } else { - writeViewChunk(destination, chunk as any as PrecomputedChunk | BinaryChunk); + writeViewChunk(destination, ((chunk: any): PrecomputedChunk | BinaryChunk)); } } @@ -187,7 +186,7 @@ export function close(destination: Destination) { destination.end(); } -export const textEncoder: TextEncoderType = new TextEncoder() as any; +export const textEncoder: TextEncoderType = (new TextEncoder(): any); export function stringToChunk(content: string): Chunk { return content; @@ -224,7 +223,7 @@ export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number { } export function closeWithError(destination: Destination, error: mixed): void { - // $FlowFixMe[incompatible-type]: This is an Error object or the destination accepts other types. + // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types. destination.destroy(error); } diff --git a/packages/react-flight-server-fb/src/client/ReactFlightClientConfigBundlerFB.js b/packages/react-flight-server-fb/src/client/ReactFlightClientConfigBundlerFB.js index 65ed4709ed4f..79213ec360ac 100644 --- a/packages/react-flight-server-fb/src/client/ReactFlightClientConfigBundlerFB.js +++ b/packages/react-flight-server-fb/src/client/ReactFlightClientConfigBundlerFB.js @@ -64,11 +64,11 @@ export function resolveServerReference<T>( config: ServerManifest, id: ServerReferenceId, ): ClientReference<T> { - return { + return ({ $$typeof: Symbol.for('react.client.reference'), $$id: id, $$hblp: null, - } as any; + }: any); } const asyncModuleCache: Map<string, Thenable<any>> = new Map(); @@ -97,12 +97,12 @@ export function preloadModule<T>( const modulePromise: Thenable<T> = jsr.load(); modulePromise.then( value => { - const fulfilledThenable: FulfilledThenable<mixed> = modulePromise as any; + const fulfilledThenable: FulfilledThenable<mixed> = (modulePromise: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = value; }, reason => { - const rejectedThenable: RejectedThenable<mixed> = modulePromise as any; + const rejectedThenable: RejectedThenable<mixed> = (modulePromise: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = reason; }, @@ -151,7 +151,7 @@ export function requireModule<T>(metadata: ClientReference<T>): T { // We cache ReactIOInfo across requests so that inner refreshes can dedupe with outer. const moduleIOInfoCache: Map<string, ReactIOInfo> = __DEV__ ? new Map() - : (null as any); + : (null: any); export function getModuleDebugInfo<T>( metadata: ClientReference<T>, @@ -164,7 +164,7 @@ export function getModuleDebugInfo<T>( if (ioInfo === undefined) { let href; try { - // $FlowFixMe[incompatible-type] + // $FlowFixMe href = new URL(filename, document.baseURI).href; } catch (_) { href = filename; @@ -182,15 +182,15 @@ export function getModuleDebugInfo<T>( start = resourceEntry.startTime; end = start + resourceEntry.duration; // $FlowFixMe[prop-missing] - byteSize = (resourceEntry.transferSize as any) || 0; + byteSize = (resourceEntry.transferSize: any) || 0; } } } const value = Promise.resolve(href); - // $FlowFixMe[prop-missing] + // $FlowFixMe value.status = 'fulfilled'; // Is there some more useful representation for the chunk? - // $FlowFixMe[prop-missing] + // $FlowFixMe value.value = href; // Create a fake stack frame that points to the beginning of the chunk. This is // probably not source mapped so will link to the compiled source rather than @@ -218,13 +218,13 @@ export function getModuleDebugInfo<T>( href + ':1:1'; } - ioInfo = { + ioInfo = ({ name: 'script', start: start, end: end, value: value, debugStack: fakeStack, - } as ReactIOInfo; + }: ReactIOInfo); if (byteSize > 0) { // $FlowFixMe[cannot-write] ioInfo.byteSize = byteSize; diff --git a/packages/react-flight-server-fb/src/client/ReactFlightDOMClientBrowser.js b/packages/react-flight-server-fb/src/client/ReactFlightDOMClientBrowser.js index 51bca925c849..4e8889261b8a 100644 --- a/packages/react-flight-server-fb/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-flight-server-fb/src/client/ReactFlightDOMClientBrowser.js @@ -173,7 +173,7 @@ function startReadingFromStream( if (done) { return onDone(); } - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); processBinaryChunk(response, streamState, buffer); return reader.read().then(progress).catch(error); } @@ -240,11 +240,11 @@ function createFromFetch<T>( options.debugChannel.readable, handleDone, ); - startReadingFromStream(response, r.body as any, handleDone, r); + startReadingFromStream(response, (r.body: any), handleDone, r); } else { startReadingFromStream( response, - r.body as any, + (r.body: any), close.bind(null, response), r, ); @@ -276,10 +276,10 @@ function encodeReply( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort((signal as any).reason); + abort((signal: any).reason); } else { const listener = () => { - abort((signal as any).reason); + abort((signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-flight-server-fb/src/server/ReactFlightDOMServerNode.js b/packages/react-flight-server-fb/src/server/ReactFlightDOMServerNode.js index 74997e5eeba4..eb481f0d772f 100644 --- a/packages/react-flight-server-fb/src/server/ReactFlightDOMServerNode.js +++ b/packages/react-flight-server-fb/src/server/ReactFlightDOMServerNode.js @@ -114,7 +114,7 @@ function startReadingFromDebugChannelReadable( } stringBuffer += chunk; } else { - const buffer: Uint8Array = chunk as any; + const buffer: Uint8Array = (chunk: any); stringBuffer += readPartialStringChunk(stringDecoder, buffer); lastWasPartial = true; } @@ -141,19 +141,19 @@ function startReadingFromDebugChannelReadable( // $FlowFixMe[method-unbinding] typeof stream.binaryType === 'string' ) { - const ws: WebSocket = stream as any; + const ws: WebSocket = (stream: any); ws.binaryType = 'arraybuffer'; ws.addEventListener('message', event => { - // $FlowFixMe[incompatible-type] + // $FlowFixMe onData(event.data); }); ws.addEventListener('error', event => { - // $FlowFixMe[prop-missing] + // $FlowFixMe onError(event.error); }); ws.addEventListener('close', onClose); } else { - const readable: Readable = stream as any; + const readable: Readable = (stream: any); readable.on('data', onData); readable.on('error', onError); readable.on('end', onClose); @@ -186,16 +186,16 @@ function renderToPipeableStream( // $FlowFixMe[method-unbinding] (typeof debugChannel.read === 'function' || typeof debugChannel.readyState === 'number') - ? (debugChannel as any) + ? (debugChannel: any) : undefined; const debugChannelWritable: void | Writable = __DEV__ && debugChannel !== undefined ? // $FlowFixMe[method-unbinding] typeof debugChannel.write === 'function' - ? (debugChannel as any) + ? (debugChannel: any) : // $FlowFixMe[method-unbinding] typeof debugChannel.send === 'function' - ? createFakeWritableFromWebSocket(debugChannel as any) + ? createFakeWritableFromWebSocket((debugChannel: any)) : undefined : undefined; const request = createRequest( @@ -250,9 +250,9 @@ function renderToPipeableStream( } function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { - return { + return ({ write(chunk: string | Uint8Array) { - webSocket.send(chunk as any); + webSocket.send((chunk: any)); return true; }, end() { @@ -268,7 +268,7 @@ function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { webSocket.close(1011); } }, - } as any; + }: any); } function decodeReplyFromBusboy<T>( @@ -339,7 +339,7 @@ function decodeReplyFromBusboy<T>( busboyStream.on('error', err => { reportGlobalError( response, - // $FlowFixMe[incompatible-type] types Error and mixed are incompatible + // $FlowFixMe[incompatible-call] types Error and mixed are incompatible err, ); }); diff --git a/packages/react-is/package.json b/packages/react-is/package.json index bf557d3a3bb5..f910553fcbc5 100644 --- a/packages/react-is/package.json +++ b/packages/react-is/package.json @@ -6,7 +6,7 @@ "sideEffects": false, "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-is" }, "keywords": [ @@ -14,7 +14,7 @@ ], "license": "MIT", "bugs": { - "url": "https://github.com/react/react/issues" + "url": "https://github.com/facebook/react/issues" }, "homepage": "https://react.dev/", "files": [ diff --git a/packages/react-markup/package.json b/packages/react-markup/package.json index b92f601f9903..564486533917 100644 --- a/packages/react-markup/package.json +++ b/packages/react-markup/package.json @@ -5,7 +5,7 @@ "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-markup" }, "keywords": [ @@ -13,7 +13,7 @@ ], "license": "MIT", "bugs": { - "url": "https://github.com/react/react/issues" + "url": "https://github.com/facebook/react/issues" }, "homepage": "https://react.dev/", "peerDependencies": { diff --git a/packages/react-markup/src/ReactMarkupClient.js b/packages/react-markup/src/ReactMarkupClient.js index e71c41d44157..25ff9ebc0c37 100644 --- a/packages/react-markup/src/ReactMarkupClient.js +++ b/packages/react-markup/src/ReactMarkupClient.js @@ -89,10 +89,10 @@ export function experimental_renderToHTML( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abortFizz(fizzRequest, (signal as any).reason); + abortFizz(fizzRequest, (signal: any).reason); } else { const listener = () => { - abortFizz(fizzRequest, (signal as any).reason); + abortFizz(fizzRequest, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-markup/src/ReactMarkupServer.js b/packages/react-markup/src/ReactMarkupServer.js index a885bd38f645..43e258bf13ef 100644 --- a/packages/react-markup/src/ReactMarkupServer.js +++ b/packages/react-markup/src/ReactMarkupServer.js @@ -179,7 +179,7 @@ export function experimental_renderToHTML( } } const flightRequest = createFlightRequest( - // $FlowFixMe[incompatible-type]: This should be a subtype but not everything is typed covariant. + // $FlowFixMe: This should be a subtype but not everything is typed covariant. children, null, handleFlightError, @@ -196,7 +196,7 @@ export function experimental_renderToHTML( ); const root = getFlightRoot<ReactNodeList>(flightResponse); const fizzRequest = createFizzRequest( - // $FlowFixMe[incompatible-type]: Thenables as children are supported. + // $FlowFixMe: Thenables as children are supported. root, resumableState, createRenderState( @@ -219,12 +219,12 @@ export function experimental_renderToHTML( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abortFlight(flightRequest, (signal as any).reason); - abortFizz(fizzRequest, (signal as any).reason); + abortFlight(flightRequest, (signal: any).reason); + abortFizz(fizzRequest, (signal: any).reason); } else { const listener = () => { - abortFlight(flightRequest, (signal as any).reason); - abortFizz(fizzRequest, (signal as any).reason); + abortFlight(flightRequest, (signal: any).reason); + abortFizz(fizzRequest, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-native-renderer/fabric.js b/packages/react-native-renderer/fabric.js index 78cb51f106ec..1bf6c861c1f6 100644 --- a/packages/react-native-renderer/fabric.js +++ b/packages/react-native-renderer/fabric.js @@ -10,6 +10,6 @@ import type {ReactFabricType} from './src/ReactNativeTypes'; import * as ReactFabric from './src/ReactFabric'; // Assert that the exports line up with the type we're going to expose. -ReactFabric as ReactFabricType; +(ReactFabric: ReactFabricType); export * from './src/ReactFabric'; diff --git a/packages/react-native-renderer/package.json b/packages/react-native-renderer/package.json index 6d4b3652f3c2..9dccf45b0449 100644 --- a/packages/react-native-renderer/package.json +++ b/packages/react-native-renderer/package.json @@ -4,7 +4,7 @@ "private": true, "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-native-renderer" }, "dependencies": { diff --git a/packages/react-native-renderer/src/ReactFabric.js b/packages/react-native-renderer/src/ReactFabric.js index d91431bd6f92..30c32433b3b6 100644 --- a/packages/react-native-renderer/src/ReactFabric.js +++ b/packages/react-native-renderer/src/ReactFabric.js @@ -151,7 +151,6 @@ function render( // TODO (bvaughn): If we decide to keep the wrapper component, // We could create a wrapper for containerTag as well to reduce special casing. root = createContainer( - // $FlowFixMe[incompatible-type] rootInstance, concurrentRoot ? ConcurrentRoot : LegacyRoot, null, @@ -167,7 +166,6 @@ function render( roots.set(containerTag, root); } - // $FlowFixMe[incompatible-type] updateContainer(element, root, null, callback); return getPublicRootInstance(root); diff --git a/packages/react-native-renderer/src/ReactFabricComponentTree.js b/packages/react-native-renderer/src/ReactFabricComponentTree.js index 4f802239f19f..f36e742a45ab 100644 --- a/packages/react-native-renderer/src/ReactFabricComponentTree.js +++ b/packages/react-native-renderer/src/ReactFabricComponentTree.js @@ -20,7 +20,7 @@ import {getPublicInstance} from './ReactFiberConfigFabric'; // This is ok in DOM because they types are interchangeable, but in React Native // they aren't. function getInstanceFromNode(node: Instance | TextInstance): Fiber | null { - const instance: Instance = node as $FlowFixMe; // In React Native, node is never a text instance + const instance: Instance = (node: $FlowFixMe); // In React Native, node is never a text instance if ( instance.canonical != null && @@ -29,7 +29,7 @@ function getInstanceFromNode(node: Instance | TextInstance): Fiber | null { return instance.canonical.internalInstanceHandle; } - // $FlowFixMe[incompatible-type] DevTools incorrectly passes a fiber in React Native. + // $FlowFixMe[incompatible-return] DevTools incorrectly passes a fiber in React Native. return node; } diff --git a/packages/react-native-renderer/src/ReactFabricEventEmitter.js b/packages/react-native-renderer/src/ReactFabricEventEmitter.js index f47a6da8ea47..c7f6aebd9028 100644 --- a/packages/react-native-renderer/src/ReactFabricEventEmitter.js +++ b/packages/react-native-renderer/src/ReactFabricEventEmitter.js @@ -51,9 +51,9 @@ function extractPluginEvents( nativeEventTarget: null | EventTarget, ): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null { let events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null = null; - const legacyPlugins = plugins as any as Array< + const legacyPlugins = ((plugins: any): Array< LegacyPluginModule<AnyNativeEvent>, - >; + >); for (let i = 0; i < legacyPlugins.length; i++) { // Not every plugin in the ordering may be loaded at runtime. const possiblePlugin = legacyPlugins[i]; @@ -94,17 +94,17 @@ export function dispatchEvent( ) { const nativeEvent: AnyNativeEvent = nativeEventParam != null && typeof nativeEventParam === 'object' - ? (nativeEventParam as any) + ? (nativeEventParam: any) : {}; - const targetFiber = target as null | Fiber; + const targetFiber = (target: null | Fiber); let eventTarget = null; if (targetFiber != null) { const stateNode = targetFiber.stateNode; // Guard against Fiber being unmounted if (stateNode != null) { - // $FlowExpectedError[incompatible-type] public instances in Fabric do not implement `EventTarget` yet. - eventTarget = getPublicInstance(stateNode) as EventTarget; + // $FlowExpectedError[incompatible-cast] public instances in Fabric do not implement `EventTarget` yet. + eventTarget = (getPublicInstance(stateNode): EventTarget); } } diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index f93795eb1ca2..29beb80db91b 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -155,7 +155,6 @@ export type RendererInspectionConfig = $ReadOnly<{ }>; // TODO: Remove this conditional once all changes have propagated. -// $FlowFixMe[constant-condition] if (registerEventHandler) { /** * Register the event emitter with the native bridge @@ -393,7 +392,6 @@ export function resolveUpdatePriority(): EventPriority { return currentUpdatePriority; } - // $FlowFixMe[constant-condition] const currentEventPriority = fabricGetCurrentEventPriority ? fabricGetCurrentEventPriority() : null; @@ -707,9 +705,8 @@ FragmentInstance.prototype.observeUsing = function ( traverseFragmentInstance(this._fragmentFiber, observeChild, observer); }; function observeChild(child: Fiber, observer: IntersectionObserver) { - // $FlowFixMe[incompatible-type] const publicInstance = getPublicInstanceFromHostFiber(child); - // $FlowFixMe[incompatible-type] DOM types expect Element + // $FlowFixMe[incompatible-call] DOM types expect Element observer.observe(publicInstance); return false; } @@ -732,9 +729,8 @@ FragmentInstance.prototype.unobserveUsing = function ( } }; function unobserveChild(child: Fiber, observer: IntersectionObserver) { - // $FlowFixMe[incompatible-type] const publicInstance = getPublicInstanceFromHostFiber(child); - // $FlowFixMe[incompatible-type] DOM types expect Element + // $FlowFixMe[incompatible-call] DOM types expect Element observer.unobserve(publicInstance); return false; } @@ -810,7 +806,7 @@ FragmentInstance.prototype.getRootNode = function ( } const parentHostInstance = getPublicInstanceFromHostFiber(parentHostFiber); // $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque - const rootNode = parentHostInstance.getRootNode(getRootNodeOptions) as Node; + const rootNode = (parentHostInstance.getRootNode(getRootNodeOptions): Node); return rootNode; }; @@ -840,9 +836,9 @@ function addFragmentHandleToFiber( fragmentInstance: FragmentInstanceType, ): boolean { if (enableFragmentRefsInstanceHandles) { - const instance = getPublicInstanceFromHostFiber( + const instance = ((getPublicInstanceFromHostFiber( child, - ) as any as PublicInstanceWithFragmentHandles; + ): any): PublicInstanceWithFragmentHandles); if (instance != null) { addFragmentHandleToInstance(instance, fragmentInstance); } @@ -865,7 +861,7 @@ function addFragmentHandleToInstance( export function createFragmentInstance( fragmentFiber: Fiber, ): FragmentInstanceType { - const fragmentInstance = new (FragmentInstance as any)(fragmentFiber); + const fragmentInstance = new (FragmentInstance: any)(fragmentFiber); if (enableFragmentRefsInstanceHandles) { traverseFragmentInstance( fragmentFiber, @@ -891,21 +887,20 @@ export function commitNewChildToFragmentInstance( if (enableFragmentRefsTextNodes && childInstance.canonical == null) { return; } - const instance: Instance = childInstance as any; + const instance: Instance = (childInstance: any); const publicInstance = getPublicInstance(instance); if (fragmentInstance._observers !== null) { if (publicInstance == null) { throw new Error('Expected to find a host node. This is a bug in React.'); } - // $FlowFixMe[incompatible-type] fragmentInstance._observers.forEach(observer => { - // $FlowFixMe[incompatible-type] Element types are behind a flag in RN + // $FlowFixMe[incompatible-call] Element types are behind a flag in RN observer.observe(publicInstance); }); } if (enableFragmentRefsInstanceHandles) { addFragmentHandleToInstance( - publicInstance as any as PublicInstanceWithFragmentHandles, + ((publicInstance: any): PublicInstanceWithFragmentHandles), fragmentInstance, ); } @@ -919,10 +914,10 @@ export function deleteChildFromFragmentInstance( if (enableFragmentRefsTextNodes && childInstance.canonical == null) { return; } - const instance: Instance = childInstance as any; - const publicInstance = getPublicInstance( + const instance: Instance = (childInstance: any); + const publicInstance = ((getPublicInstance( instance, - ) as any as PublicInstanceWithFragmentHandles; + ): any): PublicInstanceWithFragmentHandles); if (enableFragmentRefsInstanceHandles) { if (publicInstance.reactFragments != null) { publicInstance.reactFragments.delete(fragmentInstance); @@ -933,8 +928,8 @@ export function deleteChildFromFragmentInstance( export const NotPendingTransition: TransitionStatus = null; export const HostTransitionContext: ReactContext<TransitionStatus> = { $$typeof: REACT_CONTEXT_TYPE, - Provider: null as any, - Consumer: null as any, + Provider: (null: any), + Consumer: (null: any), _currentValue: NotPendingTransition, _currentValue2: NotPendingTransition, _threadCount: 0, diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabricWithViewTransition.js b/packages/react-native-renderer/src/ReactFiberConfigFabricWithViewTransition.js index c71f56a3d29b..89aa1ee4c840 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabricWithViewTransition.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabricWithViewTransition.js @@ -94,7 +94,7 @@ export function cloneRootViewTransitionContainer( if (__DEV__) { console.warn('cloneRootViewTransitionContainer is not implemented'); } - // $FlowFixMe[incompatible-type] Return empty stub + // $FlowFixMe[incompatible-return] Return empty stub return null; } @@ -204,8 +204,8 @@ export function createViewTransitionInstance( fabricCreateViewTransitionInstance(name, tag); return { name, - old: new (ViewTransitionPseudoElement as any)('old', name), - new: new (ViewTransitionPseudoElement as any)('new', name), + old: new (ViewTransitionPseudoElement: any)('old', name), + new: new (ViewTransitionPseudoElement: any)('new', name), }; } diff --git a/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js b/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js index 5594b88c0a77..b22927d2cb28 100644 --- a/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js +++ b/packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js @@ -167,7 +167,7 @@ function accumulateDirectDispatches(events: ?(Array<Object> | Object)) { type PropagationPhases = 'bubbled' | 'captured'; const ReactNativeBridgeEventPlugin: LegacyPluginModule<AnyNativeEvent> = { - eventTypes: {} as EventTypes, + eventTypes: ({}: EventTypes), extractEvents: function ( topLevelType: TopLevelType, diff --git a/packages/react-native-renderer/src/ReactNativeFiberInspector.js b/packages/react-native-renderer/src/ReactNativeFiberInspector.js index 61a993d188ea..5474c5d55052 100644 --- a/packages/react-native-renderer/src/ReactNativeFiberInspector.js +++ b/packages/react-native-renderer/src/ReactNativeFiberInspector.js @@ -123,7 +123,7 @@ if (__DEV__) { hierarchy.unshift(instance); const owner = instance._debugOwner; if (owner != null && typeof owner.tag === 'number') { - traverseOwnerTreeUp(hierarchy, owner as any); + traverseOwnerTreeUp(hierarchy, (owner: any)); } else { // TODO: Traverse Server Components owners. } diff --git a/packages/react-native-renderer/src/ReactNativePublicCompat.js b/packages/react-native-renderer/src/ReactNativePublicCompat.js index 53f78f734775..120ddd9e11ca 100644 --- a/packages/react-native-renderer/src/ReactNativePublicCompat.js +++ b/packages/react-native-renderer/src/ReactNativePublicCompat.js @@ -59,7 +59,7 @@ export function findHostInstance_DEPRECATED<TElementType: ElementType>( componentOrHandle.canonical && componentOrHandle.canonical.publicInstance ) { - // $FlowExpectedError[incompatible-type] Can't refine componentOrHandle as a Fabric instance + // $FlowExpectedError[incompatible-return] Can't refine componentOrHandle as a Fabric instance return componentOrHandle.canonical.publicInstance; } @@ -75,7 +75,7 @@ export function findHostInstance_DEPRECATED<TElementType: ElementType>( // findHostInstance handles legacy vs. Fabric differences correctly // $FlowFixMe[incompatible-exact] we need to fix the definition of `HostComponent` to use NativeMethods as an interface, not as a type. - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-return] return hostInstance; } @@ -132,11 +132,11 @@ export function findNodeHandle(componentOrHandle: any): ?number { } if (hostInstance == null) { - // $FlowFixMe[incompatible-type] Flow limitation in refining an opaque type + // $FlowFixMe[incompatible-return] Flow limitation in refining an opaque type return hostInstance; } - // $FlowFixMe[incompatible-type] Necessary when running Flow on the legacy renderer + // $FlowFixMe[incompatible-call] Necessary when running Flow on the legacy renderer return getNativeTagFromPublicInstance(hostInstance); } @@ -178,9 +178,9 @@ export function getNodeFromInternalInstanceHandle( internalInstanceHandle: mixed, ): ?Node { return ( - // $FlowExpectedError[incompatible-type] internalInstanceHandle is opaque but we need to make an exception here. + // $FlowExpectedError[incompatible-return] internalInstanceHandle is opaque but we need to make an exception here. internalInstanceHandle && - // $FlowExpectedError[incompatible-type] + // $FlowExpectedError[incompatible-return] internalInstanceHandle.stateNode && // $FlowExpectedError[incompatible-use] internalInstanceHandle.stateNode.node diff --git a/packages/react-native-renderer/src/ReactNativeTypes.js b/packages/react-native-renderer/src/ReactNativeTypes.js index 0880815214a2..cc9a81795262 100644 --- a/packages/react-native-renderer/src/ReactNativeTypes.js +++ b/packages/react-native-renderer/src/ReactNativeTypes.js @@ -24,33 +24,33 @@ import * as React from 'react'; export type AttributeType<T, V> = | true - | Readonly<{ + | $ReadOnly<{ diff?: (arg1: T, arg2: T) => boolean, process?: (arg1: V) => T, }>; -// We either force that `diff` and `process` always use unknown, +// We either force that `diff` and `process` always use mixed, // or we allow them to define specific types and use this hack export type AnyAttributeType = AttributeType<$FlowFixMe, $FlowFixMe>; -export type AttributeConfiguration = Readonly<{ +export type AttributeConfiguration = $ReadOnly<{ [propName: string]: AnyAttributeType | void, - style?: Readonly<{ + style?: $ReadOnly<{ [propName: string]: AnyAttributeType, ... }>, ... }>; -export type ViewConfig = Readonly<{ - Commands?: Readonly<{[commandName: string]: number, ...}>, - Constants?: Readonly<{[name: string]: unknown, ...}>, +export type ViewConfig = $ReadOnly<{ + Commands?: $ReadOnly<{[commandName: string]: number, ...}>, + Constants?: $ReadOnly<{[name: string]: mixed, ...}>, Manager?: string, - NativeProps?: Readonly<{[propName: string]: string, ...}>, + NativeProps?: $ReadOnly<{[propName: string]: string, ...}>, baseModuleName?: ?string, - bubblingEventTypes?: Readonly<{ - [eventName: string]: Readonly<{ - phasedRegistrationNames: Readonly<{ + bubblingEventTypes?: $ReadOnly<{ + [eventName: string]: $ReadOnly<{ + phasedRegistrationNames: $ReadOnly<{ captured: string, bubbled: string, skipBubbling?: ?boolean, @@ -58,8 +58,8 @@ export type ViewConfig = Readonly<{ }>, ... }>, - directEventTypes?: Readonly<{ - [eventName: string]: Readonly<{ + directEventTypes?: $ReadOnly<{ + [eventName: string]: $ReadOnly<{ registrationName: string, }>, ... @@ -69,7 +69,7 @@ export type ViewConfig = Readonly<{ validAttributes: AttributeConfiguration, }>; -export type PartialViewConfig = Readonly<{ +export type PartialViewConfig = $ReadOnly<{ bubblingEventTypes?: ViewConfig['bubblingEventTypes'], directEventTypes?: ViewConfig['directEventTypes'], supportsRawText?: boolean, @@ -77,22 +77,22 @@ export type PartialViewConfig = Readonly<{ validAttributes?: AttributeConfiguration, }>; -type InspectorDataProps = Readonly<{ +type InspectorDataProps = $ReadOnly<{ [propName: string]: string, ... }>; type InspectorDataGetter = ( - <TElementType extends React.ElementType>( + <TElementType: React.ElementType>( componentOrHandle: React.ElementRef<TElementType> | number, ) => ?number, -) => Readonly<{ +) => $ReadOnly<{ measure: (callback: MeasureOnSuccessCallback) => void, props: InspectorDataProps, }>; -export type InspectorData = Readonly<{ - closestInstance?: unknown, +export type InspectorData = $ReadOnly<{ + closestInstance?: mixed, hierarchy: Array<{ name: ?string, getInspectorData: InspectorDataGetter, @@ -102,11 +102,11 @@ export type InspectorData = Readonly<{ componentStack: string, }>; -export type TouchedViewDataAtPoint = Readonly< +export type TouchedViewDataAtPoint = $ReadOnly< { pointerY: number, touchedViewTag?: number, - frame: Readonly<{ + frame: $ReadOnly<{ top: number, left: number, width: number, @@ -118,39 +118,39 @@ export type TouchedViewDataAtPoint = Readonly< export type RenderRootOptions = { onUncaughtError?: ( - error: unknown, - errorInfo: {readonly componentStack?: ?string}, + error: mixed, + errorInfo: {+componentStack?: ?string}, ) => void, onCaughtError?: ( - error: unknown, + error: mixed, errorInfo: { - readonly componentStack?: ?string, + +componentStack?: ?string, // $FlowFixMe[unclear-type] unknown props and state. // $FlowFixMe[value-as-type] Component in react repo is any-typed, but it will be well typed externally. - readonly errorBoundary?: ?React.Component<any, any>, + +errorBoundary?: ?React.Component<any, any>, }, ) => void, onRecoverableError?: ( - error: unknown, - errorInfo: {readonly componentStack?: ?string}, + error: mixed, + errorInfo: {+componentStack?: ?string}, ) => void, onDefaultTransitionIndicator?: () => void | (() => void), }; -export opaque type Node = unknown; -export opaque type InternalInstanceHandle = unknown; +export opaque type Node = mixed; +export opaque type InternalInstanceHandle = mixed; export type ReactFabricType = { - findHostInstance_DEPRECATED<TElementType extends React.ElementType>( + findHostInstance_DEPRECATED<TElementType: React.ElementType>( componentOrHandle: ?(React.ElementRef<TElementType> | number), ): ?PublicInstance, - findNodeHandle<TElementType extends React.ElementType>( + findNodeHandle<TElementType: React.ElementType>( componentOrHandle: ?(React.ElementRef<TElementType> | number), ): ?number, dispatchCommand( handle: PublicInstance, command: string, - args: Array<unknown>, + args: Array<mixed>, ): void, isChildPublicInstance(parent: PublicInstance, child: PublicInstance): boolean, sendAccessibilityEvent(handle: PublicInstance, eventType: string): void, @@ -210,7 +210,7 @@ export type LayoutAnimationProperty = | 'scaleY' | 'scaleXY'; -export type LayoutAnimationAnimationConfig = Readonly<{ +export type LayoutAnimationAnimationConfig = $ReadOnly<{ duration?: number, delay?: number, springDamping?: number, @@ -219,7 +219,7 @@ export type LayoutAnimationAnimationConfig = Readonly<{ property?: LayoutAnimationProperty, }>; -export type LayoutAnimationConfig = Readonly<{ +export type LayoutAnimationConfig = $ReadOnly<{ duration: number, create?: LayoutAnimationAnimationConfig, update?: LayoutAnimationAnimationConfig, diff --git a/packages/react-native-renderer/src/legacy-events/EventPluginRegistry.js b/packages/react-native-renderer/src/legacy-events/EventPluginRegistry.js index 25059535bf02..48cfd5759b30 100644 --- a/packages/react-native-renderer/src/legacy-events/EventPluginRegistry.js +++ b/packages/react-native-renderer/src/legacy-events/EventPluginRegistry.js @@ -199,7 +199,7 @@ export const registrationNameDependencies: { */ export const possibleRegistrationNames: { [lowerCasedName: string]: string, -} = __DEV__ ? {} : (null as any); +} = __DEV__ ? {} : (null: any); // Trust the developer to only use possibleRegistrationNames in __DEV__ /** diff --git a/packages/react-native-renderer/src/legacy-events/ResponderTouchHistoryStore.js b/packages/react-native-renderer/src/legacy-events/ResponderTouchHistoryStore.js index 5c834186d08a..4b523b0a92db 100644 --- a/packages/react-native-renderer/src/legacy-events/ResponderTouchHistoryStore.js +++ b/packages/react-native-renderer/src/legacy-events/ResponderTouchHistoryStore.js @@ -56,7 +56,7 @@ function timestampForTouch(touch: Touch): number { // The legacy internal implementation provides "timeStamp", which has been // renamed to "timestamp". Let both work for now while we iron it out // TODO (evv): rename timeStamp to timestamp in internal code - return (touch as any).timeStamp || touch.timestamp; + return (touch: any).timeStamp || touch.timestamp; } /** diff --git a/packages/react-native-renderer/src/legacy-events/forEachAccumulated.js b/packages/react-native-renderer/src/legacy-events/forEachAccumulated.js index ef93bbc3189d..e9d82b0dd2a2 100644 --- a/packages/react-native-renderer/src/legacy-events/forEachAccumulated.js +++ b/packages/react-native-renderer/src/legacy-events/forEachAccumulated.js @@ -22,7 +22,7 @@ function forEachAccumulated<T>( scope: ?any, ) { if (Array.isArray(arr)) { - // $FlowFixMe[incompatible-type] if `T` is an array, `cb` cannot be called + // $FlowFixMe[incompatible-call] if `T` is an array, `cb` cannot be called arr.forEach(cb, scope); } else if (arr) { cb.call(scope, arr); diff --git a/packages/react-noop-renderer/package.json b/packages/react-noop-renderer/package.json index 8db645941ec7..aae14070714c 100644 --- a/packages/react-noop-renderer/package.json +++ b/packages/react-noop-renderer/package.json @@ -6,7 +6,7 @@ "main": "index.js", "repository": { "type" : "git", - "url" : "https://github.com/react/react.git", + "url" : "https://github.com/facebook/react.git", "directory": "packages/react-noop-renderer" }, "license": "MIT", diff --git a/packages/react-noop-renderer/src/ReactNoopFlightClient.js b/packages/react-noop-renderer/src/ReactNoopFlightClient.js index 5330906688d9..cf7e4168bd89 100644 --- a/packages/react-noop-renderer/src/ReactNoopFlightClient.js +++ b/packages/react-noop-renderer/src/ReactNoopFlightClient.js @@ -27,7 +27,6 @@ const decoderOptions = {stream: true}; const {createResponse, createStreamState, processBinaryChunk, getRoot, close} = // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-type] ReactFlightClient({ createStringDecoder() { return new TextDecoder(); @@ -47,7 +46,7 @@ const {createResponse, createStreamState, processBinaryChunk, getRoot, close} = return readModule(idx); }, bindToConsole(methodName, args, badgeName) { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] return Function.prototype.bind.apply( // eslint-disable-next-line react-internal/no-production-logging console[methodName], @@ -65,10 +64,10 @@ type ReadOptions = {| function read<T>(source: Source, options: ReadOptions): Thenable<T> { const response = createResponse( - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] source, null, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] null, undefined, undefined, @@ -79,7 +78,7 @@ function read<T>(source: Source, options: ReadOptions): Thenable<T> { true, undefined, __DEV__ && options !== undefined && options.debugChannel !== undefined - ? // $FlowFixMe[incompatible-type] + ? // $FlowFixMe[incompatible-call] options.debugChannel.onMessage : undefined, ); diff --git a/packages/react-noop-renderer/src/ReactNoopFlightServer.js b/packages/react-noop-renderer/src/ReactNoopFlightServer.js index 1bd968085083..a10edd2b7737 100644 --- a/packages/react-noop-renderer/src/ReactNoopFlightServer.js +++ b/packages/react-noop-renderer/src/ReactNoopFlightServer.js @@ -25,7 +25,6 @@ type Destination = Array<Uint8Array | string>; const textEncoder = new TextEncoder(); // $FlowFixMe[prop-missing] -// $FlowFixMe[incompatible-type] const ReactNoopFlightServer = ReactFlightServer({ scheduleMicrotask(callback: () => void) { callback(); @@ -83,7 +82,7 @@ function render(model: ReactClientValue, options?: Options): Destination { const bundlerConfig = undefined; const request = ReactNoopFlightServer.createRequest( model, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] bundlerConfig, options ? options.onError : undefined, options ? options.identifierPrefix : undefined, @@ -91,16 +90,16 @@ function render(model: ReactClientValue, options?: Options): Destination { options ? options.startTime : undefined, __DEV__ && options ? options.environmentName : undefined, __DEV__ && options ? options.filterStackFrame : undefined, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] __DEV__ && options && options.debugChannel !== undefined, ); const signal = options ? options.signal : undefined; if (signal) { if (signal.aborted) { - ReactNoopFlightServer.abort(request, (signal as any).reason); + ReactNoopFlightServer.abort(request, (signal: any).reason); } else { const listener = () => { - ReactNoopFlightServer.abort(request, (signal as any).reason); + ReactNoopFlightServer.abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -114,7 +113,7 @@ function render(model: ReactClientValue, options?: Options): Destination { ReactNoopFlightServer.startWork(request); ReactNoopFlightServer.startFlowing( request, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] destination, ); return destination; diff --git a/packages/react-noop-renderer/src/ReactNoopServer.js b/packages/react-noop-renderer/src/ReactNoopServer.js index d8397eab1ace..93f66b27b306 100644 --- a/packages/react-noop-renderer/src/ReactNoopServer.js +++ b/packages/react-noop-renderer/src/ReactNoopServer.js @@ -69,7 +69,7 @@ function write(destination: Destination, buffer: Uint8Array): void { return; } // We assume one chunk is one instance. - const instance = JSON.parse(Buffer.from(buffer as any).toString('utf8')); + const instance = JSON.parse(Buffer.from((buffer: any)).toString('utf8')); if (stack.length === 0) { destination.root = instance; } else { @@ -80,7 +80,6 @@ function write(destination: Destination, buffer: Uint8Array): void { } // $FlowFixMe[prop-missing] -// $FlowFixMe[incompatible-type] const ReactNoopServer = ReactFizzServer({ scheduleMicrotask(callback: () => void) { callback(); @@ -177,7 +176,7 @@ const ReactNoopServer = ReactFizzServer({ destination: Destination, renderState: RenderState, id: number, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-return] ): boolean { const parent = destination.stack[destination.stack.length - 1]; destination.placeholders.set(id, { @@ -362,7 +361,6 @@ type Options = { function render(children: React$Element<any>, options?: Options): Destination { // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-type] const destination: Destination = { root: null, placeholders: new Map(), @@ -373,13 +371,12 @@ function render(children: React$Element<any>, options?: Options): Destination { }, }; const request = ReactNoopServer.createRequest( - // $FlowFixMe[incompatible-type] children, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] null, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] null, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] null, options ? options.progressiveChunkSize : undefined, options ? options.onError : undefined, @@ -387,7 +384,7 @@ function render(children: React$Element<any>, options?: Options): Destination { options ? options.onShellReady : undefined, ); ReactNoopServer.startWork(request); - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] ReactNoopServer.startFlowing(request, destination); return destination; } diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index 4fb16151f3d6..7172648f997a 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -126,16 +126,14 @@ function createReactNoop( prevParent !== -1 && prevParent !== // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-type] - (parentInstance as Instance).id + (parentInstance: Instance).id ) { throw new Error('Reparenting is not allowed'); } child.parent = // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-type] - (parentInstance as Instance).id; + (parentInstance: Instance).id; const index = parentInstance.children.indexOf(child); if (index !== -1) { parentInstance.children.splice(index, 1); @@ -161,7 +159,7 @@ function createReactNoop( parentInstance: Instance, child: Instance | TextInstance, ): void { - if (typeof (parentInstance as any).rootID === 'string') { + if (typeof (parentInstance: any).rootID === 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('appendChild() first argument is not an instance.'); @@ -205,7 +203,7 @@ function createReactNoop( child: Instance | TextInstance, beforeChild: Instance | TextInstance, ) { - if (typeof (parentInstance as any).rootID === 'string') { + if (typeof (parentInstance: any).rootID === 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('insertBefore() first argument is not an instance.'); @@ -246,7 +244,7 @@ function createReactNoop( parentInstance: Instance, child: Instance | TextInstance, ): void { - if (typeof (parentInstance as any).rootID === 'string') { + if (typeof (parentInstance: any).rootID === 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('removeChild() first argument is not an instance.'); @@ -274,7 +272,7 @@ function createReactNoop( : // $FlowFixMe[incompatible-type] We're not typing immutable instances. (children ?? []), text: shouldSetTextContent(type, newProps) - ? computeText((newProps.children as any) + '', instance.context) + ? computeText((newProps.children: any) + '', instance.context) : null, prop: newProps.prop, hidden: !!newProps.hidden, @@ -411,7 +409,7 @@ function createReactNoop( }, getPublicInstance(instance: Instance): PublicInstance { - return instance as any; + return (instance: any); }, HostTransitionContext: null, @@ -440,7 +438,7 @@ function createReactNoop( parent: -1, text: shouldSetTextContent(type, props) ? // eslint-disable-next-line react-internal/safe-string-coercion - computeText((props.children as any) + '', hostContext) + computeText((props.children: any) + '', hostContext) : null, prop: props.prop, hidden: !!props.hidden, @@ -696,12 +694,12 @@ function createReactNoop( return null; }, - NotPendingTransition: null as TransitionStatus, + NotPendingTransition: (null: TransitionStatus), resetFormInstance(form: Instance) {}, bindToConsole(methodName: $FlowFixMe, args: Array<any>, badgeName: string) { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] return Function.prototype.bind.apply( // eslint-disable-next-line react-internal/no-production-logging console[methodName], @@ -737,7 +735,6 @@ function createReactNoop( oldProps: Props, newProps: Props, ): void { - // $FlowFixMe[invalid-compare] if (oldProps === null) { throw new Error('Should have old props'); } @@ -755,7 +752,7 @@ function createReactNoop( checkPropStringCoercion(newProps.children, 'children'); } instance.text = computeText( - (newProps.children as any) + '', + (newProps.children: any) + '', instance.context, ); } @@ -1014,9 +1011,7 @@ function createReactNoop( $$typeof: REACT_ELEMENT_TYPE, key: null, props: props, - // $FlowFixMe[constant-condition] _owner: null, - // $FlowFixMe[constant-condition] _store: __DEV__ ? {} : undefined, }; // $FlowFixMe[prop-missing] @@ -1071,9 +1066,9 @@ function createReactNoop( } if (isArray(child.children)) { // This is an instance. - const instance: Instance = child as any; + const instance: Instance = (child: any); const children = childToJSX(instance.children, instance.text); - const props = {prop: instance.prop} as any; + const props = ({prop: instance.prop}: any); if (instance.hidden) { props.hidden = true; } @@ -1087,7 +1082,7 @@ function createReactNoop( return createJSXElementForTestComparison(instance.type, props); } // This is a text instance - const textInstance: TextInstance = child as any; + const textInstance: TextInstance = (child: any); if (textInstance.hidden) { return ''; } @@ -1146,10 +1141,8 @@ function createReactNoop( const previousTransition = ReactSharedInternals.T; const preivousEventPriority = currentEventPriority; try { - // $FlowFixMe[constant-condition] ReactSharedInternals.T = null; currentEventPriority = DiscreteEventPriority; - // $FlowFixMe[constant-condition] if (fn) { return fn(); } else { @@ -1171,12 +1164,9 @@ function createReactNoop( } function onDefaultTransitionIndicator(): void | (() => void) {} - // $FlowFixMe[recursive-definition] - // $FlowFixMe[definition-cycle] let idCounter = 0; - // $FlowFixMe[definition-cycle] - // $FlowFixMe[recursive-definition] + // $FlowFixMe const ReactNoop = { _Scheduler: Scheduler, @@ -1230,10 +1220,9 @@ function createReactNoop( root = NoopRenderer.createContainer( // $FlowFixMe[incompatible-call] -- Discovered when typechecking noop-renderer container, - // $FlowFixMe[incompatible-type] tag, null, - // $FlowFixMe[incompatible-type] -- Discovered when typechecking noop-renderer + // $FlowFixMe[incompatible-call] -- Discovered when typechecking noop-renderer null, false, '', @@ -1258,10 +1247,9 @@ function createReactNoop( const fiberRoot = NoopRenderer.createContainer( // $FlowFixMe[incompatible-call] container, - // $FlowFixMe[incompatible-type] ConcurrentRoot, null, - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] null, false, '', @@ -1410,11 +1398,9 @@ function createReactNoop( if (disableLegacyMode) { throw new Error('createLegacyRoot: Unsupported Legacy Mode API.'); } - // $FlowFixMe[incompatible-type] const rootID = DEFAULT_ROOT_ID; const container = ReactNoop.getOrCreateRootContainer(rootID, LegacyRoot); const root = roots.get(container.rootID); - // $FlowFixMe[incompatible-type] NoopRenderer.updateContainer(element, root, null, callback); }, @@ -1425,11 +1411,9 @@ function createReactNoop( ) { const container = ReactNoop.getOrCreateRootContainer( rootID, - // $FlowFixMe[incompatible-type] ConcurrentRoot, ); const root = roots.get(container.rootID); - // $FlowFixMe[incompatible-type] NoopRenderer.updateContainer(element, root, null, callback); }, @@ -1450,7 +1434,7 @@ function createReactNoop( return null; } // Unsound duck typing. - const component = componentOrElement as any; + const component = (componentOrElement: any); if (typeof component.id === 'number') { return component; } @@ -1559,9 +1543,8 @@ function createReactNoop( // $FlowFixMe[unsafe-addition] log(indent + '- ' + child.type + '#' + child.id); - // $FlowFixMe[incompatible-type] logHostInstances( - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] child.children, depth + 1, ); @@ -1579,16 +1562,11 @@ function createReactNoop( const update = first; if (update !== null) { do { - // $FlowFixMe[unsafe-addition] - // $FlowFixMe[prop-missing] log( ' '.repeat(depth + 1) + '~', - // $FlowFixMe[invalid-compare] - // $FlowFixMe[prop-missing] - // $FlowFixMe[unsafe-addition] + // $FlowFixMe '[' + update.expirationTime + ']', ); - // $FlowFixMe[invalid-compare] } while (update !== null); } @@ -1597,17 +1575,12 @@ function createReactNoop( const firstPending = lastPending.next; const pendingUpdate = firstPending; if (pendingUpdate !== null) { - // $FlowFixMe[unsafe-addition] - // $FlowFixMe[prop-missing] do { log( - // $FlowFixMe[invalid-compare] ' '.repeat(depth + 1) + '~', - // $FlowFixMe[prop-missing] - // $FlowFixMe[unsafe-addition] + // $FlowFixMe '[' + pendingUpdate.expirationTime + ']', ); - // $FlowFixMe[invalid-compare] } while (pendingUpdate !== null && pendingUpdate !== firstPending); } } @@ -1624,12 +1597,11 @@ function createReactNoop( // $FlowFixMe[prop-missing] fiber.childExpirationTime + (fiber.pendingProps ? '*' : '') + - // $FlowFixMe[incompatible-type] ']', ); if (fiber.updateQueue) { logUpdateQueue( - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] fiber.updateQueue, depth, ); diff --git a/packages/react-reconciler/package.json b/packages/react-reconciler/package.json index e78f937a21ec..91c38415dba7 100644 --- a/packages/react-reconciler/package.json +++ b/packages/react-reconciler/package.json @@ -6,7 +6,7 @@ "react" ], "homepage": "https://react.dev/", - "bugs": "https://github.com/react/react/issues", + "bugs": "https://github.com/facebook/react/issues", "license": "MIT", "files": [ "LICENSE", @@ -19,7 +19,7 @@ "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-reconciler" }, "engines": { diff --git a/packages/react-reconciler/src/ReactCapturedValue.js b/packages/react-reconciler/src/ReactCapturedValue.js index de48fe6de84f..d53489cc3b72 100644 --- a/packages/react-reconciler/src/ReactCapturedValue.js +++ b/packages/react-reconciler/src/ReactCapturedValue.js @@ -25,7 +25,6 @@ export function createCapturedValueAtFiber<T>( ): CapturedValue<T> { // If the value is an error, call this function immediately after it is thrown // so the stack is accurate. - // $FlowFixMe[invalid-compare] if (typeof value === 'object' && value !== null) { const existing = CapturedStacks.get(value); if (existing !== undefined) { @@ -51,7 +50,7 @@ export function createCapturedValueFromError( value: Error, stack: null | string, ): CapturedValue<Error> { - const captured: CapturedValue<Error> = { + const captured = { value, source: null, stack: stack, diff --git a/packages/react-reconciler/src/ReactChildFiber.js b/packages/react-reconciler/src/ReactChildFiber.js index ecb2e159d829..42f1b70918d3 100644 --- a/packages/react-reconciler/src/ReactChildFiber.js +++ b/packages/react-reconciler/src/ReactChildFiber.js @@ -145,9 +145,9 @@ if (__DEV__) { * object keys are not valid. This allows us to keep track of children between * updates. */ - ownerHasKeyUseWarning = {} as {[string]: boolean}; - ownerHasFunctionTypeWarning = {} as {[string]: boolean}; - ownerHasSymbolTypeWarning = {} as {[string]: boolean}; + ownerHasKeyUseWarning = ({}: {[string]: boolean}); + ownerHasFunctionTypeWarning = ({}: {[string]: boolean}); + ownerHasSymbolTypeWarning = ({}: {[string]: boolean}); warnForMissingKey = ( returnFiber: Fiber, @@ -188,7 +188,7 @@ if (__DEV__) { let currentComponentErrorInfo = ''; if (parentOwner && typeof parentOwner.tag === 'number') { - const name = getComponentNameFromFiber(parentOwner as any); + const name = getComponentNameFromFiber((parentOwner: any)); if (name) { currentComponentErrorInfo = '\n\nCheck the render method of `' + name + '`.'; @@ -207,7 +207,7 @@ if (__DEV__) { if (childOwner != null && parentOwner !== childOwner) { let ownerName = null; if (typeof childOwner.tag === 'number') { - ownerName = getComponentNameFromFiber(childOwner as any); + ownerName = getComponentNameFromFiber((childOwner: any)); } else if (typeof childOwner.name === 'string') { ownerName = childOwner.name; } @@ -765,7 +765,7 @@ function createChildReconciler( } case REACT_LAZY_TYPE: { const prevDebugInfo = pushDebugInfo(newChild._debugInfo); - const resolvedChild = resolveLazy(newChild as any); + const resolvedChild = resolveLazy((newChild: any)); const created = createChild(returnFiber, resolvedChild, lanes); currentDebugInfo = prevDebugInfo; return created; @@ -801,7 +801,7 @@ function createChildReconciler( // // Unwrap the inner value and recursively call this function again. if (typeof newChild.then === 'function') { - const thenable: Thenable<any> = newChild as any; + const thenable: Thenable<any> = (newChild: any); const prevDebugInfo = pushDebugInfo(newChild._debugInfo); const created = createChild( returnFiber, @@ -812,9 +812,8 @@ function createChildReconciler( return created; } - // $FlowFixMe[invalid-compare] if (newChild.$$typeof === REACT_CONTEXT_TYPE) { - const context: ReactContext<mixed> = newChild as any; + const context: ReactContext<mixed> = (newChild: any); return createChild( returnFiber, readContextDuringReconciliation(returnFiber, context, lanes), @@ -904,7 +903,7 @@ function createChildReconciler( } case REACT_LAZY_TYPE: { const prevDebugInfo = pushDebugInfo(newChild._debugInfo); - const resolvedChild = resolveLazy(newChild as any); + const resolvedChild = resolveLazy((newChild: any)); const updated = updateSlot( returnFiber, oldFiber, @@ -942,8 +941,8 @@ function createChildReconciler( // // Unwrap the inner value and recursively call this function again. if (typeof newChild.then === 'function') { - const thenable: Thenable<any> = newChild as any; - const prevDebugInfo = pushDebugInfo((thenable as any)._debugInfo); + const thenable: Thenable<any> = (newChild: any); + const prevDebugInfo = pushDebugInfo((thenable: any)._debugInfo); const updated = updateSlot( returnFiber, oldFiber, @@ -954,9 +953,8 @@ function createChildReconciler( return updated; } - // $FlowFixMe[invalid-compare] if (newChild.$$typeof === REACT_CONTEXT_TYPE) { - const context: ReactContext<mixed> = newChild as any; + const context: ReactContext<mixed> = (newChild: any); return updateSlot( returnFiber, oldFiber, @@ -1038,7 +1036,7 @@ function createChildReconciler( } case REACT_LAZY_TYPE: { const prevDebugInfo = pushDebugInfo(newChild._debugInfo); - const resolvedChild = resolveLazy(newChild as any); + const resolvedChild = resolveLazy((newChild: any)); const updated = updateFromMap( existingChildren, returnFiber, @@ -1074,8 +1072,8 @@ function createChildReconciler( // // Unwrap the inner value and recursively call this function again. if (typeof newChild.then === 'function') { - const thenable: Thenable<any> = newChild as any; - const prevDebugInfo = pushDebugInfo((thenable as any)._debugInfo); + const thenable: Thenable<any> = (newChild: any); + const prevDebugInfo = pushDebugInfo((thenable: any)._debugInfo); const updated = updateFromMap( existingChildren, returnFiber, @@ -1087,9 +1085,8 @@ function createChildReconciler( return updated; } - // $FlowFixMe[invalid-compare] if (newChild.$$typeof === REACT_CONTEXT_TYPE) { - const context: ReactContext<mixed> = newChild as any; + const context: ReactContext<mixed> = (newChild: any); return updateFromMap( existingChildren, returnFiber, @@ -1156,7 +1153,7 @@ function createChildReconciler( }); break; case REACT_LAZY_TYPE: { - const resolvedChild = resolveLazy(child as any); + const resolvedChild = resolveLazy((child: any)); warnOnInvalidKey( returnFiber, workInProgress, @@ -1412,7 +1409,7 @@ function createChildReconciler( } didWarnAboutGenerators = true; } - } else if ((newChildrenIterable as any).entries === iteratorFn) { + } else if ((newChildrenIterable: any).entries === iteratorFn) { // Warn about using Maps as children if (!didWarnAboutMaps) { console.error( @@ -1474,11 +1471,11 @@ function createChildReconciler( // To save bytes, we reuse the logic by creating a synchronous Iterable and // reusing that code path. - const iterator: Iterator<mixed> = { + const iterator: Iterator<mixed> = ({ next(): IteratorResult<mixed, void> { return unwrapThenable(newChildren.next()); }, - } as any; + }: any); return reconcileChildrenIterator( returnFiber, @@ -1906,7 +1903,7 @@ function createChildReconciler( ); case REACT_LAZY_TYPE: { const prevDebugInfo = pushDebugInfo(newChild._debugInfo); - const result = resolveLazy(newChild as any); + const result = resolveLazy((newChild: any)); const firstChild = reconcileChildFibersImpl( returnFiber, currentFirstChild, @@ -1974,8 +1971,8 @@ function createChildReconciler( // depending on the type of work, not always at the end. We should // consider as an future improvement. if (typeof newChild.then === 'function') { - const thenable: Thenable<any> = newChild as any; - const prevDebugInfo = pushDebugInfo((thenable as any)._debugInfo); + const thenable: Thenable<any> = (newChild: any); + const prevDebugInfo = pushDebugInfo((thenable: any)._debugInfo); const firstChild = reconcileChildFibersImpl( returnFiber, currentFirstChild, @@ -1986,9 +1983,8 @@ function createChildReconciler( return firstChild; } - // $FlowFixMe[invalid-compare] if (newChild.$$typeof === REACT_CONTEXT_TYPE) { - const context: ReactContext<mixed> = newChild as any; + const context: ReactContext<mixed> = (newChild: any); return reconcileChildFibersImpl( returnFiber, currentFirstChild, @@ -2089,7 +2085,7 @@ function createChildReconciler( if (debugInfo != null) { for (let i = debugInfo.length - 1; i >= 0; i--) { if (typeof debugInfo[i].stack === 'string') { - throwFiber._debugOwner = debugInfo[i] as any; + throwFiber._debugOwner = (debugInfo[i]: any); throwFiber._debugTask = debugInfo[i].debugTask; break; } @@ -2161,7 +2157,7 @@ function validateSuspenseListNestedChild(childSlot: mixed, index: number) { enableAsyncIterableChildren && typeof childSlot === 'object' && childSlot !== null && - typeof (childSlot as any)[ASYNC_ITERATOR] === 'function'; + typeof (childSlot: any)[ASYNC_ITERATOR] === 'function'; if (isAnArray || isIterable || isAsyncIterable) { const type = isAnArray ? 'array' @@ -2220,7 +2216,7 @@ export function validateSuspenseListChildren( } } else if ( enableAsyncIterableChildren && - typeof (children as any)[ASYNC_ITERATOR] === 'function' + typeof (children: any)[ASYNC_ITERATOR] === 'function' ) { // TODO: Technically we should warn for nested arrays inside the // async iterable but it would require unwrapping the array. @@ -2229,11 +2225,10 @@ export function validateSuspenseListChildren( enableAsyncIterableChildren && children.$$typeof === REACT_ELEMENT_TYPE && typeof children.type === 'function' && - // $FlowFixMe[method-unbinding] + // $FlowFixMe (Object.prototype.toString.call(children.type) === '[object GeneratorFunction]' || - // $FlowFixMe[incompatible-use] - // $FlowFixMe[method-unbinding] + // $FlowFixMe Object.prototype.toString.call(children.type) === '[object AsyncGeneratorFunction]') ) { diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 83b12a04a799..7ab798ea22bc 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -581,7 +581,6 @@ export function createFiberFromTypeAndProps( } } } else if (typeof type === 'string') { - // $FlowFixMe[constant-condition] if (supportsResources && supportsSingletons) { const hostContext = getHostContext(); fiberTag = isHostHoistableType(type, pendingProps, hostContext) @@ -589,13 +588,11 @@ export function createFiberFromTypeAndProps( : isHostSingletonType(type) ? HostSingleton : HostComponent; - // $FlowFixMe[constant-condition] } else if (supportsResources) { const hostContext = getHostContext(); fiberTag = isHostHoistableType(type, pendingProps, hostContext) ? HostHoistable : HostComponent; - // $FlowFixMe[constant-condition] } else if (supportsSingletons) { fiberTag = isHostSingletonType(type) ? HostSingleton : HostComponent; } else { @@ -603,13 +600,10 @@ export function createFiberFromTypeAndProps( } } else { getTag: switch (type) { - // $FlowFixMe[invalid-compare] case REACT_ACTIVITY_TYPE: return createFiberFromActivity(pendingProps, mode, lanes, key); - // $FlowFixMe[invalid-compare] case REACT_FRAGMENT_TYPE: return createFiberFromFragment(pendingProps.children, mode, lanes, key); - // $FlowFixMe[invalid-compare] case REACT_STRICT_MODE_TYPE: fiberTag = Mode; mode |= StrictLegacyMode; @@ -618,61 +612,51 @@ export function createFiberFromTypeAndProps( mode |= StrictEffectsMode; } break; - // $FlowFixMe[invalid-compare] case REACT_PROFILER_TYPE: return createFiberFromProfiler(pendingProps, mode, lanes, key); - // $FlowFixMe[invalid-compare] case REACT_SUSPENSE_TYPE: return createFiberFromSuspense(pendingProps, mode, lanes, key); - // $FlowFixMe[invalid-compare] case REACT_SUSPENSE_LIST_TYPE: return createFiberFromSuspenseList(pendingProps, mode, lanes, key); - // $FlowFixMe[invalid-compare] case REACT_LEGACY_HIDDEN_TYPE: if (enableLegacyHidden) { return createFiberFromLegacyHidden(pendingProps, mode, lanes, key); } - // $FlowFixMe[invalid-compare] -- falls through + // Fall through case REACT_VIEW_TRANSITION_TYPE: if (enableViewTransition) { return createFiberFromViewTransition(pendingProps, mode, lanes, key); } - // $FlowFixMe[invalid-compare] -- falls through + // Fall through case REACT_SCOPE_TYPE: if (enableScopeAPI) { return createFiberFromScope(type, pendingProps, mode, lanes, key); } - // $FlowFixMe[invalid-compare] -- falls through + // Fall through case REACT_TRACING_MARKER_TYPE: if (enableTransitionTracing) { return createFiberFromTracingMarker(pendingProps, mode, lanes, key); } // Fall through default: { - // $FlowFixMe[invalid-compare] if (typeof type === 'object' && type !== null) { switch (type.$$typeof) { - // $FlowFixMe[invalid-compare] case REACT_CONTEXT_TYPE: fiberTag = ContextProvider; break getTag; - // $FlowFixMe[invalid-compare] case REACT_CONSUMER_TYPE: fiberTag = ContextConsumer; break getTag; // Fall through - // $FlowFixMe[invalid-compare] case REACT_FORWARD_REF_TYPE: fiberTag = ForwardRef; if (__DEV__) { resolvedType = resolveForwardRefForHotReloading(resolvedType); } break getTag; - // $FlowFixMe[invalid-compare] case REACT_MEMO_TYPE: fiberTag = MemoComponent; break getTag; - // $FlowFixMe[invalid-compare] case REACT_LAZY_TYPE: fiberTag = LazyComponent; resolvedType = null; @@ -685,7 +669,6 @@ export function createFiberFromTypeAndProps( if ( type === undefined || (typeof type === 'object' && - // $FlowFixMe[invalid-compare] type !== null && Object.keys(type).length === 0) ) { @@ -694,14 +677,12 @@ export function createFiberFromTypeAndProps( "it's defined in, or you might have mixed up default and named imports."; } - // $FlowFixMe[invalid-compare] if (type === null) { typeString = 'null'; } else if (isArray(type)) { typeString = 'array'; } else if ( type !== undefined && - // $FlowFixMe[invalid-compare] type.$$typeof === REACT_ELEMENT_TYPE ) { typeString = `<${ @@ -718,7 +699,6 @@ export function createFiberFromTypeAndProps( info += '\n\nCheck the render method of `' + ownerName + '`.'; } } else { - // $FlowFixMe[invalid-compare] typeString = type === null ? 'null' : typeof type; } diff --git a/packages/react-reconciler/src/ReactFiberAct.js b/packages/react-reconciler/src/ReactFiberAct.js index 9a6fb22c8c0a..b611d7472b09 100644 --- a/packages/react-reconciler/src/ReactFiberAct.js +++ b/packages/react-reconciler/src/ReactFiberAct.js @@ -30,7 +30,6 @@ export function isLegacyActEnvironment(fiber: Fiber): boolean { // $FlowFixMe[cannot-resolve-name] - Flow doesn't know about jest const jestIsDefined = typeof jest !== 'undefined'; return ( - // $FlowFixMe[constant-condition] warnsIfNotActing && jestIsDefined && isReactActEnvironmentGlobal !== false ); } diff --git a/packages/react-reconciler/src/ReactFiberApplyGesture.js b/packages/react-reconciler/src/ReactFiberApplyGesture.js index 0e5a3b8b8e26..32e593958457 100644 --- a/packages/react-reconciler/src/ReactFiberApplyGesture.js +++ b/packages/react-reconciler/src/ReactFiberApplyGesture.js @@ -437,7 +437,6 @@ function recursivelyInsertNewFiber( break; } case HostHoistable: { - // $FlowFixMe[constant-condition] if (supportsResources) { // TODO: Hoistables should get optimistically inserted and then removed. recursivelyInsertNew( @@ -451,7 +450,6 @@ function recursivelyInsertNewFiber( // Fall through } case HostSingleton: { - // $FlowFixMe[constant-condition] if (supportsSingletons) { recursivelyInsertNew( finishedWork, @@ -504,7 +502,6 @@ function recursivelyInsertNewFiber( } case HostText: { const textInstance: TextInstance = finishedWork.stateNode; - // $FlowFixMe[invalid-compare] if (textInstance === null) { throw new Error( 'This should have a text node initialized. This error is likely ' + @@ -646,7 +643,6 @@ function recursivelyInsertClonesFromExistingTree( } case HostText: { const textInstance: TextInstance = child.stateNode; - // $FlowFixMe[invalid-compare] if (textInstance === null) { throw new Error( 'This should have a text node initialized. This error is likely ' + @@ -812,7 +808,6 @@ function insertDestinationClonesOfFiber( // to reconciliation, because those can be set on all fiber types. switch (finishedWork.tag) { case HostHoistable: { - // $FlowFixMe[constant-condition] if (supportsResources) { // TODO: Hoistables should get optimistically inserted and then removed. recursivelyInsertClones( @@ -826,7 +821,6 @@ function insertDestinationClonesOfFiber( // Fall through } case HostSingleton: { - // $FlowFixMe[constant-condition] if (supportsSingletons) { recursivelyInsertClones( finishedWork, @@ -922,7 +916,6 @@ function insertDestinationClonesOfFiber( } case HostText: { const textInstance: TextInstance = finishedWork.stateNode; - // $FlowFixMe[invalid-compare] if (textInstance === null) { throw new Error( 'This should have a text node initialized. This error is likely ' + @@ -967,7 +960,6 @@ function insertDestinationClonesOfFiber( parentViewTransition, nextPhase, ); - // $FlowFixMe[invalid-compare] } else if (current !== null && current.memoizedState === null) { // Was previously mounted as visible but is now hidden. trackEnterViewTransitions(current); @@ -1252,9 +1244,9 @@ export function applyDepartureTransitions( if (cancelableChildren !== null) { for (let i = 0; i < cancelableChildren.length; i += 3) { cancelViewTransitionName( - cancelableChildren[i] as any as Instance, - cancelableChildren[i + 1] as any as string, - cancelableChildren[i + 2] as any as Props, + ((cancelableChildren[i]: any): Instance), + ((cancelableChildren[i + 1]: any): string), + ((cancelableChildren[i + 2]: any): Props), ); } } @@ -1315,7 +1307,6 @@ function restoreViewTransitionsOnFiber(finishedWork: Fiber) { const isHidden = newState !== null; if (!isHidden) { restoreEnterOrExitViewTransitions(finishedWork); - // $FlowFixMe[invalid-compare] } else if (current !== null && current.memoizedState === null) { // Was previously mounted as visible but is now hidden. restoreEnterOrExitViewTransitions(current); diff --git a/packages/react-reconciler/src/ReactFiberAsyncAction.js b/packages/react-reconciler/src/ReactFiberAsyncAction.js index 0de3f311fe22..9d1194874d46 100644 --- a/packages/react-reconciler/src/ReactFiberAsyncAction.js +++ b/packages/react-reconciler/src/ReactFiberAsyncAction.js @@ -122,7 +122,7 @@ function pingEngtangledActionScope() { // and notify all the listeners. if (currentEntangledActionThenable !== null) { const fulfilledThenable: FulfilledThenable<void> = - currentEntangledActionThenable as any; + (currentEntangledActionThenable: any); fulfilledThenable.status = 'fulfilled'; } const listeners = currentEntangledListeners; @@ -160,7 +160,7 @@ export function chainThenableValue<T>( thenable.then( (value: T) => { const fulfilledThenable: FulfilledThenable<T> = - thenableWithOverride as any; + (thenableWithOverride: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = result; for (let i = 0; i < listeners.length; i++) { @@ -169,7 +169,7 @@ export function chainThenableValue<T>( } }, error => { - const rejectedThenable: RejectedThenable<T> = thenableWithOverride as any; + const rejectedThenable: RejectedThenable<T> = (thenableWithOverride: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; for (let i = 0; i < listeners.length; i++) { @@ -179,7 +179,7 @@ export function chainThenableValue<T>( // consumer of these promises, and it passes the same listener to both. // We also know that it will read the error directly off the // `.reason` field. - listener(undefined as any); + listener((undefined: any)); } }, ); diff --git a/packages/react-reconciler/src/ReactFiberAsyncDispatcher.js b/packages/react-reconciler/src/ReactFiberAsyncDispatcher.js index b4f6a30d3a71..2dfee307e2c2 100644 --- a/packages/react-reconciler/src/ReactFiberAsyncDispatcher.js +++ b/packages/react-reconciler/src/ReactFiberAsyncDispatcher.js @@ -17,7 +17,7 @@ import {current as currentOwner} from './ReactCurrentFiber'; function getCacheForType<T>(resourceType: () => T): T { const cache: Cache = readContext(CacheContext); - let cacheForType: T | void = cache.data.get(resourceType) as any; + let cacheForType: T | void = (cache.data.get(resourceType): any); if (cacheForType === undefined) { cacheForType = resourceType(); cache.data.set(resourceType, cacheForType); @@ -30,10 +30,10 @@ function cacheSignal(): null | AbortSignal { return cache.controller.signal; } -export const DefaultAsyncDispatcher: AsyncDispatcher = { +export const DefaultAsyncDispatcher: AsyncDispatcher = ({ getCacheForType, cacheSignal, -} as any; +}: any); if (__DEV__) { DefaultAsyncDispatcher.getOwner = (): null | Fiber => { diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 9a4c4c4fb1fd..4f41a70e56eb 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -328,14 +328,14 @@ let didWarnAboutTailOptions; let didWarnAboutClassNameOnViewTransition; if (__DEV__) { - didWarnAboutBadClass = {} as {[string]: boolean}; - didWarnAboutContextTypeOnFunctionComponent = {} as {[string]: boolean}; - didWarnAboutContextTypes = {} as {[string]: boolean}; - didWarnAboutGetDerivedStateOnFunctionComponent = {} as {[string]: boolean}; + didWarnAboutBadClass = ({}: {[string]: boolean}); + didWarnAboutContextTypeOnFunctionComponent = ({}: {[string]: boolean}); + didWarnAboutContextTypes = ({}: {[string]: boolean}); + didWarnAboutGetDerivedStateOnFunctionComponent = ({}: {[string]: boolean}); didWarnAboutReassigningProps = false; - didWarnAboutRevealOrder = {} as {[string]: boolean}; - didWarnAboutTailOptions = {} as {[string]: boolean}; - didWarnAboutClassNameOnViewTransition = {} as {[string]: boolean}; + didWarnAboutRevealOrder = ({}: {[string]: boolean}); + didWarnAboutTailOptions = ({}: {[string]: boolean}); + didWarnAboutClassNameOnViewTransition = ({}: {[string]: boolean}); } export function reconcileChildren( @@ -421,7 +421,7 @@ function updateForwardRef( // `ref` is just a prop now, but `forwardRef` expects it to not appear in // the props object. This used to happen in the JSX runtime, but now we do // it here. - propsWithoutRef = {} as {[string]: any}; + propsWithoutRef = ({}: {[string]: any}); for (const key in nextProps) { // Since `ref` should only appear in props via the JSX transform, we can // assume that this is a plain object. So we don't need a @@ -512,7 +512,7 @@ function updateMemoComponent( workInProgress.child = child; return child; } - const currentChild = current.child as any as Fiber; // This is always exactly one child + const currentChild = ((current.child: any): Fiber); // This is always exactly one child const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext( current, renderLanes, @@ -703,7 +703,7 @@ function updateOffscreenComponent( } reuseHiddenContextOnStack(workInProgress); pushOffscreenSuspenseHandler(workInProgress); - } else if (!includesSomeLane(renderLanes, OffscreenLane as Lane)) { + } else if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) { // We're hidden, and we're not rendering at Offscreen. We will bail out // and resume this tree later. @@ -894,7 +894,7 @@ function mountActivityChildren( renderLanes: Lanes, ) { if (__DEV__) { - const hiddenProp = (nextProps as any).hidden; + const hiddenProp = (nextProps: any).hidden; if (hiddenProp !== undefined) { console.error( '<Activity> doesn\'t accept a hidden prop. Use mode="hidden" instead.\n' + @@ -992,7 +992,7 @@ function updateDehydratedActivityComponent( // but after we've already committed once. warnIfHydrating(); - if (includesSomeLane(renderLanes, OffscreenLane as Lane)) { + if (includesSomeLane(renderLanes, (OffscreenLane: Lane))) { // If we're rendering Offscreen and we're entering the activity then it's possible // that the only reason we rendered was because this boundary left work. Provide // it as a cause if another one doesn't already exist. @@ -1098,9 +1098,7 @@ function updateDehydratedActivityComponent( workInProgress, renderLanes, ); - } else if ( - (workInProgress.memoizedState as null | ActivityState) !== null - ) { + } else if ((workInProgress.memoizedState: null | ActivityState) !== null) { // Something suspended and we should still be in dehydrated mode. // Leave the existing child in place. @@ -1183,7 +1181,7 @@ function updateActivityComponent( ); } - const currentChild: Fiber = current.child as any; + const currentChild: Fiber = (current.child: any); const nextChildren = nextProps.children; const nextMode = nextProps.mode; @@ -1193,7 +1191,7 @@ function updateActivityComponent( }; if ( - includesSomeLane(renderLanes, OffscreenLane as Lane) && + includesSomeLane(renderLanes, (OffscreenLane: Lane)) && includesSomeLane(renderLanes, current.lanes) ) { // If we're rendering Offscreen and we're entering the activity then it's possible @@ -1255,7 +1253,7 @@ function updateCacheComponent( // queue is empty, persist the derived state onto the base state. workInProgress.memoizedState = derivedState; if (workInProgress.lanes === NoLanes) { - const updateQueue: UpdateQueue<any> = workInProgress.updateQueue as any; + const updateQueue: UpdateQueue<any> = (workInProgress.updateQueue: any); workInProgress.memoizedState = updateQueue.baseState = derivedState; } @@ -1787,7 +1785,7 @@ function finishClassComponent( } function pushHostRootContext(workInProgress: Fiber) { - const root = workInProgress.stateNode as FiberRoot; + const root = (workInProgress.stateNode: FiberRoot); if (root.pendingContext) { pushTopLevelContextObject( workInProgress, @@ -1841,7 +1839,6 @@ function updateHostRoot( // Caution: React DevTools currently depends on this property // being called "element". const nextChildren = nextState.element; - // $FlowFixMe[constant-condition] if (supportsHydration && prevState.isDehydrated) { // This is a hydration root whose shell has not yet hydrated. We should // attempt to hydrate. @@ -1854,7 +1851,7 @@ function updateHostRoot( cache: nextState.cache, }; const updateQueue: UpdateQueue<RootState> = - workInProgress.updateQueue as any; + (workInProgress.updateQueue: any); // `baseState` can always be the last state because the root doesn't // have reducer functions so it doesn't need rebasing. updateQueue.baseState = overrideState; @@ -1992,7 +1989,6 @@ function updateHostComponent( // and forms cannot be nested. If we did support nested providers, then // we would need to push a context value even for host fibers that // haven't been upgraded yet. - // $FlowFixMe[constant-condition] if (isPrimaryRenderer) { HostTransitionContext._currentValue = newState; } else { @@ -2127,10 +2123,8 @@ function mountLazyComponent( renderLanes, ); } - // $FlowFixMe[invalid-compare] } else if (Component !== undefined && Component !== null) { const $$typeof = Component.$$typeof; - // $FlowFixMe[invalid-compare] if ($$typeof === REACT_FORWARD_REF_TYPE) { workInProgress.tag = ForwardRef; if (__DEV__) { @@ -2144,7 +2138,6 @@ function mountLazyComponent( props, renderLanes, ); - // $FlowFixMe[invalid-compare] } else if ($$typeof === REACT_MEMO_TYPE) { workInProgress.tag = MemoComponent; return updateMemoComponent( @@ -2154,7 +2147,6 @@ function mountLazyComponent( props, renderLanes, ); - // $FlowFixMe[invalid-compare] } else if ($$typeof === REACT_CONTEXT_TYPE) { workInProgress.tag = ContextProvider; workInProgress.type = Component; @@ -2165,10 +2157,8 @@ function mountLazyComponent( let hint = ''; if (__DEV__) { if ( - // $FlowFixMe[invalid-compare] Component !== null && typeof Component === 'object' && - // $FlowFixMe[invalid-compare] Component.$$typeof === REACT_LAZY_TYPE ) { hint = ' Did you wrap a component in React.lazy() more than once?'; @@ -2285,7 +2275,6 @@ function updateSuspenseOffscreenState( let cachePool: SpawnedCachePool | null = null; const prevCachePool: SpawnedCachePool | null = prevOffscreenState.cachePool; if (prevCachePool !== null) { - // $FlowFixMe[constant-condition] const parentCache = isPrimaryRenderer ? CacheContext._currentValue : CacheContext._currentValue2; @@ -2324,7 +2313,6 @@ function shouldRemainOnFallback( // whether the current fiber (if it exists) was visible in the previous tree. if (current !== null) { const suspenseState: SuspenseState = current.memoizedState; - // $FlowFixMe[invalid-compare] if (suspenseState === null) { // Currently showing content. Don't hide it, even if ForceSuspenseFallback // is true. More precise name might be "ForceRemainSuspenseFallback". @@ -2338,7 +2326,7 @@ function shouldRemainOnFallback( const suspenseContext: SuspenseContext = suspenseStackCursor.current; return hasSuspenseListContext( suspenseContext, - ForceSuspenseFallback as SuspenseContext, + (ForceSuspenseFallback: SuspenseContext), ); } @@ -2448,7 +2436,7 @@ function updateSuspenseComponent( nextFallbackChildren, renderLanes, ); - const primaryChildFragment: Fiber = workInProgress.child as any; + const primaryChildFragment: Fiber = (workInProgress.child: any); primaryChildFragment.memoizedState = mountSuspenseOffscreenState(renderLanes); primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree( @@ -2462,7 +2450,7 @@ function updateSuspenseComponent( if (currentTransitions !== null) { const parentMarkerInstances = getMarkerInstances(); const offscreenQueue: OffscreenQueue | null = - primaryChildFragment.updateQueue as any; + (primaryChildFragment.updateQueue: any); if (offscreenQueue === null) { const newOffscreenQueue: OffscreenQueue = { transitions: currentTransitions, @@ -2489,7 +2477,7 @@ function updateSuspenseComponent( nextFallbackChildren, renderLanes, ); - const primaryChildFragment: Fiber = workInProgress.child as any; + const primaryChildFragment: Fiber = (workInProgress.child: any); primaryChildFragment.memoizedState = mountSuspenseOffscreenState(renderLanes); primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree( @@ -2552,8 +2540,8 @@ function updateSuspenseComponent( nextFallbackChildren, renderLanes, ); - const primaryChildFragment: Fiber = workInProgress.child as any; - const prevOffscreenState: OffscreenState | null = (current.child as any) + const primaryChildFragment: Fiber = (workInProgress.child: any); + const prevOffscreenState: OffscreenState | null = (current.child: any) .memoizedState; primaryChildFragment.memoizedState = prevOffscreenState === null @@ -2564,9 +2552,9 @@ function updateSuspenseComponent( if (currentTransitions !== null) { const parentMarkerInstances = getMarkerInstances(); const offscreenQueue: OffscreenQueue | null = - primaryChildFragment.updateQueue as any; + (primaryChildFragment.updateQueue: any); const currentOffscreenQueue: OffscreenQueue | null = - current.updateQueue as any; + (current.updateQueue: any); if (offscreenQueue === null) { const newOffscreenQueue: OffscreenQueue = { transitions: currentTransitions, @@ -2736,7 +2724,7 @@ function updateSuspensePrimaryChildren( primaryChildren: $FlowFixMe, renderLanes: Lanes, ) { - const currentPrimaryChildFragment: Fiber = current.child as any; + const currentPrimaryChildFragment: Fiber = (current.child: any); const currentFallbackChildFragment: Fiber | null = currentPrimaryChildFragment.sibling; @@ -2775,7 +2763,7 @@ function updateSuspenseFallbackChildren( renderLanes: Lanes, ) { const mode = workInProgress.mode; - const currentPrimaryChildFragment: Fiber = current.child as any; + const currentPrimaryChildFragment: Fiber = (current.child: any); const currentFallbackChildFragment: Fiber | null = currentPrimaryChildFragment.sibling; @@ -2798,7 +2786,7 @@ function updateSuspenseFallbackChildren( // only codepath.) workInProgress.child !== currentPrimaryChildFragment ) { - const progressedPrimaryFragment: Fiber = workInProgress.child as any; + const progressedPrimaryFragment: Fiber = (workInProgress.child: any); primaryChildFragment = progressedPrimaryFragment; primaryChildFragment.childLanes = NoLanes; primaryChildFragment.pendingProps = primaryChildProps; @@ -2967,7 +2955,7 @@ function updateDehydratedSuspenseComponent( // but after we've already committed once. warnIfHydrating(); - if (includesSomeLane(renderLanes, OffscreenLane as Lane)) { + if (includesSomeLane(renderLanes, (OffscreenLane: Lane))) { // If we're rendering Offscreen and we're entering the activity then it's possible // that the only reason we rendered was because this boundary left work. Provide // it as a cause if another one doesn't already exist. @@ -3002,7 +2990,7 @@ function updateDehydratedSuspenseComponent( } // Replace the stack with the server stack error.stack = (__DEV__ && stack) || ''; - (error as any).digest = digest; + (error: any).digest = digest; const capturedValue = createCapturedValueFromError( error, componentStack === undefined ? null : componentStack, @@ -3137,9 +3125,7 @@ function updateDehydratedSuspenseComponent( workInProgress, renderLanes, ); - } else if ( - (workInProgress.memoizedState as null | SuspenseState) !== null - ) { + } else if ((workInProgress.memoizedState: null | SuspenseState) !== null) { // Something suspended and we should still be in dehydrated mode. // Leave the existing child in place. @@ -3165,7 +3151,7 @@ function updateDehydratedSuspenseComponent( nextFallbackChildren, renderLanes, ); - const primaryChildFragment: Fiber = workInProgress.child as any; + const primaryChildFragment: Fiber = (workInProgress.child: any); primaryChildFragment.memoizedState = mountSuspenseOffscreenState(renderLanes); primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree( @@ -3272,13 +3258,9 @@ function validateRevealOrder(revealOrder: SuspenseListRevealOrder) { didWarnAboutRevealOrder[cacheKey] = true; if (typeof revealOrder === 'string') { switch (revealOrder.toLowerCase()) { - // $FlowFixMe[invalid-compare] case 'together': - // $FlowFixMe[invalid-compare] -- falls through case 'forwards': - // $FlowFixMe[invalid-compare] -- falls through case 'backwards': - // $FlowFixMe[invalid-compare] -- falls through case 'independent': { console.error( '"%s" is not a valid value for revealOrder on <SuspenseList />. ' + @@ -3288,9 +3270,7 @@ function validateRevealOrder(revealOrder: SuspenseListRevealOrder) { ); break; } - // $FlowFixMe[invalid-compare] case 'forward': - // $FlowFixMe[invalid-compare] -- falls through case 'backward': { console.error( '"%s" is not a valid value for revealOrder on <SuspenseList />. ' + @@ -3368,7 +3348,7 @@ function initSuspenseListRenderState( const renderState: null | SuspenseListRenderState = workInProgress.memoizedState; if (renderState === null) { - workInProgress.memoizedState = { + workInProgress.memoizedState = ({ isBackwards: isBackwards, rendering: null, renderingStartTime: 0, @@ -3376,7 +3356,7 @@ function initSuspenseListRenderState( tail: tail, tailMode: tailMode, treeForkCount: treeForkCount, - } as SuspenseListRenderState; + }: SuspenseListRenderState); } else { // We can reuse the existing object from previous renders. renderState.isBackwards = isBackwards; @@ -3428,7 +3408,7 @@ function updateSuspenseListComponent( const shouldForceFallback = hasSuspenseListContext( suspenseContext, - ForceSuspenseFallback as SuspenseContext, + (ForceSuspenseFallback: SuspenseContext), ); if (shouldForceFallback) { suspenseContext = setShallowSuspenseListContext( @@ -3850,7 +3830,6 @@ function remountFiber( newWorkInProgress.return = oldWorkInProgress.return; newWorkInProgress.ref = oldWorkInProgress.ref; - // $FlowFixMe[constant-condition] if (__DEV__) { newWorkInProgress._debugInfo = oldWorkInProgress._debugInfo; } @@ -4030,7 +4009,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate( workInProgress, renderLanes, ); - const primaryChildFragment: Fiber = workInProgress.child as any; + const primaryChildFragment: Fiber = (workInProgress.child: any); const primaryChildLanes = primaryChildFragment.childLanes; if ( contextChanged || @@ -4318,13 +4297,11 @@ function beginWork( case HostRoot: return updateHostRoot(current, workInProgress, renderLanes); case HostHoistable: - // $FlowFixMe[constant-condition] if (supportsResources) { return updateHostHoistable(current, workInProgress, renderLanes); } // Fall through case HostSingleton: - // $FlowFixMe[constant-condition] if (supportsSingletons) { return updateHostSingleton(current, workInProgress, renderLanes); } diff --git a/packages/react-reconciler/src/ReactFiberCacheComponent.js b/packages/react-reconciler/src/ReactFiberCacheComponent.js index 4de6a07b1200..0ab12bd4ec4d 100644 --- a/packages/react-reconciler/src/ReactFiberCacheComponent.js +++ b/packages/react-reconciler/src/ReactFiberCacheComponent.js @@ -63,11 +63,11 @@ const { export const CacheContext: ReactContext<Cache> = { $$typeof: REACT_CONTEXT_TYPE, // We don't use Consumer/Provider for Cache components. So we'll cheat. - Consumer: null as any, - Provider: null as any, + Consumer: (null: any), + Provider: (null: any), // We'll initialize these at the root. - _currentValue: null as any, - _currentValue2: null as any, + _currentValue: (null: any), + _currentValue2: (null: any), _threadCount: 0, }; diff --git a/packages/react-reconciler/src/ReactFiberCallUserSpace.js b/packages/react-reconciler/src/ReactFiberCallUserSpace.js index c18c0b44cc56..ce88814797c5 100644 --- a/packages/react-reconciler/src/ReactFiberCallUserSpace.js +++ b/packages/react-reconciler/src/ReactFiberCallUserSpace.js @@ -41,8 +41,8 @@ export const callComponentInDEV: <Props, Arg, R>( secondArg: Arg, ) => R = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. - (callComponent.react_stack_bottom_frame.bind(callComponent) as any) - : (null as any); + (callComponent.react_stack_bottom_frame.bind(callComponent): any) + : (null: any); interface ClassInstance<R> { render(): R; @@ -72,8 +72,8 @@ const callRender = { export const callRenderInDEV: <R>(instance: ClassInstance<R>) => R => R = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. - (callRender.react_stack_bottom_frame.bind(callRender) as any) - : (null as any); + (callRender.react_stack_bottom_frame.bind(callRender): any) + : (null: any); const callComponentDidMount = { react_stack_bottom_frame: function ( @@ -95,8 +95,8 @@ export const callComponentDidMountInDEV: ( ? // We use this technique to trick minifiers to preserve the function name. (callComponentDidMount.react_stack_bottom_frame.bind( callComponentDidMount, - ) as any) - : (null as any); + ): any) + : (null: any); const callComponentDidUpdate = { react_stack_bottom_frame: function ( @@ -124,8 +124,8 @@ export const callComponentDidUpdateInDEV: ( ? // We use this technique to trick minifiers to preserve the function name. (callComponentDidUpdate.react_stack_bottom_frame.bind( callComponentDidUpdate, - ) as any) - : (null as any); + ): any) + : (null: any); const callComponentDidCatch = { react_stack_bottom_frame: function ( @@ -147,8 +147,8 @@ export const callComponentDidCatchInDEV: ( ? // We use this technique to trick minifiers to preserve the function name. (callComponentDidCatch.react_stack_bottom_frame.bind( callComponentDidCatch, - ) as any) - : (null as any); + ): any) + : (null: any); const callComponentWillUnmount = { react_stack_bottom_frame: function ( @@ -172,8 +172,8 @@ export const callComponentWillUnmountInDEV: ( ? // We use this technique to trick minifiers to preserve the function name. (callComponentWillUnmount.react_stack_bottom_frame.bind( callComponentWillUnmount, - ) as any) - : (null as any); + ): any) + : (null: any); const callCreate = { react_stack_bottom_frame: function ( @@ -189,8 +189,8 @@ const callCreate = { export const callCreateInDEV: (effect: Effect) => (() => void) | void = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. - (callCreate.react_stack_bottom_frame.bind(callCreate) as any) - : (null as any); + (callCreate.react_stack_bottom_frame.bind(callCreate): any) + : (null: any); const callDestroy = { react_stack_bottom_frame: function ( @@ -212,8 +212,8 @@ export const callDestroyInDEV: ( destroy: (() => void) | (({...}) => void), ) => void = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. - (callDestroy.react_stack_bottom_frame.bind(callDestroy) as any) - : (null as any); + (callDestroy.react_stack_bottom_frame.bind(callDestroy): any) + : (null: any); const callLazyInit = { react_stack_bottom_frame: function (lazy: LazyComponent<any, any>): any { @@ -225,5 +225,5 @@ const callLazyInit = { export const callLazyInitInDEV: (lazy: LazyComponent<any, any>) => any = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. - (callLazyInit.react_stack_bottom_frame.bind(callLazyInit) as any) - : (null as any); + (callLazyInit.react_stack_bottom_frame.bind(callLazyInit): any) + : (null: any); diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index af3f725adc30..1fae90f07b1a 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -157,7 +157,7 @@ function applyDerivedStateFromProps( // base state. if (workInProgress.lanes === NoLanes) { // Queue is always non-null for classes - const updateQueue: UpdateQueue<any> = workInProgress.updateQueue as any; + const updateQueue: UpdateQueue<any> = (workInProgress.updateQueue: any); updateQueue.baseState = memoizedState; } } @@ -574,7 +574,7 @@ function constructClassInstance( } if (typeof contextType === 'object' && contextType !== null) { - context = readContext(contextType as any); + context = readContext((contextType: any)); } else if (!disableLegacyContext) { unmaskedContext = getUnmaskedContext(workInProgress, ctor, true); const contextTypes = ctor.contextTypes; @@ -1070,11 +1070,9 @@ function updateClassInstance( !hasContextChanged() && !checkHasForceUpdateAfterProcessing() && !( - // prettier-ignore - // $FlowFixMe[invalid-compare] - (current !== null && - current.dependencies !== null && - checkIfContextChanged(current.dependencies)) + current !== null && + current.dependencies !== null && + checkIfContextChanged(current.dependencies) ) ) { // If an update was already in progress, we should schedule an Update @@ -1123,7 +1121,6 @@ function updateClassInstance( // both before and after `shouldComponentUpdate` has been called. Not ideal, // but I'm loath to refactor this function. This only happens for memoized // components so it's not that common. - // $FlowFixMe[invalid-compare] (current !== null && current.dependencies !== null && checkIfContextChanged(current.dependencies)); @@ -1192,7 +1189,7 @@ export function resolveClassComponentProps( // Remove ref from the props object, if it exists. if ('ref' in baseProps) { - newProps = {} as any; + newProps = ({}: any); for (const propName in baseProps) { if (propName !== 'ref') { newProps[propName] = baseProps[propName]; diff --git a/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js b/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js index 769c8d3b9e4e..23bef1e6b098 100644 --- a/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js +++ b/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js @@ -193,8 +193,8 @@ export function cloneUpdateQueue<State>( workInProgress: Fiber, ): void { // Clone the update queue from current. Unless it's already a clone. - const queue: UpdateQueue<State> = workInProgress.updateQueue as any; - const currentQueue: UpdateQueue<State> = current.updateQueue as any; + const queue: UpdateQueue<State> = (workInProgress.updateQueue: any); + const currentQueue: UpdateQueue<State> = (current.updateQueue: any); if (queue === currentQueue) { const clone: UpdateQueue<State> = { baseState: currentQueue.baseState, @@ -231,7 +231,7 @@ export function enqueueUpdate<State>( return null; } - const sharedQueue: SharedQueue<State> = (updateQueue as any).shared; + const sharedQueue: SharedQueue<State> = (updateQueue: any).shared; if (__DEV__) { if ( @@ -280,7 +280,7 @@ export function entangleTransitions(root: FiberRoot, fiber: Fiber, lane: Lane) { return; } - const sharedQueue: SharedQueue<mixed> = (updateQueue as any).shared; + const sharedQueue: SharedQueue<mixed> = (updateQueue: any).shared; if (isTransitionLane(lane)) { let queueLanes = sharedQueue.lanes; @@ -308,12 +308,12 @@ export function enqueueCapturedUpdate<State>( // Captured updates are updates that are thrown by a child during the render // phase. They should be discarded if the render is aborted. Therefore, // we should only put them on the work-in-progress queue, not the current one. - let queue: UpdateQueue<State> = workInProgress.updateQueue as any; + let queue: UpdateQueue<State> = (workInProgress.updateQueue: any); // Check if the work-in-progress queue is a clone. const current = workInProgress.alternate; if (current !== null) { - const currentQueue: UpdateQueue<State> = current.updateQueue as any; + const currentQueue: UpdateQueue<State> = (current.updateQueue: any); if (queue === currentQueue) { // The work-in-progress queue is the same as current. This happens when // we bail out on a parent fiber that then captures an error thrown by @@ -350,7 +350,6 @@ export function enqueueCapturedUpdate<State>( } while (update !== null); // Append the captured update the end of the cloned list. - // $FlowFixMe[invalid-compare] if (newLast === null) { newFirst = newLast = capturedUpdate; } else { @@ -493,7 +492,7 @@ export function processUpdateQueue<State>( didReadFromEntangledAsyncAction = false; // This is always non-null on a ClassComponent or HostRoot - const queue: UpdateQueue<State> = workInProgress.updateQueue as any; + const queue: UpdateQueue<State> = (workInProgress.updateQueue: any); hasForceUpdate = false; @@ -530,7 +529,7 @@ export function processUpdateQueue<State>( const current = workInProgress.alternate; if (current !== null) { // This is always non-null on a ClassComponent or HostRoot - const currentQueue: UpdateQueue<State> = current.updateQueue as any; + const currentQueue: UpdateQueue<State> = (current.updateQueue: any); const currentLastBaseUpdate = currentQueue.lastBaseUpdate; if (currentLastBaseUpdate !== lastBaseUpdate) { if (currentLastBaseUpdate === null) { @@ -656,7 +655,7 @@ export function processUpdateQueue<State>( // Intentionally unsound. Pending updates form a circular list, but we // unravel them when transferring them to the base queue. const firstPendingUpdate = - lastPendingUpdate.next as any as Update<State>; + ((lastPendingUpdate.next: any): Update<State>); lastPendingUpdate.next = null; update = firstPendingUpdate; queue.lastBaseUpdate = lastPendingUpdate; @@ -669,11 +668,10 @@ export function processUpdateQueue<State>( newBaseState = newState; } - queue.baseState = newBaseState as any as State; + queue.baseState = ((newBaseState: any): State); queue.firstBaseUpdate = newFirstBaseUpdate; queue.lastBaseUpdate = newLastBaseUpdate; - // $FlowFixMe[invalid-compare] if (firstBaseUpdate === null) { // `queue.lanes` is used for entangling transitions. We can set it back to // zero once the queue is empty. diff --git a/packages/react-reconciler/src/ReactFiberCommitEffects.js b/packages/react-reconciler/src/ReactFiberCommitEffects.js index 6ee3c9bcad7d..f7e20fe92619 100644 --- a/packages/react-reconciler/src/ReactFiberCommitEffects.js +++ b/packages/react-reconciler/src/ReactFiberCommitEffects.js @@ -144,7 +144,7 @@ export function commitHookEffectListMount( ) { try { const updateQueue: FunctionComponentUpdateQueue | null = - finishedWork.updateQueue as any; + (finishedWork.updateQueue: any); const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; if (lastEffect !== null) { const firstEffect = lastEffect.next; @@ -195,12 +195,11 @@ export function commitHookEffectListMount( hookName = 'useEffect'; } let addendum; - // $FlowFixMe[invalid-compare] if (destroy === null) { addendum = ' You returned null. If your effect does not require clean ' + 'up, return undefined (or nothing).'; - // $FlowFixMe[incompatible-type] (@poteto) this check is safe on arbitrary non-null/void objects + // $FlowFixMe (@poteto) this check is safe on arbitrary non-null/void objects } else if (typeof destroy.then === 'function') { addendum = '\n\nIt looks like you wrote ' + @@ -253,7 +252,7 @@ export function commitHookEffectListUnmount( ) { try { const updateQueue: FunctionComponentUpdateQueue | null = - finishedWork.updateQueue as any; + (finishedWork.updateQueue: any); const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; if (lastEffect !== null) { const firstEffect = lastEffect.next; @@ -519,7 +518,7 @@ export function commitClassCallbacks(finishedWork: Fiber) { // TODO: I think this is now always non-null by the time it reaches the // commit phase. Consider removing the type check. const updateQueue: UpdateQueue<mixed> | null = - finishedWork.updateQueue as any; + (finishedWork.updateQueue: any); if (updateQueue !== null) { const instance = finishedWork.stateNode; if (__DEV__) { @@ -569,7 +568,7 @@ export function commitClassHiddenCallbacks(finishedWork: Fiber) { // Commit any callbacks that would have fired while the component // was hidden. const updateQueue: UpdateQueue<mixed> | null = - finishedWork.updateQueue as any; + (finishedWork.updateQueue: any); if (updateQueue !== null) { const instance = finishedWork.stateNode; try { @@ -593,7 +592,7 @@ export function commitRootCallbacks(finishedWork: Fiber) { // TODO: I think this is now always non-null by the time it reaches the // commit phase. Consider removing the type check. const updateQueue: UpdateQueue<mixed> | null = - finishedWork.updateQueue as any; + (finishedWork.updateQueue: any); if (updateQueue !== null) { let instance = null; if (finishedWork.child !== null) { @@ -682,7 +681,7 @@ export function commitClassSnapshot(finishedWork: Fiber, current: Fiber) { prevState, ); const didWarnSet = - didWarnAboutUndefinedSnapshotBeforeUpdate as any as Set<mixed>; + ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>); if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) { didWarnSet.add(finishedWork.type); runWithFiberInDEV(finishedWork, () => { @@ -883,7 +882,7 @@ export function safelyDetachRef( try { startEffectTimer(); if (__DEV__) { - runWithFiberInDEV(current, ref, null) as void; + (runWithFiberInDEV(current, ref, null): void); } else { ref(null); } @@ -892,7 +891,7 @@ export function safelyDetachRef( } } else { if (__DEV__) { - runWithFiberInDEV(current, ref, null) as void; + (runWithFiberInDEV(current, ref, null): void); } else { ref(null); } @@ -925,7 +924,7 @@ function safelyCallDestroy( ); } else { try { - // $FlowFixMe[incompatible-type](incompatible-call) Already bound to resource + // $FlowFixMe(incompatible-call) Already bound to resource destroy_(); } catch (error) { captureCommitPhaseError(current, nearestMountedAncestor, error); @@ -939,7 +938,7 @@ function commitProfiler( commitStartTime: number, effectDuration: number, ) { - const {id, onCommit, onRender} = finishedWork.memoizedProps as ProfilerProps; + const {id, onCommit, onRender} = (finishedWork.memoizedProps: ProfilerProps); let phase: ProfilerPhase = current === null ? 'mount' : 'update'; if (enableProfilerNestedUpdatePhase) { @@ -952,11 +951,11 @@ function commitProfiler( onRender( id, phase, - // $FlowFixMe[incompatible-type]: This should be always a number in profiling mode + // $FlowFixMe: This should be always a number in profiling mode finishedWork.actualDuration, - // $FlowFixMe[incompatible-type]: This should be always a number in profiling mode + // $FlowFixMe: This should be always a number in profiling mode finishedWork.treeBaseDuration, - // $FlowFixMe[incompatible-type]: This should be always a number in profiling mode + // $FlowFixMe: This should be always a number in profiling mode finishedWork.actualStartTime, commitStartTime, ); diff --git a/packages/react-reconciler/src/ReactFiberCommitHostEffects.js b/packages/react-reconciler/src/ReactFiberCommitHostEffects.js index 043db3fdcacc..3626561e1b49 100644 --- a/packages/react-reconciler/src/ReactFiberCommitHostEffects.js +++ b/packages/react-reconciler/src/ReactFiberCommitHostEffects.js @@ -311,9 +311,7 @@ function isHostParent(fiber: Fiber): boolean { return ( fiber.tag === HostComponent || fiber.tag === HostRoot || - // $FlowFixMe[constant-condition] (supportsResources ? fiber.tag === HostHoistable : false) || - // $FlowFixMe[constant-condition] (supportsSingletons ? fiber.tag === HostSingleton && isSingletonScope(fiber.type) : false) || @@ -353,7 +351,6 @@ function getHostSibling(fiber: Fiber): ?Instance { // singleton scope. If it is a singleton scope we skip over it because // you only insert against this scope when you are already inside of it if ( - // $FlowFixMe[constant-condition] supportsSingletons && node.tag === HostSingleton && isSingletonScope(node.type) @@ -412,7 +409,6 @@ function insertOrAppendPlacementNodeIntoContainer( } if ( - // $FlowFixMe[constant-condition] (supportsSingletons ? tag === HostSingleton : false) && isSingletonScope(node.type) ) { @@ -471,7 +467,6 @@ function insertOrAppendPlacementNode( } if ( - // $FlowFixMe[constant-condition] (supportsSingletons ? tag === HostSingleton : false) && isSingletonScope(node.type) ) { @@ -517,7 +512,6 @@ function commitPlacement(finishedWork: Fiber): void { parentFiber = parentFiber.return; } - // $FlowFixMe[constant-condition] if (!supportsMutation) { if (enableFragmentRefs) { commitImmutablePlacementNodeToFragmentInstances( @@ -537,7 +531,6 @@ function commitPlacement(finishedWork: Fiber): void { switch (hostParentFiber.tag) { case HostSingleton: { - // $FlowFixMe[constant-condition] if (supportsSingletons) { const parent: Instance = hostParentFiber.stateNode; const before = getHostSibling(finishedWork); diff --git a/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js b/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js index d1b72b5a8336..760270010dbc 100644 --- a/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js +++ b/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js @@ -140,7 +140,6 @@ function applyViewTransitionToHostInstancesRecursive( collectMeasurements: null | Array<InstanceMeasurement>, stopAtNestedViewTransitions: boolean, ): boolean { - // $FlowFixMe[constant-condition] if (!supportsMutation) { if (enableViewTransitionForPersistenceMode) { while (child !== null) { @@ -242,7 +241,6 @@ function restoreViewTransitionOnHostInstances( child: null | Fiber, stopAtNestedViewTransitions: boolean, ): void { - // $FlowFixMe[constant-condition] if (!supportsMutation) { return; } @@ -690,7 +688,6 @@ function measureViewTransitionHostInstancesRecursive( previousMeasurements: null | Array<InstanceMeasurement>, stopAtNestedViewTransitions: boolean, ): boolean { - // $FlowFixMe[constant-condition] if (!supportsMutation) { if (enableViewTransitionForPersistenceMode) { while (child !== null) { diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 77524c70d5ce..322c858bb9c0 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -333,7 +333,7 @@ function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean { ); } else if (finishedWork.tag === HostRoot) { return ( - (current.memoizedState as RootState).isDehydrated && + (current.memoizedState: RootState).isDehydrated && (finishedWork.flags & ForceClientRender) === NoFlags ); } else { @@ -487,7 +487,7 @@ function commitBeforeMutationEffectsOnFiber( if ( finishedWork.tag === SuspenseComponent && isSuspenseBoundaryBeingHidden(current, finishedWork) && - // $FlowFixMe[incompatible-type] found when upgrading Flow + // $FlowFixMe[incompatible-call] found when upgrading Flow doesFiberContain(finishedWork, focusedInstanceHandle) ) { shouldFireAfterActiveInstanceBlur = true; @@ -502,7 +502,7 @@ function commitBeforeMutationEffectsOnFiber( case SimpleMemoComponent: { if (!enableEffectEventMutationPhase && (flags & Update) !== NoFlags) { const updateQueue: FunctionComponentUpdateQueue | null = - finishedWork.updateQueue as any; + (finishedWork.updateQueue: any); const eventPayloads = updateQueue !== null ? updateQueue.events : null; if (eventPayloads !== null) { for (let ii = 0; ii < eventPayloads.length; ii++) { @@ -523,7 +523,6 @@ function commitBeforeMutationEffectsOnFiber( } case HostRoot: { if ((flags & Snapshot) !== NoFlags) { - // $FlowFixMe[constant-condition] if (supportsMutation) { const root = finishedWork.stateNode; clearContainer(root.containerInfo); @@ -579,7 +578,7 @@ function commitBeforeMutationEffectsDeletion( // Maybe we can repurpose one of the subtreeFlags positions for this instead? // Use it to store which part of the tree the focused instance is in? // This assumes we can safely determine that instance during the "render" phase. - if (doesFiberContain(deletion, focusedInstanceHandle as any as Fiber)) { + if (doesFiberContain(deletion, ((focusedInstanceHandle: any): Fiber))) { shouldFireAfterActiveInstanceBlur = true; beforeActiveInstanceBlur(deletion); } @@ -653,7 +652,6 @@ function commitLayoutEffectOnFiber( break; } case HostSingleton: { - // $FlowFixMe[constant-condition] if (supportsSingletons) { // We acquire the singleton instance first so it has appropriate // styles before other layout effects run. This isn't perfect because @@ -928,9 +926,9 @@ function abortRootTransitions( const rootTransitions = root.incompleteTransitions; deletedTransitions.forEach(transition => { if (rootTransitions.has(transition)) { - const transitionInstance: TracingMarkerInstance = rootTransitions.get( + const transitionInstance: TracingMarkerInstance = (rootTransitions.get( transition, - ) as any; + ): any); if (transitionInstance.aborts === null) { transitionInstance.aborts = []; } @@ -971,7 +969,6 @@ function abortTracingMarkerTransitions( // If one of the transitions on the tracing marker is a transition // that was in an aborted subtree, we will abort that tracing marker if ( - // $FlowFixMe[invalid-compare] abortedFiber !== null && markerTransitions.has(transition) && (markerInstance.aborts === null || @@ -1184,7 +1181,6 @@ function commitTransitionProgress(offscreenFiber: Fiber) { } function hideOrUnhideAllChildren(parentFiber: Fiber, isHidden: boolean) { - // $FlowFixMe[constant-condition] if (!supportsMutation) { return; } @@ -1198,7 +1194,6 @@ function hideOrUnhideAllChildren(parentFiber: Fiber, isHidden: boolean) { } function hideOrUnhideAllChildrenOnFiber(fiber: Fiber, isHidden: boolean) { - // $FlowFixMe[constant-condition] if (!supportsMutation) { return; } @@ -1247,7 +1242,6 @@ function hideOrUnhideAllChildrenOnFiber(fiber: Fiber, isHidden: boolean) { } function hideOrUnhideNearestPortals(parentFiber: Fiber, isHidden: boolean) { - // $FlowFixMe[constant-condition] if (!supportsMutation) { return; } @@ -1261,7 +1255,6 @@ function hideOrUnhideNearestPortals(parentFiber: Fiber, isHidden: boolean) { } function hideOrUnhideNearestPortalsOnFiber(fiber: Fiber, isHidden: boolean) { - // $FlowFixMe[constant-condition] if (!supportsMutation) { return; } @@ -1336,7 +1329,6 @@ function detachFiberAfterEffects(fiber: Fiber) { // one, too. if (fiber.tag === HostComponent) { const hostInstance: Instance = fiber.stateNode; - // $FlowFixMe[invalid-compare] if (hostInstance !== null) { detachDeletedInstance(hostInstance); } @@ -1374,7 +1366,6 @@ function commitDeletionEffects( ) { const prevEffectStart = pushComponentEffectStart(); - // $FlowFixMe[constant-condition] if (supportsMutation) { // We only have the top Fiber that was deleted but we need to recurse down its // children to find all the terminal nodes. @@ -1398,7 +1389,6 @@ function commitDeletionEffects( findParent: while (parent !== null) { switch (parent.tag) { case HostSingleton: { - // $FlowFixMe[constant-condition] if (supportsSingletons) { if (isSingletonScope(parent.type)) { hostParent = parent.stateNode; @@ -1489,7 +1479,6 @@ function commitDeletionEffectsOnFiber( // that don't modify the stack. switch (deletedFiber.tag) { case HostHoistable: { - // $FlowFixMe[constant-condition] if (supportsResources) { if (!offscreenSubtreeWasHidden) { safelyDetachRef(deletedFiber, nearestMountedAncestor); @@ -1509,7 +1498,6 @@ function commitDeletionEffectsOnFiber( // Fall through } case HostSingleton: { - // $FlowFixMe[constant-condition] if (supportsSingletons) { if (!offscreenSubtreeWasHidden) { safelyDetachRef(deletedFiber, nearestMountedAncestor); @@ -1558,7 +1546,6 @@ function commitDeletionEffectsOnFiber( // We only need to remove the nearest host child. Set the host parent // to `null` on the stack to indicate that nested children don't // need to be removed. - // $FlowFixMe[constant-condition] if (supportsMutation) { const prevHostParent = hostParent; const prevHostParentIsContainer = hostParentIsContainer; @@ -1578,15 +1565,15 @@ function commitDeletionEffectsOnFiber( commitHostRemoveChildFromContainer( deletedFiber, nearestMountedAncestor, - hostParent as any as Container, - deletedFiber.stateNode as Instance | TextInstance, + ((hostParent: any): Container), + (deletedFiber.stateNode: Instance | TextInstance), ); } else { commitHostRemoveChild( deletedFiber, nearestMountedAncestor, - hostParent as any as Instance, - deletedFiber.stateNode as Instance | TextInstance, + ((hostParent: any): Instance), + (deletedFiber.stateNode: Instance | TextInstance), ); } } @@ -1607,7 +1594,7 @@ function commitDeletionEffectsOnFiber( const onDeleted = hydrationCallbacks.onDeleted; if (onDeleted) { onDeleted( - deletedFiber.stateNode as SuspenseInstance | ActivityInstance, + (deletedFiber.stateNode: SuspenseInstance | ActivityInstance), ); } } catch (error) { @@ -1623,18 +1610,17 @@ function commitDeletionEffectsOnFiber( // Dehydrated fragments don't have any children // Delete the dehydrated suspense boundary and all of its content. - // $FlowFixMe[constant-condition] if (supportsMutation) { if (hostParent !== null) { if (hostParentIsContainer) { clearSuspenseBoundaryFromContainer( - hostParent as any as Container, - deletedFiber.stateNode as SuspenseInstance, + ((hostParent: any): Container), + (deletedFiber.stateNode: SuspenseInstance), ); } else { clearSuspenseBoundary( - hostParent as any as Instance, - deletedFiber.stateNode as SuspenseInstance, + ((hostParent: any): Instance), + (deletedFiber.stateNode: SuspenseInstance), ); } } @@ -1642,7 +1628,6 @@ function commitDeletionEffectsOnFiber( break; } case HostPortal: { - // $FlowFixMe[constant-condition] if (supportsMutation) { // When we go into a portal, it becomes the parent to remove from. const prevHostParent = hostParent; @@ -1657,7 +1642,6 @@ function commitDeletionEffectsOnFiber( hostParent = prevHostParent; hostParentIsContainer = prevHostParentIsContainer; } else { - // $FlowFixMe[constant-condition] if (supportsPersistence) { commitHostPortalContainerChildren( deletedFiber.stateNode, @@ -1831,7 +1815,7 @@ function commitSuspenseCallback(finishedWork: Fiber) { if (enableSuspenseCallback && newState !== null) { const suspenseCallback = finishedWork.memoizedProps.suspenseCallback; if (typeof suspenseCallback === 'function') { - const retryQueue: RetryQueue | null = finishedWork.updateQueue as any; + const retryQueue: RetryQueue | null = (finishedWork.updateQueue: any); if (retryQueue !== null) { suspenseCallback(new Set(retryQueue)); } @@ -1847,7 +1831,6 @@ function commitActivityHydrationCallbacks( finishedRoot: FiberRoot, finishedWork: Fiber, ) { - // $FlowFixMe[constant-condition] if (!supportsHydration) { return; } @@ -1882,7 +1865,6 @@ function commitSuspenseHydrationCallbacks( finishedRoot: FiberRoot, finishedWork: Fiber, ) { - // $FlowFixMe[constant-condition] if (!supportsHydration) { return; } @@ -2067,7 +2049,7 @@ function commitMutationEffectsOnFiber( if (enableEffectEventMutationPhase) { if (flags & Update) { const updateQueue: FunctionComponentUpdateQueue | null = - finishedWork.updateQueue as any; + (finishedWork.updateQueue: any); const eventPayloads = updateQueue !== null ? updateQueue.events : null; if (eventPayloads !== null) { @@ -2109,7 +2091,7 @@ function commitMutationEffectsOnFiber( if (flags & Callback && offscreenSubtreeIsHidden) { const updateQueue: UpdateQueue<mixed> | null = - finishedWork.updateQueue as any; + (finishedWork.updateQueue: any); if (updateQueue !== null) { deferHiddenCallbacks(updateQueue); } @@ -2117,11 +2099,10 @@ function commitMutationEffectsOnFiber( break; } case HostHoistable: { - // $FlowFixMe[constant-condition] if (supportsResources) { // We cast because we always set the root at the React root and so it cannot be // null while we are processing mutation effects - const hoistableRoot: HoistableRoot = currentHoistableRoot as any; + const hoistableRoot: HoistableRoot = (currentHoistableRoot: any); recursivelyTraverseMutationEffects(root, finishedWork, lanes); commitReconciliationEffects(finishedWork, lanes); @@ -2196,7 +2177,6 @@ function commitMutationEffectsOnFiber( // Fall through } case HostSingleton: { - // $FlowFixMe[constant-condition] if (supportsSingletons) { recursivelyTraverseMutationEffects(root, finishedWork, lanes); commitReconciliationEffects(finishedWork, lanes); @@ -2230,7 +2210,6 @@ function commitMutationEffectsOnFiber( safelyDetachRef(current, current.return); } } - // $FlowFixMe[constant-condition] if (supportsMutation) { // TODO: ContentReset gets cleared by the children during the commit // phase. This is a refactor hazard because it means we must read @@ -2271,7 +2250,6 @@ function commitMutationEffectsOnFiber( } } else { if (enableEagerAlternateStateNodeCleanup) { - // $FlowFixMe[constant-condition] if (supportsPersistence) { if (finishedWork.alternate !== null) { // `finishedWork.alternate.stateNode` is pointing to a stale shadow @@ -2292,7 +2270,6 @@ function commitMutationEffectsOnFiber( commitReconciliationEffects(finishedWork, lanes); if (flags & Update) { - // $FlowFixMe[constant-condition] if (supportsMutation) { if (finishedWork.stateNode === null) { throw new Error( @@ -2317,7 +2294,6 @@ function commitMutationEffectsOnFiber( const prevProfilerEffectDuration = pushNestedEffectDurations(); pushRootMutationContext(); - // $FlowFixMe[constant-condition] if (supportsResources) { prepareToCommitHoistables(); @@ -2334,7 +2310,6 @@ function commitMutationEffectsOnFiber( } if (flags & Update) { - // $FlowFixMe[constant-condition] if (supportsMutation && supportsHydration) { if (current !== null) { const prevRootState: RootState = current.memoizedState; @@ -2343,7 +2318,6 @@ function commitMutationEffectsOnFiber( } } } - // $FlowFixMe[constant-condition] if (supportsPersistence) { commitHostRootContainerChildren(root, finishedWork); } @@ -2394,7 +2368,6 @@ function commitMutationEffectsOnFiber( const prevOffscreenDirectParentIsHidden = offscreenDirectParentIsHidden; offscreenDirectParentIsHidden = offscreenSubtreeIsHidden; const prevMutationContext = pushMutationContext(); - // $FlowFixMe[constant-condition] if (supportsResources) { const previousHoistableRoot = currentHoistableRoot; currentHoistableRoot = getHoistableRoot( @@ -2418,7 +2391,6 @@ function commitMutationEffectsOnFiber( offscreenDirectParentIsHidden = prevOffscreenDirectParentIsHidden; if (flags & Update) { - // $FlowFixMe[constant-condition] if (supportsPersistence) { commitHostPortalContainerChildren( finishedWork.stateNode, @@ -2449,7 +2421,7 @@ function commitMutationEffectsOnFiber( recursivelyTraverseMutationEffects(root, finishedWork, lanes); commitReconciliationEffects(finishedWork, lanes); if (flags & Update) { - const retryQueue: RetryQueue | null = finishedWork.updateQueue as any; + const retryQueue: RetryQueue | null = (finishedWork.updateQueue: any); if (retryQueue !== null) { finishedWork.updateQueue = null; attachSuspenseRetryListeners(finishedWork, retryQueue); @@ -2472,14 +2444,14 @@ function commitMutationEffectsOnFiber( // // Also, all this logic could/should move to the passive phase so it // doesn't block paint. - const offscreenFiber: Fiber = finishedWork.child as any; + const offscreenFiber: Fiber = (finishedWork.child: any); if (offscreenFiber.flags & Visibility) { // Throttle the appearance and disappearance of Suspense fallbacks. const isShowingFallback = - (finishedWork.memoizedState as SuspenseState | null) !== null; + (finishedWork.memoizedState: SuspenseState | null) !== null; const wasShowingFallback = current !== null && - (current.memoizedState as SuspenseState | null) !== null; + (current.memoizedState: SuspenseState | null) !== null; if (alwaysThrottleRetries) { if (isShowingFallback !== wasShowingFallback) { @@ -2501,7 +2473,7 @@ function commitMutationEffectsOnFiber( } catch (error) { captureCommitPhaseError(finishedWork, finishedWork.return, error); } - const retryQueue: RetryQueue | null = finishedWork.updateQueue as any; + const retryQueue: RetryQueue | null = (finishedWork.updateQueue: any); if (retryQueue !== null) { finishedWork.updateQueue = null; attachSuspenseRetryListeners(finishedWork, retryQueue); @@ -2605,7 +2577,6 @@ function commitMutationEffectsOnFiber( } } - // $FlowFixMe[constant-condition] if (supportsMutation) { // If it's trying to unhide but the parent is still hidden, then we should not unhide. if (isHidden || !offscreenDirectParentIsHidden) { @@ -2617,7 +2588,7 @@ function commitMutationEffectsOnFiber( // TODO: Move to passive phase if (flags & Update) { const offscreenQueue: OffscreenQueue | null = - finishedWork.updateQueue as any; + (finishedWork.updateQueue: any); if (offscreenQueue !== null) { const retryQueue = offscreenQueue.retryQueue; if (retryQueue !== null) { @@ -2634,7 +2605,7 @@ function commitMutationEffectsOnFiber( if (flags & Update) { const retryQueue: Set<Wakeable> | null = - finishedWork.updateQueue as any; + (finishedWork.updateQueue: any); if (retryQueue !== null) { finishedWork.updateQueue = null; attachSuspenseRetryListeners(finishedWork, retryQueue); @@ -2652,7 +2623,6 @@ function commitMutationEffectsOnFiber( const prevMutationContext = pushMutationContext(); const prevUpdate = inUpdateViewTransition; const isViewTransitionEligible = - // $FlowFixMe[constant-condition] enableViewTransition && includesOnlyViewTransitionEligibleLanes(lanes); const props = finishedWork.memoizedProps; @@ -2863,9 +2833,9 @@ function commitAfterMutationEffectsOnFiber( if (cancelableChildren !== null) { for (let i = 0; i < cancelableChildren.length; i += 3) { cancelViewTransitionName( - cancelableChildren[i] as any as Instance, - cancelableChildren[i + 1] as any as string, - cancelableChildren[i + 2] as any as Props, + ((cancelableChildren[i]: any): Instance), + ((cancelableChildren[i + 1]: any): string), + ((cancelableChildren[i + 2]: any): Props), ); } } @@ -3046,7 +3016,6 @@ export function disappearLayoutEffects(finishedWork: Fiber) { break; } case HostSingleton: { - // $FlowFixMe[constant-condition] if (supportsSingletons) { // TODO (Offscreen) Check: flags & RefStatic commitHostSingletonRelease(finishedWork); @@ -3191,7 +3160,6 @@ export function reappearLayoutEffects( // ... // } case HostSingleton: { - // $FlowFixMe[constant-condition] if (supportsSingletons) { // We acquire the singleton instance first so it has appropriate // styles before other layout effects run. This isn't perfect because @@ -3293,7 +3261,6 @@ export function reappearLayoutEffects( } case OffscreenComponent: { const offscreenState: OffscreenState = finishedWork.memoizedState; - // $FlowFixMe[invalid-compare] const isHidden = offscreenState !== null; if (isHidden) { // Nested Offscreen tree is still hidden. Don't re-appear its effects. @@ -3429,9 +3396,8 @@ function commitOffscreenPassiveMountEffects( // may add separate logs for pre-rendering, but it's not part of the // primary metrics. const offscreenState: OffscreenState = finishedWork.memoizedState; - const queue: OffscreenQueue | null = finishedWork.updateQueue as any; + const queue: OffscreenQueue | null = (finishedWork.updateQueue: any); - // $FlowFixMe[invalid-compare] const isHidden = offscreenState !== null; if (queue !== null) { if (isHidden) { @@ -3579,7 +3545,7 @@ function recursivelyTraversePassiveMountEffects( committedLanes, committedTransitions, nextSibling !== null - ? (nextSibling.actualStartTime as any as number) + ? ((nextSibling.actualStartTime: any): number) : endTime, ); child = nextSibling; @@ -3653,12 +3619,12 @@ function commitPassiveMountOnFiber( enableProfilerTimer && enableComponentPerformanceTrack && (finishedWork.mode & ProfileMode) !== NoMode && - (finishedWork.actualStartTime as any as number) > 0 && + ((finishedWork.actualStartTime: any): number) > 0 && (finishedWork.flags & PerformedWork) !== NoFlags ) { logComponentRender( finishedWork, - finishedWork.actualStartTime as any as number, + ((finishedWork.actualStartTime: any): number), endTime, inHydratedSubtree, committedLanes, @@ -3689,12 +3655,12 @@ function commitPassiveMountOnFiber( enableProfilerTimer && enableComponentPerformanceTrack && (finishedWork.mode & ProfileMode) !== NoMode && - (finishedWork.actualStartTime as any as number) > 0 + ((finishedWork.actualStartTime: any): number) > 0 ) { if ((finishedWork.flags & DidCapture) !== NoFlags) { logComponentErrored( finishedWork, - finishedWork.actualStartTime as any as number, + ((finishedWork.actualStartTime: any): number), endTime, // TODO: The captured values are all hidden inside the updater/callback closures so // we can't get to the errors but they're there so we should be able to log them. @@ -3703,7 +3669,7 @@ function commitPassiveMountOnFiber( } else if ((finishedWork.flags & PerformedWork) !== NoFlags) { logComponentRender( finishedWork, - finishedWork.actualStartTime as any as number, + ((finishedWork.actualStartTime: any): number), endTime, inHydratedSubtree, committedLanes, @@ -3729,7 +3695,7 @@ function commitPassiveMountOnFiber( // dehydrated and this wasn't a forced client render. inHydratedSubtree = finishedWork.alternate !== null && - (finishedWork.alternate.memoizedState as RootState).isDehydrated && + (finishedWork.alternate.memoizedState: RootState).isDehydrated && (finishedWork.flags & ForceClientRender) === NoFlags; } @@ -3746,7 +3712,6 @@ function commitPassiveMountOnFiber( } if (isViewTransitionEligible) { - // $FlowFixMe[constant-condition] if (supportsMutation && rootViewTransitionNameCanceled) { restoreRootViewTransitionName(finishedRoot.containerInfo); } @@ -3871,7 +3836,7 @@ function commitPassiveMountOnFiber( // If there were no hydration errors, that suggests that this was an intentional client // rendered boundary. if (hydrationErrors !== null) { - const startTime: number = finishedWork.actualStartTime as any; + const startTime: number = (finishedWork.actualStartTime: any); logComponentErrored( finishedWork, startTime, @@ -3929,7 +3894,7 @@ function commitPassiveMountOnFiber( // If there were no hydration errors, that suggests that this was an intentional client // rendered boundary. if (hydrationErrors !== null) { - const startTime: number = finishedWork.actualStartTime as any; + const startTime: number = (finishedWork.actualStartTime: any); logComponentErrored( finishedWork, startTime, @@ -4082,7 +4047,7 @@ function commitPassiveMountOnFiber( !inHydratedSubtree ) { // Log the reappear in the render phase. - const startTime = finishedWork.actualStartTime as any as number; + const startTime = ((finishedWork.actualStartTime: any): number); if (startTime >= 0 && endTime - startTime > 0.05) { logComponentReappeared(finishedWork, startTime, endTime); } @@ -4187,7 +4152,7 @@ function commitPassiveMountOnFiber( finishedWork.return.alternate !== null; if (isMount) { // Log the mount in the render phase. - const startTime = finishedWork.actualStartTime as any as number; + const startTime = ((finishedWork.actualStartTime: any): number); if (startTime >= 0 && endTime - startTime > 0.05) { logComponentMount(finishedWork, startTime, endTime); } @@ -4251,7 +4216,7 @@ function recursivelyTraverseReconnectPassiveEffects( committedTransitions, childShouldIncludeWorkInProgressEffects, nextSibling !== null - ? (nextSibling.actualStartTime as any as number) + ? ((nextSibling.actualStartTime: any): number) : endTime, ); child = nextSibling; @@ -4295,12 +4260,12 @@ export function reconnectPassiveEffects( enableComponentPerformanceTrack && includeWorkInProgressEffects && (finishedWork.mode & ProfileMode) !== NoMode && - (finishedWork.actualStartTime as any as number) > 0 && + ((finishedWork.actualStartTime: any): number) > 0 && (finishedWork.flags & PerformedWork) !== NoFlags ) { logComponentRender( finishedWork, - finishedWork.actualStartTime as any as number, + ((finishedWork.actualStartTime: any): number), endTime, inHydratedSubtree, committedLanes, @@ -4521,7 +4486,7 @@ function recursivelyTraverseAtomicPassiveEffects( committedLanes, committedTransitions, nextSibling !== null - ? (nextSibling.actualStartTime as any as number) + ? ((nextSibling.actualStartTime: any): number) : endTime, ); child = nextSibling; @@ -4554,12 +4519,12 @@ function commitAtomicPassiveEffects( enableProfilerTimer && enableComponentPerformanceTrack && (finishedWork.mode & ProfileMode) !== NoMode && - (finishedWork.actualStartTime as any as number) > 0 && + ((finishedWork.actualStartTime: any): number) > 0 && (finishedWork.flags & PerformedWork) !== NoFlags ) { logComponentRender( finishedWork, - finishedWork.actualStartTime as any as number, + ((finishedWork.actualStartTime: any): number), endTime, inHydratedSubtree, committedLanes, @@ -4681,7 +4646,7 @@ function accumulateSuspenseyCommitOnFiber( suspendResource( suspendedState, // This should always be set by visiting HostRoot first - currentHoistableRoot as any, + (currentHoistableRoot: any), fiber.memoizedState, fiber.memoizedProps, ); @@ -4722,7 +4687,6 @@ function accumulateSuspenseyCommitOnFiber( } case HostRoot: case HostPortal: { - // $FlowFixMe[constant-condition] if (supportsResources) { const previousHoistableRoot = currentHoistableRoot; const container: Container = fiber.stateNode.containerInfo; @@ -4744,14 +4708,14 @@ function accumulateSuspenseyCommitOnFiber( break; } case OffscreenComponent: { - const isHidden = (fiber.memoizedState as OffscreenState | null) !== null; + const isHidden = (fiber.memoizedState: OffscreenState | null) !== null; if (isHidden) { // Don't suspend in hidden trees } else { const current = fiber.alternate; const wasHidden = current !== null && - (current.memoizedState as OffscreenState | null) !== null; + (current.memoizedState: OffscreenState | null) !== null; if (wasHidden) { // This tree is being revealed. Visit all newly visible suspensey // instances, even if they're in the current tree. @@ -5210,7 +5174,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber( case SuspenseComponent: { if (enableTransitionTracing) { // We need to mark this fiber's parents as deleted - const offscreenFiber: Fiber = current.child as any; + const offscreenFiber: Fiber = (current.child: any); const instance: OffscreenInstance = offscreenFiber.stateNode; const transitions = instance._transitions; if (transitions !== null) { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 29260e5c6e26..6fc4297e1b33 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -205,7 +205,6 @@ function markUpdate(workInProgress: Fiber) { * it received an update that requires a clone of the tree above. */ function markCloned(workInProgress: Fiber) { - // $FlowFixMe[constant-condition] if (supportsPersistence) { workInProgress.flags |= Cloned; } @@ -246,7 +245,6 @@ function appendAllChildren( needsVisibilityToggle: boolean, isHidden: boolean, ) { - // $FlowFixMe[constant-condition] if (supportsMutation) { // We only have the top Fiber that was created but we need recurse down its // children to find all the terminal nodes. @@ -256,7 +254,6 @@ function appendAllChildren( appendInitialChild(parent, node.stateNode); } else if ( node.tag === HostPortal || - // $FlowFixMe[constant-condition] (supportsSingletons ? node.tag === HostSingleton : false) ) { // If we have a portal child, then we don't want to traverse @@ -283,7 +280,6 @@ function appendAllChildren( node.sibling.return = node.return; node = node.sibling; } - // $FlowFixMe[constant-condition] } else if (supportsPersistence) { // We only have the top Fiber that was created but we need recurse down its // children to find all the terminal nodes. @@ -361,7 +357,6 @@ function appendAllChildrenToContainer( // about their presence, we track and return if they were added to the // child set. let hasOffscreenComponentChild = false; - // $FlowFixMe[constant-condition] if (supportsPersistence) { // We only have the top Fiber that was created but we need recurse down its // children to find all the terminal nodes. @@ -411,7 +406,7 @@ function appendAllChildrenToContainer( node = node.child; continue; } - node = node as Fiber; + node = (node: Fiber); if (node === workInProgress) { return hasOffscreenComponentChild; } @@ -433,7 +428,6 @@ function appendAllChildrenToContainer( } function updateHostContainer(current: null | Fiber, workInProgress: Fiber) { - // $FlowFixMe[constant-condition] if (supportsPersistence) { if (doesRequireClone(current, workInProgress)) { const portalOrRoot: { @@ -465,7 +459,6 @@ function updateHostComponent( newProps: Props, renderLanes: Lanes, ) { - // $FlowFixMe[constant-condition] if (supportsMutation) { // If we have an alternate, that means this is an update and we need to // schedule a side-effect to do the updates. @@ -477,7 +470,6 @@ function updateHostComponent( } markUpdate(workInProgress); - // $FlowFixMe[constant-condition] } else if (supportsPersistence) { const currentInstance = current.stateNode; const oldProps = current.memoizedProps; @@ -675,13 +667,11 @@ function updateHostText( oldText: string, newText: string, ) { - // $FlowFixMe[constant-condition] if (supportsMutation) { // If the text differs, mark it as an update. All the work in done in commitWork. if (oldText !== newText) { markUpdate(workInProgress); } - // $FlowFixMe[constant-condition] } else if (supportsPersistence) { if (oldText !== newText) { // If the text content differs, we'll create a new text instance for it. @@ -802,7 +792,7 @@ function bubbleProperties(completedWork: Fiber) { // In profiling mode, resetChildExpirationTime is also used to reset // profiler durations. let actualDuration = completedWork.actualDuration; - let treeBaseDuration = completedWork.selfBaseDuration as any as number; + let treeBaseDuration = ((completedWork.selfBaseDuration: any): number); let child = completedWork.child; while (child !== null) { @@ -857,7 +847,7 @@ function bubbleProperties(completedWork: Fiber) { if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) { // In profiling mode, resetChildExpirationTime is also used to reset // profiler durations. - let treeBaseDuration = completedWork.selfBaseDuration as any as number; + let treeBaseDuration = ((completedWork.selfBaseDuration: any): number); let child = completedWork.child; while (child !== null) { @@ -932,7 +922,6 @@ function completeDehydratedActivityBoundary( bubbleProperties(workInProgress); if (enableProfilerTimer) { if ((workInProgress.mode & ProfileMode) !== NoMode) { - // $FlowFixMe[invalid-compare] const isTimedOutSuspense = nextState !== null; if (isTimedOutSuspense) { // Don't count time spent in a timed out Suspense subtree as part of the base duration. @@ -940,7 +929,7 @@ function completeDehydratedActivityBoundary( if (primaryChildFragment !== null) { // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator workInProgress.treeBaseDuration -= - primaryChildFragment.treeBaseDuration as any as number; + ((primaryChildFragment.treeBaseDuration: any): number); } } } @@ -971,7 +960,7 @@ function completeDehydratedActivityBoundary( if (primaryChildFragment !== null) { // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator workInProgress.treeBaseDuration -= - primaryChildFragment.treeBaseDuration as any as number; + ((primaryChildFragment.treeBaseDuration: any): number); } } } @@ -1015,7 +1004,6 @@ function completeDehydratedSuspenseBoundary( bubbleProperties(workInProgress); if (enableProfilerTimer) { if ((workInProgress.mode & ProfileMode) !== NoMode) { - // $FlowFixMe[invalid-compare] const isTimedOutSuspense = nextState !== null; if (isTimedOutSuspense) { // Don't count time spent in a timed out Suspense subtree as part of the base duration. @@ -1023,7 +1011,7 @@ function completeDehydratedSuspenseBoundary( if (primaryChildFragment !== null) { // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator workInProgress.treeBaseDuration -= - primaryChildFragment.treeBaseDuration as any as number; + ((primaryChildFragment.treeBaseDuration: any): number); } } } @@ -1054,7 +1042,7 @@ function completeDehydratedSuspenseBoundary( if (primaryChildFragment !== null) { // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator workInProgress.treeBaseDuration -= - primaryChildFragment.treeBaseDuration as any as number; + ((primaryChildFragment.treeBaseDuration: any): number); } } } @@ -1115,7 +1103,7 @@ function completeWork( return null; } case HostRoot: { - const fiberRoot = workInProgress.stateNode as FiberRoot; + const fiberRoot = (workInProgress.stateNode: FiberRoot); if (enableTransitionTracing) { const transitions = getWorkInProgressTransitions(); @@ -1196,7 +1184,6 @@ function completeWork( return null; } case HostHoistable: { - // $FlowFixMe[constant-condition] if (supportsResources) { // The branching here is more complicated than you might expect because // a HostHoistable sometimes corresponds to a Resource and sometimes @@ -1266,7 +1253,6 @@ function completeWork( const oldProps = current.memoizedProps; // This is an Instance // We may have props to update on the Hoistable instance. - // $FlowFixMe[constant-condition] if (supportsMutation) { if (oldProps !== newProps) { markUpdate(workInProgress); @@ -1298,13 +1284,11 @@ function completeWork( // Fall through } case HostSingleton: { - // $FlowFixMe[constant-condition] if (supportsSingletons) { popHostContext(workInProgress); const rootContainerInstance = getRootHostContainer(); const type = workInProgress.type; if (current !== null && workInProgress.stateNode != null) { - // $FlowFixMe[constant-condition] if (supportsMutation) { const oldProps = current.memoizedProps; if (oldProps !== newProps) { @@ -1598,10 +1582,10 @@ function completeWork( const nextDidTimeout = nextState !== null; const prevDidTimeout = current !== null && - (current.memoizedState as null | SuspenseState) !== null; + (current.memoizedState: null | SuspenseState) !== null; if (nextDidTimeout) { - const offscreenFiber: Fiber = workInProgress.child as any; + const offscreenFiber: Fiber = (workInProgress.child: any); let previousCache: Cache | null = null; if ( offscreenFiber.alternate !== null && @@ -1627,7 +1611,7 @@ function completeWork( // a passive effect, which is when we process the transitions if (nextDidTimeout !== prevDidTimeout) { if (enableTransitionTracing) { - const offscreenFiber: Fiber = workInProgress.child as any; + const offscreenFiber: Fiber = (workInProgress.child: any); offscreenFiber.flags |= Passive; } @@ -1643,12 +1627,12 @@ function completeWork( // phase will handle scheduling the effect. It's only when the fallback // is active that we have to do anything special. if (nextDidTimeout) { - const offscreenFiber: Fiber = workInProgress.child as any; + const offscreenFiber: Fiber = (workInProgress.child: any); offscreenFiber.flags |= Visibility; } } - const retryQueue: RetryQueue | null = workInProgress.updateQueue as any; + const retryQueue: RetryQueue | null = (workInProgress.updateQueue: any); scheduleRetryEffect(workInProgress, retryQueue); if ( @@ -1669,7 +1653,7 @@ function completeWork( if (primaryChildFragment !== null) { // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator workInProgress.treeBaseDuration -= - primaryChildFragment.treeBaseDuration as any as number; + ((primaryChildFragment.treeBaseDuration: any): number); } } } @@ -1759,7 +1743,7 @@ function completeWork( // doesn't matter since that means that the other boundaries that // we did find already has their listeners attached. const retryQueue: RetryQueue | null = - suspended.updateQueue as any; + (suspended.updateQueue: any); workInProgress.updateQueue = retryQueue; scheduleRetryEffect(workInProgress, retryQueue); @@ -1823,7 +1807,7 @@ function completeWork( // Ensure we transfer the update queue to the parent so that it doesn't // get lost if this row ends up dropped during a second pass. - const retryQueue: RetryQueue | null = suspended.updateQueue as any; + const retryQueue: RetryQueue | null = (suspended.updateQueue: any); workInProgress.updateQueue = retryQueue; scheduleRetryEffect(workInProgress, retryQueue); @@ -1997,7 +1981,7 @@ function completeWork( // Don't bubble properties for hidden children unless we're rendering // at offscreen priority. if ( - includesSomeLane(renderLanes, OffscreenLane as Lane) && + includesSomeLane(renderLanes, (OffscreenLane: Lane)) && // Also don't bubble if the tree suspended (workInProgress.flags & DidCapture) === NoLanes ) { @@ -2016,7 +2000,7 @@ function completeWork( } const offscreenQueue: OffscreenQueue | null = - workInProgress.updateQueue as any; + (workInProgress.updateQueue: any); if (offscreenQueue !== null) { const retryQueue = offscreenQueue.retryQueue; scheduleRetryEffect(workInProgress, retryQueue); diff --git a/packages/react-reconciler/src/ReactFiberComponentStack.js b/packages/react-reconciler/src/ReactFiberComponentStack.js index d97689f9eb5e..da804171da5b 100644 --- a/packages/react-reconciler/src/ReactFiberComponentStack.js +++ b/packages/react-reconciler/src/ReactFiberComponentStack.js @@ -120,7 +120,7 @@ export function getOwnerStackByFiberInDev(workInProgress: Fiber): string { if (workInProgress.tag === HostText) { // Text nodes never have an owner/stack because they're not created through JSX. // We use the parent since text nodes are always created through a host parent. - workInProgress = workInProgress.return as any; + workInProgress = (workInProgress.return: any); } // The owner stack of the current fiber will be where it was created, i.e. inside its owner. @@ -175,7 +175,7 @@ export function getOwnerStackByFiberInDev(workInProgress: Fiber): string { while (owner) { if (typeof owner.tag === 'number') { - const fiber: Fiber = owner as any; + const fiber: Fiber = (owner: any); owner = fiber._debugOwner; const debugStack = fiber._debugStack; // If we don't actually print the stack if there is no owner of this JSX element. diff --git a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js index 1af1e8669e38..f8c67a1c3fed 100644 --- a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js +++ b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js @@ -64,7 +64,6 @@ export function finishQueueingConcurrentUpdates(): void { const lane: Lane = concurrentQueues[i]; concurrentQueues[i++] = null; - // $FlowFixMe[invalid-compare] if (queue !== null && update !== null) { const pending = queue.pending; if (pending === null) { @@ -118,8 +117,8 @@ export function enqueueConcurrentHookUpdate<S, A>( update: HookUpdate<S, A>, lane: Lane, ): FiberRoot | null { - const concurrentQueue: ConcurrentQueue = queue as any; - const concurrentUpdate: ConcurrentUpdate = update as any; + const concurrentQueue: ConcurrentQueue = (queue: any); + const concurrentUpdate: ConcurrentUpdate = (update: any); enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane); return getRootForUpdatedFiber(fiber); } @@ -133,8 +132,8 @@ export function enqueueConcurrentHookUpdateAndEagerlyBailout<S, A>( // only reason we queue it is in case there's a subsequent higher priority // update that causes it to be rebased. const lane = NoLane; - const concurrentQueue: ConcurrentQueue = queue as any; - const concurrentUpdate: ConcurrentUpdate = update as any; + const concurrentQueue: ConcurrentQueue = (queue: any); + const concurrentUpdate: ConcurrentUpdate = (update: any); enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane); // Usually we can rely on the upcoming render phase to process the concurrent @@ -156,8 +155,8 @@ export function enqueueConcurrentClassUpdate<State>( update: ClassUpdate<State>, lane: Lane, ): FiberRoot | null { - const concurrentQueue: ConcurrentQueue = queue as any; - const concurrentUpdate: ConcurrentUpdate = update as any; + const concurrentQueue: ConcurrentQueue = (queue: any); + const concurrentUpdate: ConcurrentUpdate = (update: any); enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane); return getRootForUpdatedFiber(fiber); } @@ -272,7 +271,7 @@ function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null { node = parent; parent = node.return; } - return node.tag === HostRoot ? (node.stateNode as FiberRoot) : null; + return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null; } function detectUpdateOnUnmountedFiber(sourceFiber: Fiber, parent: Fiber) { diff --git a/packages/react-reconciler/src/ReactFiberDuplicateViewTransitions.js b/packages/react-reconciler/src/ReactFiberDuplicateViewTransitions.js index e1daca6d727b..aa8a6eceed62 100644 --- a/packages/react-reconciler/src/ReactFiberDuplicateViewTransitions.js +++ b/packages/react-reconciler/src/ReactFiberDuplicateViewTransitions.js @@ -18,12 +18,12 @@ import {runWithFiberInDEV} from './ReactCurrentFiber'; // assigned view-transition-name outside React too. const mountedNamedViewTransitions: Map<string, Fiber> = __DEV__ ? new Map() - : (null as any); -const didWarnAboutName: {[string]: boolean} = __DEV__ ? {} : (null as any); + : (null: any); +const didWarnAboutName: {[string]: boolean} = __DEV__ ? {} : (null: any); export function trackNamedViewTransition(fiber: Fiber): void { if (__DEV__) { - const name = (fiber.memoizedProps as ViewTransitionProps).name; + const name = (fiber.memoizedProps: ViewTransitionProps).name; if (name != null && name !== 'auto') { const existing = mountedNamedViewTransitions.get(name); if (existing !== undefined) { @@ -57,7 +57,7 @@ export function trackNamedViewTransition(fiber: Fiber): void { export function untrackNamedViewTransition(fiber: Fiber): void { if (__DEV__) { - const name = (fiber.memoizedProps as ViewTransitionProps).name; + const name = (fiber.memoizedProps: ViewTransitionProps).name; if (name != null && name !== 'auto') { const existing = mountedNamedViewTransitions.get(name); if ( diff --git a/packages/react-reconciler/src/ReactFiberErrorLogger.js b/packages/react-reconciler/src/ReactFiberErrorLogger.js index c8e83d744a6a..e6111d8be74a 100644 --- a/packages/react-reconciler/src/ReactFiberErrorLogger.js +++ b/packages/react-reconciler/src/ReactFiberErrorLogger.js @@ -138,7 +138,7 @@ export function logUncaughtError( : null; errorBoundaryName = null; } - const error = errorInfo.value as any; + const error = (errorInfo.value: any); if (__DEV__ && ReactSharedInternals.actQueue !== null) { // For uncaught errors inside act, we track them on the act and then // rethrow them into the test. @@ -172,7 +172,7 @@ export function logCaughtError( : null; errorBoundaryName = getComponentNameFromFiber(boundary); } - const error = errorInfo.value as any; + const error = (errorInfo.value: any); const onCaughtError = root.onCaughtError; onCaughtError(error, { componentStack: errorInfo.stack, diff --git a/packages/react-reconciler/src/ReactFiberGestureScheduler.js b/packages/react-reconciler/src/ReactFiberGestureScheduler.js index 797f17bb90d0..373e8167cab7 100644 --- a/packages/react-reconciler/src/ReactFiberGestureScheduler.js +++ b/packages/react-reconciler/src/ReactFiberGestureScheduler.js @@ -53,7 +53,6 @@ export function scheduleGesture( return prev; } const next = prev.next; - // $FlowFixMe[invalid-compare] if (next === null) { break; } @@ -120,7 +119,6 @@ export function startScheduledGesture( return prev; } const next = prev.next; - // $FlowFixMe[invalid-compare] if (next === null) { break; } diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index c5b4f6d1821b..29c83c7d7263 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -258,7 +258,7 @@ type Dispatch<A> = A => void; let renderLanes: Lanes = NoLanes; // The work-in-progress fiber. I've named it differently to distinguish it from // the work-in-progress hook. -let currentlyRenderingFiber: Fiber = null as any; +let currentlyRenderingFiber: Fiber = (null: any); // Hooks are stored as a linked list on the fiber's memoizedState field. The // current hook list is the list that belongs to the current fiber. The @@ -307,7 +307,7 @@ let ignorePreviousDependencies: boolean = false; function mountHookTypesDev(): void { if (__DEV__) { - const hookName = currentHookNameInDev as any as HookType; + const hookName = ((currentHookNameInDev: any): HookType); if (hookTypesDev === null) { hookTypesDev = [hookName]; @@ -319,7 +319,7 @@ function mountHookTypesDev(): void { function updateHookTypesDev(): void { if (__DEV__) { - const hookName = currentHookNameInDev as any as HookType; + const hookName = ((currentHookNameInDev: any): HookType); if (hookTypesDev !== null) { hookTypesUpdateIndexDev++; @@ -356,10 +356,10 @@ function warnOnHookMismatchInDev(currentHookName: HookType): void { const secondColumnStart = 30; - for (let i = 0; i <= (hookTypesUpdateIndexDev as any as number); i++) { + for (let i = 0; i <= ((hookTypesUpdateIndexDev: any): number); i++) { const oldHookName = hookTypesDev[i]; const newHookName = - i === (hookTypesUpdateIndexDev as any as number) + i === ((hookTypesUpdateIndexDev: any): number) ? currentHookName : oldHookName; @@ -415,9 +415,9 @@ function warnIfAsyncClientComponent(Component: Function) { // for transpiled async functions. Neither mechanism is completely // bulletproof but together they cover the most common cases. const isAsyncFunction = - // $FlowFixMe[method-unbinding] + // $FlowIgnore[method-unbinding] Object.prototype.toString.call(Component) === '[object AsyncFunction]' || - // $FlowFixMe[method-unbinding] + // $FlowIgnore[method-unbinding] Object.prototype.toString.call(Component) === '[object AsyncGeneratorFunction]'; if (isAsyncFunction) { @@ -513,7 +513,7 @@ export function renderWithHooks<Props, SecondArg>( if (__DEV__) { hookTypesDev = current !== null - ? (current._debugHookTypes as any as Array<HookType>) + ? ((current._debugHookTypes: any): Array<HookType>) : null; hookTypesUpdateIndexDev = -1; // Used for hot reloading: @@ -661,7 +661,7 @@ function finishRenderingHooks<Props, SecondArg>( currentHook !== null && currentHook.next !== null; renderLanes = NoLanes; - currentlyRenderingFiber = null as any; + currentlyRenderingFiber = (null: any); currentHook = null; workInProgressHook = null; @@ -833,7 +833,7 @@ function renderWithHooksAgain<Props, SecondArg>( workInProgressHook = null; if (workInProgress.updateQueue != null) { - resetFunctionComponentUpdateQueue(workInProgress.updateQueue as any); + resetFunctionComponentUpdateQueue((workInProgress.updateQueue: any)); } if (__DEV__) { @@ -872,7 +872,7 @@ export function TransitionAwareHostComponent(): TransitionStatus { const [maybeThenable] = dispatcher.useState(); let nextState; if (typeof maybeThenable.then === 'function') { - const thenable: Thenable<TransitionStatus> = maybeThenable as any; + const thenable: Thenable<TransitionStatus> = (maybeThenable: any); nextState = useThenable(thenable); } else { const status: TransitionStatus = maybeThenable; @@ -929,7 +929,7 @@ export function resetHooksAfterThrow(): void { // // It should only reset things like the current dispatcher, to prevent hooks // from being called outside of a component. - currentlyRenderingFiber = null as any; + currentlyRenderingFiber = (null: any); // We can assume the previous dispatcher is always this one, since we set it // at the beginning of the render phase and there's no re-entrance. @@ -958,7 +958,7 @@ export function resetHooksOnUnwind(workInProgress: Fiber): void { } renderLanes = NoLanes; - currentlyRenderingFiber = null as any; + currentlyRenderingFiber = (null: any); currentHook = null; workInProgressHook = null; @@ -1148,15 +1148,14 @@ function useThenable<T>(thenable: Thenable<T>): T { } function use<T>(usable: Usable<T>): T { - // $FlowFixMe[invalid-compare] if (usable !== null && typeof usable === 'object') { // $FlowFixMe[method-unbinding] if (typeof usable.then === 'function') { // This is a thenable. - const thenable: Thenable<T> = usable as any; + const thenable: Thenable<T> = (usable: any); return useThenable(thenable); } else if (usable.$$typeof === REACT_CONTEXT_TYPE) { - const context: ReactContext<T> = usable as any; + const context: ReactContext<T> = (usable: any); return readContext(context); } } @@ -1169,7 +1168,7 @@ function useMemoCache(size: number): Array<mixed> { let memoCache = null; // Fast-path, load memo cache from wip fiber if already prepared let updateQueue: FunctionComponentUpdateQueue | null = - currentlyRenderingFiber.updateQueue as any; + (currentlyRenderingFiber.updateQueue: any); if (updateQueue !== null) { memoCache = updateQueue.memoCache; } @@ -1178,7 +1177,7 @@ function useMemoCache(size: number): Array<mixed> { const current: Fiber | null = currentlyRenderingFiber.alternate; if (current !== null) { const currentUpdateQueue: FunctionComponentUpdateQueue | null = - current.updateQueue as any; + (current.updateQueue: any); if (currentUpdateQueue !== null) { const currentMemoCache: ?MemoCache = currentUpdateQueue.memoCache; if (currentMemoCache != null) { @@ -1272,7 +1271,7 @@ function mountReducer<S, I, A>( } } } else { - initialState = initialArg as any as S; + initialState = ((initialArg: any): S); } hook.memoizedState = hook.baseState = initialState; const queue: UpdateQueue<S, A> = { @@ -1280,14 +1279,14 @@ function mountReducer<S, I, A>( lanes: NoLanes, dispatch: null, lastRenderedReducer: reducer, - lastRenderedState: initialState as any, + lastRenderedState: (initialState: any), }; hook.queue = queue; - const dispatch: Dispatch<A> = (queue.dispatch = dispatchReducerAction.bind( + const dispatch: Dispatch<A> = (queue.dispatch = (dispatchReducerAction.bind( null, currentlyRenderingFiber, queue, - ) as any); + ): any)); return [hook.memoizedState, dispatch]; } @@ -1297,7 +1296,7 @@ function updateReducer<S, I, A>( init?: I => S, ): [S, Dispatch<A>] { const hook = updateWorkInProgressHook(); - return updateReducerImpl(hook, currentHook as any as Hook, reducer); + return updateReducerImpl(hook, ((currentHook: any): Hook), reducer); } function updateReducerImpl<S, A>( @@ -1414,7 +1413,7 @@ function updateReducerImpl<S, A>( action: update.action, hasEagerState: update.hasEagerState, eagerState: update.eagerState, - next: null as any, + next: (null: any), }; if (newBaseQueueLast === null) { newBaseQueueFirst = newBaseQueueLast = clone; @@ -1450,7 +1449,7 @@ function updateReducerImpl<S, A>( action: update.action, hasEagerState: update.hasEagerState, eagerState: update.eagerState, - next: null as any, + next: (null: any), }; newBaseQueueLast = newBaseQueueLast.next = clone; } @@ -1494,7 +1493,7 @@ function updateReducerImpl<S, A>( action: update.action, hasEagerState: update.hasEagerState, eagerState: update.eagerState, - next: null as any, + next: (null: any), }; if (newBaseQueueLast === null) { newBaseQueueFirst = newBaseQueueLast = clone; @@ -1521,19 +1520,18 @@ function updateReducerImpl<S, A>( if (update.hasEagerState) { // If this update is a state update (not a reducer) and was processed eagerly, // we can use the eagerly computed state - newState = update.eagerState as any as S; + newState = ((update.eagerState: any): S); } else { newState = reducer(newState, action); } } update = update.next; - // $FlowFixMe[invalid-compare] } while (update !== null && update !== first); if (newBaseQueueLast === null) { newBaseState = newState; } else { - newBaseQueueLast.next = newBaseQueueFirst as any; + newBaseQueueLast.next = (newBaseQueueFirst: any); } // Mark that the fiber performed work, but only if the new state is @@ -1571,7 +1569,7 @@ function updateReducerImpl<S, A>( queue.lanes = NoLanes; } - const dispatch: Dispatch<A> = queue.dispatch as any; + const dispatch: Dispatch<A> = (queue.dispatch: any); return [hook.memoizedState, dispatch]; } @@ -1594,7 +1592,7 @@ function rerenderReducer<S, I, A>( // This is a re-render. Apply the new render phase updates to the previous // work-in-progress hook. - const dispatch: Dispatch<A> = queue.dispatch as any; + const dispatch: Dispatch<A> = (queue.dispatch: any); const lastRenderPhaseUpdate = queue.pending; let newState = hook.memoizedState; if (lastRenderPhaseUpdate !== null) { @@ -1820,10 +1818,10 @@ function pushStoreConsistencyCheck<T>( value: renderedSnapshot, }; let componentUpdateQueue: null | FunctionComponentUpdateQueue = - currentlyRenderingFiber.updateQueue as any; + (currentlyRenderingFiber.updateQueue: any); if (componentUpdateQueue === null) { componentUpdateQueue = createFunctionComponentUpdateQueue(); - currentlyRenderingFiber.updateQueue = componentUpdateQueue as any; + currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any); componentUpdateQueue.stores = [check]; } else { const stores = componentUpdateQueue.stores; @@ -1915,7 +1913,7 @@ function mountStateImpl<S>(initialState: (() => S) | S): Hook { lanes: NoLanes, dispatch: null, lastRenderedReducer: basicStateReducer, - lastRenderedState: initialState as any, + lastRenderedState: (initialState: any), }; hook.queue = queue; return hook; @@ -1926,11 +1924,11 @@ function mountState<S>( ): [S, Dispatch<BasicStateAction<S>>] { const hook = mountStateImpl(initialState); const queue = hook.queue; - const dispatch: Dispatch<BasicStateAction<S>> = dispatchSetState.bind( + const dispatch: Dispatch<BasicStateAction<S>> = (dispatchSetState.bind( null, currentlyRenderingFiber, queue, - ) as any; + ): any); queue.dispatch = dispatch; return [hook.memoizedState, dispatch]; } @@ -1963,12 +1961,12 @@ function mountOptimistic<S, A>( }; hook.queue = queue; // This is different than the normal setState function. - const dispatch: A => void = dispatchOptimisticSetState.bind( + const dispatch: A => void = (dispatchOptimisticSetState.bind( null, currentlyRenderingFiber, true, queue, - ) as any; + ): any); queue.dispatch = dispatch; return [passthrough, dispatch]; } @@ -1980,7 +1978,7 @@ function updateOptimistic<S, A>( const hook = updateWorkInProgressHook(); return updateOptimisticImpl( hook, - currentHook as any as Hook, + ((currentHook: any): Hook), passthrough, reducer, ); @@ -2002,9 +2000,9 @@ function updateOptimisticImpl<S, A>( // If a reducer is not provided, default to the same one used by useState. const resolvedReducer: (S, A) => S = - typeof reducer === 'function' ? reducer : (basicStateReducer as any); + typeof reducer === 'function' ? reducer : (basicStateReducer: any); - return updateReducerImpl(hook, currentHook as any as Hook, resolvedReducer); + return updateReducerImpl(hook, ((currentHook: any): Hook), resolvedReducer); } function rerenderOptimistic<S, A>( @@ -2024,7 +2022,7 @@ function rerenderOptimistic<S, A>( // This is an update. Process the update queue. return updateOptimisticImpl( hook, - currentHook as any as Hook, + ((currentHook: any): Hook), passthrough, reducer, ); @@ -2099,7 +2097,8 @@ function dispatchActionState<S, P>( const actionNode: ActionStateQueueNode<S, P> = { payload, action: currentAction, - next: null as any, // circular + next: (null: any), // circular + isTransition: true, status: 'pending', @@ -2165,7 +2164,7 @@ function runActionStateAction<S, P>( // This is a fork of startTransition const prevTransition = ReactSharedInternals.T; - const currentTransition: Transition = {} as any; + const currentTransition: Transition = ({}: any); if (enableViewTransition) { currentTransition.types = prevTransition !== null @@ -2254,7 +2253,7 @@ function handleActionReturnValue<S, P>( // $FlowFixMe[method-unbinding] typeof returnValue.then === 'function' ) { - const thenable = returnValue as any as Thenable<Awaited<S>>; + const thenable = ((returnValue: any): Thenable<Awaited<S>>); if (__DEV__) { // Keep track of the number of async transitions still running so we can warn. ReactSharedInternals.asyncTransitions++; @@ -2280,7 +2279,7 @@ function handleActionReturnValue<S, P>( } } } else { - const nextState = returnValue as any as Awaited<S>; + const nextState = ((returnValue: any): Awaited<S>); onActionSuccess(actionQueue, node, nextState); } } @@ -2360,7 +2359,7 @@ function mountActionState<S, P>( ): [Awaited<S>, (P) => void, boolean] { let initialState: Awaited<S> = initialStateProp; if (getIsHydrating()) { - const root: FiberRoot = getWorkInProgressRoot() as any; + const root: FiberRoot = (getWorkInProgressRoot(): any); const ssrFormState = root.formState; // If a formState option was passed to the root, there are form state // markers that we need to hydrate. These indicate whether the form state @@ -2384,30 +2383,30 @@ function mountActionState<S, P>( const stateQueue = { pending: null, lanes: NoLanes, - dispatch: null as any, + dispatch: (null: any), lastRenderedReducer: actionStateReducer, lastRenderedState: initialState, }; stateHook.queue = stateQueue; - const setState: Dispatch<S | Awaited<S>> = dispatchSetState.bind( + const setState: Dispatch<S | Awaited<S>> = (dispatchSetState.bind( null, currentlyRenderingFiber, - stateQueue as any as UpdateQueue<S | Awaited<S>, S | Awaited<S>>, - ) as any; + ((stateQueue: any): UpdateQueue<S | Awaited<S>, S | Awaited<S>>), + ): any); stateQueue.dispatch = setState; // Pending state. This is used to store the pending state of the action. // Tracked optimistically, like a transition pending state. - const pendingStateHook = mountStateImpl(false as Thenable<boolean> | boolean); - const setPendingState: boolean => void = dispatchOptimisticSetState.bind( + const pendingStateHook = mountStateImpl((false: Thenable<boolean> | boolean)); + const setPendingState: boolean => void = (dispatchOptimisticSetState.bind( null, currentlyRenderingFiber, false, - pendingStateHook.queue as any as UpdateQueue< + ((pendingStateHook.queue: any): UpdateQueue< S | Awaited<S>, S | Awaited<S>, - >, - ) as any; + >), + ): any); // Action queue hook. This is used to queue pending actions. The queue is // shared between all instances of the hook. Similar to a regular state queue, @@ -2416,12 +2415,12 @@ function mountActionState<S, P>( const actionQueueHook = mountWorkInProgressHook(); const actionQueue: ActionStateQueue<S, P> = { state: initialState, - dispatch: null as any, // circular + dispatch: (null: any), // circular action, pending: null, }; actionQueueHook.queue = actionQueue; - const dispatch = (dispatchActionState as any).bind( + const dispatch = (dispatchActionState: any).bind( null, currentlyRenderingFiber, actionQueue, @@ -2444,7 +2443,7 @@ function updateActionState<S, P>( permalink?: string, ): [Awaited<S>, (P) => void, boolean] { const stateHook = updateWorkInProgressHook(); - const currentStateHook = currentHook as any as Hook; + const currentStateHook = ((currentHook: any): Hook); return updateActionStateImpl( stateHook, currentStateHook, @@ -2473,13 +2472,12 @@ function updateActionStateImpl<S, P>( let state: Awaited<S>; if ( typeof actionResult === 'object' && - // $FlowFixMe[invalid-compare] actionResult !== null && // $FlowFixMe[method-unbinding] typeof actionResult.then === 'function' ) { try { - state = useThenable(actionResult as any as Thenable<Awaited<S>>); + state = useThenable(((actionResult: any): Thenable<Awaited<S>>)); } catch (x) { if (x === SuspenseException) { // If we Suspend here, mark this separately so that we can track this @@ -2490,7 +2488,7 @@ function updateActionStateImpl<S, P>( } } } else { - state = actionResult as any; + state = (actionResult: any); } const actionQueueHook = updateWorkInProgressHook(); @@ -2573,17 +2571,17 @@ function pushSimpleEffect( deps, inst, // Circular - next: null as any, + next: (null: any), }; return pushEffectImpl(effect); } function pushEffectImpl(effect: Effect): Effect { let componentUpdateQueue: null | FunctionComponentUpdateQueue = - currentlyRenderingFiber.updateQueue as any; + (currentlyRenderingFiber.updateQueue: any); if (componentUpdateQueue === null) { componentUpdateQueue = createFunctionComponentUpdateQueue(); - currentlyRenderingFiber.updateQueue = componentUpdateQueue as any; + currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any); } const lastEffect = componentUpdateQueue.lastEffect; if (lastEffect === null) { @@ -2647,7 +2645,7 @@ function updateEffectImpl( if (nextDeps !== null) { const prevEffect: Effect = currentHook.memoizedState; const prevDeps = prevEffect.deps; - // $FlowFixMe[incompatible-type] (@poteto) + // $FlowFixMe[incompatible-call] (@poteto) if (areHookInputsEqual(nextDeps, prevDeps)) { hook.memoizedState = pushSimpleEffect( hookFlags, @@ -2706,10 +2704,10 @@ function useEffectEventImpl<Args, Return, F: (...Array<Args>) => Return>( ) { currentlyRenderingFiber.flags |= UpdateEffect; let componentUpdateQueue: null | FunctionComponentUpdateQueue = - currentlyRenderingFiber.updateQueue as any; + (currentlyRenderingFiber.updateQueue: any); if (componentUpdateQueue === null) { componentUpdateQueue = createFunctionComponentUpdateQueue(); - currentlyRenderingFiber.updateQueue = componentUpdateQueue as any; + currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any); componentUpdateQueue.events = [payload]; } else { const events = componentUpdateQueue.events; @@ -2727,7 +2725,7 @@ function mountEvent<Args, Return, F: (...Array<Args>) => Return>( const hook = mountWorkInProgressHook(); const ref = {impl: callback}; hook.memoizedState = ref; - // $FlowFixMe[incompatible-type] + // $FlowIgnore[incompatible-return] return function eventFn() { if (isInvalidExecutionContextForEventFunction()) { throw new Error( @@ -2744,7 +2742,7 @@ function updateEvent<Args, Return, F: (...Array<Args>) => Return>( const hook = updateWorkInProgressHook(); const ref = hook.memoizedState; useEffectEventImpl({ref, nextImpl: callback}); - // $FlowFixMe[incompatible-type] + // $FlowIgnore[incompatible-return] return function eventFn() { if (isInvalidExecutionContextForEventFunction()) { throw new Error( @@ -2835,7 +2833,6 @@ function mountImperativeHandle<T>( console.error( 'Expected useImperativeHandle() second argument to be a function ' + 'that creates a handle. Instead received: %s.', - // $FlowFixMe[invalid-compare] create !== null ? typeof create : 'null', ); } @@ -2870,7 +2867,6 @@ function updateImperativeHandle<T>( console.error( 'Expected useImperativeHandle() second argument to be a function ' + 'that creates a handle. Instead received: %s.', - // $FlowFixMe[invalid-compare] create !== null ? typeof create : 'null', ); } @@ -2970,7 +2966,7 @@ function mountDeferredValue<T>(value: T, initialValue?: T): T { function updateDeferredValue<T>(value: T, initialValue?: T): T { const hook = updateWorkInProgressHook(); - const resolvedCurrentHook: Hook = currentHook as any; + const resolvedCurrentHook: Hook = (currentHook: any); const prevValue: T = resolvedCurrentHook.memoizedState; return updateDeferredValueImpl(hook, prevValue, value, initialValue); } @@ -3104,7 +3100,7 @@ function startTransition<S>( ); const prevTransition = ReactSharedInternals.T; - const currentTransition: Transition = {} as any; + const currentTransition: Transition = ({}: any); if (enableViewTransition) { currentTransition.types = prevTransition !== null @@ -3157,7 +3153,7 @@ function startTransition<S>( typeof returnValue === 'object' && typeof returnValue.then === 'function' ) { - const thenable = returnValue as any as Thenable<mixed>; + const thenable = ((returnValue: any): Thenable<mixed>); if (__DEV__) { // Keep track of the number of async transitions still running so we can warn. ReactSharedInternals.asyncTransitions++; @@ -3172,7 +3168,7 @@ function startTransition<S>( dispatchSetStateInternal( fiber, queue, - thenableForFinishedState as any, + (thenableForFinishedState: any), requestUpdateLane(fiber), ); } else { @@ -3303,7 +3299,7 @@ function ensureFormComponentIsStateful(formFiber: Fiber) { lanes: NoLanes, // We're going to cheat and intentionally not create a bound dispatch // method, because we can call it directly in startTransition. - dispatch: null as any, + dispatch: (null: any), lastRenderedReducer: basicStateReducer, lastRenderedState: NoPendingHostTransition, }; @@ -3326,7 +3322,7 @@ function ensureFormComponentIsStateful(formFiber: Fiber) { lanes: NoLanes, // We're going to cheat and intentionally not create a bound dispatch // method, because we can call it directly in startTransition. - dispatch: null as any, + dispatch: (null: any), lastRenderedReducer: basicStateReducer, lastRenderedState: initialResetState, }; @@ -3386,9 +3382,9 @@ export function requestFormReset(formFiber: Fiber) { // instead. // TODO: We should really stash the Queue somewhere stateful // just like how setState binds the Queue. - stateHook = (formFiber.alternate as any).memoizedState; + stateHook = (formFiber.alternate: any).memoizedState; } - const resetStateHook: Hook = stateHook.next as any; + const resetStateHook: Hook = (stateHook.next: any); const resetStateQueue = resetStateHook.queue; dispatchSetStateInternal( formFiber, @@ -3402,7 +3398,7 @@ function mountTransition(): [ boolean, (callback: () => void, options?: StartTransitionOptions) => void, ] { - const stateHook = mountStateImpl(false as Thenable<boolean> | boolean); + const stateHook = mountStateImpl((false: Thenable<boolean> | boolean)); // The `start` method never changes. const start = startTransition.bind( null, @@ -3453,7 +3449,7 @@ function useHostTransitionStatus(): TransitionStatus { function mountId(): string { const hook = mountWorkInProgressHook(); - const root = getWorkInProgressRoot() as any as FiberRoot; + const root = ((getWorkInProgressRoot(): any): FiberRoot); // TODO: In Fizz, id generation is specific to each server config. Maybe we // should do this in Fiber, too? Deferring this decision for now because // there's no other place to store the prefix except for an internal field on @@ -3582,7 +3578,7 @@ function dispatchReducerAction<S, A>( action, hasEagerState: false, eagerState: null, - next: null as any, + next: (null: any), }; if (isRenderPhaseUpdate(fiber)) { @@ -3642,7 +3638,7 @@ function dispatchSetStateInternal<S, A>( action, hasEagerState: false, eagerState: null, - next: null as any, + next: (null: any), }; if (isRenderPhaseUpdate(fiber)) { @@ -3664,7 +3660,7 @@ function dispatchSetStateInternal<S, A>( ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV; } try { - const currentState: S = queue.lastRenderedState as any; + const currentState: S = (queue.lastRenderedState: any); const eagerState = lastRenderedReducer(currentState, action); // Stash the eagerly computed state, and the reducer used to compute // it, on the update object. If the reducer hasn't changed by the @@ -3761,7 +3757,7 @@ function dispatchOptimisticSetState<S, A>( action, hasEagerState: false, eagerState: null, - next: null as any, + next: (null: any), }; if (isRenderPhaseUpdate(fiber)) { @@ -3826,8 +3822,8 @@ function enqueueRenderPhaseUpdate<S, A>( // This is a render phase update. Stash it in a lazily-created map of // queue -> linked list of updates. After this render pass, we'll restart // and apply the stashed updates on top of the work-in-progress hook. - didScheduleRenderPhaseUpdateDuringThisPass = - didScheduleRenderPhaseUpdate = true; + didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = + true; const pending = queue.pending; if (pending === null) { // This is the first update. Create a circular list. diff --git a/packages/react-reconciler/src/ReactFiberHostContext.js b/packages/react-reconciler/src/ReactFiberHostContext.js index 10e89e8f088d..2d2ec4c88acd 100644 --- a/packages/react-reconciler/src/ReactFiberHostContext.js +++ b/packages/react-reconciler/src/ReactFiberHostContext.js @@ -46,7 +46,7 @@ function requiredContext<Value>(c: Value | null): Value { ); } } - return c as any; + return (c: any); } function getCurrentRootHostContainer(): null | Container { @@ -105,7 +105,6 @@ function pushHostContext(fiber: Fiber): void { // we would need to push a context value even for host fibers that // haven't been upgraded yet. const transitionStatus: TransitionStatus = stateHook.memoizedState; - // $FlowFixMe[constant-condition] if (isPrimaryRenderer) { HostTransitionContext._currentValue = transitionStatus; } else { @@ -150,7 +149,6 @@ function popHostContext(fiber: Fiber): void { // to `NotPendingTransition`. We can do this because you're not allowed to nest forms. If // we allowed for multiple nested host transition providers, then we'd // need to reset this to the parent provider's status. - // $FlowFixMe[constant-condition] if (isPrimaryRenderer) { HostTransitionContext._currentValue = NotPendingTransition; } else { diff --git a/packages/react-reconciler/src/ReactFiberHotReloading.js b/packages/react-reconciler/src/ReactFiberHotReloading.js index 461b62b0b4f8..984f832359f7 100644 --- a/packages/react-reconciler/src/ReactFiberHotReloading.js +++ b/packages/react-reconciler/src/ReactFiberHotReloading.js @@ -107,7 +107,7 @@ export function resolveForwardRefForHotReloading(type: any): any { render: currentRender, }; if (type.displayName !== undefined) { - (syntheticType as any).displayName = type.displayName; + (syntheticType: any).displayName = type.displayName; } return syntheticType; } diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.js b/packages/react-reconciler/src/ReactFiberHydrationContext.js index e8ebec1c8bcf..0c758202b52f 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.js @@ -160,7 +160,6 @@ export function markDidThrowWhileHydratingDEV() { } function enterHydrationState(fiber: Fiber): boolean { - // $FlowFixMe[constant-condition] if (!supportsHydration) { return false; } @@ -183,7 +182,6 @@ function reenterHydrationStateFromDehydratedActivityInstance( activityInstance: ActivityInstance, treeContext: TreeContext | null, ): boolean { - // $FlowFixMe[constant-condition] if (!supportsHydration) { return false; } @@ -206,7 +204,6 @@ function reenterHydrationStateFromDehydratedSuspenseInstance( suspenseInstance: SuspenseInstance, treeContext: TreeContext | null, ): boolean { - // $FlowFixMe[constant-condition] if (!supportsHydration) { return false; } @@ -260,10 +257,8 @@ function tryHydrateInstance( fiber.pendingProps, rootOrSingletonContext, ); - // $FlowFixMe[invalid-compare] - // $FlowFixMe[invalid-compare] if (instance !== null) { - fiber.stateNode = instance as Instance; + fiber.stateNode = (instance: Instance); if (__DEV__) { if (!didSuspendOrErrorDEV) { @@ -272,9 +267,7 @@ function tryHydrateInstance( fiber.type, fiber.pendingProps, hostContext, - // $FlowFixMe[invalid-compare] ); - // $FlowFixMe[invalid-compare] if (differences !== null) { const diffNode = buildHydrationDiffNode(fiber, 0); diffNode.serverProps = differences; @@ -296,12 +289,10 @@ function tryHydrateText(fiber: Fiber, nextInstance: any) { const textInstance = canHydrateTextInstance( nextInstance, text, - // $FlowFixMe[invalid-compare] rootOrSingletonContext, ); - // $FlowFixMe[invalid-compare] if (textInstance !== null) { - fiber.stateNode = textInstance as TextInstance; + fiber.stateNode = (textInstance: TextInstance); hydrationParentFiber = fiber; // Text Instances don't have children so there's nothing to hydrate. nextHydratableInstance = null; @@ -316,11 +307,9 @@ function tryHydrateActivity( ): null | ActivityInstance { // fiber is a ActivityComponent Fiber const activityInstance = canHydrateActivityInstance( - // $FlowFixMe[invalid-compare] nextInstance, rootOrSingletonContext, ); - // $FlowFixMe[invalid-compare] if (activityInstance !== null) { const activityState: ActivityState = { dehydrated: activityInstance, @@ -350,12 +339,10 @@ function tryHydrateSuspense( nextInstance: any, ): null | SuspenseInstance { // fiber is a SuspenseComponent Fiber - // $FlowFixMe[invalid-compare] const suspenseInstance = canHydrateSuspenseInstance( nextInstance, rootOrSingletonContext, ); - // $FlowFixMe[invalid-compare] if (suspenseInstance !== null) { const suspenseState: SuspenseState = { dehydrated: suspenseInstance, @@ -416,7 +403,6 @@ function throwOnHydrationMismatch(fiber: Fiber, fromText: boolean = false) { } function claimHydratableSingleton(fiber: Fiber): void { - // $FlowFixMe[constant-condition] if (supportsSingletons) { if (!isHydrating) { return; @@ -434,13 +420,11 @@ function claimHydratableSingleton(fiber: Fiber): void { if (__DEV__) { if (!didSuspendOrErrorDEV) { const differences = diffHydratedPropsForDevWarnings( - // $FlowFixMe[invalid-compare] instance, fiber.type, fiber.pendingProps, currentHostContext, ); - // $FlowFixMe[invalid-compare] if (differences !== null) { const diffNode = buildHydrationDiffNode(fiber, 0); diffNode.serverProps = differences; @@ -559,7 +543,6 @@ function prepareToHydrateHostInstance( fiber: Fiber, hostContext: HostContext, ): void { - // $FlowFixMe[constant-condition] if (!supportsHydration) { throw new Error( 'Expected prepareToHydrateHostInstance() to never be called. ' + @@ -581,7 +564,6 @@ function prepareToHydrateHostInstance( } function prepareToHydrateHostTextInstance(fiber: Fiber): void { - // $FlowFixMe[constant-condition] if (!supportsHydration) { throw new Error( 'Expected prepareToHydrateHostTextInstance() to never be called. ' + @@ -600,14 +582,12 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): void { switch (returnFiber.tag) { case HostRoot: { if (__DEV__) { - // $FlowFixMe[invalid-compare] if (shouldWarnIfMismatchDev) { const difference = diffHydratedTextForDevWarnings( textInstance, textContent, parentProps, ); - // $FlowFixMe[invalid-compare] if (difference !== null) { const diffNode = buildHydrationDiffNode(fiber, 0); diffNode.serverProps = difference; @@ -619,7 +599,6 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): void { case HostSingleton: case HostComponent: { parentProps = returnFiber.memoizedProps; - // $FlowFixMe[invalid-compare] if (__DEV__) { if (shouldWarnIfMismatchDev) { const difference = diffHydratedTextForDevWarnings( @@ -627,7 +606,6 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): void { textContent, parentProps, ); - // $FlowFixMe[invalid-compare] if (difference !== null) { const diffNode = buildHydrationDiffNode(fiber, 0); diffNode.serverProps = difference; @@ -652,7 +630,6 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): void { } function prepareToHydrateHostActivityInstance(fiber: Fiber): void { - // $FlowFixMe[constant-condition] if (!supportsHydration) { throw new Error( 'Expected prepareToHydrateHostActivityInstance() to never be called. ' + @@ -674,7 +651,6 @@ function prepareToHydrateHostActivityInstance(fiber: Fiber): void { } function prepareToHydrateHostSuspenseInstance(fiber: Fiber): void { - // $FlowFixMe[constant-condition] if (!supportsHydration) { throw new Error( 'Expected prepareToHydrateHostSuspenseInstance() to never be called. ' + @@ -716,7 +692,6 @@ function skipPastDehydratedActivityInstance( function skipPastDehydratedSuspenseInstance( fiber: Fiber, ): null | HydratableInstance { - // $FlowFixMe[constant-condition] if (!supportsHydration) { throw new Error( 'Expected skipPastDehydratedSuspenseInstance() to never be called. ' + @@ -757,7 +732,6 @@ function popToNextHostParent(fiber: Fiber): void { } function popHydrationState(fiber: Fiber): boolean { - // $FlowFixMe[constant-condition] if (!supportsHydration) { return false; } @@ -777,7 +751,6 @@ function popHydrationState(fiber: Fiber): boolean { const tag = fiber.tag; - // $FlowFixMe[constant-condition] if (supportsSingletons) { // With float we never clear the Root, or Singleton instances. We also do not clear Instances // that have singleton text content @@ -819,7 +792,6 @@ function popHydrationState(fiber: Fiber): boolean { nextHydratableInstance = skipPastDehydratedSuspenseInstance(fiber); } else if (tag === ActivityComponent) { nextHydratableInstance = skipPastDehydratedActivityInstance(fiber); - // $FlowFixMe[constant-condition] } else if (supportsSingletons && tag === HostSingleton) { nextHydratableInstance = getNextHydratableSiblingAfterSingleton( fiber.type, @@ -833,7 +805,6 @@ function popHydrationState(fiber: Fiber): boolean { return true; } -// $FlowFixMe[invalid-compare] function warnIfUnhydratedTailNodes(fiber: Fiber) { if (__DEV__) { let nextInstance = nextHydratableInstance; @@ -842,9 +813,8 @@ function warnIfUnhydratedTailNodes(fiber: Fiber) { const description = describeHydratableInstanceForDevWarnings(nextInstance); diffNode.serverTail.push(description); - // $FlowFixMe[invalid-compare] if (description.type === 'Suspense') { - const suspenseInstance: SuspenseInstance = nextInstance as any; + const suspenseInstance: SuspenseInstance = (nextInstance: any); nextInstance = getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance); } else { @@ -855,7 +825,6 @@ function warnIfUnhydratedTailNodes(fiber: Fiber) { } function resetHydrationState(): void { - // $FlowFixMe[constant-condition] if (!supportsHydration) { return; } @@ -876,7 +845,6 @@ function resetHydrationState(): void { // before re-running beginWork on the same fiber, or when throwAndUnwindWorkLoop // calls unwindWork on ancestor fibers. function popHydrationStateOnInterruptedWork(fiber: Fiber): void { - // $FlowFixMe[constant-condition] if (!supportsHydration) { return; } diff --git a/packages/react-reconciler/src/ReactFiberHydrationDiffs.js b/packages/react-reconciler/src/ReactFiberHydrationDiffs.js index 7ad466d88975..51df2a25d6c5 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationDiffs.js +++ b/packages/react-reconciler/src/ReactFiberHydrationDiffs.js @@ -197,8 +197,8 @@ function describeValue(value: mixed, maxLength: number): string { if (isArray(value)) { return '[...]'; } - if ((value as any).$$typeof === REACT_ELEMENT_TYPE) { - const type = getComponentNameFromType((value as any).type); + if ((value: any).$$typeof === REACT_ELEMENT_TYPE) { + const type = getComponentNameFromType((value: any).type); return type ? '<' + type + '>' : '<...>'; } const name = objectName(value); @@ -228,12 +228,12 @@ function describeValue(value: mixed, maxLength: number): string { } return '{' + properties + '}'; } else if (enableSrcObject && (name === 'Blob' || name === 'File')) { - return name + ':' + (value as any).type; + return name + ':' + (value: any).type; } return name; } case 'function': { - const name = (value as any).displayName || value.name; + const name = (value: any).displayName || value.name; return name ? 'function ' + name : 'function'; } default: diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index 19c873e70fc6..987f0338ad1a 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -942,7 +942,6 @@ export function markRootFinished( // commits, they behave like regular updates. for (let i = 0; i < hiddenUpdatesForLane.length; i++) { const update = hiddenUpdatesForLane[i]; - // $FlowFixMe[invalid-compare] if (update !== null) { update.lane &= ~OffscreenLane; } diff --git a/packages/react-reconciler/src/ReactFiberLegacyContext.js b/packages/react-reconciler/src/ReactFiberLegacyContext.js index 2c05ea91cefa..e8a586e856e1 100644 --- a/packages/react-reconciler/src/ReactFiberLegacyContext.js +++ b/packages/react-reconciler/src/ReactFiberLegacyContext.js @@ -19,7 +19,7 @@ import {createCursor, push, pop} from './ReactFiberStack'; let warnedAboutMissingGetChildContext; if (__DEV__) { - warnedAboutMissingGetChildContext = {} as {[string]: boolean}; + warnedAboutMissingGetChildContext = ({}: {[string]: boolean}); } export const emptyContextObject: {} = {}; diff --git a/packages/react-reconciler/src/ReactFiberNewContext.js b/packages/react-reconciler/src/ReactFiberNewContext.js index c5107bac04a0..ac9d766d6ae3 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.js @@ -84,7 +84,6 @@ export function pushProvider<T>( context: ReactContext<T>, nextValue: T, ): void { - // $FlowFixMe[constant-condition] if (isPrimaryRenderer) { push(valueCursor, context._currentValue, providerFiber); @@ -132,7 +131,6 @@ export function popProvider( ): void { const currentValue = valueCursor.current; - // $FlowFixMe[constant-condition] if (isPrimaryRenderer) { context._currentValue = currentValue; if (__DEV__) { @@ -238,7 +236,6 @@ function propagateContextChanges<T>( findContext: for (let i = 0; i < contexts.length; i++) { const context: ReactContext<T> = contexts[i]; // Check if the context matches. - // $FlowFixMe[invalid-compare] if (dependency.context === context) { // Match! Schedule an update on this fiber. @@ -523,7 +520,6 @@ export function checkIfContextChanged( let dependency = currentDependencies.firstContext; while (dependency !== null) { const context = dependency.context; - // $FlowFixMe[constant-condition] const newValue = isPrimaryRenderer ? context._currentValue : context._currentValue2; @@ -581,13 +577,12 @@ function readContextForConsumer<T>( consumer: Fiber | null, context: ReactContext<T>, ): T { - // $FlowFixMe[constant-condition] const value = isPrimaryRenderer ? context._currentValue : context._currentValue2; const contextItem = { - context: context as any as ReactContext<mixed>, + context: ((context: any): ReactContext<mixed>), memoizedValue: value, next: null, }; @@ -603,24 +598,20 @@ function readContextForConsumer<T>( } // This is the first dependency for this component. Create a new list. - // $FlowFixMe[incompatible-type] lastContextDependency = contextItem; consumer.dependencies = __DEV__ - ? // $FlowFixMe[incompatible-type] - { + ? { lanes: NoLanes, firstContext: contextItem, _debugThenableState: null, } - : // $FlowFixMe[incompatible-type] - { + : { lanes: NoLanes, firstContext: contextItem, }; consumer.flags |= NeedsPropagation; } else { // Append a new context item. - // $FlowFixMe[incompatible-type] lastContextDependency = lastContextDependency.next = contextItem; } return value; diff --git a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js index f1baba342ce7..a1a2d76cecc0 100644 --- a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js +++ b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js @@ -189,7 +189,7 @@ export function popDeepEquality(prev: boolean): void { const reusableComponentDevToolDetails = { color: 'primary', - properties: null as null | Array<[string, string]>, + properties: (null: null | Array<[string, string]>), tooltipText: '', track: COMPONENTS_TRACK, }; @@ -232,10 +232,10 @@ export function logComponentRender( } if (supportsUserTiming) { const alternate = fiber.alternate; - let selfTime: number = fiber.actualDuration as any; + let selfTime: number = (fiber.actualDuration: any); if (alternate === null || alternate.child !== fiber.child) { for (let child = fiber.child; child !== null; child = child.sibling) { - selfTime -= child.actualDuration as any; + selfTime -= (child.actualDuration: any); } } const color = @@ -284,7 +284,7 @@ export function logComponentRender( isDeeplyEqual && !alreadyWarnedForDeepEquality && !includesSomeLane(alternate.lanes, committedLanes) && - (fiber.actualDuration as any) > 100 + (fiber.actualDuration: any) > 100 ) { alreadyWarnedForDeepEquality = true; // This is the first component in a subtree which rerendered with deeply equal props @@ -511,7 +511,6 @@ function logComponentEffectErrored( performance.measure.bind(performance, measureName, options), ); } else { - // $FlowFixMe[incompatible-type] performance.measure(measureName, options); } performance.clearMeasures(measureName); @@ -784,7 +783,6 @@ export function logBlockingStart( performance.measure.bind(performance, label, measureOptions), ); } else { - // $FlowFixMe[incompatible-type] performance.measure(label, measureOptions); } performance.clearMeasures(label); @@ -891,7 +889,6 @@ export function logGestureStart( performance.measure.bind(performance, label, measureOptions), ); } else { - // $FlowFixMe[incompatible-type] performance.measure(label, measureOptions); } performance.clearMeasures(label); @@ -1033,7 +1030,6 @@ export function logTransitionStart( performance.measure.bind(performance, label, measureOptions), ); } else { - // $FlowFixMe[incompatible-type] performance.measure(label, measureOptions); } performance.clearMeasures(label); diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index df12b7cbae7d..4a36b39c43c8 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -134,7 +134,7 @@ let didWarnAboutFindNodeInStrictMode; if (__DEV__) { didWarnAboutNestedUpdates = false; - didWarnAboutFindNodeInStrictMode = {} as {[string]: boolean}; + didWarnAboutFindNodeInStrictMode = ({}: {[string]: boolean}); } function getContextForSubtree( @@ -609,7 +609,7 @@ if (__DEV__) { const updated = isArray(obj) ? obj.slice() : {...obj}; if (index + 1 === path.length) { if (isArray(updated)) { - updated.splice(key as any as number, 1); + updated.splice(((key: any): number), 1); } else { delete updated[key]; } @@ -640,7 +640,7 @@ if (__DEV__) { // $FlowFixMe[incompatible-use] number or string is fine here updated[newKey] = updated[oldKey]; if (isArray(updated)) { - updated.splice(oldKey as any as number, 1); + updated.splice(((oldKey: any): number), 1); } else { delete updated[oldKey]; } @@ -859,7 +859,7 @@ function getLaneLabelMap(): Map<Lane, string> | null { let lane = 1; for (let index = 0; index < TotalLanes; index++) { - const label = getLabelForLane(lane) as any as string; + const label = ((getLabelForLane(lane): any): string); map.set(lane, label); lane *= 2; } @@ -880,9 +880,8 @@ export function injectIntoDevTools(): boolean { // which may not match for third party renderers. reconcilerVersion: ReactVersion, }; - // $FlowFixMe[invalid-compare] if (extraDevToolsConfig !== null) { - internals.rendererConfig = extraDevToolsConfig as RendererInspectionConfig; + internals.rendererConfig = (extraDevToolsConfig: RendererInspectionConfig); } if (__DEV__) { internals.overrideHookState = overrideHookState; diff --git a/packages/react-reconciler/src/ReactFiberRoot.js b/packages/react-reconciler/src/ReactFiberRoot.js index 2ed5ce255b83..26386597ee44 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.js +++ b/packages/react-reconciler/src/ReactFiberRoot.js @@ -186,7 +186,7 @@ export function createFiberRoot( transitionCallbacks: null | TransitionTracingCallbacks, ): FiberRoot { // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - const root: FiberRoot = new FiberRootNode( + const root: FiberRoot = (new FiberRootNode( containerInfo, tag, hydrate, @@ -196,7 +196,7 @@ export function createFiberRoot( onRecoverableError, onDefaultTransitionIndicator, formState, - ) as any; + ): any); if (enableSuspenseCallback) { root.hydrationCallbacks = hydrationCallbacks; } diff --git a/packages/react-reconciler/src/ReactFiberRootScheduler.js b/packages/react-reconciler/src/ReactFiberRootScheduler.js index cc97e21d54cb..26b625522762 100644 --- a/packages/react-reconciler/src/ReactFiberRootScheduler.js +++ b/packages/react-reconciler/src/ReactFiberRootScheduler.js @@ -662,7 +662,6 @@ function scheduleImmediateRootScheduleTask() { // TODO: Can we land supportsMicrotasks? Which environments don't support it? // Alternatively, can we move this check to the host config? - // $FlowFixMe[constant-condition] if (supportsMicrotasks) { scheduleMicrotask(() => { // In Safari, appending an iframe forces microtasks to run. diff --git a/packages/react-reconciler/src/ReactFiberScope.js b/packages/react-reconciler/src/ReactFiberScope.js index b2271700d3ea..8f9f1cdea577 100644 --- a/packages/react-reconciler/src/ReactFiberScope.js +++ b/packages/react-reconciler/src/ReactFiberScope.js @@ -25,7 +25,7 @@ import {HostComponent, ScopeComponent, ContextProvider} from './ReactWorkTags'; import {enableScopeAPI} from 'shared/ReactFeatureFlags'; function getSuspenseFallbackChild(fiber: Fiber): Fiber | null { - return ((fiber.child as any as Fiber).sibling as any as Fiber).child; + return ((((fiber.child: any): Fiber).sibling: any): Fiber).child; } const emptyObject = {}; @@ -40,7 +40,6 @@ function collectScopedNodes( const {type, memoizedProps, stateNode} = node; const instance = getPublicInstance(stateNode); if ( - // $FlowFixMe[invalid-compare] instance !== null && fn(type, memoizedProps || emptyObject, instance) === true ) { @@ -66,7 +65,6 @@ function collectFirstScopedNode( if (node.tag === HostComponent) { const {type, memoizedProps, stateNode} = node; const instance = getPublicInstance(stateNode); - // $FlowFixMe[invalid-compare] if (instance !== null && fn(type, memoizedProps, instance) === true) { return instance; } @@ -147,13 +145,11 @@ function DO_NOT_USE_queryAllNodes( fn: ReactScopeQuery, ): null | Array<Object> { const currentFiber = getInstanceFromScope(this); - // $FlowFixMe[invalid-compare] if (currentFiber === null) { return null; } const child = currentFiber.child; const scopedNodes: Array<any> = []; - // $FlowFixMe[invalid-compare] if (child !== null) { collectScopedNodesFromChildren(child, fn, scopedNodes); } @@ -165,12 +161,10 @@ function DO_NOT_USE_queryFirstNode( fn: ReactScopeQuery, ): null | Object { const currentFiber = getInstanceFromScope(this); - // $FlowFixMe[invalid-compare] if (currentFiber === null) { return null; } const child = currentFiber.child; - // $FlowFixMe[invalid-compare] if (child !== null) { return collectFirstScopedNodeFromChildren(child, fn); } @@ -193,13 +187,11 @@ function getChildContextValues<T>( context: ReactContext<T>, ): Array<T> { const currentFiber = getInstanceFromScope(this); - // $FlowFixMe[invalid-compare] if (currentFiber === null) { return []; } const child = currentFiber.child; const childContextValues: Array<T> = []; - // $FlowFixMe[invalid-compare] if (child !== null) { collectNearestChildContextValues(child, context, childContextValues); } diff --git a/packages/react-reconciler/src/ReactFiberSuspenseComponent.js b/packages/react-reconciler/src/ReactFiberSuspenseComponent.js index dea7812a0354..695c36971546 100644 --- a/packages/react-reconciler/src/ReactFiberSuspenseComponent.js +++ b/packages/react-reconciler/src/ReactFiberSuspenseComponent.js @@ -62,7 +62,6 @@ export type RetryQueue = Set<Wakeable>; export function findFirstSuspended(row: Fiber): null | Fiber { let node = row; - // $FlowFixMe[invalid-compare] while (node !== null) { if (node.tag === SuspenseComponent) { const state: SuspenseState | null = node.memoizedState; diff --git a/packages/react-reconciler/src/ReactFiberSuspenseContext.js b/packages/react-reconciler/src/ReactFiberSuspenseContext.js index 3abea66ba606..aec47af4656e 100644 --- a/packages/react-reconciler/src/ReactFiberSuspenseContext.js +++ b/packages/react-reconciler/src/ReactFiberSuspenseContext.js @@ -92,7 +92,6 @@ export function pushPrimaryTreeSuspenseHandler(handler: Fiber): void { shellBoundary = handler; } else { const prevState: SuspenseState = current.memoizedState; - // $FlowFixMe[invalid-compare] if (prevState !== null) { // This boundary is showing a fallback in the current UI. shellBoundary = handler; diff --git a/packages/react-reconciler/src/ReactFiberThenable.js b/packages/react-reconciler/src/ReactFiberThenable.js index 59557c61f620..b8b6c5d4b852 100644 --- a/packages/react-reconciler/src/ReactFiberThenable.js +++ b/packages/react-reconciler/src/ReactFiberThenable.js @@ -38,10 +38,10 @@ export opaque type ThenableState = ThenableStateDev | ThenableStateProd; function getThenablesFromState(state: ThenableState): Array<Thenable<any>> { if (__DEV__) { - const devState: ThenableStateDev = state as any; + const devState: ThenableStateDev = (state: any); return devState.thenables; } else { - const prodState = state as any; + const prodState = (state: any); return prodState; } } @@ -122,7 +122,7 @@ export function trackUsedThenable<T>( // they represent the same value, because components are idempotent. if (__DEV__) { - const thenableStateDev: ThenableStateDev = thenableState as any; + const thenableStateDev: ThenableStateDev = (thenableState: any); if (!thenableStateDev.didWarnAboutUncachedPromise) { // We should only warn the first time an uncached thenable is // discovered per component, because if there are multiple, the @@ -168,7 +168,7 @@ export function trackUsedThenable<T>( name: typeof displayName === 'string' ? displayName : 'Promise', start: startTime, end: startTime, - value: thenable as any, + value: (thenable: any), // We don't know the requesting owner nor stack. }; // We can infer the await owner/stack lazily from where this promise ends up @@ -254,19 +254,19 @@ export function trackUsedThenable<T>( ); } - const pendingThenable: PendingThenable<T> = thenable as any; + const pendingThenable: PendingThenable<T> = (thenable: any); pendingThenable.status = 'pending'; pendingThenable.then( fulfilledValue => { if (thenable.status === 'pending') { - const fulfilledThenable: FulfilledThenable<T> = thenable as any; + const fulfilledThenable: FulfilledThenable<T> = (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = fulfilledValue; } }, (error: mixed) => { if (thenable.status === 'pending') { - const rejectedThenable: RejectedThenable<T> = thenable as any; + const rejectedThenable: RejectedThenable<T> = (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; } @@ -275,13 +275,13 @@ export function trackUsedThenable<T>( } // Check one more time in case the thenable resolved synchronously. - switch ((thenable as Thenable<T>).status) { + switch ((thenable: Thenable<T>).status) { case 'fulfilled': { - const fulfilledThenable: FulfilledThenable<T> = thenable as any; + const fulfilledThenable: FulfilledThenable<T> = (thenable: any); return fulfilledThenable.value; } case 'rejected': { - const rejectedThenable: RejectedThenable<T> = thenable as any; + const rejectedThenable: RejectedThenable<T> = (thenable: any); const rejectedError = rejectedThenable.reason; checkIfUseWrappedInAsyncCatch(rejectedError); throw rejectedError; @@ -308,7 +308,6 @@ export function suspendCommit(): void { // This extra indirection only exists so it can handle passing // noopSuspenseyCommitThenable through to throwException. // TODO: Factor the thenable check out of throwException - // $FlowFixMe[incompatible-type] suspendedThenable = noopSuspenseyCommitThenable; throw SuspenseyCommitException; } diff --git a/packages/react-reconciler/src/ReactFiberThrow.js b/packages/react-reconciler/src/ReactFiberThrow.js index 2e0dc6c3adb0..d04bff34faed 100644 --- a/packages/react-reconciler/src/ReactFiberThrow.js +++ b/packages/react-reconciler/src/ReactFiberThrow.js @@ -187,7 +187,7 @@ function initializeClassErrorUpdate( // If componentDidCatch is the only error boundary method defined, // then it needs to call setState to recover from errors. // If no state update is scheduled then the boundary will swallow the error. - if (!includesSomeLane(fiber.lanes, SyncLane as Lane)) { + if (!includesSomeLane(fiber.lanes, (SyncLane: Lane))) { console.error( '%s: Error boundaries should implement getDerivedStateFromError(). ' + 'In that method, return a state update to display an error message or fallback UI.', @@ -381,7 +381,7 @@ function throwException( if (value !== null && typeof value === 'object') { if (typeof value.then === 'function') { // This is a wakeable. The component suspended. - const wakeable: Wakeable = value as any; + const wakeable: Wakeable = (value: any); resetSuspendedComponent(sourceFiber, rootRenderLanes); if (__DEV__) { @@ -468,7 +468,7 @@ function throwException( suspenseBoundary.flags |= ScheduleRetry; } else { const retryQueue: RetryQueue | null = - suspenseBoundary.updateQueue as any; + (suspenseBoundary.updateQueue: any); if (retryQueue === null) { suspenseBoundary.updateQueue = new Set([wakeable]); } else { @@ -493,7 +493,7 @@ function throwException( suspenseBoundary.flags |= ScheduleRetry; } else { const offscreenQueue: OffscreenQueue | null = - suspenseBoundary.updateQueue as any; + (suspenseBoundary.updateQueue: any); if (offscreenQueue === null) { const newOffscreenQueue: OffscreenQueue = { transitions: null, @@ -604,7 +604,7 @@ function throwException( createCapturedValueAtFiber(wrapperError, sourceFiber), ); } - const workInProgress: Fiber = (root.current as any).alternate; + const workInProgress: Fiber = (root.current: any).alternate; // Schedule an update at the root to log the error but this shouldn't // actually happen because we should recover. workInProgress.flags |= ShouldCapture; @@ -681,7 +681,7 @@ function throwException( break; case OffscreenComponent: { const offscreenState: OffscreenState | null = - workInProgress.memoizedState as any; + (workInProgress.memoizedState: any); if (offscreenState !== null) { // An error was thrown inside a hidden Offscreen boundary. This should // not be allowed to escape into the visible part of the UI. Mark the diff --git a/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.js b/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.js index 8fbf5aefd3dd..a54025294bb5 100644 --- a/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.js +++ b/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.js @@ -63,7 +63,6 @@ export function processTransitionCallbacks( callbacks: TransitionTracingCallbacks, ): void { if (enableTransitionTracing) { - // $FlowFixMe[invalid-compare] if (pendingTransitions !== null) { const transitionStart = pendingTransitions.transitionStart; const onTransitionStart = callbacks.onTransitionStart; @@ -79,11 +78,9 @@ export function processTransitionCallbacks( const onMarkerProgress = callbacks.onMarkerProgress; if (onMarkerProgress != null && markerProgress !== null) { markerProgress.forEach((markerInstance, markerName) => { - // $FlowFixMe[invalid-compare] if (markerInstance.transitions !== null) { // TODO: Clone the suspense object so users can't modify it const pending = - // $FlowFixMe[invalid-compare] markerInstance.pendingBoundaries !== null ? Array.from(markerInstance.pendingBoundaries.values()) : []; diff --git a/packages/react-reconciler/src/ReactFiberTransition.js b/packages/react-reconciler/src/ReactFiberTransition.js index 098e593a05b8..180a09b3659f 100644 --- a/packages/react-reconciler/src/ReactFiberTransition.js +++ b/packages/react-reconciler/src/ReactFiberTransition.js @@ -94,7 +94,7 @@ ReactSharedInternals.S = function onStartTransitionFinishForReconciler( startAsyncTransitionTimer(); // This is an async action - const thenable: Thenable<mixed> = returnValue as any; + const thenable: Thenable<mixed> = (returnValue: any); entangleAsyncAction(transition, thenable); } if (enableViewTransition) { @@ -135,7 +135,6 @@ function chainGestureCancellation( prevCancel: null | (() => void), ): () => void { return function cancelGesture(): void { - // $FlowFixMe[invalid-compare] if (scheduledGesture !== null) { cancelScheduledGesture(root, scheduledGesture); } @@ -218,7 +217,7 @@ function peekCacheFromPool(): Cache | null { } // Otherwise, check the root's cache pool. - const root = getWorkInProgressRoot() as any; + const root = (getWorkInProgressRoot(): any); const cacheFromRootCachePool = root.pooledCache; return cacheFromRootCachePool; @@ -242,11 +241,10 @@ export function requestCacheFromPool(renderLanes: Lanes): Cache { // - One of several fiber types: host root, cache boundary, suspense // component. These retain and release in the commit phase. - const root = getWorkInProgressRoot() as any; + const root = (getWorkInProgressRoot(): any); const freshCache = createCache(); root.pooledCache = freshCache; retainCache(freshCache); - // $FlowFixMe[invalid-compare] if (freshCache !== null) { root.pooledCacheLanes |= renderLanes; } @@ -331,7 +329,6 @@ export function getSuspendedCache(): SpawnedCachePool | null { return { // We must also save the parent, so that when we resume we can detect // a refresh. - // $FlowFixMe[constant-condition] parent: isPrimaryRenderer ? CacheContext._currentValue : CacheContext._currentValue2, @@ -348,7 +345,6 @@ export function getOffscreenDeferredCache(): SpawnedCachePool | null { return { // We must also store the parent, so that when we resume we can detect // a refresh. - // $FlowFixMe[constant-condition] parent: isPrimaryRenderer ? CacheContext._currentValue : CacheContext._currentValue2, diff --git a/packages/react-reconciler/src/ReactFiberTreeReflection.js b/packages/react-reconciler/src/ReactFiberTreeReflection.js index 8d8766ae4d5d..8b4c52b4ea2d 100644 --- a/packages/react-reconciler/src/ReactFiberTreeReflection.js +++ b/packages/react-reconciler/src/ReactFiberTreeReflection.js @@ -102,7 +102,7 @@ export function getActivityInstanceFromFiber( export function getContainerFromFiber(fiber: Fiber): null | Container { return fiber.tag === HostRoot - ? (fiber.stateNode.containerInfo as Container) + ? (fiber.stateNode.containerInfo: Container) : null; } diff --git a/packages/react-reconciler/src/ReactFiberViewTransitionComponent.js b/packages/react-reconciler/src/ReactFiberViewTransitionComponent.js index cde693cfa445..37768bcccbd3 100644 --- a/packages/react-reconciler/src/ReactFiberViewTransitionComponent.js +++ b/packages/react-reconciler/src/ReactFiberViewTransitionComponent.js @@ -37,7 +37,7 @@ export function getViewTransitionName( } // We assume we always call this in the commit phase. - const root = getCommittingRoot() as any as FiberRoot; + const root = ((getCommittingRoot(): any): FiberRoot); const identifierPrefix = root.identifierPrefix; const globalClientId = globalClientIdCounter++; const name = diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 2e2bbf6eebbd..d45a2d18cf93 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -560,7 +560,7 @@ export function addTransitionStartCallbackToPendingTransition( if (currentPendingTransitionCallbacks.transitionStart === null) { currentPendingTransitionCallbacks.transitionStart = - [] as Array<Transition>; + ([]: Array<Transition>); } currentPendingTransitionCallbacks.transitionStart.push(transition); @@ -574,14 +574,14 @@ export function addMarkerProgressCallbackToPendingTransition( ) { if (enableTransitionTracing) { if (currentPendingTransitionCallbacks === null) { - currentPendingTransitionCallbacks = { + currentPendingTransitionCallbacks = ({ transitionStart: null, transitionProgress: null, transitionComplete: null, markerProgress: new Map(), markerIncomplete: null, markerComplete: null, - } as PendingTransitionCallbacks; + }: PendingTransitionCallbacks); } if (currentPendingTransitionCallbacks.markerProgress === null) { @@ -694,7 +694,7 @@ export function addTransitionCompleteCallbackToPendingTransition( if (currentPendingTransitionCallbacks.transitionComplete === null) { currentPendingTransitionCallbacks.transitionComplete = - [] as Array<Transition>; + ([]: Array<Transition>); } currentPendingTransitionCallbacks.transitionComplete.push(transition); @@ -728,8 +728,8 @@ const PENDING_PASSIVE_PHASE = 5; const PENDING_GESTURE_MUTATION_PHASE = 6; const PENDING_GESTURE_ANIMATION_PHASE = 7; let pendingEffectsStatus: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 = 0; -let pendingEffectsRoot: FiberRoot = null as any; -let pendingFinishedWork: Fiber = null as any; +let pendingEffectsRoot: FiberRoot = (null: any); +let pendingFinishedWork: Fiber = (null: any); let pendingEffectsLanes: Lanes = NoLanes; let pendingEffectsRemainingLanes: Lanes = NoLanes; let pendingEffectsRenderEndTime: number = -0; // Profiling-only @@ -811,7 +811,7 @@ export function requestUpdateLane(fiber: Fiber): Lane { // Special cases const mode = fiber.mode; if (!disableLegacyMode && (mode & ConcurrentMode) === NoMode) { - return SyncLane as Lane; + return (SyncLane: Lane); } else if ( (executionContext & RenderContext) !== NoContext && workInProgressRootRenderLanes !== NoLanes @@ -861,7 +861,7 @@ function requestRetryLane(fiber: Fiber) { // Special cases const mode = fiber.mode; if (!disableLegacyMode && (mode & ConcurrentMode) === NoMode) { - return SyncLane as Lane; + return (SyncLane: Lane); } return claimNextRetryLane(); @@ -1205,7 +1205,7 @@ export function performWorkOnRoot( // TODO: It's possible that even a concurrent render may never have yielded // to the main thread, if it was fast enough, or if it expired. We could // skip the consistency check in that case, too. - const finishedWork: Fiber = root.current.alternate as any; + const finishedWork: Fiber = (root.current.alternate: any); if ( renderWasConcurrent && !isRenderConsistentWithExternalStores(finishedWork) @@ -1321,9 +1321,7 @@ function recoverFromConcurrentError( // Before rendering again, save the errors from the previous attempt. const errorsFromFirstAttempt = workInProgressRootConcurrentErrors; - // $FlowFixMe[constant-condition] const wasRootDehydrated = supportsHydration && isRootDehydrated(root); - // $FlowFixMe[constant-condition] if (wasRootDehydrated) { // The shell failed to hydrate. Set a flag to force a client rendering // during the next attempt. To do this, we call prepareFreshStack now @@ -1695,7 +1693,7 @@ function isRenderConsistentWithExternalStores(finishedWork: Fiber): boolean { node.flags & StoreConsistency ) { const updateQueue: FunctionComponentUpdateQueue | null = - node.updateQueue as any; + (node.updateQueue: any); if (updateQueue !== null) { const checks = updateQueue.stores; if (checks !== null) { @@ -2384,7 +2382,7 @@ function handleThrow(root: FiberRoot, thrownValue: any): void { case SuspendedOnImmediate: case SuspendedOnDeprecatedThrowPromise: case SuspendedAndReadyToContinue: { - const wakeable: Wakeable = thrownValue as any; + const wakeable: Wakeable = (thrownValue: any); markComponentSuspended( erroredWork, wakeable, @@ -2823,7 +2821,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes): RootExitStatus { } case SuspendedOnData: case SuspendedOnAction: { - const thenable: Thenable<mixed> = thrownValue as any; + const thenable: Thenable<mixed> = (thrownValue: any); if (isThenableResolved(thenable)) { // The data resolved. Try rendering the component again. workInProgressSuspendedReason = NotSuspended; @@ -2868,7 +2866,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes): RootExitStatus { break outer; } case SuspendedAndReadyToContinue: { - const thenable: Thenable<mixed> = thrownValue as any; + const thenable: Thenable<mixed> = (thrownValue: any); if (isThenableResolved(thenable)) { // The data resolved. Try rendering the component again. workInProgressSuspendedReason = NotSuspended; @@ -3044,7 +3042,7 @@ function workLoopConcurrent(nonIdle: boolean) { if (workInProgress !== null) { const yieldAfter = now() + (nonIdle ? 25 : 5); do { - // $FlowFixMe[incompatible-type] flow doesn't know that now() is side-effect free + // $FlowFixMe[incompatible-call] flow doesn't know that now() is side-effect free performUnitOfWork(workInProgress); } while (workInProgress !== null && now() < yieldAfter); } @@ -3054,7 +3052,7 @@ function workLoopConcurrent(nonIdle: boolean) { function workLoopConcurrentByScheduler() { // Perform work until Scheduler asks us to yield while (workInProgress !== null && !shouldYield()) { - // $FlowFixMe[incompatible-type] flow doesn't know that shouldYield() is side-effect free + // $FlowFixMe[incompatible-call] flow doesn't know that shouldYield() is side-effect free performUnitOfWork(workInProgress); } } @@ -3547,7 +3545,7 @@ function completeRoot( const hydrationFailed = finishedWork !== null && finishedWork.alternate !== null && - (finishedWork.alternate.memoizedState as RootState).isDehydrated && + (finishedWork.alternate.memoizedState: RootState).isDehydrated && (finishedWork.flags & ForceClientRender) !== NoFlags; logRecoveredRenderPhase( completedRenderStartTime, @@ -3659,8 +3657,8 @@ function completeRoot( finalizeRender(lanes, completedRenderEndTime); } // We are no longer committing. - pendingEffectsRoot = null as any; // Clear for GC purposes. - pendingFinishedWork = null as any; // Clear for GC purposes. + pendingEffectsRoot = (null: any); // Clear for GC purposes. + pendingFinishedWork = (null: any); // Clear for GC purposes. pendingEffectsLanes = NoLanes; } // Schedule the root to be committed when the gesture completes. @@ -3887,11 +3885,11 @@ function commitRoot( flushSpawnedWork, flushPassiveEffects, reportViewTransitionError, - enableProfilerTimer ? suspendedViewTransition : (null as any), + enableProfilerTimer ? suspendedViewTransition : (null: any), enableProfilerTimer ? // This callback fires after "pendingEffects" so we need to snapshot the arguments. finishedViewTransition.bind(null, lanes) - : (null as any), + : (null: any), ); } else { // Flush synchronously. @@ -4190,8 +4188,8 @@ function flushSpawnedWork(): void { pendingEffectsStatus = PENDING_PASSIVE_PHASE; } else { pendingEffectsStatus = NO_PENDING_EFFECTS; - pendingEffectsRoot = null as any; // Clear for GC purposes. - pendingFinishedWork = null as any; // Clear for GC purposes. + pendingEffectsRoot = (null: any); // Clear for GC purposes. + pendingFinishedWork = (null: any); // Clear for GC purposes. // There were no passive effects, so we can immediately release the cache // pool for this render. releaseRootPooledCache(root, root.pendingLanes); @@ -4373,7 +4371,6 @@ function flushSpawnedWork(): void { // Eagerly flush any event replaying that we unblocked within this commit. // This ensures that those are observed before we render any new changes. - // $FlowFixMe[constant-condition] if (supportsHydration) { flushHydrationEvents(); } @@ -4469,7 +4466,7 @@ function applyGestureOnRoot( enableProfilerTimer ? // This callback fires after "pendingEffects" so we need to snapshot the arguments. finishedViewTransition.bind(null, pendingEffectsLanes) - : (null as any), + : (null: any), ); } @@ -4542,8 +4539,8 @@ function flushGestureAnimations(): void { pendingEffectsStatus = NO_PENDING_EFFECTS; const root = pendingEffectsRoot; const finishedWork = pendingFinishedWork; - pendingEffectsRoot = null as any; // Clear for GC purposes. - pendingFinishedWork = null as any; // Clear for GC purposes. + pendingEffectsRoot = (null: any); // Clear for GC purposes. + pendingFinishedWork = (null: any); // Clear for GC purposes. pendingEffectsLanes = NoLanes; pendingViewTransition = null; // The view transition has now fully started. @@ -4605,7 +4602,7 @@ function makeErrorInfo(componentStack: ?string) { componentStack, }; if (__DEV__) { - Object.defineProperty(errorInfo as any, 'digest', { + Object.defineProperty((errorInfo: any), 'digest', { get() { console.error( 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' + @@ -4714,8 +4711,8 @@ function flushPassiveEffectsImpl() { const root = pendingEffectsRoot; const lanes = pendingEffectsLanes; pendingEffectsStatus = NO_PENDING_EFFECTS; - pendingEffectsRoot = null as any; // Clear for GC purposes. - pendingFinishedWork = null as any; // Clear for GC purposes. + pendingEffectsRoot = (null: any); // Clear for GC purposes. + pendingFinishedWork = (null: any); // Clear for GC purposes. // TODO: This is sometimes out of sync with pendingEffectsRoot. // Figure out why and fix it. It's not causing any known issues (probably // because it's only used for profiling), but it's a refactor hazard. @@ -4882,9 +4879,9 @@ function captureCommitPhaseErrorOnRoot( const update = createRootErrorUpdate( rootFiber.stateNode, errorInfo, - SyncLane as Lane, + (SyncLane: Lane), ); - const root = enqueueUpdate(rootFiber, update, SyncLane as Lane); + const root = enqueueUpdate(rootFiber, update, (SyncLane: Lane)); if (root !== null) { markRootUpdated(root, SyncLane); ensureRootIsScheduled(root); @@ -4923,8 +4920,8 @@ export function captureCommitPhaseError( if (enableProfilerTimer && enableComponentPerformanceTrack) { recordEffectError(errorInfo); } - const update = createClassErrorUpdate(SyncLane as Lane); - const root = enqueueUpdate(fiber, update, SyncLane as Lane); + const update = createClassErrorUpdate((SyncLane: Lane)); + const root = enqueueUpdate(fiber, update, (SyncLane: Lane)); if (root !== null) { initializeClassErrorUpdate(update, root, fiber, errorInfo); markRootUpdated(root, SyncLane); diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index 8089d8602ff5..42289ea30a1d 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -107,7 +107,7 @@ export let idleClampTime: number = -0; export let animatingLanes: Lanes = NoLanes; export let animatingTask: null | ConsoleTask = null; // First ViewTransition applying an Animation. -export let yieldReason: SuspendedReason = 0 as any; +export let yieldReason: SuspendedReason = (0: any); export let yieldStartTime: number = -1.1; // The time when we yielded to the event loop export function startYieldTimer(reason: SuspendedReason) { @@ -590,7 +590,7 @@ export function startProfilerTimer(fiber: Fiber): void { profilerStartTime = now(); - if ((fiber.actualStartTime as any as number) < 0) { + if (((fiber.actualStartTime: any): number) < 0) { fiber.actualStartTime = profilerStartTime; } } diff --git a/packages/react-reconciler/src/ReactStrictModeWarnings.js b/packages/react-reconciler/src/ReactStrictModeWarnings.js index 24151ac8b1f2..99a37206ed53 100644 --- a/packages/react-reconciler/src/ReactStrictModeWarnings.js +++ b/packages/react-reconciler/src/ReactStrictModeWarnings.js @@ -321,7 +321,7 @@ if (__DEV__) { }; ReactStrictModeWarnings.flushLegacyContextWarning = () => { - (pendingLegacyContextWarning as any as FiberToFiberComponentsMap).forEach( + ((pendingLegacyContextWarning: any): FiberToFiberComponentsMap).forEach( (fiberArray: FiberArray, strictRoot) => { if (fiberArray.length === 0) { return; diff --git a/packages/react-reconciler/src/ReactTestSelectors.js b/packages/react-reconciler/src/ReactTestSelectors.js index 26cea5443b56..ec30b2112b21 100644 --- a/packages/react-reconciler/src/ReactTestSelectors.js +++ b/packages/react-reconciler/src/ReactTestSelectors.js @@ -118,7 +118,7 @@ export function createTestNameSelector(id: string): TestNameSelector { } function findFiberRootForHostRoot(hostRoot: Instance): Fiber { - const maybeFiber = getInstanceFromNode(hostRoot as any); + const maybeFiber = getInstanceFromNode((hostRoot: any)); if (maybeFiber != null) { if (typeof maybeFiber.memoizedProps['data-testname'] !== 'string') { throw new Error( @@ -126,11 +126,10 @@ function findFiberRootForHostRoot(hostRoot: Instance): Fiber { ); } - return maybeFiber as any as Fiber; + return ((maybeFiber: any): Fiber); } else { const fiberRoot = findFiberRoot(hostRoot); - // $FlowFixMe[invalid-compare] if (fiberRoot === null) { throw new Error( 'Could not find React container within specified host subtree.', @@ -139,7 +138,7 @@ function findFiberRootForHostRoot(hostRoot: Instance): Fiber { // The Flow type for FiberRoot is a little funky. // createFiberRoot() cheats this by treating the root as :any and adding stateNode lazily. - return (fiberRoot as any).stateNode.current as Fiber; + return ((fiberRoot: any).stateNode.current: Fiber); } } @@ -154,7 +153,7 @@ function matchSelector(fiber: Fiber, selector: Selector): boolean { case HAS_PSEUDO_CLASS_TYPE: return hasMatchingPaths( fiber, - (selector as any as HasPseudoClassSelector).value, + ((selector: any): HasPseudoClassSelector).value, ); case ROLE_TYPE: if ( @@ -164,7 +163,7 @@ function matchSelector(fiber: Fiber, selector: Selector): boolean { ) { const node = fiber.stateNode; if ( - matchAccessibilityRole(node, (selector as any as RoleSelector).value) + matchAccessibilityRole(node, ((selector: any): RoleSelector).value) ) { return true; } @@ -178,11 +177,9 @@ function matchSelector(fiber: Fiber, selector: Selector): boolean { tag === HostSingleton ) { const textContent = getTextContent(fiber); - // $FlowFixMe[invalid-compare] if ( - // $FlowFixMe[invalid-compare] textContent !== null && - textContent.indexOf((selector as any as TextSelector).value) >= 0 + textContent.indexOf(((selector: any): TextSelector).value) >= 0 ) { return true; } @@ -198,7 +195,7 @@ function matchSelector(fiber: Fiber, selector: Selector): boolean { if ( typeof dataTestID === 'string' && dataTestID.toLowerCase() === - (selector as any as TestNameSelector).value.toLowerCase() + ((selector: any): TestNameSelector).value.toLowerCase() ) { return true; } @@ -219,11 +216,11 @@ function selectorToString(selector: Selector): string | null { case HAS_PSEUDO_CLASS_TYPE: return `:has(${selectorToString(selector) || ''})`; case ROLE_TYPE: - return `[role="${(selector as any as RoleSelector).value}"]`; + return `[role="${((selector: any): RoleSelector).value}"]`; case TEXT_TYPE: - return `"${(selector as any as TextSelector).value}"`; + return `"${((selector: any): TextSelector).value}"`; case TEST_NAME_TYPE: - return `[data-testname="${(selector as any as TestNameSelector).value}"]`; + return `[data-testname="${((selector: any): TestNameSelector).value}"]`; default: throw new Error('Invalid selector type specified.'); } @@ -235,9 +232,9 @@ function findPaths(root: Fiber, selectors: Array<Selector>): Array<Fiber> { const stack = [root, 0]; let index = 0; while (index < stack.length) { - const fiber = stack[index++] as any as Fiber; + const fiber = ((stack[index++]: any): Fiber); const tag = fiber.tag; - let selectorIndex = stack[index++] as any as number; + let selectorIndex = ((stack[index++]: any): number); let selector = selectors[selectorIndex]; if ( @@ -273,9 +270,9 @@ function hasMatchingPaths(root: Fiber, selectors: Array<Selector>): boolean { const stack = [root, 0]; let index = 0; while (index < stack.length) { - const fiber = stack[index++] as any as Fiber; + const fiber = ((stack[index++]: any): Fiber); const tag = fiber.tag; - let selectorIndex = stack[index++] as any as number; + let selectorIndex = ((stack[index++]: any): number); let selector = selectors[selectorIndex]; if ( @@ -310,7 +307,6 @@ export function findAllNodes( hostRoot: Instance, selectors: Array<Selector>, ): Array<Instance> { - // $FlowFixMe[constant-condition] if (!supportsTestSelectors) { throw new Error('Test selector API is not supported by this renderer.'); } @@ -323,7 +319,7 @@ export function findAllNodes( const stack = Array.from(matchingFibers); let index = 0; while (index < stack.length) { - const node = stack[index++] as any as Fiber; + const node = ((stack[index++]: any): Fiber); const tag = node.tag; if ( tag === HostComponent || @@ -350,7 +346,6 @@ export function getFindAllNodesFailureDescription( hostRoot: Instance, selectors: Array<Selector>, ): string | null { - // $FlowFixMe[constant-condition] if (!supportsTestSelectors) { throw new Error('Test selector API is not supported by this renderer.'); } @@ -364,9 +359,9 @@ export function getFindAllNodesFailureDescription( const stack = [root, 0]; let index = 0; while (index < stack.length) { - const fiber = stack[index++] as any as Fiber; + const fiber = ((stack[index++]: any): Fiber); const tag = fiber.tag; - let selectorIndex = stack[index++] as any as number; + let selectorIndex = ((stack[index++]: any): number); const selector = selectors[selectorIndex]; if ( @@ -422,7 +417,6 @@ export function findBoundingRects( hostRoot: Instance, selectors: Array<Selector>, ): Array<BoundingRect> { - // $FlowFixMe[constant-condition] if (!supportsTestSelectors) { throw new Error('Test selector API is not supported by this renderer.'); } @@ -513,7 +507,6 @@ export function focusWithin( hostRoot: Instance, selectors: Array<Selector>, ): boolean { - // $FlowFixMe[constant-condition] if (!supportsTestSelectors) { throw new Error('Test selector API is not supported by this renderer.'); } @@ -524,7 +517,7 @@ export function focusWithin( const stack = Array.from(matchingFibers); let index = 0; while (index < stack.length) { - const fiber = stack[index++] as any as Fiber; + const fiber = ((stack[index++]: any): Fiber); const tag = fiber.tag; if (isHiddenSubtree(fiber)) { continue; @@ -552,7 +545,6 @@ export function focusWithin( const commitHooks: Array<Function> = []; export function onCommitRoot(): void { - // $FlowFixMe[constant-condition] if (supportsTestSelectors) { commitHooks.forEach(commitHook => commitHook()); } @@ -570,7 +562,6 @@ export function observeVisibleRects( callback: (intersections: Array<{ratio: number, rect: BoundingRect}>) => void, options?: IntersectionObserverOptions, ): {disconnect: () => void} { - // $FlowFixMe[constant-condition] if (!supportsTestSelectors) { throw new Error('Test selector API is not supported by this renderer.'); } diff --git a/packages/react-reconciler/src/__tests__/Activity-test.js b/packages/react-reconciler/src/__tests__/Activity-test.js index f34eedf0d735..9abebad1dd09 100644 --- a/packages/react-reconciler/src/__tests__/Activity-test.js +++ b/packages/react-reconciler/src/__tests__/Activity-test.js @@ -1527,6 +1527,7 @@ describe('Activity', () => { expect(root).toMatchRenderedOutput(<span prop={2} />); }); + // @gate enableActivity it('getSnapshotBeforeUpdate does not run in hidden trees', async () => { let setState; @@ -1606,6 +1607,7 @@ describe('Activity', () => { ]); }); + // @gate enableActivity it('warns if you pass a hidden prop', async () => { function App() { return ( diff --git a/packages/react-reconciler/src/__tests__/useEffectEvent-test.js b/packages/react-reconciler/src/__tests__/useEffectEvent-test.js index 89fd240447af..1018b4975ad1 100644 --- a/packages/react-reconciler/src/__tests__/useEffectEvent-test.js +++ b/packages/react-reconciler/src/__tests__/useEffectEvent-test.js @@ -1266,6 +1266,7 @@ describe('useEffectEvent', () => { assertLog(['ContextReader (Effect event): second']); }); + // @gate enableActivity it('effect events are fresh inside Activity', async () => { function Child({value}) { const getValue = useEffectEvent(() => { diff --git a/packages/react-reconciler/src/getComponentNameFromFiber.js b/packages/react-reconciler/src/getComponentNameFromFiber.js index b42988238707..8719539cfc08 100644 --- a/packages/react-reconciler/src/getComponentNameFromFiber.js +++ b/packages/react-reconciler/src/getComponentNameFromFiber.js @@ -60,7 +60,7 @@ function getWrappedName( ): string { const functionName = innerType.displayName || innerType.name || ''; return ( - (outerType as any).displayName || + (outerType: any).displayName || (functionName !== '' ? `${wrapperName}(${functionName})` : wrapperName) ); } @@ -74,7 +74,7 @@ export function getComponentNameFromOwner( owner: Fiber | ReactComponentInfo, ): string | null { if (typeof owner.tag === 'number') { - return getComponentNameFromFiber(owner as any); + return getComponentNameFromFiber((owner: any)); } if (typeof owner.name === 'string') { return owner.name; @@ -90,10 +90,10 @@ export default function getComponentNameFromFiber(fiber: Fiber): string | null { case CacheComponent: return 'Cache'; case ContextConsumer: - const consumer: ReactConsumerType<any> = type as any; + const consumer: ReactConsumerType<any> = (type: any); return getContextName(consumer._context) + '.Consumer'; case ContextProvider: - const context: ReactContext<any> = type as any; + const context: ReactContext<any> = (type: any); return getContextName(context); case DehydratedFragment: return 'DehydratedFragment'; @@ -153,7 +153,7 @@ export default function getComponentNameFromFiber(fiber: Fiber): string | null { case MemoComponent: case SimpleMemoComponent: if (typeof type === 'function') { - return (type as any).displayName || type.name || null; + return (type: any).displayName || type.name || null; } if (typeof type === 'string') { return type; diff --git a/packages/react-refresh/package.json b/packages/react-refresh/package.json index 1b1d1c8fc379..105af71485a3 100644 --- a/packages/react-refresh/package.json +++ b/packages/react-refresh/package.json @@ -6,7 +6,7 @@ ], "version": "0.19.0", "homepage": "https://react.dev/", - "bugs": "https://github.com/react/react/issues", + "bugs": "https://github.com/facebook/react/issues", "license": "MIT", "files": [ "LICENSE", @@ -24,7 +24,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react" }, "engines": { diff --git a/packages/react-refresh/src/ReactFreshRuntime.js b/packages/react-refresh/src/ReactFreshRuntime.js index 9e00ea9b15ef..079db6594ad7 100644 --- a/packages/react-refresh/src/ReactFreshRuntime.js +++ b/packages/react-refresh/src/ReactFreshRuntime.js @@ -463,7 +463,7 @@ export function injectIntoGlobalHook(globalObject: any): void { typeof injected.setRefreshHandler === 'function' ) { // This version supports React Refresh. - helpersByRendererID.set(id, injected as any as RendererHelpers); + helpersByRendererID.set(id, ((injected: any): RendererHelpers)); } return id; }; @@ -477,7 +477,7 @@ export function injectIntoGlobalHook(globalObject: any): void { typeof injected.setRefreshHandler === 'function' ) { // This version supports React Refresh. - helpersByRendererID.set(id, injected as any as RendererHelpers); + helpersByRendererID.set(id, ((injected: any): RendererHelpers)); } }); diff --git a/packages/react-server-dom-esm/package.json b/packages/react-server-dom-esm/package.json index e1ead23996a4..79f73eb9844e 100644 --- a/packages/react-server-dom-esm/package.json +++ b/packages/react-server-dom-esm/package.json @@ -6,7 +6,7 @@ "react" ], "homepage": "https://react.dev/", - "bugs": "https://github.com/react/react/issues", + "bugs": "https://github.com/facebook/react/issues", "license": "MIT", "files": [ "LICENSE", @@ -47,7 +47,7 @@ "main": "index.js", "repository": { "type" : "git", - "url" : "https://github.com/react/react.git", + "url" : "https://github.com/facebook/react.git", "directory": "packages/react-server-dom-esm" }, "engines": { diff --git a/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js b/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js index 258993e59c62..450f1b25d022 100644 --- a/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js +++ b/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js @@ -726,7 +726,7 @@ async function transformModuleIfNeeded( if (sourceMappingURL) { const sourceMapResult = await loader( sourceMappingURL, - // $FlowFixMe[incompatible-type] + // $FlowFixMe { format: 'json', conditions: [], @@ -738,7 +738,7 @@ async function transformModuleIfNeeded( const sourceMapString = typeof sourceMapResult.source === 'string' ? sourceMapResult.source - : // $FlowFixMe[extra-arg] + : // $FlowFixMe sourceMapResult.source.toString('utf8'); sourceMap = JSON.parse(sourceMapString); diff --git a/packages/react-server-dom-esm/src/ReactFlightESMReferences.js b/packages/react-server-dom-esm/src/ReactFlightESMReferences.js index d1c9e5fc9c7e..8ad9f222faf7 100644 --- a/packages/react-server-dom-esm/src/ReactFlightESMReferences.js +++ b/packages/react-server-dom-esm/src/ReactFlightESMReferences.js @@ -49,7 +49,7 @@ const FunctionBind = Function.prototype.bind; // $FlowFixMe[method-unbinding] const ArraySlice = Array.prototype.slice; function bind(this: ServerReference<any>): any { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] const newFn = FunctionBind.apply(this, arguments); if (this.$$typeof === SERVER_REFERENCE_TAG) { if (__DEV__) { @@ -65,7 +65,7 @@ function bind(this: ServerReference<any>): any { const $$id = {value: this.$$id}; const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args}; return Object.defineProperties( - newFn as any, + (newFn: any), (__DEV__ ? { $$typeof, @@ -106,7 +106,7 @@ export function registerServerReference<T: Function>( }; const $$bound = {value: null, configurable: true}; return Object.defineProperties( - reference as any, + (reference: any), (__DEV__ ? { $$typeof, diff --git a/packages/react-server-dom-esm/src/client/ReactFlightClientConfigBundlerESM.js b/packages/react-server-dom-esm/src/client/ReactFlightClientConfigBundlerESM.js index 061c74e20cb1..ebd281298331 100644 --- a/packages/react-server-dom-esm/src/client/ReactFlightClientConfigBundlerESM.js +++ b/packages/react-server-dom-esm/src/client/ReactFlightClientConfigBundlerESM.js @@ -95,12 +95,12 @@ export function preloadModule<T>( modulePromise.then( value => { const fulfilledThenable: FulfilledThenable<mixed> = - modulePromise as any; + (modulePromise: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = value; }, reason => { - const rejectedThenable: RejectedThenable<mixed> = modulePromise as any; + const rejectedThenable: RejectedThenable<mixed> = (modulePromise: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = reason; }, @@ -126,7 +126,7 @@ export function requireModule<T>(metadata: ClientReference<T>): T { // We cache ReactIOInfo across requests so that inner refreshes can dedupe with outer. const moduleIOInfoCache: Map<string, ReactIOInfo> = __DEV__ ? new Map() - : (null as any); + : (null: any); export function getModuleDebugInfo<T>( metadata: ClientReference<T>, @@ -139,7 +139,7 @@ export function getModuleDebugInfo<T>( if (ioInfo === undefined) { let href; try { - // $FlowFixMe[incompatible-type] + // $FlowFixMe href = new URL(filename, document.baseURI).href; } catch (_) { href = filename; @@ -157,15 +157,15 @@ export function getModuleDebugInfo<T>( start = resourceEntry.startTime; end = start + resourceEntry.duration; // $FlowFixMe[prop-missing] - byteSize = (resourceEntry.transferSize as any) || 0; + byteSize = (resourceEntry.transferSize: any) || 0; } } } const value = Promise.resolve(href); - // $FlowFixMe[prop-missing] + // $FlowFixMe value.status = 'fulfilled'; // Is there some more useful representation for the chunk? - // $FlowFixMe[prop-missing] + // $FlowFixMe value.value = href; // Create a fake stack frame that points to the beginning of the chunk. This is // probably not source mapped so will link to the compiled source rather than @@ -193,13 +193,13 @@ export function getModuleDebugInfo<T>( href + ':1:1'; } - ioInfo = { + ioInfo = ({ name: 'script', start: start, end: end, value: value, debugStack: fakeStack, - } as ReactIOInfo; + }: ReactIOInfo); if (byteSize > 0) { // $FlowFixMe[cannot-write] ioInfo.byteSize = byteSize; diff --git a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js index 5029c092a79c..1c07d4369b85 100644 --- a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js @@ -174,7 +174,7 @@ function startReadingFromStream( if (done) { return onDone(); } - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); processBinaryChunk(response, streamState, buffer); return reader.read().then(progress).catch(error); } @@ -241,11 +241,11 @@ function createFromFetch<T>( options.debugChannel.readable, handleDone, ); - startReadingFromStream(response, r.body as any, handleDone, r); + startReadingFromStream(response, (r.body: any), handleDone, r); } else { startReadingFromStream( response, - r.body as any, + (r.body: any), close.bind(null, response), r, ); @@ -277,10 +277,10 @@ function encodeReply( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort((signal as any).reason); + abort((signal: any).reason); } else { const listener = () => { - abort((signal as any).reason); + abort((signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js b/packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js index acdbe46e56ef..0224bb382bfa 100644 --- a/packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js @@ -94,7 +94,7 @@ function startReadingFromDebugChannelReadable( } stringBuffer += chunk; } else { - const buffer: Uint8Array = chunk as any; + const buffer: Uint8Array = (chunk: any); stringBuffer += readPartialStringChunk(stringDecoder, buffer); lastWasPartial = true; } @@ -121,19 +121,19 @@ function startReadingFromDebugChannelReadable( // $FlowFixMe[method-unbinding] typeof stream.binaryType === 'string' ) { - const ws: WebSocket = stream as any; + const ws: WebSocket = (stream: any); ws.binaryType = 'arraybuffer'; ws.addEventListener('message', event => { - // $FlowFixMe[incompatible-type] + // $FlowFixMe onData(event.data); }); ws.addEventListener('error', event => { - // $FlowFixMe[prop-missing] + // $FlowFixMe onError(event.error); }); ws.addEventListener('close', onClose); } else { - const readable: Readable = stream as any; + const readable: Readable = (stream: any); readable.on('data', onData); readable.on('error', onError); readable.on('end', onClose); @@ -167,16 +167,16 @@ function renderToPipeableStream( // $FlowFixMe[method-unbinding] (typeof debugChannel.read === 'function' || typeof debugChannel.readyState === 'number') - ? (debugChannel as any) + ? (debugChannel: any) : undefined; const debugChannelWritable: void | Writable = __DEV__ && debugChannel !== undefined ? // $FlowFixMe[method-unbinding] typeof debugChannel.write === 'function' - ? (debugChannel as any) + ? (debugChannel: any) : // $FlowFixMe[method-unbinding] typeof debugChannel.send === 'function' - ? createFakeWritableFromWebSocket(debugChannel as any) + ? createFakeWritableFromWebSocket((debugChannel: any)) : undefined : undefined; const request = createRequest( @@ -231,9 +231,9 @@ function renderToPipeableStream( } function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { - return { + return ({ write(chunk: string | Uint8Array) { - webSocket.send(chunk as any); + webSocket.send((chunk: any)); return true; }, end() { @@ -249,13 +249,13 @@ function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { webSocket.close(1011); } }, - } as any; + }: any); } function createFakeWritable(readable: any): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk: string | Uint8Array) { return readable.push(chunk); }, @@ -265,7 +265,7 @@ function createFakeWritable(readable: any): Writable { destroy(error) { readable.destroy(error); }, - } as any; + }: any); } type PrerenderOptions = { @@ -315,11 +315,11 @@ function prerenderToNodeStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -468,7 +468,7 @@ function decodeReplyFromBusboy<T>( busboyStream.on('error', err => { reportGlobalError( response, - // $FlowFixMe[incompatible-type] types Error and mixed are incompatible + // $FlowFixMe[incompatible-call] types Error and mixed are incompatible err, ); }); diff --git a/packages/react-server-dom-fb/package.json b/packages/react-server-dom-fb/package.json index c84c458657ef..4041e0ab46ba 100644 --- a/packages/react-server-dom-fb/package.json +++ b/packages/react-server-dom-fb/package.json @@ -4,7 +4,7 @@ "private": true, "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-server-dom-fb" }, "dependencies": { diff --git a/packages/react-server-dom-fb/src/ReactDOMServerFB.js b/packages/react-server-dom-fb/src/ReactDOMServerFB.js index 1b770988d4be..b610a00566ca 100644 --- a/packages/react-server-dom-fb/src/ReactDOMServerFB.js +++ b/packages/react-server-dom-fb/src/ReactDOMServerFB.js @@ -44,7 +44,7 @@ opaque type Stream = { }; function renderToStream(children: ReactNodeList, options: Options): Stream { - const destination: Destination = { + const destination = { buffer: '', done: false, fatal: false, @@ -103,7 +103,7 @@ function hasFinished(stream: Stream): boolean { function debug(stream: Stream): any { // convert to any to silence flow errors from opaque type - const request = stream.request as any; + const request = (stream.request: any); return { pendingRootTasks: request.pendingRootTasks, clientRenderedBoundaries: request.clientRenderedBoundaries.length, diff --git a/packages/react-server-dom-parcel/package.json b/packages/react-server-dom-parcel/package.json index 334c21dfd35d..6732c962cb16 100644 --- a/packages/react-server-dom-parcel/package.json +++ b/packages/react-server-dom-parcel/package.json @@ -6,7 +6,7 @@ "react" ], "homepage": "https://reactjs.org/", - "bugs": "https://github.com/react/react/issues", + "bugs": "https://github.com/facebook/react/issues", "license": "MIT", "files": [ "LICENSE", @@ -72,7 +72,7 @@ "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-server-dom-parcel" }, "engines": { diff --git a/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js b/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js index 4a2fa3712878..3dd67e733cee 100644 --- a/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js +++ b/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js @@ -56,7 +56,7 @@ const FunctionBind = Function.prototype.bind; // $FlowFixMe[method-unbinding] const ArraySlice = Array.prototype.slice; function bind(this: ServerReference<any>): any { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] const newFn = FunctionBind.apply(this, arguments); if (this.$$typeof === SERVER_REFERENCE_TAG) { if (__DEV__) { @@ -72,7 +72,7 @@ function bind(this: ServerReference<any>): any { const $$id = {value: this.$$id}; const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args}; return Object.defineProperties( - newFn as any, + (newFn: any), (__DEV__ ? { $$typeof, @@ -113,7 +113,7 @@ export function registerServerReference<T>( }; const $$bound = {value: null, configurable: true}; return Object.defineProperties( - reference as any, + (reference: any), (__DEV__ ? { $$typeof, diff --git a/packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js b/packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js index 4010bd94d41a..67b0abc9cc9c 100644 --- a/packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js +++ b/packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js @@ -83,7 +83,7 @@ export function requireModule<T>(metadata: ClientReference<T>): T { if (hasOwnProperty.call(moduleExports, metadata[NAME])) { return moduleExports[metadata[NAME]]; } - return undefined as any; + return (undefined: any); } export function getModuleDebugInfo<T>( diff --git a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js index 9faafe2ea62b..a034a460f809 100644 --- a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js @@ -197,7 +197,7 @@ function startReadingFromStream( if (done) { return onDone(); } - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); processBinaryChunk(response, streamState, buffer); return reader.read().then(progress).catch(error); } @@ -275,11 +275,11 @@ export function createFromFetch<T>( options.debugChannel.readable, handleDone, ); - startReadingFromStream(response, r.body as any, handleDone, r); + startReadingFromStream(response, (r.body: any), handleDone, r); } else { startReadingFromStream( response, - r.body as any, + (r.body: any), close.bind(null, response), r, ); @@ -311,10 +311,10 @@ export function encodeReply( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort((signal as any).reason); + abort((signal: any).reason); } else { const listener = () => { - abort((signal as any).reason); + abort((signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js index ab0f5c02ded9..57afee2e914c 100644 --- a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js +++ b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js @@ -140,7 +140,7 @@ function startReadingFromStream( if (done) { return onDone(); } - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); processBinaryChunk(response, streamState, buffer); return reader.read().then(progress).catch(error); } @@ -206,11 +206,11 @@ export function createFromFetch<T>( options.debugChannel.readable, handleDone, ); - startReadingFromStream(response, r.body as any, handleDone, r); + startReadingFromStream(response, (r.body: any), handleDone, r); } else { startReadingFromStream( response, - r.body as any, + (r.body: any), close.bind(null, response), r, ); @@ -242,10 +242,10 @@ export function encodeReply( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort((signal as any).reason); + abort((signal: any).reason); } else { const listener = () => { - abort((signal as any).reason); + abort((signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerBrowser.js index 8476fcafe2af..aa677216d0a0 100644 --- a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerBrowser.js +++ b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerBrowser.js @@ -86,7 +86,7 @@ function startReadingFromDebugChannelReadableStream( value: ?any, ... }): void | Promise<void> { - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); stringBuffer += done ? readFinalStringChunk(stringDecoder, new Uint8Array(0)) : readPartialStringChunk(stringDecoder, buffer); @@ -138,10 +138,10 @@ export function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -156,7 +156,6 @@ export function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); debugStream.pipeTo(debugChannelWritable); @@ -179,7 +178,6 @@ export function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); return stream; @@ -208,7 +206,6 @@ export function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); resolve({prelude: stream}); @@ -229,11 +226,11 @@ export function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; diff --git a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js index 0d2689840ed7..18813139526f 100644 --- a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js +++ b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js @@ -91,7 +91,7 @@ function startReadingFromDebugChannelReadableStream( value: ?any, ... }): void | Promise<void> { - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); stringBuffer += done ? readFinalStringChunk(stringDecoder, new Uint8Array(0)) : readPartialStringChunk(stringDecoder, buffer); @@ -143,10 +143,10 @@ export function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -161,7 +161,6 @@ export function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); debugStream.pipeTo(debugChannelWritable); @@ -184,7 +183,6 @@ export function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); return stream; @@ -213,7 +211,6 @@ export function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); resolve({prelude: stream}); @@ -234,11 +231,11 @@ export function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -311,9 +308,9 @@ export function decodeReplyFromAsyncIterable<T>( } function error(reason: Error) { reportGlobalError(response, reason); - if (typeof (iterator as any).throw === 'function') { + if (typeof (iterator: any).throw === 'function') { // The iterator protocol doesn't necessarily include this but a generator do. - // $FlowFixMe[prop-missing] should be able to pass mixed + // $FlowFixMe should be able to pass mixed iterator.throw(reason).then(error, error); } } diff --git a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js index 3994a6ecbf65..d39081aecfef 100644 --- a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js @@ -107,7 +107,7 @@ function startReadingFromDebugChannelReadable( } stringBuffer += chunk; } else { - const buffer: Uint8Array = chunk as any; + const buffer: Uint8Array = (chunk: any); stringBuffer += readPartialStringChunk(stringDecoder, buffer); lastWasPartial = true; } @@ -134,19 +134,19 @@ function startReadingFromDebugChannelReadable( // $FlowFixMe[method-unbinding] typeof stream.binaryType === 'string' ) { - const ws: WebSocket = stream as any; + const ws: WebSocket = (stream: any); ws.binaryType = 'arraybuffer'; ws.addEventListener('message', event => { - // $FlowFixMe[incompatible-type] + // $FlowFixMe onData(event.data); }); ws.addEventListener('error', event => { - // $FlowFixMe[prop-missing] + // $FlowFixMe onError(event.error); }); ws.addEventListener('close', onClose); } else { - const readable: Readable = stream as any; + const readable: Readable = (stream: any); readable.on('data', onData); readable.on('error', onError); readable.on('end', onClose); @@ -179,16 +179,16 @@ export function renderToPipeableStream( // $FlowFixMe[method-unbinding] (typeof debugChannel.read === 'function' || typeof debugChannel.readyState === 'number') - ? (debugChannel as any) + ? (debugChannel: any) : undefined; const debugChannelWritable: void | Writable = __DEV__ && debugChannel !== undefined ? // $FlowFixMe[method-unbinding] typeof debugChannel.write === 'function' - ? (debugChannel as any) + ? (debugChannel: any) : // $FlowFixMe[method-unbinding] typeof debugChannel.send === 'function' - ? createFakeWritableFromWebSocket(debugChannel as any) + ? createFakeWritableFromWebSocket((debugChannel: any)) : undefined : undefined; const request = createRequest( @@ -243,9 +243,9 @@ export function renderToPipeableStream( } function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { - return { + return ({ write(chunk: string | Uint8Array) { - webSocket.send(chunk as any); + webSocket.send((chunk: any)); return true; }, end() { @@ -261,7 +261,7 @@ function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { webSocket.close(1011); } }, - } as any; + }: any); } function createFakeWritableFromReadableStreamController( @@ -269,7 +269,7 @@ function createFakeWritableFromReadableStreamController( ): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk: string | Uint8Array) { if (typeof chunk === 'string') { chunk = textEncoder.encode(chunk); @@ -290,7 +290,7 @@ function createFakeWritableFromReadableStreamController( controller.close(); } }, - } as any; + }: any); } function startReadingFromDebugChannelReadableStream( @@ -308,7 +308,7 @@ function startReadingFromDebugChannelReadableStream( value: ?any, ... }): void | Promise<void> { - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); stringBuffer += done ? readFinalStringChunk(stringDecoder, new Uint8Array(0)) : readPartialStringChunk(stringDecoder, buffer); @@ -363,10 +363,10 @@ export function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -386,7 +386,6 @@ export function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); debugStream.pipeTo(debugChannelWritable); @@ -411,7 +410,6 @@ export function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); return stream; @@ -420,7 +418,7 @@ export function renderToReadableStream( function createFakeWritableFromNodeReadable(readable: any): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk: string | Uint8Array) { return readable.push(chunk); }, @@ -430,7 +428,7 @@ function createFakeWritableFromNodeReadable(readable: any): Writable { destroy(error) { readable.destroy(error); }, - } as any; + }: any); } type PrerenderOptions = { @@ -479,11 +477,11 @@ export function prerenderToNodeStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -522,7 +520,6 @@ export function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); resolve({prelude: stream}); @@ -543,11 +540,11 @@ export function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -701,7 +698,7 @@ export function decodeReplyFromBusboy<T>( busboyStream.on('error', err => { reportGlobalError( response, - // $FlowFixMe[incompatible-type] types Error and mixed are incompatible + // $FlowFixMe[incompatible-call] types Error and mixed are incompatible err, ); }); @@ -769,9 +766,9 @@ export function decodeReplyFromAsyncIterable<T>( } function error(reason: Error) { reportGlobalError(response, reason); - if (typeof (iterator as any).throw === 'function') { + if (typeof (iterator: any).throw === 'function') { // The iterator protocol doesn't necessarily include this but a generator do. - // $FlowFixMe[prop-missing] should be able to pass mixed + // $FlowFixMe should be able to pass mixed iterator.throw(reason).then(error, error); } } diff --git a/packages/react-server-dom-turbopack/package.json b/packages/react-server-dom-turbopack/package.json index f2faa193a8be..e3adedd68db1 100644 --- a/packages/react-server-dom-turbopack/package.json +++ b/packages/react-server-dom-turbopack/package.json @@ -6,7 +6,7 @@ "react" ], "homepage": "https://react.dev/", - "bugs": "https://github.com/react/react/issues", + "bugs": "https://github.com/facebook/react/issues", "license": "MIT", "files": [ "LICENSE", @@ -72,7 +72,7 @@ "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-server-dom-turbopack" }, "engines": { diff --git a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js index 189c76617ff1..50f83d31d8b1 100644 --- a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js +++ b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js @@ -63,7 +63,7 @@ const FunctionBind = Function.prototype.bind; // $FlowFixMe[method-unbinding] const ArraySlice = Array.prototype.slice; function bind(this: ServerReference<any>): any { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] const newFn = FunctionBind.apply(this, arguments); if (this.$$typeof === SERVER_REFERENCE_TAG) { if (__DEV__) { @@ -79,7 +79,7 @@ function bind(this: ServerReference<any>): any { const $$id = {value: this.$$id}; const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args}; return Object.defineProperties( - newFn as any, + (newFn: any), (__DEV__ ? { $$typeof, @@ -120,7 +120,7 @@ export function registerServerReference<T: Function>( }; const $$bound = {value: null, configurable: true}; return Object.defineProperties( - reference as any, + (reference: any), (__DEV__ ? { $$typeof, @@ -237,14 +237,14 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { // an ESM compat module but then we'll check again on the client. const moduleId = target.$$id; target.default = registerClientReferenceImpl( - function () { + (function () { throw new Error( `Attempted to call the default export of ${moduleId} from the server ` + `but it's on the client. It's not possible to invoke a client function from ` + `the server, it can only be rendered as a Component or passed to props of a ` + `Client Component.`, ); - } as any, + }: any), target.$$id + '#', target.$$async, ); @@ -260,9 +260,7 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { // the client. const clientReference: ClientReference<any> = - registerClientReferenceImpl({} as any, target.$$id, true); - // $FlowFixMe[incompatible-variance] - // $FlowFixMe[incompatible-type] + registerClientReferenceImpl(({}: any), target.$$id, true); const proxy = new Proxy(clientReference, proxyHandlers); // Treat this as a resolved Promise for React's use() @@ -270,10 +268,10 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { target.value = proxy; const then = (target.then = registerClientReferenceImpl( - function then(resolve, reject: any) { + (function then(resolve, reject: any) { // Expose to React. return Promise.resolve(resolve(proxy)); - } as any, + }: any), // If this is not used as a Promise but is treated as a reference to a `.then` // export then we should treat it as a reference to that name. target.$$id + '#then', @@ -296,18 +294,20 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { let cachedReference = target[name]; if (!cachedReference) { const reference: ClientReference<any> = registerClientReferenceImpl( - function () { + (function () { throw new Error( // eslint-disable-next-line react-internal/safe-string-coercion - `Attempted to call ${String(name)}() from the server but ${String(name)} is on the client. ` + + `Attempted to call ${String(name)}() from the server but ${String( + name, + )} is on the client. ` + `It's not possible to invoke a client function from the server, it can ` + `only be rendered as a Component or passed to props of a Client Component.`, ); - } as any, + }: any), target.$$id + '#' + name, target.$$async, ); - Object.defineProperty(reference as any, 'name', {value: name}); + Object.defineProperty((reference: any), 'name', {value: name}); cachedReference = target[name] = new Proxy(reference, deepProxyHandlers); } return cachedReference; @@ -350,13 +350,10 @@ export function createClientModuleProxy<T>( moduleId: string, ): ClientReference<T> { const clientReference: ClientReference<T> = registerClientReferenceImpl( - {} as any, + ({}: any), // Represents the whole Module object instead of a particular import. moduleId, false, ); - // $FlowFixMe[incompatible-variance] - // $FlowFixMe[incompatible-type] - // $FlowFixMe[incompatible-exact] return new Proxy(clientReference, proxyHandlers); } diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js b/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js index 7c658ca7a5e7..2c26859280ad 100644 --- a/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js +++ b/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js @@ -163,12 +163,12 @@ function requireAsyncModule(id: string): null | Thenable<any> { // Instrument the Promise to stash the result. promise.then( value => { - const fulfilledThenable: FulfilledThenable<mixed> = promise as any; + const fulfilledThenable: FulfilledThenable<mixed> = (promise: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = value; }, reason => { - const rejectedThenable: RejectedThenable<mixed> = promise as any; + const rejectedThenable: RejectedThenable<mixed> = (promise: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = reason; }, @@ -250,7 +250,7 @@ export function requireModule<T>(metadata: ClientReference<T>): T { if (hasOwnProperty.call(moduleExports, metadata[NAME])) { return moduleExports[metadata[NAME]]; } - return undefined as any; + return (undefined: any); } export function getModuleDebugInfo<T>( diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackBrowser.js b/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackBrowser.js index a2135651df2c..b00fe3cb06f0 100644 --- a/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackBrowser.js +++ b/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackBrowser.js @@ -20,7 +20,7 @@ export function loadChunk(filename: string): Promise<mixed> { // We cache ReactIOInfo across requests so that inner refreshes can dedupe with outer. const chunkIOInfoCache: Map<string, ReactIOInfo> = __DEV__ ? new Map() - : (null as any); + : (null: any); export function addChunkDebugInfo( target: ReactDebugInfo, @@ -33,7 +33,7 @@ export function addChunkDebugInfo( if (ioInfo === undefined) { let href; try { - // $FlowFixMe[incompatible-type] + // $FlowFixMe href = new URL(filename, document.baseURI).href; } catch (_) { href = filename; @@ -51,15 +51,15 @@ export function addChunkDebugInfo( start = resourceEntry.startTime; end = start + resourceEntry.duration; // $FlowFixMe[prop-missing] - byteSize = (resourceEntry.transferSize as any) || 0; + byteSize = (resourceEntry.transferSize: any) || 0; } } } const value = Promise.resolve(href); - // $FlowFixMe[prop-missing] + // $FlowFixMe value.status = 'fulfilled'; // Is there some more useful representation for the chunk? - // $FlowFixMe[prop-missing] + // $FlowFixMe value.value = href; // Create a fake stack frame that points to the beginning of the chunk. This is // probably not source mapped so will link to the compiled source rather than @@ -87,13 +87,13 @@ export function addChunkDebugInfo( href + ':1:1'; } - ioInfo = { + ioInfo = ({ name: 'script', start: start, end: end, value: value, debugStack: fakeStack, - } as ReactIOInfo; + }: ReactIOInfo); if (byteSize > 0) { // $FlowFixMe[cannot-write] ioInfo.byteSize = byteSize; diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js index c42728a9f9ef..c38d5fd05133 100644 --- a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js @@ -173,7 +173,7 @@ function startReadingFromStream( if (done) { return onDone(); } - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); processBinaryChunk(response, streamState, buffer); return reader.read().then(progress).catch(error); } @@ -241,11 +241,11 @@ function createFromFetch<T>( options.debugChannel.readable, handleDone, ); - startReadingFromStream(response, r.body as any, handleDone, r); + startReadingFromStream(response, (r.body: any), handleDone, r); } else { startReadingFromStream( response, - r.body as any, + (r.body: any), close.bind(null, response), r, ); @@ -277,10 +277,10 @@ function encodeReply( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort((signal as any).reason); + abort((signal: any).reason); } else { const listener = () => { - abort((signal as any).reason); + abort((signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js index 314d6e125b6c..6b781f897fc1 100644 --- a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js +++ b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js @@ -142,7 +142,7 @@ function startReadingFromStream( if (done) { return onDone(); } - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); processBinaryChunk(response, streamState, buffer); return reader.read().then(progress).catch(error); } @@ -208,11 +208,11 @@ function createFromFetch<T>( options.debugChannel.readable, handleDone, ); - startReadingFromStream(response, r.body as any, handleDone, r); + startReadingFromStream(response, (r.body: any), handleDone, r); } else { startReadingFromStream( response, - r.body as any, + (r.body: any), close.bind(null, response), r, ); @@ -244,10 +244,10 @@ function encodeReply( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort((signal as any).reason); + abort((signal: any).reason); } else { const listener = () => { - abort((signal as any).reason); + abort((signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerBrowser.js index dca1b364fb46..0100789347b0 100644 --- a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerBrowser.js +++ b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerBrowser.js @@ -82,7 +82,7 @@ function startReadingFromDebugChannelReadableStream( value: ?any, ... }): void | Promise<void> { - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); stringBuffer += done ? readFinalStringChunk(stringDecoder, new Uint8Array(0)) : readPartialStringChunk(stringDecoder, buffer); @@ -135,10 +135,10 @@ function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -153,7 +153,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); debugStream.pipeTo(debugChannelWritable); @@ -176,7 +175,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); return stream; @@ -206,7 +204,6 @@ function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); resolve({prelude: stream}); @@ -227,11 +224,11 @@ function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; diff --git a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js index 1b2959be27e7..18e71fdeab6c 100644 --- a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js +++ b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js @@ -87,7 +87,7 @@ function startReadingFromDebugChannelReadableStream( value: ?any, ... }): void | Promise<void> { - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); stringBuffer += done ? readFinalStringChunk(stringDecoder, new Uint8Array(0)) : readPartialStringChunk(stringDecoder, buffer); @@ -140,10 +140,10 @@ function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -158,7 +158,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); debugStream.pipeTo(debugChannelWritable); @@ -181,7 +180,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); return stream; @@ -211,7 +209,6 @@ function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); resolve({prelude: stream}); @@ -232,11 +229,11 @@ function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -310,9 +307,9 @@ function decodeReplyFromAsyncIterable<T>( } function error(reason: Error) { reportGlobalError(response, reason); - if (typeof (iterator as any).throw === 'function') { + if (typeof (iterator: any).throw === 'function') { // The iterator protocol doesn't necessarily include this but a generator do. - // $FlowFixMe[prop-missing] should be able to pass mixed + // $FlowFixMe should be able to pass mixed iterator.throw(reason).then(error, error); } } diff --git a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js index dfb178c742b2..6c8d759a99ac 100644 --- a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js @@ -100,7 +100,7 @@ function startReadingFromDebugChannelReadable( } stringBuffer += chunk; } else { - const buffer: Uint8Array = chunk as any; + const buffer: Uint8Array = (chunk: any); stringBuffer += readPartialStringChunk(stringDecoder, buffer); lastWasPartial = true; } @@ -127,19 +127,19 @@ function startReadingFromDebugChannelReadable( // $FlowFixMe[method-unbinding] typeof stream.binaryType === 'string' ) { - const ws: WebSocket = stream as any; + const ws: WebSocket = (stream: any); ws.binaryType = 'arraybuffer'; ws.addEventListener('message', event => { - // $FlowFixMe[incompatible-type] + // $FlowFixMe onData(event.data); }); ws.addEventListener('error', event => { - // $FlowFixMe[prop-missing] + // $FlowFixMe onError(event.error); }); ws.addEventListener('close', onClose); } else { - const readable: Readable = stream as any; + const readable: Readable = (stream: any); readable.on('data', onData); readable.on('error', onError); readable.on('end', onClose); @@ -173,16 +173,16 @@ function renderToPipeableStream( // $FlowFixMe[method-unbinding] (typeof debugChannel.read === 'function' || typeof debugChannel.readyState === 'number') - ? (debugChannel as any) + ? (debugChannel: any) : undefined; const debugChannelWritable: void | Writable = __DEV__ && debugChannel !== undefined ? // $FlowFixMe[method-unbinding] typeof debugChannel.write === 'function' - ? (debugChannel as any) + ? (debugChannel: any) : // $FlowFixMe[method-unbinding] typeof debugChannel.send === 'function' - ? createFakeWritableFromWebSocket(debugChannel as any) + ? createFakeWritableFromWebSocket((debugChannel: any)) : undefined : undefined; const request = createRequest( @@ -237,9 +237,9 @@ function renderToPipeableStream( } function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { - return { + return ({ write(chunk: string | Uint8Array) { - webSocket.send(chunk as any); + webSocket.send((chunk: any)); return true; }, end() { @@ -255,7 +255,7 @@ function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { webSocket.close(1011); } }, - } as any; + }: any); } function createFakeWritableFromReadableStreamController( @@ -263,7 +263,7 @@ function createFakeWritableFromReadableStreamController( ): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk: string | Uint8Array) { if (typeof chunk === 'string') { chunk = textEncoder.encode(chunk); @@ -284,7 +284,7 @@ function createFakeWritableFromReadableStreamController( controller.close(); } }, - } as any; + }: any); } function startReadingFromDebugChannelReadableStream( @@ -302,7 +302,7 @@ function startReadingFromDebugChannelReadableStream( value: ?any, ... }): void | Promise<void> { - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); stringBuffer += done ? readFinalStringChunk(stringDecoder, new Uint8Array(0)) : readPartialStringChunk(stringDecoder, buffer); @@ -358,10 +358,10 @@ function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -381,7 +381,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); debugStream.pipeTo(debugChannelWritable); @@ -406,7 +405,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); return stream; @@ -415,7 +413,7 @@ function renderToReadableStream( function createFakeWritableFromNodeReadable(readable: any): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk: string | Uint8Array) { return readable.push(chunk); }, @@ -425,7 +423,7 @@ function createFakeWritableFromNodeReadable(readable: any): Writable { destroy(error) { readable.destroy(error); }, - } as any; + }: any); } type PrerenderOptions = { @@ -475,11 +473,11 @@ function prerenderToNodeStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -519,7 +517,6 @@ function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); resolve({prelude: stream}); @@ -540,11 +537,11 @@ function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -693,7 +690,7 @@ function decodeReplyFromBusboy<T>( busboyStream.on('error', err => { reportGlobalError( response, - // $FlowFixMe[incompatible-type] types Error and mixed are incompatible + // $FlowFixMe[incompatible-call] types Error and mixed are incompatible err, ); }); @@ -763,9 +760,9 @@ function decodeReplyFromAsyncIterable<T>( } function error(reason: Error) { reportGlobalError(response, reason); - if (typeof (iterator as any).throw === 'function') { + if (typeof (iterator: any).throw === 'function') { // The iterator protocol doesn't necessarily include this but a generator do. - // $FlowFixMe[prop-missing] should be able to pass mixed + // $FlowFixMe should be able to pass mixed iterator.throw(reason).then(error, error); } } diff --git a/packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeLoader.js b/packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeLoader.js index aa0448b2735c..9799acc3a07b 100644 --- a/packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeLoader.js +++ b/packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeLoader.js @@ -726,7 +726,7 @@ async function transformModuleIfNeeded( if (sourceMappingURL) { const sourceMapResult = await loader( sourceMappingURL, - // $FlowFixMe[incompatible-type] + // $FlowFixMe { format: 'json', conditions: [], @@ -738,7 +738,7 @@ async function transformModuleIfNeeded( const sourceMapString = typeof sourceMapResult.source === 'string' ? sourceMapResult.source - : // $FlowFixMe[extra-arg] + : // $FlowFixMe sourceMapResult.source.toString('utf8'); sourceMap = JSON.parse(sourceMapString); diff --git a/packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeRegister.js b/packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeRegister.js index 843a63d9781d..5b4cd9de2528 100644 --- a/packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeRegister.js +++ b/packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeRegister.js @@ -73,14 +73,14 @@ module.exports = function register() { } if (useClient) { - const moduleId: string = url.pathToFileURL(filename).href as any; + const moduleId: string = (url.pathToFileURL(filename).href: any); this.exports = createClientModuleProxy(moduleId); } if (useServer) { originalCompile.apply(this, arguments); - const moduleId: string = url.pathToFileURL(filename).href as any; + const moduleId: string = (url.pathToFileURL(filename).href: any); const exports = this.exports; @@ -89,7 +89,7 @@ module.exports = function register() { if (typeof exports === 'function') { // The module exports a function directly, registerServerReference( - exports as any, + (exports: any), moduleId, // Represents the whole Module object instead of a particular import. null, @@ -100,7 +100,7 @@ module.exports = function register() { const key = keys[i]; const value = exports[keys[i]]; if (typeof value === 'function') { - registerServerReference(value as any, moduleId, key); + registerServerReference((value: any), moduleId, key); } } } diff --git a/packages/react-server-dom-unbundled/src/ReactFlightUnbundledReferences.js b/packages/react-server-dom-unbundled/src/ReactFlightUnbundledReferences.js index 066eefa57b09..b9f90b4d14b8 100644 --- a/packages/react-server-dom-unbundled/src/ReactFlightUnbundledReferences.js +++ b/packages/react-server-dom-unbundled/src/ReactFlightUnbundledReferences.js @@ -63,7 +63,7 @@ const FunctionBind = Function.prototype.bind; // $FlowFixMe[method-unbinding] const ArraySlice = Array.prototype.slice; function bind(this: ServerReference<any>): any { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] const newFn = FunctionBind.apply(this, arguments); if (this.$$typeof === SERVER_REFERENCE_TAG) { if (__DEV__) { @@ -79,7 +79,7 @@ function bind(this: ServerReference<any>): any { const $$id = {value: this.$$id}; const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args}; return Object.defineProperties( - newFn as any, + (newFn: any), (__DEV__ ? { $$typeof, @@ -120,7 +120,7 @@ export function registerServerReference<T: Function>( }; const $$bound = {value: null, configurable: true}; return Object.defineProperties( - reference as any, + (reference: any), __DEV__ ? ({ $$typeof, @@ -237,14 +237,14 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { // an ESM compat module but then we'll check again on the client. const moduleId = target.$$id; target.default = registerClientReferenceImpl( - function () { + (function () { throw new Error( `Attempted to call the default export of ${moduleId} from the server ` + `but it's on the client. It's not possible to invoke a client function from ` + `the server, it can only be rendered as a Component or passed to props of a ` + `Client Component.`, ); - } as any, + }: any), target.$$id + '#', target.$$async, ); @@ -260,9 +260,7 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { // the client. const clientReference: ClientReference<any> = - registerClientReferenceImpl({} as any, target.$$id, true); - // $FlowFixMe[incompatible-variance] - // $FlowFixMe[incompatible-type] + registerClientReferenceImpl(({}: any), target.$$id, true); const proxy = new Proxy(clientReference, proxyHandlers); // Treat this as a resolved Promise for React's use() @@ -270,10 +268,10 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { target.value = proxy; const then = (target.then = registerClientReferenceImpl( - function then(resolve, reject: any) { + (function then(resolve, reject: any) { // Expose to React. return Promise.resolve(resolve(proxy)); - } as any, + }: any), // If this is not used as a Promise but is treated as a reference to a `.then` // export then we should treat it as a reference to that name. target.$$id + '#then', @@ -296,7 +294,7 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { let cachedReference = target[name]; if (!cachedReference) { const reference: ClientReference<any> = registerClientReferenceImpl( - function () { + (function () { throw new Error( // eslint-disable-next-line react-internal/safe-string-coercion `Attempted to call ${String(name)}() from the server but ${String( @@ -305,11 +303,11 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { `It's not possible to invoke a client function from the server, it can ` + `only be rendered as a Component or passed to props of a Client Component.`, ); - } as any, + }: any), target.$$id + '#' + name, target.$$async, ); - Object.defineProperty(reference as any, 'name', {value: name}); + Object.defineProperty((reference: any), 'name', {value: name}); cachedReference = target[name] = new Proxy(reference, deepProxyHandlers); } return cachedReference; @@ -352,13 +350,10 @@ export function createClientModuleProxy<T>( moduleId: string, ): ClientReference<T> { const clientReference: ClientReference<T> = registerClientReferenceImpl( - {} as any, + ({}: any), // Represents the whole Module object instead of a particular import. moduleId, false, ); - // $FlowFixMe[incompatible-variance] - // $FlowFixMe[incompatible-type] - // $FlowFixMe[incompatible-exact] return new Proxy(clientReference, proxyHandlers); } diff --git a/packages/react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode.js b/packages/react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode.js index fbe1ea9b2bba..63049b2ca408 100644 --- a/packages/react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode.js +++ b/packages/react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode.js @@ -119,18 +119,18 @@ export function preloadModule<T>( // Node.js so we have to get the default export to get the // full module exports. modulePromise = modulePromise.then(function (value) { - return (value as any).default; + return (value: any).default; }); } modulePromise.then( value => { const fulfilledThenable: FulfilledThenable<mixed> = - modulePromise as any; + (modulePromise: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = value; }, reason => { - const rejectedThenable: RejectedThenable<mixed> = modulePromise as any; + const rejectedThenable: RejectedThenable<mixed> = (modulePromise: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = reason; }, @@ -163,7 +163,7 @@ export function requireModule<T>(metadata: ClientReference<T>): T { if (hasOwnProperty.call(moduleExports, metadata.name)) { return moduleExports[metadata.name]; } - return undefined as any; + return (undefined: any); } export function getModuleDebugInfo<T>(metadata: ClientReference<T>): null { diff --git a/packages/react-server-dom-unbundled/src/client/ReactFlightDOMClientEdge.js b/packages/react-server-dom-unbundled/src/client/ReactFlightDOMClientEdge.js index 2ccbd6a1d67a..8bcdcdbfe0d2 100644 --- a/packages/react-server-dom-unbundled/src/client/ReactFlightDOMClientEdge.js +++ b/packages/react-server-dom-unbundled/src/client/ReactFlightDOMClientEdge.js @@ -142,7 +142,7 @@ function startReadingFromStream( if (done) { return onDone(); } - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); processBinaryChunk(response, streamState, buffer); return reader.read().then(progress).catch(error); } @@ -208,11 +208,11 @@ function createFromFetch<T>( options.debugChannel.readable, handleDone, ); - startReadingFromStream(response, r.body as any, handleDone, r); + startReadingFromStream(response, (r.body: any), handleDone, r); } else { startReadingFromStream( response, - r.body as any, + (r.body: any), close.bind(null, response), r, ); @@ -244,10 +244,10 @@ function encodeReply( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort((signal as any).reason); + abort((signal: any).reason); } else { const listener = () => { - abort((signal as any).reason); + abort((signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-server-dom-unbundled/src/server/ReactFlightDOMServerNode.js b/packages/react-server-dom-unbundled/src/server/ReactFlightDOMServerNode.js index 46222148e03a..eac2f9c48317 100644 --- a/packages/react-server-dom-unbundled/src/server/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-unbundled/src/server/ReactFlightDOMServerNode.js @@ -100,7 +100,7 @@ function startReadingFromDebugChannelReadable( } stringBuffer += chunk; } else { - const buffer: Uint8Array = chunk as any; + const buffer: Uint8Array = (chunk: any); stringBuffer += readPartialStringChunk(stringDecoder, buffer); lastWasPartial = true; } @@ -127,19 +127,19 @@ function startReadingFromDebugChannelReadable( // $FlowFixMe[method-unbinding] typeof stream.binaryType === 'string' ) { - const ws: WebSocket = stream as any; + const ws: WebSocket = (stream: any); ws.binaryType = 'arraybuffer'; ws.addEventListener('message', event => { - // $FlowFixMe[incompatible-type] + // $FlowFixMe onData(event.data); }); ws.addEventListener('error', event => { - // $FlowFixMe[prop-missing] + // $FlowFixMe onError(event.error); }); ws.addEventListener('close', onClose); } else { - const readable: Readable = stream as any; + const readable: Readable = (stream: any); readable.on('data', onData); readable.on('error', onError); readable.on('end', onClose); @@ -173,16 +173,16 @@ function renderToPipeableStream( // $FlowFixMe[method-unbinding] (typeof debugChannel.read === 'function' || typeof debugChannel.readyState === 'number') - ? (debugChannel as any) + ? (debugChannel: any) : undefined; const debugChannelWritable: void | Writable = __DEV__ && debugChannel !== undefined ? // $FlowFixMe[method-unbinding] typeof debugChannel.write === 'function' - ? (debugChannel as any) + ? (debugChannel: any) : // $FlowFixMe[method-unbinding] typeof debugChannel.send === 'function' - ? createFakeWritableFromWebSocket(debugChannel as any) + ? createFakeWritableFromWebSocket((debugChannel: any)) : undefined : undefined; const request = createRequest( @@ -237,9 +237,9 @@ function renderToPipeableStream( } function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { - return { + return ({ write(chunk: string | Uint8Array) { - webSocket.send(chunk as any); + webSocket.send((chunk: any)); return true; }, end() { @@ -255,7 +255,7 @@ function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { webSocket.close(1011); } }, - } as any; + }: any); } function createFakeWritableFromReadableStreamController( @@ -263,7 +263,7 @@ function createFakeWritableFromReadableStreamController( ): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk: string | Uint8Array) { if (typeof chunk === 'string') { chunk = textEncoder.encode(chunk); @@ -284,7 +284,7 @@ function createFakeWritableFromReadableStreamController( controller.close(); } }, - } as any; + }: any); } function startReadingFromDebugChannelReadableStream( @@ -302,7 +302,7 @@ function startReadingFromDebugChannelReadableStream( value: ?any, ... }): void | Promise<void> { - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); stringBuffer += done ? readFinalStringChunk(stringDecoder, new Uint8Array(0)) : readPartialStringChunk(stringDecoder, buffer); @@ -358,10 +358,10 @@ function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -381,7 +381,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); debugStream.pipeTo(debugChannelWritable); @@ -406,7 +405,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); return stream; @@ -415,7 +413,7 @@ function renderToReadableStream( function createFakeWritableFromNodeReadable(readable: any): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk: string | Uint8Array) { return readable.push(chunk); }, @@ -425,7 +423,7 @@ function createFakeWritableFromNodeReadable(readable: any): Writable { destroy(error) { readable.destroy(error); }, - } as any; + }: any); } type PrerenderOptions = { @@ -475,11 +473,11 @@ function prerenderToNodeStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -519,7 +517,6 @@ function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); resolve({prelude: stream}); @@ -540,11 +537,11 @@ function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -693,7 +690,7 @@ function decodeReplyFromBusboy<T>( busboyStream.on('error', err => { reportGlobalError( response, - // $FlowFixMe[incompatible-type] types Error and mixed are incompatible + // $FlowFixMe[incompatible-call] types Error and mixed are incompatible err, ); }); @@ -763,9 +760,9 @@ function decodeReplyFromAsyncIterable<T>( } function error(reason: Error) { reportGlobalError(response, reason); - if (typeof (iterator as any).throw === 'function') { + if (typeof (iterator: any).throw === 'function') { // The iterator protocol doesn't necessarily include this but a generator do. - // $FlowFixMe[prop-missing] should be able to pass mixed + // $FlowFixMe should be able to pass mixed iterator.throw(reason).then(error, error); } } diff --git a/packages/react-server-dom-webpack/package.json b/packages/react-server-dom-webpack/package.json index 3b8951b61454..672212027211 100644 --- a/packages/react-server-dom-webpack/package.json +++ b/packages/react-server-dom-webpack/package.json @@ -6,7 +6,7 @@ "react" ], "homepage": "https://react.dev/", - "bugs": "https://github.com/react/react/issues", + "bugs": "https://github.com/facebook/react/issues", "license": "MIT", "files": [ "LICENSE", @@ -78,7 +78,7 @@ "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-server-dom-webpack" }, "engines": { diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js index aa0448b2735c..9799acc3a07b 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js @@ -726,7 +726,7 @@ async function transformModuleIfNeeded( if (sourceMappingURL) { const sourceMapResult = await loader( sourceMappingURL, - // $FlowFixMe[incompatible-type] + // $FlowFixMe { format: 'json', conditions: [], @@ -738,7 +738,7 @@ async function transformModuleIfNeeded( const sourceMapString = typeof sourceMapResult.source === 'string' ? sourceMapResult.source - : // $FlowFixMe[extra-arg] + : // $FlowFixMe sourceMapResult.source.toString('utf8'); sourceMap = JSON.parse(sourceMapString); diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js index 25f483b66a90..6dbc772444f7 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js @@ -73,14 +73,14 @@ module.exports = function register() { } if (useClient) { - const moduleId: string = url.pathToFileURL(filename).href as any; + const moduleId: string = (url.pathToFileURL(filename).href: any); this.exports = createClientModuleProxy(moduleId); } if (useServer) { originalCompile.apply(this, arguments); - const moduleId: string = url.pathToFileURL(filename).href as any; + const moduleId: string = (url.pathToFileURL(filename).href: any); const exports = this.exports; @@ -89,7 +89,7 @@ module.exports = function register() { if (typeof exports === 'function') { // The module exports a function directly, registerServerReference( - exports as any, + (exports: any), moduleId, // Represents the whole Module object instead of a particular import. null, @@ -100,7 +100,7 @@ module.exports = function register() { const key = keys[i]; const value = exports[keys[i]]; if (typeof value === 'function') { - registerServerReference(value as any, moduleId, key); + registerServerReference((value: any), moduleId, key); } } } diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackPlugin.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackPlugin.js index 7aa93c5c7685..59723c903f64 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackPlugin.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackPlugin.js @@ -90,7 +90,7 @@ export default class ReactFlightWebpackPlugin { typeof options.clientReferences === 'string' || !isArray(options.clientReferences) ) { - this.clientReferences = [options.clientReferences as $FlowFixMe]; + this.clientReferences = [(options.clientReferences: $FlowFixMe)]; } else { // $FlowFixMe[incompatible-type] found when upgrading Flow this.clientReferences = options.clientReferences; diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js index 69f18e1c7280..b9f90b4d14b8 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js @@ -63,7 +63,7 @@ const FunctionBind = Function.prototype.bind; // $FlowFixMe[method-unbinding] const ArraySlice = Array.prototype.slice; function bind(this: ServerReference<any>): any { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] const newFn = FunctionBind.apply(this, arguments); if (this.$$typeof === SERVER_REFERENCE_TAG) { if (__DEV__) { @@ -79,7 +79,7 @@ function bind(this: ServerReference<any>): any { const $$id = {value: this.$$id}; const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args}; return Object.defineProperties( - newFn as any, + (newFn: any), (__DEV__ ? { $$typeof, @@ -120,7 +120,7 @@ export function registerServerReference<T: Function>( }; const $$bound = {value: null, configurable: true}; return Object.defineProperties( - reference as any, + (reference: any), __DEV__ ? ({ $$typeof, @@ -237,14 +237,14 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { // an ESM compat module but then we'll check again on the client. const moduleId = target.$$id; target.default = registerClientReferenceImpl( - function () { + (function () { throw new Error( `Attempted to call the default export of ${moduleId} from the server ` + `but it's on the client. It's not possible to invoke a client function from ` + `the server, it can only be rendered as a Component or passed to props of a ` + `Client Component.`, ); - } as any, + }: any), target.$$id + '#', target.$$async, ); @@ -260,9 +260,7 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { // the client. const clientReference: ClientReference<any> = - registerClientReferenceImpl({} as any, target.$$id, true); - // $FlowFixMe[incompatible-variance] - // $FlowFixMe[incompatible-type] + registerClientReferenceImpl(({}: any), target.$$id, true); const proxy = new Proxy(clientReference, proxyHandlers); // Treat this as a resolved Promise for React's use() @@ -270,10 +268,10 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { target.value = proxy; const then = (target.then = registerClientReferenceImpl( - function then(resolve, reject: any) { + (function then(resolve, reject: any) { // Expose to React. return Promise.resolve(resolve(proxy)); - } as any, + }: any), // If this is not used as a Promise but is treated as a reference to a `.then` // export then we should treat it as a reference to that name. target.$$id + '#then', @@ -296,18 +294,20 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { let cachedReference = target[name]; if (!cachedReference) { const reference: ClientReference<any> = registerClientReferenceImpl( - function () { + (function () { throw new Error( // eslint-disable-next-line react-internal/safe-string-coercion - `Attempted to call ${String(name)}() from the server but ${String(name)} is on the client. ` + + `Attempted to call ${String(name)}() from the server but ${String( + name, + )} is on the client. ` + `It's not possible to invoke a client function from the server, it can ` + `only be rendered as a Component or passed to props of a Client Component.`, ); - } as any, + }: any), target.$$id + '#' + name, target.$$async, ); - Object.defineProperty(reference as any, 'name', {value: name}); + Object.defineProperty((reference: any), 'name', {value: name}); cachedReference = target[name] = new Proxy(reference, deepProxyHandlers); } return cachedReference; @@ -350,13 +350,10 @@ export function createClientModuleProxy<T>( moduleId: string, ): ClientReference<T> { const clientReference: ClientReference<T> = registerClientReferenceImpl( - {} as any, + ({}: any), // Represents the whole Module object instead of a particular import. moduleId, false, ); - // $FlowFixMe[incompatible-variance] - // $FlowFixMe[incompatible-type] - // $FlowFixMe[incompatible-exact] return new Proxy(clientReference, proxyHandlers); } diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js index 27853fc23810..6997aa84ebce 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js @@ -2051,7 +2051,7 @@ describe('ReactFlightDOMNode', () => { ); expect(result).toContain( - 'Switched to client rendering because the server rendering aborted due to:\\n\\n' + + 'Switched to client rendering because the server rendering aborted due to:\n\n' + 'ssr-abort', ); }); @@ -2092,157 +2092,4 @@ describe('ReactFlightDOMNode', () => { globalThis.eval = previousEval; } }); - - // Shared scenario for the two regression tests below. Both guard against - // the same destination-backpressure bug — emitTextChunk and - // emitTypedArrayChunk each push a [headerChunk, contentChunk] pair into - // completedRegularChunks. Before the fix, a flush that broke between the - // two writes left the content chunk stranded at the head of the queue, - // and the next flush emitted any newly-arrived Import rows ahead of it — - // splicing Import bytes into the position the Flight Client expects to - // read as the row's content. - // - // The scenario embeds `payload` in the model under the key `payload` and - // returns the deserialized model. Each test asserts that result.payload - // round-trips identically to what the Flight Server emitted. - async function runScenarioWithBackpressureBetweenHeaderAndContent(payload) { - function Client1() { - return <span>client1</span>; - } - // Client1's Import row must exceed VIEW_SIZE (4096) so writeStringChunk - // takes its BIG path and calls destination.write directly. That write - // returning false is what triggers the backpressure we want to test. - const Client1Reference = clientExports( - Client1, - 1, - '/' + 'a'.repeat(5000), - Promise.resolve(), - ); - - function Client2() { - return <span>client2</span>; - } - const Client2Reference = clientExports( - Client2, - 2, - '/client2.js', - Promise.resolve(), - ); - - let resolveAsync; - const asyncPromise = new Promise(resolve => { - resolveAsync = resolve; - }); - - async function AsyncWrapper() { - await asyncPromise; - return <Client2Reference />; - } - - const model = { - client: <Client1Reference />, - payload, - async: <AsyncWrapper />, - }; - - const heldCallbacks = []; - const collectedChunks = []; - - // A destination that returns false from every write (highWaterMark: 1 in - // byte mode) and never completes any of them until the test releases the - // stored callback. This gives us deterministic control over when each write - // finishes and when 'drain' fires. - const destination = new Stream.Writable({ - highWaterMark: 1, - write(chunk, encoding, callback) { - collectedChunks.push( - Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding), - ); - heldCallbacks.push(callback); - }, - }); - - const finished = new Promise((resolve, reject) => { - destination.on('finish', resolve); - destination.on('error', reject); - }); - - // First flush: Client1's huge Import row hits backpressure, so the flush - // loop reaches the payload row's [headerChunk, contentChunk] pair while - // destinationHasCapacity is already false. Before the fix, this would - // have encoded just the header into currentView, broken the loop, and - // let completeWriting flush the header as its own write — stranding - // the content chunk at the front of completedRegularChunks. - const {pipe} = await serverAct(() => - ReactServerDOMServer.renderToPipeableStream(model, webpackMap), - ); - await serverAct(() => { - pipe(destination); - }); - - // While the destination is still paused, push Client2's Import row into - // completedImportChunks. No flush runs (request.destination is null after - // the first flush's backpressure break). - await serverAct(() => { - resolveAsync(); - }); - - // Release callbacks one at a time. The drain that empties the writable - // buffer triggers flushCompletedChunks; before the fix, this is where - // Client2's newly-queued Import row would have been emitted ahead of - // the still-orphaned payload content chunk. - while (heldCallbacks.length > 0) { - await serverAct(() => { - const cb = heldCallbacks.shift(); - cb(); - }); - } - - await finished; - - const readable = new Stream.Readable({read() {}}); - for (let i = 0; i < collectedChunks.length; i++) { - readable.push(collectedChunks[i]); - } - readable.push(null); - - const response = ReactServerDOMClient.createFromNodeStream(readable, { - moduleMap: null, - moduleLoading: null, - }); - return await response; - } - - it("keeps a Text row's header and content chunks adjacent when a flush hits backpressure between them", async () => { - // length >= 1024 makes the Flight Server outline this as a Text row via - // serializeLargeTextString, which is where emitTextChunk pushes its - // [headerChunk, textChunk] pair. - const largeText = 'x'.repeat(2048); - - const result = - await runScenarioWithBackpressureBetweenHeaderAndContent(largeText); - - // Before the fix, the Flight Client would have framed Client2's Import - // row bytes as text-row content, making result.payload `<id>:I[...]...` - // garbage rather than the x's the Flight Server emitted. - expect(result.payload).toBe(largeText); - }); - - it("keeps a TypedArray row's header and content chunks adjacent when a flush hits backpressure between them", async () => { - // emitTypedArrayChunk pushes the same [headerChunk, contentChunk] pair as - // emitTextChunk. Before the fix, a flush break after the header would have - // stranded the content chunk in exactly the same way. - const binaryData = new Uint8Array(1024); - for (let i = 0; i < binaryData.length; i++) { - binaryData[i] = i % 256; - } - - const result = - await runScenarioWithBackpressureBetweenHeaderAndContent(binaryData); - - // Before the fix, the typed array's bytes would have been replaced by - // Client2's Import row bytes followed by whatever happened to land in - // the next 1024-byte window. - expect(result.payload).toEqual(binaryData); - }); }); diff --git a/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js b/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js index 6a2fd9f3aa6d..23825c4dcf7b 100644 --- a/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js +++ b/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js @@ -176,12 +176,12 @@ function requireAsyncModule(id: string): null | Thenable<any> { // Instrument the Promise to stash the result. promise.then( value => { - const fulfilledThenable: FulfilledThenable<mixed> = promise as any; + const fulfilledThenable: FulfilledThenable<mixed> = (promise: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = value; }, reason => { - const rejectedThenable: RejectedThenable<mixed> = promise as any; + const rejectedThenable: RejectedThenable<mixed> = (promise: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = reason; }, @@ -258,7 +258,7 @@ export function requireModule<T>(metadata: ClientReference<T>): T { if (hasOwnProperty.call(moduleExports, metadata[NAME])) { return moduleExports[metadata[NAME]]; } - return undefined as any; + return (undefined: any); } export function getModuleDebugInfo<T>( diff --git a/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackBrowser.js b/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackBrowser.js index 692b44135143..7f49e9fd15a8 100644 --- a/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackBrowser.js +++ b/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackBrowser.js @@ -36,7 +36,7 @@ export function loadChunk(chunkId: string, filename: string): Promise<mixed> { // We cache ReactIOInfo across requests so that inner refreshes can dedupe with outer. const chunkIOInfoCache: Map<string, ReactIOInfo> = __DEV__ ? new Map() - : (null as any); + : (null: any); export function addChunkDebugInfo( target: ReactDebugInfo, @@ -51,7 +51,7 @@ export function addChunkDebugInfo( const scriptFilename = __webpack_get_script_filename__(chunkId); let href; try { - // $FlowFixMe[incompatible-type] + // $FlowFixMe href = new URL(scriptFilename, document.baseURI).href; } catch (_) { href = scriptFilename; @@ -69,14 +69,14 @@ export function addChunkDebugInfo( start = resourceEntry.startTime; end = start + resourceEntry.duration; // $FlowFixMe[prop-missing] - byteSize = (resourceEntry.transferSize as any) || 0; + byteSize = (resourceEntry.transferSize: any) || 0; } } } const value = Promise.resolve(href); - // $FlowFixMe[prop-missing] + // $FlowFixMe value.status = 'fulfilled'; - // $FlowFixMe[prop-missing] + // $FlowFixMe value.value = { chunkId: chunkId, href: href, @@ -108,13 +108,13 @@ export function addChunkDebugInfo( href + ':1:1'; } - ioInfo = { + ioInfo = ({ name: 'script', start: start, end: end, value: value, debugStack: fakeStack, - } as ReactIOInfo; + }: ReactIOInfo); if (byteSize > 0) { // $FlowFixMe[cannot-write] ioInfo.byteSize = byteSize; diff --git a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js index c42728a9f9ef..c38d5fd05133 100644 --- a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js @@ -173,7 +173,7 @@ function startReadingFromStream( if (done) { return onDone(); } - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); processBinaryChunk(response, streamState, buffer); return reader.read().then(progress).catch(error); } @@ -241,11 +241,11 @@ function createFromFetch<T>( options.debugChannel.readable, handleDone, ); - startReadingFromStream(response, r.body as any, handleDone, r); + startReadingFromStream(response, (r.body: any), handleDone, r); } else { startReadingFromStream( response, - r.body as any, + (r.body: any), close.bind(null, response), r, ); @@ -277,10 +277,10 @@ function encodeReply( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort((signal as any).reason); + abort((signal: any).reason); } else { const listener = () => { - abort((signal as any).reason); + abort((signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js index 2ccbd6a1d67a..8bcdcdbfe0d2 100644 --- a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js +++ b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js @@ -142,7 +142,7 @@ function startReadingFromStream( if (done) { return onDone(); } - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); processBinaryChunk(response, streamState, buffer); return reader.read().then(progress).catch(error); } @@ -208,11 +208,11 @@ function createFromFetch<T>( options.debugChannel.readable, handleDone, ); - startReadingFromStream(response, r.body as any, handleDone, r); + startReadingFromStream(response, (r.body: any), handleDone, r); } else { startReadingFromStream( response, - r.body as any, + (r.body: any), close.bind(null, response), r, ); @@ -244,10 +244,10 @@ function encodeReply( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort((signal as any).reason); + abort((signal: any).reason); } else { const listener = () => { - abort((signal as any).reason); + abort((signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); diff --git a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerBrowser.js index 5ea5f21dd112..d1d0772186e6 100644 --- a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerBrowser.js +++ b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerBrowser.js @@ -82,7 +82,7 @@ function startReadingFromDebugChannelReadableStream( value: ?any, ... }): void | Promise<void> { - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); stringBuffer += done ? readFinalStringChunk(stringDecoder, new Uint8Array(0)) : readPartialStringChunk(stringDecoder, buffer); @@ -135,10 +135,10 @@ function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -153,7 +153,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); debugStream.pipeTo(debugChannelWritable); @@ -176,7 +175,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); return stream; @@ -206,7 +204,6 @@ function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); resolve({prelude: stream}); @@ -227,11 +224,11 @@ function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; diff --git a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js index 933b74e2fec6..1347b07dd4df 100644 --- a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js +++ b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js @@ -87,7 +87,7 @@ function startReadingFromDebugChannelReadableStream( value: ?any, ... }): void | Promise<void> { - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); stringBuffer += done ? readFinalStringChunk(stringDecoder, new Uint8Array(0)) : readPartialStringChunk(stringDecoder, buffer); @@ -140,10 +140,10 @@ function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -158,7 +158,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); debugStream.pipeTo(debugChannelWritable); @@ -181,7 +180,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); return stream; @@ -211,7 +209,6 @@ function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); resolve({prelude: stream}); @@ -232,11 +229,11 @@ function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -310,9 +307,9 @@ function decodeReplyFromAsyncIterable<T>( } function error(reason: Error) { reportGlobalError(response, reason); - if (typeof (iterator as any).throw === 'function') { + if (typeof (iterator: any).throw === 'function') { // The iterator protocol doesn't necessarily include this but a generator do. - // $FlowFixMe[prop-missing] should be able to pass mixed + // $FlowFixMe should be able to pass mixed iterator.throw(reason).then(error, error); } } diff --git a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js index f2551e8c6c57..c8809b040460 100644 --- a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js @@ -100,7 +100,7 @@ function startReadingFromDebugChannelReadable( } stringBuffer += chunk; } else { - const buffer: Uint8Array = chunk as any; + const buffer: Uint8Array = (chunk: any); stringBuffer += readPartialStringChunk(stringDecoder, buffer); lastWasPartial = true; } @@ -127,19 +127,19 @@ function startReadingFromDebugChannelReadable( // $FlowFixMe[method-unbinding] typeof stream.binaryType === 'string' ) { - const ws: WebSocket = stream as any; + const ws: WebSocket = (stream: any); ws.binaryType = 'arraybuffer'; ws.addEventListener('message', event => { - // $FlowFixMe[incompatible-type] + // $FlowFixMe onData(event.data); }); ws.addEventListener('error', event => { - // $FlowFixMe[prop-missing] + // $FlowFixMe onError(event.error); }); ws.addEventListener('close', onClose); } else { - const readable: Readable = stream as any; + const readable: Readable = (stream: any); readable.on('data', onData); readable.on('error', onError); readable.on('end', onClose); @@ -173,16 +173,16 @@ function renderToPipeableStream( // $FlowFixMe[method-unbinding] (typeof debugChannel.read === 'function' || typeof debugChannel.readyState === 'number') - ? (debugChannel as any) + ? (debugChannel: any) : undefined; const debugChannelWritable: void | Writable = __DEV__ && debugChannel !== undefined ? // $FlowFixMe[method-unbinding] typeof debugChannel.write === 'function' - ? (debugChannel as any) + ? (debugChannel: any) : // $FlowFixMe[method-unbinding] typeof debugChannel.send === 'function' - ? createFakeWritableFromWebSocket(debugChannel as any) + ? createFakeWritableFromWebSocket((debugChannel: any)) : undefined : undefined; const request = createRequest( @@ -237,9 +237,9 @@ function renderToPipeableStream( } function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { - return { + return ({ write(chunk: string | Uint8Array) { - webSocket.send(chunk as any); + webSocket.send((chunk: any)); return true; }, end() { @@ -255,7 +255,7 @@ function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable { webSocket.close(1011); } }, - } as any; + }: any); } function createFakeWritableFromReadableStreamController( @@ -263,7 +263,7 @@ function createFakeWritableFromReadableStreamController( ): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk: string | Uint8Array) { if (typeof chunk === 'string') { chunk = textEncoder.encode(chunk); @@ -284,7 +284,7 @@ function createFakeWritableFromReadableStreamController( controller.close(); } }, - } as any; + }: any); } function startReadingFromDebugChannelReadableStream( @@ -302,7 +302,7 @@ function startReadingFromDebugChannelReadableStream( value: ?any, ... }): void | Promise<void> { - const buffer: Uint8Array = value as any; + const buffer: Uint8Array = (value: any); stringBuffer += done ? readFinalStringChunk(stringDecoder, new Uint8Array(0)) : readPartialStringChunk(stringDecoder, buffer); @@ -358,10 +358,10 @@ function renderToReadableStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); } else { const listener = () => { - abort(request, (signal as any).reason); + abort(request, (signal: any).reason); signal.removeEventListener('abort', listener); }; signal.addEventListener('abort', listener); @@ -381,7 +381,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); debugStream.pipeTo(debugChannelWritable); @@ -406,7 +405,6 @@ function renderToReadableStream( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); return stream; @@ -415,7 +413,7 @@ function renderToReadableStream( function createFakeWritableFromNodeReadable(readable: any): Writable { // The current host config expects a Writable so we create // a fake writable for now to push into the Readable. - return { + return ({ write(chunk: string | Uint8Array) { return readable.push(chunk); }, @@ -425,7 +423,7 @@ function createFakeWritableFromNodeReadable(readable: any): Writable { destroy(error) { readable.destroy(error); }, - } as any; + }: any); } type PrerenderOptions = { @@ -475,11 +473,11 @@ function prerenderToNodeStream( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -519,7 +517,6 @@ function prerender( }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. - // $FlowFixMe[incompatible-type] {highWaterMark: 0}, ); resolve({prelude: stream}); @@ -540,11 +537,11 @@ function prerender( if (options && options.signal) { const signal = options.signal; if (signal.aborted) { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); } else { const listener = () => { - const reason = (signal as any).reason; + const reason = (signal: any).reason; abort(request, reason); signal.removeEventListener('abort', listener); }; @@ -694,7 +691,6 @@ function decodeReplyFromBusboy<T>( reportGlobalError( response, // $FlowFixMe[incompatible-call] types Error and mixed are incompatible - // $FlowFixMe[incompatible-type] err, ); }); @@ -764,9 +760,9 @@ function decodeReplyFromAsyncIterable<T>( } function error(reason: Error) { reportGlobalError(response, reason); - if (typeof (iterator as any).throw === 'function') { + if (typeof (iterator: any).throw === 'function') { // The iterator protocol doesn't necessarily include this but a generator do. - // $FlowFixMe[prop-missing] should be able to pass mixed + // $FlowFixMe should be able to pass mixed iterator.throw(reason).then(error, error); } } diff --git a/packages/react-server/package.json b/packages/react-server/package.json index 2a57f31bb2a3..405d68dbc3a7 100644 --- a/packages/react-server/package.json +++ b/packages/react-server/package.json @@ -7,7 +7,7 @@ "react" ], "homepage": "https://react.dev/", - "bugs": "https://github.com/react/react/issues", + "bugs": "https://github.com/facebook/react/issues", "license": "MIT", "files": [ "LICENSE", @@ -19,7 +19,7 @@ "main": "index.js", "repository": { "type" : "git", - "url" : "https://github.com/react/react.git", + "url" : "https://github.com/facebook/react.git", "directory": "packages/react-server" }, "engines": { diff --git a/packages/react-server/src/ReactFizzAsyncDispatcher.js b/packages/react-server/src/ReactFizzAsyncDispatcher.js index c05aeb29f813..d20acdd5e9a8 100644 --- a/packages/react-server/src/ReactFizzAsyncDispatcher.js +++ b/packages/react-server/src/ReactFizzAsyncDispatcher.js @@ -20,10 +20,10 @@ function cacheSignal(): null | AbortSignal { throw new Error('Not implemented.'); } -export const DefaultAsyncDispatcher: AsyncDispatcher = { +export const DefaultAsyncDispatcher: AsyncDispatcher = ({ getCacheForType, cacheSignal, -} as any; +}: any); if (__DEV__) { DefaultAsyncDispatcher.getOwner = (): ComponentStackNode | null => { diff --git a/packages/react-server/src/ReactFizzCallUserSpace.js b/packages/react-server/src/ReactFizzCallUserSpace.js index 2a0384c3e21a..d90a80ec0e84 100644 --- a/packages/react-server/src/ReactFizzCallUserSpace.js +++ b/packages/react-server/src/ReactFizzCallUserSpace.js @@ -28,8 +28,8 @@ export const callComponentInDEV: <Props, Arg, R>( secondArg: Arg, ) => R = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. - (callComponent.react_stack_bottom_frame.bind(callComponent) as any) - : (null as any); + (callComponent.react_stack_bottom_frame.bind(callComponent): any) + : (null: any); interface ClassInstance<R> { render(): R; @@ -44,8 +44,8 @@ const callRender = { export const callRenderInDEV: <R>(instance: ClassInstance<R>) => R => R = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. - (callRender.react_stack_bottom_frame.bind(callRender) as any) - : (null as any); + (callRender.react_stack_bottom_frame.bind(callRender): any) + : (null: any); const callLazyInit = { react_stack_bottom_frame: function (lazy: LazyComponent<any, any>): any { @@ -57,5 +57,5 @@ const callLazyInit = { export const callLazyInitInDEV: (lazy: LazyComponent<any, any>) => any = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. - (callLazyInit.react_stack_bottom_frame.bind(callLazyInit) as any) - : (null as any); + (callLazyInit.react_stack_bottom_frame.bind(callLazyInit): any) + : (null: any); diff --git a/packages/react-server/src/ReactFizzClassComponent.js b/packages/react-server/src/ReactFizzClassComponent.js index 4346c8f9025d..28c8ac6edcb3 100644 --- a/packages/react-server/src/ReactFizzClassComponent.js +++ b/packages/react-server/src/ReactFizzClassComponent.js @@ -214,7 +214,7 @@ export function constructClassInstance( } if (typeof contextType === 'object' && contextType !== null) { - context = readContext(contextType as any); + context = readContext((contextType: any)); } else if (!disableLegacyContext) { context = maskedLegacyContext; } diff --git a/packages/react-server/src/ReactFizzComponentStack.js b/packages/react-server/src/ReactFizzComponentStack.js index b69e4c8a8751..467cf48646c9 100644 --- a/packages/react-server/src/ReactFizzComponentStack.js +++ b/packages/react-server/src/ReactFizzComponentStack.js @@ -65,17 +65,16 @@ function describeComponentStackByType( return describeFunctionComponentFrame(type); } } - // $FlowFixMe[invalid-compare] if (typeof type === 'object' && type !== null) { switch (type.$$typeof) { case REACT_FORWARD_REF_TYPE: { - return describeFunctionComponentFrame((type as any).render); + return describeFunctionComponentFrame((type: any).render); } case REACT_MEMO_TYPE: { - return describeFunctionComponentFrame((type as any).type); + return describeFunctionComponentFrame((type: any).type); } case REACT_LAZY_TYPE: { - const lazyComponent: LazyComponent<any, any> = type as any; + const lazyComponent: LazyComponent<any, any> = (type: any); const payload = lazyComponent._payload; const init = lazyComponent._init; try { @@ -174,7 +173,7 @@ export function getOwnerStackByComponentStackNodeInDev( owner = owner.owner; } else { // Client Component - const node: ComponentStackNode = owner as any; + const node: ComponentStackNode = (owner: any); if (node.stack != null) { if (typeof node.stack !== 'string') { ownerStack = node.stack = formatOwnerStack(node.stack); diff --git a/packages/react-server/src/ReactFizzHooks.js b/packages/react-server/src/ReactFizzHooks.js index 7f2e933c9f9a..24e676fc7108 100644 --- a/packages/react-server/src/ReactFizzHooks.js +++ b/packages/react-server/src/ReactFizzHooks.js @@ -350,7 +350,7 @@ export function useState<S>( return useReducer( basicStateReducer, // useReducer has a special case to support lazy useState initializers - initialState as any, + (initialState: any), ); } @@ -360,7 +360,6 @@ export function useReducer<S, I, A>( init?: I => S, ): [S, Dispatch<A>] { if (__DEV__) { - // $FlowFixMe[invalid-compare] if (reducer !== basicStateReducer) { currentHookNameInDev = 'useReducer'; } @@ -370,8 +369,8 @@ export function useReducer<S, I, A>( if (isReRender) { // This is a re-render. Apply the new render phase updates to the previous // current hook. - const queue: UpdateQueue<A> = workInProgressHook.queue as any; - const dispatch: Dispatch<A> = queue.dispatch as any; + const queue: UpdateQueue<A> = (workInProgressHook.queue: any); + const dispatch: Dispatch<A> = (queue.dispatch: any); if (renderPhaseUpdates !== null) { // Render phase updates are stored in a map of queue -> linked list const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue); @@ -410,16 +409,15 @@ export function useReducer<S, I, A>( isInHookUserCodeInDev = true; } let initialState; - // $FlowFixMe[invalid-compare] if (reducer === basicStateReducer) { // Special case for `useState`. initialState = typeof initialArg === 'function' - ? (initialArg as any as () => S)() - : (initialArg as any as S); + ? ((initialArg: any): () => S)() + : ((initialArg: any): S); } else { initialState = - init !== undefined ? init(initialArg) : (initialArg as any as S); + init !== undefined ? init(initialArg) : ((initialArg: any): S); } if (__DEV__) { isInHookUserCodeInDev = false; @@ -431,11 +429,11 @@ export function useReducer<S, I, A>( last: null, dispatch: null, }); - const dispatch: Dispatch<A> = (queue.dispatch = dispatchAction.bind( + const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind( null, currentlyRenderingComponent, queue, - ) as any); + ): any)); // $FlowFixMe[incompatible-use] found when upgrading Flow return [workInProgressHook.memoizedState, dispatch]; } @@ -447,7 +445,6 @@ function useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T { const nextDeps = deps === undefined ? null : deps; - // $FlowFixMe[invalid-compare] if (workInProgressHook !== null) { const prevState = workInProgressHook.memoizedState; if (prevState !== null) { @@ -548,7 +545,7 @@ function throwOnUseEffectEventCall() { export function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>( callback: F, ): F { - // $FlowFixMe[incompatible-type] + // $FlowIgnore[incompatible-return] return throwOnUseEffectEventCall; } @@ -630,9 +627,9 @@ function useActionState<S, P>( // track the position of this useActionState hook relative to the other ones in // this component, so we can generate a unique key for each one. const actionStateHookIndex = actionStateCounter++; - const request: Request = currentlyRenderingRequest as any; + const request: Request = (currentlyRenderingRequest: any); - // $FlowFixMe[prop-missing] + // $FlowIgnore[prop-missing] const formAction = action.$$FORM_ACTION; if (typeof formAction === 'function') { // This is a server action. These have additional features to enable @@ -652,9 +649,9 @@ function useActionState<S, P>( // Otherwise, we'll use the initial state argument. We will emit a comment // marker into the stream that indicates whether the state was reused. let state = initialState; - const componentKeyPath = currentlyRenderingKeyPath as any; + const componentKeyPath = (currentlyRenderingKeyPath: any); const postbackActionState = getFormState(request); - // $FlowFixMe[prop-missing] + // $FlowIgnore[prop-missing] const isSignatureEqual = action.$$IS_SIGNATURE_EQUAL; if ( postbackActionState !== null && @@ -690,7 +687,7 @@ function useActionState<S, P>( // $FlowIgnore[prop-missing] if (typeof boundAction.$$FORM_ACTION === 'function') { - // $FlowFixMe[prop-missing] + // $FlowIgnore[prop-missing] dispatch.$$FORM_ACTION = (prefix: string) => { const metadata: ReactCustomFormAction = boundAction.$$FORM_ACTION(prefix); @@ -734,7 +731,7 @@ function useActionState<S, P>( } function useId(): string { - const task: Task = currentlyRenderingTask as any; + const task: Task = (currentlyRenderingTask: any); const treeId = getTreeId(task.treeContext); const resumableState = currentResumableState; @@ -749,15 +746,14 @@ function useId(): string { } function use<T>(usable: Usable<T>): T { - // $FlowFixMe[invalid-compare] if (usable !== null && typeof usable === 'object') { // $FlowFixMe[method-unbinding] if (typeof usable.then === 'function') { // This is a thenable. - const thenable: Thenable<T> = usable as any; + const thenable: Thenable<T> = (usable: any); return unwrapThenable(thenable); } else if (usable.$$typeof === REACT_CONTEXT_TYPE) { - const context: ReactContext<T> = usable as any; + const context: ReactContext<T> = (usable: any); return readContext(context); } } @@ -807,7 +803,6 @@ function clientHookNotSupported() { ); } -// $FlowFixMe[constant-condition] export const HooksDispatcher: Dispatcher = supportsClientAPIs ? { readContext, @@ -866,7 +861,7 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs useEffectEvent, }; -export let currentResumableState: null | ResumableState = null as any; +export let currentResumableState: null | ResumableState = (null: any); export function setCurrentResumableState( resumableState: null | ResumableState, ): void { diff --git a/packages/react-server/src/ReactFizzLegacyContext.js b/packages/react-server/src/ReactFizzLegacyContext.js index 24cc11c47504..b17827cef68a 100644 --- a/packages/react-server/src/ReactFizzLegacyContext.js +++ b/packages/react-server/src/ReactFizzLegacyContext.js @@ -13,7 +13,7 @@ import getComponentNameFromType from 'shared/getComponentNameFromType'; let warnedAboutMissingGetChildContext; if (__DEV__) { - warnedAboutMissingGetChildContext = {} as {[string]: boolean}; + warnedAboutMissingGetChildContext = ({}: {[string]: boolean}); } export const emptyContextObject: {} = {}; diff --git a/packages/react-server/src/ReactFizzNewContext.js b/packages/react-server/src/ReactFizzNewContext.js index a481272916b1..0a9fe3df4c35 100644 --- a/packages/react-server/src/ReactFizzNewContext.js +++ b/packages/react-server/src/ReactFizzNewContext.js @@ -39,7 +39,6 @@ export const rootContextSnapshot: ContextSnapshot = null; let currentActiveSnapshot: ContextSnapshot = null; function popNode(prev: ContextNode<any>): void { - // $FlowFixMe[constant-condition] if (isPrimaryRenderer) { prev.context._currentValue = prev.parentValue; } else { @@ -48,7 +47,6 @@ function popNode(prev: ContextNode<any>): void { } function pushNode(next: ContextNode<any>): void { - // $FlowFixMe[constant-condition] if (isPrimaryRenderer) { next.context._currentValue = next.value; } else { @@ -164,7 +162,7 @@ export function switchContext(newSnapshot: ContextSnapshot): void { const next = newSnapshot; if (prev !== next) { if (prev === null) { - // $FlowFixMe[incompatible-type]: This has to be non-null since it's not equal to prev. + // $FlowFixMe[incompatible-call]: This has to be non-null since it's not equal to prev. pushAllNext(next); } else if (next === null) { popAllPrevious(prev); @@ -184,7 +182,6 @@ export function pushProvider<T>( nextValue: T, ): ContextSnapshot { let prevValue; - // $FlowFixMe[constant-condition] if (isPrimaryRenderer) { prevValue = context._currentValue; context._currentValue = nextValue; @@ -246,7 +243,6 @@ export function popProvider<T>(context: ReactContext<T>): ContextSnapshot { ); } } - // $FlowFixMe[constant-condition] if (isPrimaryRenderer) { const value = prevSnapshot.parentValue; prevSnapshot.context._currentValue = value; @@ -288,7 +284,6 @@ export function getActiveContext(): ContextSnapshot { } export function readContext<T>(context: ReactContext<T>): T { - // $FlowFixMe[constant-condition] const value = isPrimaryRenderer ? context._currentValue : context._currentValue2; diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 76509b652e1d..73b3e435d189 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -88,7 +88,6 @@ import { hoistHoistables, createHoistableState, createPreambleState, - isWorkLoopExternallyDriven, supportsRequestStorage, requestStorage, pushFormStateMarkerIsMatching, @@ -276,16 +275,11 @@ type SuspenseBoundary = { errorComponentStack?: null | string, // the error component stack if it errors }; -type Ping = { - resolve: () => void, - reject: (error: mixed) => void, -}; - type RenderTask = { replay: null, node: ReactNodeList, childIndex: number, - ping: Ping, + ping: () => void, blockedBoundary: Root | SuspenseBoundary, blockedSegment: Segment, // the segment we'll write to blockedPreamble: null | PreambleState, @@ -316,7 +310,7 @@ type ReplayTask = { replay: ReplaySet, node: ReactNodeList, childIndex: number, - ping: Ping, + ping: () => void, blockedBoundary: Root | SuspenseBoundary, blockedSegment: null, // we don't write to anything when we replay blockedPreamble: null, @@ -341,11 +335,12 @@ const FLUSHED = 2; const ABORTED = 3; const ERRORED = 4; const POSTPONED = 5; +const RENDERING = 6; type Root = null; type Segment = { - status: 0 | 1 | 2 | 3 | 4 | 5, + status: 0 | 1 | 2 | 3 | 4 | 5 | 6, parentFlushed: boolean, // typically a segment will be flushed by its parent, except if its parent was already flushed id: number, // starts as 0 and is lazily assigned if the parent flushes early +index: number, // the index within the parent's chunks or 0 at the root @@ -361,14 +356,12 @@ type Segment = { textEmbedded: boolean, }; -// The ordering of these statuses matters. OPENING and OPEN are the only -// statuses in which newly scheduled work may be performed. Any status greater -// than OPEN represents a request that no longer admits work. const OPENING = 10; const OPEN = 11; -const CLOSING = 12; -const CLOSED = 13; -const STALLED_DEV = 14; +const ABORTING = 12; +const CLOSING = 13; +const CLOSED = 14; +const STALLED_DEV = 15; export opaque type Request = { destination: null | Destination, @@ -377,9 +370,8 @@ export opaque type Request = { +renderState: RenderState, +rootFormatContext: FormatContext, +progressiveChunkSize: number, - status: 10 | 11 | 12 | 13 | 14, + status: 10 | 11 | 12 | 13 | 14 | 15, fatalError: mixed, - aborted: boolean, nextSegmentId: number, allPendingTasks: number, // when it reaches zero, we can close the connection. pendingRootTasks: number, // when this reaches zero, we've finished at least the root boundary. @@ -388,7 +380,6 @@ export opaque type Request = { byteSize: number, // counts the number of bytes accumulated in the shell abortableTasks: Set<Task>, pingedTasks: Array<Task>, // High priority tasks that should be worked on first. - currentTask: null | Task, // The task currently executing in this request. // Queues to flush in order of priority clientRenderedBoundaries: Array<SuspenseBoundary>, // Errored or client rendered but not yet flushed. completedBoundaries: Array<SuspenseBoundary>, // Completed but not yet fully flushed boundaries to show. @@ -536,10 +527,8 @@ function RequestInstance( progressiveChunkSize === undefined ? DEFAULT_PROGRESSIVE_CHUNK_SIZE : progressiveChunkSize; - // $FlowFixMe[constant-condition] - this.status = isWorkLoopExternallyDriven ? OPEN : OPENING; + this.status = OPENING; this.fatalError = null; - this.aborted = false; this.nextSegmentId = 0; this.allPendingTasks = 0; this.pendingRootTasks = 0; @@ -548,10 +537,9 @@ function RequestInstance( this.byteSize = 0; this.abortableTasks = abortSet; this.pingedTasks = pingedTasks; - this.currentTask = null; - this.clientRenderedBoundaries = [] as Array<SuspenseBoundary>; - this.completedBoundaries = [] as Array<SuspenseBoundary>; - this.partialBoundaries = [] as Array<SuspenseBoundary>; + this.clientRenderedBoundaries = ([]: Array<SuspenseBoundary>); + this.completedBoundaries = ([]: Array<SuspenseBoundary>); + this.partialBoundaries = ([]: Array<SuspenseBoundary>); this.trackedPostpones = null; this.onError = onError === undefined ? defaultErrorHandler : onError; this.onAllReady = onAllReady === undefined ? noop : onAllReady; @@ -792,7 +780,6 @@ let currentRequest: null | Request = null; export function resolveRequest(): null | Request { if (currentRequest) return currentRequest; - // $FlowFixMe[constant-condition] if (supportsRequestStorage) { const store = requestStorage.getStore(); if (store) return store; @@ -803,42 +790,16 @@ export function resolveRequest(): null | Request { function pingTask(request: Request, task: Task): void { const pingedTasks = request.pingedTasks; pingedTasks.push(task); - // $FlowFixMe[constant-condition] - if (isWorkLoopExternallyDriven) { - return; - } else { - if (request.pingedTasks.length === 1) { - request.flushScheduled = request.destination !== null; - if (request.trackedPostpones !== null || request.status === OPENING) { - scheduleMicrotask(() => performWork(request)); - } else { - scheduleWork(() => performWork(request)); - } + if (request.pingedTasks.length === 1) { + request.flushScheduled = request.destination !== null; + if (request.trackedPostpones !== null || request.status === OPENING) { + scheduleMicrotask(() => performWork(request)); + } else { + scheduleWork(() => performWork(request)); } } } -function pingRejectedTask(request: Request, task: Task, error: mixed): void { - if (!request.aborted) { - // Replaying the task is what gives ordinary render errors their complete - // component stack. - pingTask(request, task); - return; - } - if (!task.abortSet.delete(task)) { - // finishAbort already completed this task with the request's abort reason. - return; - } - // abortTask synchronously claimed this task before abort listeners could - // reject its wakeable. Finish it with the more specific reason before the - // scheduled final abort uses the reason for the whole request. - if (__DEV__) { - finishAbortedTaskDEV(task, request, error); - } else { - finishAbortedTask(task, request, error); - } -} - function createSuspenseBoundary( request: Request, row: null | SuspenseListRow, @@ -914,14 +875,11 @@ function createRenderTask( if (row !== null) { row.pendingTasks++; } - const task: RenderTask = { + const task: RenderTask = ({ replay: null, node, childIndex, - ping: { - resolve: () => pingTask(request, task), - reject: error => pingRejectedTask(request, task, error), - }, + ping: () => pingTask(request, task), blockedBoundary, blockedSegment, blockedPreamble, @@ -934,7 +892,7 @@ function createRenderTask( row, componentStack, thenableState, - } as any; + }: any); if (!disableLegacyContext) { task.legacyContext = legacyContext; } @@ -973,14 +931,11 @@ function createReplayTask( row.pendingTasks++; } replay.pendingTasks++; - const task: ReplayTask = { + const task: ReplayTask = ({ replay, node, childIndex, - ping: { - resolve: () => pingTask(request, task), - reject: error => pingRejectedTask(request, task, error), - }, + ping: () => pingTask(request, task), blockedBoundary, blockedSegment: null, blockedPreamble: null, @@ -993,7 +948,7 @@ function createReplayTask( row, componentStack, thenableState, - } as any; + }: any); if (!disableLegacyContext) { task.legacyContext = legacyContext; } @@ -1057,7 +1012,7 @@ function pushHaltedAwaitOnComponentStack( for (let i = debugInfo.length - 1; i >= 0; i--) { const info = debugInfo[i]; if (info.awaited != null) { - const asyncInfo: ReactAsyncInfo = info as any; + const asyncInfo: ReactAsyncInfo = (info: any); const bestStack = asyncInfo.debugStack == null ? asyncInfo.awaited : asyncInfo; if (bestStack.debugStack !== undefined) { @@ -1067,7 +1022,7 @@ function pushHaltedAwaitOnComponentStack( owner: bestStack.owner, stack: bestStack.debugStack, }; - task.debugTask = bestStack.debugTask as any; + task.debugTask = (bestStack.debugTask: any); break; } } @@ -1078,11 +1033,7 @@ function pushHaltedAwaitOnComponentStack( // performWork + retryTask without mutation function rerenderStalledTask(request: Request, task: Task): void { const prevStatus = request.status; - const prevAborted = request.aborted; request.status = STALLED_DEV; - // This diagnostic replay must reach the suspended call site instead of - // taking the abort path. - request.aborted = false; const prevContext = getActiveContext(); const prevDispatcher = ReactSharedInternals.H; @@ -1126,7 +1077,6 @@ function rerenderStalledTask(request: Request, task: Task): void { } currentRequest = prevRequest; request.status = prevStatus; - request.aborted = prevAborted; } } @@ -1137,7 +1087,7 @@ function pushSuspendedCallSiteOnComponentStack( setCaptureSuspendedCallSiteDEV(true); const restoreThenableState = ensureSuspendableThenableStateDEV( // refined at the callsite - task.thenableState as any as ThenableState, + ((task.thenableState: any): ThenableState), ); try { rerenderStalledTask(request, task); @@ -1177,7 +1127,7 @@ function pushServerComponentStack( if (debugInfo != null) { const stack: ReactDebugInfo = debugInfo; for (let i = 0; i < stack.length; i++) { - const componentInfo: ReactComponentInfo = stack[i] as any; + const componentInfo: ReactComponentInfo = (stack[i]: any); if (typeof componentInfo.name !== 'string') { continue; } @@ -1190,7 +1140,7 @@ function pushServerComponentStack( owner: componentInfo.owner, stack: componentInfo.debugStack, }; - task.debugTask = componentInfo.debugTask as any; + task.debugTask = (componentInfo.debugTask: any); } } } @@ -1201,7 +1151,7 @@ function pushComponentStack(task: Task): void { // It's unfortunate that we need to do this refinement twice. Once for // the stack frame and then once again while actually if (typeof node === 'object' && node !== null) { - switch ((node as any).$$typeof) { + switch ((node: any).$$typeof) { case REACT_ELEMENT_TYPE: { const element: any = node; const type = element.type; @@ -1221,7 +1171,7 @@ function pushComponentStack(task: Task): void { } case REACT_LAZY_TYPE: { if (__DEV__) { - const lazyNode: LazyComponentType<any, any> = node as any; + const lazyNode: LazyComponentType<any, any> = (node: any); pushServerComponentStack(task, lazyNode._debugInfo); } break; @@ -1230,7 +1180,7 @@ function pushComponentStack(task: Task): void { if (__DEV__) { const maybeUsable: Object = node; if (typeof maybeUsable.then === 'function') { - const thenable: Thenable<ReactNodeList> = maybeUsable as any; + const thenable: Thenable<ReactNodeList> = (maybeUsable: any); pushServerComponentStack(task, thenable._debugInfo); } } @@ -1413,7 +1363,7 @@ function renderSuspenseBoundary( } return; } - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. const task: RenderTask = someTask; const prevKeyPath = task.keyPath; @@ -1488,7 +1438,7 @@ function renderSuspenseBoundary( const fallbackReplayNode: ReplayNode = [ fallbackKeyPath[1], fallbackKeyPath[2], - [] as Array<ReplayNode>, + ([]: Array<ReplayNode>), null, ]; trackedPostpones.workingMap.set(fallbackKeyPath, fallbackReplayNode); @@ -1512,6 +1462,7 @@ function renderSuspenseBoundary( replaceSuspenseComponentStackWithSuspenseFallbackStack( suspenseComponentStack, ); + boundarySegment.status = RENDERING; try { renderNode(request, task, fallback, -1); pushSegmentFinale( @@ -1523,7 +1474,7 @@ function renderSuspenseBoundary( boundarySegment.status = COMPLETED; finishedSegment(request, parentBoundary, boundarySegment); } catch (thrownValue: mixed) { - if (request.aborted) { + if (request.status === ABORTING) { boundarySegment.status = ABORTED; } else { boundarySegment.status = ERRORED; @@ -1586,6 +1537,8 @@ function renderSuspenseBoundary( prevContext, ); task.row = null; + contentRootSegment.status = RENDERING; + try { // We use the safe form because we don't handle suspending here. Only error handling. renderNode(request, task, content, -1); @@ -1631,7 +1584,7 @@ function renderSuspenseBoundary( } catch (thrownValue: mixed) { newBoundary.status = CLIENT_RENDERED; let error: mixed; - if (request.aborted) { + if (request.status === ABORTING) { contentRootSegment.status = ABORTED; error = request.fatalError; } else { @@ -1780,9 +1733,8 @@ function replaySuspenseBoundary( // faster return; } - } catch (thrownValue: mixed) { + } catch (error: mixed) { resumedBoundary.status = CLIENT_RENDERED; - const error = request.aborted ? request.fatalError : thrownValue; const thrownInfo = getThrownInfo(task.componentStack); const errorDigest = logRecoverableError( request, @@ -1987,10 +1939,7 @@ function renderSuspenseListRows( previousDebugTask = task.debugTask; // We read debugInfo from task.node.props.children instead of rows because it // might have been an unwrapped iterable so we read from the original node. - pushServerComponentStack( - task, - (task.node as any).props.children._debugInfo, - ); + pushServerComponentStack(task, (task.node: any).props.children._debugInfo); } task.keyPath = keyPath; @@ -2055,7 +2004,7 @@ function renderSuspenseListRows( } } } else { - task = task as any as RenderTask; // Refined + task = ((task: any): RenderTask); // Refined if ( revealOrder !== 'backwards' && revealOrder !== 'unstable_legacy-backwards' @@ -2121,7 +2070,7 @@ function renderSuspenseListRows( finishSuspenseListRow(request, previousSuspenseListRow); } } catch (thrownValue: mixed) { - if (request.aborted) { + if (request.status === ABORTING) { newSegment.status = ABORTED; } else { newSegment.status = ERRORED; @@ -2198,14 +2147,14 @@ function renderSuspenseList( } if ( enableAsyncIterableChildren && - typeof (children as any)[ASYNC_ITERATOR] === 'function' + typeof (children: any)[ASYNC_ITERATOR] === 'function' ) { - const iterator: AsyncIterator<ReactNodeList> = (children as any)[ + const iterator: AsyncIterator<ReactNodeList> = (children: any)[ ASYNC_ITERATOR ](); if (iterator) { if (__DEV__) { - validateAsyncIterable(task, children as any, -1, iterator); + validateAsyncIterable(task, (children: any), -1, iterator); } // TODO: Update the task.children to be the iterator to avoid asking // for new iterators, but we currently warn for rendering these @@ -2226,12 +2175,11 @@ function renderSuspenseList( let done = false; - // $FlowFixMe[invalid-compare] if (iterator === children) { // If it's an iterator we need to continue reading where we left // off. We can do that by reading the first few rows from the previous // thenable state. - // $FlowFixMe[underconstrained-implicit-instantiation] + // $FlowFixMe let step = readPreviousThenableFromState(); while (step !== undefined) { if (step.done) { @@ -2304,6 +2252,7 @@ function renderPreamble( blockedSegment.preambleChildren.push(preambleSegment); task.blockedSegment = preambleSegment; try { + preambleSegment.status = RENDERING; renderNode(request, task, node, -1); pushSegmentFinale( preambleSegment.chunks, @@ -2367,8 +2316,8 @@ function renderHostElement( props, )); if (isPreambleContext(newContext)) { - // $FlowFixMe[incompatible-type]: Refined - renderPreamble(request, task as RenderTask, segment, children); + // $FlowFixMe: Refined + renderPreamble(request, (task: RenderTask), segment, children); } else { // We use the non-destructive form because if something suspends, we still // need to pop back up and finish this subtree of HTML. @@ -2434,11 +2383,11 @@ function finishClassComponent( ): ReactNodeList { let nextChildren; if (__DEV__) { - nextChildren = callRenderInDEV(instance) as any; + nextChildren = (callRenderInDEV(instance): any); } else { nextChildren = instance.render(); } - if (request.aborted) { + if (request.status === ABORTING) { // eslint-disable-next-line no-throw-literal throw null; } @@ -2487,7 +2436,7 @@ export function resolveClassComponentProps( // Remove ref from the props object, if it exists. if ('ref' in baseProps) { - newProps = {} as any; + newProps = ({}: any); for (const propName in baseProps) { if (propName !== 'ref') { newProps[propName] = baseProps[propName]; @@ -2587,7 +2536,7 @@ function renderFunctionComponent( props, legacyContext, ); - if (request.aborted) { + if (request.status === ABORTING) { // eslint-disable-next-line no-throw-literal throw null; } @@ -2744,7 +2693,7 @@ function renderForwardRef( // `ref` is just a prop now, but `forwardRef` expects it to not appear in // the props object. This used to happen in the JSX runtime, but now we do // it here. - propsWithoutRef = {} as {[string]: any}; + propsWithoutRef = ({}: {[string]: any}); for (const key in props) { // Since `ref` should only appear in props via the JSX transform, we can // assume that this is a plain object. So we don't need a @@ -2864,7 +2813,12 @@ function renderLazyComponent( const init = lazyComponent._init; Component = init(payload); } - if (request.aborted) { + if ( + request.status === ABORTING && + // We're going to discard this render anyway. + // We just need to reach the point where we suspended in dev. + (!__DEV__ || request.status !== STALLED_DEV) + ) { // eslint-disable-next-line no-throw-literal throw null; } @@ -2992,13 +2946,9 @@ function renderElement( // TODO: Delete in LegacyHidden. It's an unstable API only used in the // www build. As a migration step, we could add a special prop to Offscreen // that simulates the old behavior (no hiding, no change to effects). - // $FlowFixMe[invalid-compare] case REACT_LEGACY_HIDDEN_TYPE: - // $FlowFixMe[invalid-compare] -- falls through case REACT_STRICT_MODE_TYPE: - // $FlowFixMe[invalid-compare] -- falls through case REACT_PROFILER_TYPE: - // $FlowFixMe[invalid-compare] -- falls through case REACT_FRAGMENT_TYPE: { const prevKeyPath = task.keyPath; task.keyPath = keyPath; @@ -3006,17 +2956,14 @@ function renderElement( task.keyPath = prevKeyPath; return; } - // $FlowFixMe[invalid-compare] case REACT_ACTIVITY_TYPE: { renderActivity(request, task, keyPath, props); return; } - // $FlowFixMe[invalid-compare] case REACT_SUSPENSE_LIST_TYPE: { renderSuspenseList(request, task, keyPath, props); return; } - // $FlowFixMe[invalid-compare] case REACT_VIEW_TRANSITION_TYPE: { if (enableViewTransition) { renderViewTransition(request, task, keyPath, props); @@ -3024,7 +2971,6 @@ function renderElement( } // Fallthrough } - // $FlowFixMe[invalid-compare] case REACT_SCOPE_TYPE: { if (enableScopeAPI) { const prevKeyPath = task.keyPath; @@ -3035,40 +2981,33 @@ function renderElement( } throw new Error('ReactDOMServer does not yet support scope components.'); } - // $FlowFixMe[invalid-compare] case REACT_SUSPENSE_TYPE: { renderSuspenseBoundary(request, task, keyPath, props); return; } } - // $FlowFixMe[invalid-compare] if (typeof type === 'object' && type !== null) { switch (type.$$typeof) { - // $FlowFixMe[invalid-compare] case REACT_FORWARD_REF_TYPE: { renderForwardRef(request, task, keyPath, type, props, ref); return; } - // $FlowFixMe[invalid-compare] case REACT_MEMO_TYPE: { renderMemo(request, task, keyPath, type, props, ref); return; } - // $FlowFixMe[invalid-compare] case REACT_CONTEXT_TYPE: { const context = type; renderContextProvider(request, task, keyPath, context, props); return; } - // $FlowFixMe[invalid-compare] case REACT_CONSUMER_TYPE: { - const context: ReactContext<any> = (type as ReactConsumerType<any>) + const context: ReactContext<any> = (type: ReactConsumerType<any>) ._context; renderContextConsumer(request, task, keyPath, context, props); return; } - // $FlowFixMe[invalid-compare] case REACT_LAZY_TYPE: { renderLazyComponent(request, task, keyPath, type, props, ref); return; @@ -3081,7 +3020,6 @@ function renderElement( if ( type === undefined || (typeof type === 'object' && - // $FlowFixMe[invalid-compare] type !== null && Object.keys(type).length === 0) ) { @@ -3120,7 +3058,7 @@ function resumeNode( resumedSegment.parentFlushed = true; try { // Convert the current ReplayTask to a RenderTask. - const renderTask: RenderTask = task as any; + const renderTask: RenderTask = (task: any); renderTask.replay = null; renderTask.blockedSegment = resumedSegment; renderNode(request, task, node, childIndex); @@ -3167,7 +3105,7 @@ function replayElement( if (name !== null && name !== node[0]) { throw new Error( 'Expected the resume to render <' + - (node[0] as any) + + (node[0]: any) + '> in this slot but instead it rendered <' + name + '>. ' + @@ -3217,7 +3155,7 @@ function replayElement( erroredReplay( request, task.blockedBoundary, - request.aborted ? request.fatalError : x, + x, thrownInfo, childNodes, childSlots, @@ -3295,7 +3233,7 @@ function validateIterable( } didWarnAboutGenerators = true; } - } else if ((iterable as any).entries === iteratorFn) { + } else if ((iterable: any).entries === iteratorFn) { // Warn about using Maps as children if (!didWarnAboutMaps) { console.error( @@ -3408,7 +3346,7 @@ function retryNode(request: Request, task: Task): void { // Handle object types if (typeof node === 'object') { - switch ((node as any).$$typeof) { + switch ((node: any).$$typeof) { case REACT_ELEMENT_TYPE: { const element: any = node; const type = element.type; @@ -3489,7 +3427,7 @@ function retryNode(request: Request, task: Task): void { 'Render them conditionally so that they only appear on the client render.', ); case REACT_LAZY_TYPE: { - const lazyNode: LazyComponentType<any, any> = node as any; + const lazyNode: LazyComponentType<any, any> = (node: any); let resolvedNode; if (__DEV__) { resolvedNode = callLazyInitInDEV(lazyNode); @@ -3498,7 +3436,7 @@ function retryNode(request: Request, task: Task): void { const init = lazyNode._init; resolvedNode = init(payload); } - if (request.aborted) { + if (request.status === ABORTING) { // eslint-disable-next-line no-throw-literal throw null; } @@ -3540,14 +3478,14 @@ function retryNode(request: Request, task: Task): void { if ( enableAsyncIterableChildren && - typeof (node as any)[ASYNC_ITERATOR] === 'function' + typeof (node: any)[ASYNC_ITERATOR] === 'function' ) { - const iterator: AsyncIterator<ReactNodeList> = (node as any)[ + const iterator: AsyncIterator<ReactNodeList> = (node: any)[ ASYNC_ITERATOR ](); if (iterator) { if (__DEV__) { - validateAsyncIterable(task, node as any, childIndex, iterator); + validateAsyncIterable(task, (node: any), childIndex, iterator); } // TODO: Update the task.node to be the iterator to avoid asking // for new iterators, but we currently warn for rendering these @@ -3567,12 +3505,11 @@ function retryNode(request: Request, task: Task): void { let done = false; - // $FlowFixMe[invalid-compare] if (iterator === node) { // If it's an iterator we need to continue reading where we left // off. We can do that by reading the first few rows from the previous // thenable state. - // $FlowFixMe[underconstrained-implicit-instantiation] + // $FlowFixMe let step = readPreviousThenableFromState(); while (step !== undefined) { if (step.done) { @@ -3609,7 +3546,7 @@ function retryNode(request: Request, task: Task): void { if (typeof maybeUsable.then === 'function') { // Clear any previous thenable state that was created by the unwrapping. task.thenableState = null; - const thenable: Thenable<ReactNodeList> = maybeUsable as any; + const thenable: Thenable<ReactNodeList> = (maybeUsable: any); const result = renderNodeDestructive( request, task, @@ -3620,7 +3557,7 @@ function retryNode(request: Request, task: Task): void { } if (maybeUsable.$$typeof === REACT_CONTEXT_TYPE) { - const context: ReactContext<ReactNodeList> = maybeUsable as any; + const context: ReactContext<ReactNodeList> = (maybeUsable: any); return renderNodeDestructive( request, task, @@ -3733,7 +3670,7 @@ function replayFragment( erroredReplay( request, task.blockedBoundary, - request.aborted ? request.fatalError : x, + x, thrownInfo, childNodes, childSlots, @@ -3827,9 +3764,9 @@ function warnForMissingKey(request: Request, task: Task, child: mixed): void { const previousComponentStack = task.componentStack; const stackFrame = createComponentStackFromType( task.componentStack, - (child as any).type, - (child as any)._owner, - (child as any)._debugStack, + (child: any).type, + (child: any)._owner, + (child: any)._debugStack, ); task.componentStack = stackFrame; console.error( @@ -3855,14 +3792,14 @@ function renderChildrenArray( previousDebugTask = task.debugTask; // We read debugInfo from task.node instead of children because it might have been an // unwrapped iterable so we read from the original node. - pushServerComponentStack(task, (task.node as any)._debugInfo); + pushServerComponentStack(task, (task.node: any)._debugInfo); } if (childIndex !== -1) { task.keyPath = [task.keyPath, 'Fragment', childIndex]; if (task.replay !== null) { replayFragment( request, - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. task, children, childIndex, @@ -3974,7 +3911,7 @@ function trackPostponedBoundary( return suspenseBoundary; } else { // Upgrade to ReplaySuspenseBoundary. - const suspenseBoundary: ReplaySuspenseBoundary = boundaryNode as any; + const suspenseBoundary: ReplaySuspenseBoundary = (boundaryNode: any); suspenseBoundary[4] = fallbackReplayNode; suspenseBoundary[5] = boundary.rootSegmentID; return suspenseBoundary; @@ -4002,7 +3939,6 @@ function trackPostpone( return; } - // $FlowFixMe[invalid-compare] if (boundary !== null && boundary.status === PENDING) { const boundaryNode = trackPostponedBoundary( request, @@ -4034,7 +3970,6 @@ function trackPostpone( // We know that this will leave a hole so we might as well assign an ID now. // We might have one already if we had a parent that gave us its ID. if (segment.id === -1) { - // $FlowFixMe[invalid-compare] if (segment.parentFlushed && boundary !== null) { // If this segment's parent was already flushed, it means we really just // skipped the parent and this segment is now the root. @@ -4055,7 +3990,7 @@ function trackPostpone( resumableNode = [ keyPath[1], keyPath[2], - [] as Array<ReplayNode>, + ([]: Array<ReplayNode>), segment.id, ]; addToReplayParent(resumableNode, keyPath[0], trackedPostpones); @@ -4068,7 +4003,7 @@ function trackPostpone( if (keyPath === null) { slots = trackedPostpones.rootSlots; if (slots === null) { - slots = trackedPostpones.rootSlots = {} as {[index: number]: number}; + slots = trackedPostpones.rootSlots = ({}: {[index: number]: number}); } else if (typeof slots === 'number') { throw new Error( 'It should not be possible to postpone both at the root of an element ' + @@ -4079,19 +4014,19 @@ function trackPostpone( const workingMap = trackedPostpones.workingMap; let resumableNode = workingMap.get(keyPath); if (resumableNode === undefined) { - slots = {} as {[index: number]: number}; - resumableNode = [ + slots = ({}: {[index: number]: number}); + resumableNode = ([ keyPath[1], keyPath[2], - [] as Array<ReplayNode>, + ([]: Array<ReplayNode>), slots, - ] as ReplayNode; + ]: ReplayNode); workingMap.set(keyPath, resumableNode); addToReplayParent(resumableNode, keyPath[0], trackedPostpones); } else { slots = resumableNode[3]; if (slots === null) { - slots = resumableNode[3] = {} as {[index: number]: number}; + slots = resumableNode[3] = ({}: {[index: number]: number}); } else if (typeof slots === 'number') { throw new Error( 'It should not be possible to postpone both at the root of an element ' + @@ -4228,7 +4163,7 @@ function renderNode( const segment = task.blockedSegment; if (segment === null) { // Replay - task = task as any as ReplayTask; // Refined + task = ((task: any): ReplayTask); // Refined const previousReplaySet: ReplaySet = task.replay; try { return renderNodeDestructive(request, task, node, childIndex); @@ -4245,25 +4180,24 @@ function renderNode( getSuspendedThenable() : thrownValue; - if (request.aborted) { + if (request.status === ABORTING) { // We are aborting so we can just bubble up to the task by falling through - // $FlowFixMe[invalid-compare] } else if (typeof x === 'object' && x !== null) { // $FlowFixMe[method-unbinding] if (typeof x.then === 'function') { - const wakeable: Wakeable = x as any; + const wakeable: Wakeable = (x: any); const thenableState = thrownValue === SuspenseException ? getThenableStateAfterSuspending() : null; const newTask = spawnNewSuspendedReplayTask( request, - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. task, thenableState, ); const ping = newTask.ping; - wakeable.then(ping.resolve, ping.reject); + wakeable.then(ping, ping); // Restore the context. We assume that this will be restored by the inner // functions in case nothing throws so we don't use "finally" here. @@ -4294,7 +4228,7 @@ function renderNode( : null; const newTask = spawnNewSuspendedReplayTask( request, - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. task, thenableState, ); @@ -4347,25 +4281,24 @@ function renderNode( getSuspendedThenable() : thrownValue; - if (request.aborted) { + if (request.status === ABORTING) { // We are aborting so we can just bubble up to the task by falling through - // $FlowFixMe[invalid-compare] } else if (typeof x === 'object' && x !== null) { // $FlowFixMe[method-unbinding] if (typeof x.then === 'function') { - const wakeable: Wakeable = x as any; + const wakeable: Wakeable = (x: any); const thenableState = thrownValue === SuspenseException ? getThenableStateAfterSuspending() : null; const newTask = spawnNewSuspendedRenderTask( request, - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. task, thenableState, ); const ping = newTask.ping; - wakeable.then(ping.resolve, ping.reject); + wakeable.then(ping, ping); // Restore the context. We assume that this will be restored by the inner // functions in case nothing throws so we don't use "finally" here. @@ -4395,7 +4328,7 @@ function renderNode( : null; const newTask = spawnNewSuspendedRenderTask( request, - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. task, thenableState, ); @@ -4648,32 +4581,37 @@ function abortRemainingReplayNodes( // Empty the set if (typeof slots === 'object') { for (const index in slots) { - delete slots[index as any]; + delete slots[(index: any)]; } } } } -function abortTask(task: Task, request: Request): void { - // Mark pending tasks as aborted synchronously so any work that was already - // scheduled cannot begin after abort is called. - if (task === request.currentTask) { - // This is a currently rendering Task. The render itself will abort the task. - return; - } +function abortTask(task: Task, request: Request, error: mixed): void { + // This aborts the task and aborts the parent that it blocks, putting it into + // client rendered mode. const boundary = task.blockedBoundary; const segment = task.blockedSegment; if (segment !== null) { + if (segment.status === RENDERING) { + // This is the a currently rendering Segment. The render itself will + // abort the task. + return; + } segment.status = ABORTED; } + const errorInfo = getThrownInfo(task.componentStack); if (__DEV__ && enableAsyncDebugInfo) { - // Capture async debug information at the point abort begins. The task may - // receive more data before finishAbort runs and no longer suspend at the - // call site we need to report. + // If the task is not rendering, then this is an async abort. Conceptually it's as if + // the abort happened inside the async gap. The abort reason's stack frame won't have that + // on the stack so instead we use the owner stack and debug task of any halted async debug info. let node: any = task.node; if (node !== null && typeof node === 'object') { + // Push a fake component stack frame that represents the await. let debugInfo = node._debugInfo; + // First resolve lazy nodes to find debug info that has been transferred + // to the inner value. while ( typeof node === 'object' && node !== null && @@ -4691,9 +4629,7 @@ function abortTask(task: Task, request: Request): void { node !== null && (isArray(node) || typeof node[ASYNC_ITERATOR] === 'function' || - // $FlowFixMe[invalid-compare] node.$$typeof === REACT_ELEMENT_TYPE || - // $FlowFixMe[invalid-compare] node.$$typeof === REACT_LAZY_TYPE) && isArray(node._debugInfo) ) { @@ -4706,75 +4642,51 @@ function abortTask(task: Task, request: Request): void { } } - if (boundary !== null) { - boundary.fallbackAbortableTasks.forEach(fallbackTask => - abortTask(fallbackTask, request), - ); - } -} - -function finishAbortedTask(task: Task, request: Request, error: mixed): void { - // Report and complete a task that was synchronously claimed by abortTask. - // A currently rendering task remains responsible for unwinding itself. - if (task === request.currentTask) { - return; - } - const boundary = task.blockedBoundary; - const segment = task.blockedSegment; - if (segment !== null) { - if (segment.status !== ABORTED) { - return; - } - } - - const errorInfo = getThrownInfo(task.componentStack); - if (boundary === null) { - const replay: null | ReplaySet = task.replay; - if (replay === null) { - // We didn't complete the root so we have nothing to show. We can close - // the request; - if (request.trackedPostpones !== null && segment !== null) { - const trackedPostpones = request.trackedPostpones; - // We are aborting a prerender and must treat the shell as halted - // We log the error but we still resolve the prerender - logRecoverableError(request, error, errorInfo, task.debugTask); - trackPostpone(request, trackedPostpones, task, segment); - finishedTask(request, null, task.row, segment); - } else { - logRecoverableError(request, error, errorInfo, task.debugTask); - if (request.status !== CLOSING && request.status !== CLOSED) { + if (request.status !== CLOSING && request.status !== CLOSED) { + const replay: null | ReplaySet = task.replay; + if (replay === null) { + // We didn't complete the root so we have nothing to show. We can close + // the request; + if (request.trackedPostpones !== null && segment !== null) { + const trackedPostpones = request.trackedPostpones; + // We are aborting a prerender and must treat the shell as halted + // We log the error but we still resolve the prerender + logRecoverableError(request, error, errorInfo, task.debugTask); + trackPostpone(request, trackedPostpones, task, segment); + finishedTask(request, null, task.row, segment); + } else { + logRecoverableError(request, error, errorInfo, task.debugTask); fatalError(request, error, errorInfo, task.debugTask); } - } - return; - } - if (request.status !== CLOSING && request.status !== CLOSED) { - // If the shell aborts during a replay, that's not a fatal error. Instead - // we should be able to recover by client rendering all the root boundaries in - // the ReplaySet. - replay.pendingTasks--; - if (replay.pendingTasks === 0 && replay.nodes.length > 0) { - const errorDigest = logRecoverableError( - request, - error, - errorInfo, - null, - ); - abortRemainingReplayNodes( - request, - null, - replay.nodes, - replay.slots, - error, - errorDigest, - errorInfo, - true, - ); - } - request.pendingRootTasks--; - if (request.pendingRootTasks === 0) { - completeShell(request); + return; + } else { + // If the shell aborts during a replay, that's not a fatal error. Instead + // we should be able to recover by client rendering all the root boundaries in + // the ReplaySet. + replay.pendingTasks--; + if (replay.pendingTasks === 0 && replay.nodes.length > 0) { + const errorDigest = logRecoverableError( + request, + error, + errorInfo, + null, + ); + abortRemainingReplayNodes( + request, + null, + replay.nodes, + replay.slots, + error, + errorDigest, + errorInfo, + true, + ); + } + request.pendingRootTasks--; + if (request.pendingRootTasks === 0) { + completeShell(request); + } } } } else { @@ -4790,7 +4702,7 @@ function finishAbortedTask(task: Task, request: Request, error: mixed): void { // If this boundary was still pending then we haven't already cancelled its fallbacks. // We'll need to abort the fallbacks, which will also error that parent boundary. boundary.fallbackAbortableTasks.forEach(fallbackTask => - finishAbortedTask(fallbackTask, request, error), + abortTask(fallbackTask, request, error), ); boundary.fallbackAbortableTasks.clear(); return finishedTask(request, boundary, task.row, segment); @@ -4827,7 +4739,7 @@ function finishAbortedTask(task: Task, request: Request, error: mixed): void { // If this boundary was still pending then we haven't already cancelled its fallbacks. // We'll need to abort the fallbacks, which will also error that parent boundary. boundary.fallbackAbortableTasks.forEach(fallbackTask => - finishAbortedTask(fallbackTask, request, error), + abortTask(fallbackTask, request, error), ); boundary.fallbackAbortableTasks.clear(); } @@ -4845,39 +4757,14 @@ function finishAbortedTask(task: Task, request: Request, error: mixed): void { } } -function finishAbortedTaskDEV( - task: Task, - request: Request, - error: mixed, -): void { - if (__DEV__) { - const prevTaskInDEV = currentTaskInDEV; - const prevGetCurrentStackImpl = ReactSharedInternals.getCurrentStack; - setCurrentTaskInDEV(task); - ReactSharedInternals.getCurrentStack = getCurrentStackInDEV; - try { - finishAbortedTask(task, request, error); - } finally { - setCurrentTaskInDEV(prevTaskInDEV); - ReactSharedInternals.getCurrentStack = prevGetCurrentStackImpl; - } - } else { - // These errors should never make it into a build so we don't need to encode them in codes.json - // eslint-disable-next-line react-internal/prod-error-codes - throw new Error( - 'finishAbortedTaskDEV should never be called in production mode. This is a bug in React.', - ); - } -} - -function abortTaskDEV(task: Task, request: Request): void { +function abortTaskDEV(task: Task, request: Request, error: mixed): void { if (__DEV__) { const prevTaskInDEV = currentTaskInDEV; const prevGetCurrentStackImpl = ReactSharedInternals.getCurrentStack; setCurrentTaskInDEV(task); ReactSharedInternals.getCurrentStack = getCurrentStackInDEV; try { - abortTask(task, request); + abortTask(task, request, error); } finally { setCurrentTaskInDEV(prevTaskInDEV); ReactSharedInternals.getCurrentStack = prevGetCurrentStackImpl; @@ -4891,23 +4778,6 @@ function abortTaskDEV(task: Task, request: Request): void { } } -function abortUnwoundTask(task: Task, request: Request): void { - // This task was rendering when abort began, so the synchronous abort sweep - // left it alone. It has now unwound from user code and can be completed - // through the normal abort path. - if (__DEV__) { - abortTaskDEV(task, request); - } else { - abortTask(task, request); - } - task.abortSet.delete(task); - if (__DEV__) { - finishAbortedTaskDEV(task, request, request.fatalError); - } else { - finishAbortedTask(task, request, request.fatalError); - } -} - function safelyEmitEarlyPreloads( request: Request, shellComplete: boolean, @@ -5010,7 +4880,6 @@ function finishedSegment( boundary: Root | SuspenseBoundary, segment: Segment, ) { - // $FlowFixMe[invalid-compare] if (byteLengthOfChunk !== null) { // Count the bytes of all the chunks of this segment. const chunks = segment.chunks; @@ -5180,13 +5049,13 @@ function retryTask(request: Request, task: Task): void { if (segment === null) { retryReplayTask( request, - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. task, ); } else { retryRenderTask( request, - // $FlowFixMe[incompatible-type]: Refined. + // $FlowFixMe: Refined. task, segment, ); @@ -5203,8 +5072,8 @@ function retryRenderTask( return; } - const prevTask = request.currentTask; - request.currentTask = task; + // We track when a Segment is rendering so we can handle aborts while rendering + segment.status = RENDERING; // We restore the context to what it was when we suspended. // We don't restore it after we leave because it's likely that we'll end up @@ -5249,22 +5118,25 @@ function retryRenderTask( // (unstable) API for suspending. This implementation detail can change // later, once we deprecate the old API in favor of `use`. getSuspendedThenable() - : thrownValue; + : request.status === ABORTING + ? request.fatalError + : thrownValue; - if (request.aborted) { - if (thrownValue === SuspenseException) { - // This task was rendering when abort() was called, so it never took - // the normal suspension path below that stores the thenable state. - // Preserve it before finishing the abort so DEV can replay the task - // and include this suspended use() call site in the owner stack. - task.thenableState = getThenableStateAfterSuspending(); - } - // The task has unwound from user code, so it must no longer appear to - // be the currently rendering task while we synchronously finish it. - // Restore the parent instead of clearing this field because finishing - // can reenter Fizz and abort an outer render that is still on the stack. - request.currentTask = prevTask; - abortUnwoundTask(task, request); + if (request.status === ABORTING && request.trackedPostpones !== null) { + // We are aborting a prerender and need to halt this task. + const trackedPostpones = request.trackedPostpones; + const thrownInfo = getThrownInfo(task.componentStack); + task.abortSet.delete(task); + + logRecoverableError( + request, + x, + thrownInfo, + __DEV__ ? task.debugTask : null, + ); + + trackPostpone(request, trackedPostpones, task, segment); + finishedTask(request, task.blockedBoundary, task.row, segment); return; } @@ -5279,7 +5151,7 @@ function retryRenderTask( : null; const ping = task.ping; // We've asserted that x is a thenable above - (x as any).then(ping.resolve, ping.reject); + (x: any).then(ping, ping); return; } } @@ -5298,7 +5170,6 @@ function retryRenderTask( ); return; } finally { - request.currentTask = prevTask; if (__DEV__) { setCurrentTaskInDEV(prevTaskInDEV); } @@ -5311,9 +5182,6 @@ function retryReplayTask(request: Request, task: ReplayTask): void { return; } - const prevTask = request.currentTask; - request.currentTask = task; - // We restore the context to what it was when we suspended. // We don't restore it after we leave because it's likely that we'll end up // needing a very similar context soon again. @@ -5357,30 +5225,12 @@ function retryReplayTask(request: Request, task: ReplayTask): void { getSuspendedThenable() : thrownValue; - if (request.aborted) { - if (thrownValue === SuspenseException) { - // This task was rendering when abort() was called, so it never took - // the normal suspension path below that stores the thenable state. - // Preserve it before finishing the abort so DEV can replay the task - // and include this suspended use() call site in the owner stack. - task.thenableState = getThenableStateAfterSuspending(); - } - // The task has unwound from user code, so it must no longer appear to - // be the currently rendering task while we synchronously finish it. - // Restore the parent instead of clearing this field because finishing - // can reenter Fizz and abort an outer render that is still on the stack. - request.currentTask = prevTask; - abortUnwoundTask(task, request); - return; - } - - // $FlowFixMe[invalid-compare] if (typeof x === 'object' && x !== null) { // $FlowFixMe[method-unbinding] if (typeof x.then === 'function') { // Something suspended again, let's pick it back up later. const ping = task.ping; - x.then(ping.resolve, ping.reject); + x.then(ping, ping); task.thenableState = thrownValue === SuspenseException ? getThenableStateAfterSuspending() @@ -5394,7 +5244,7 @@ function retryReplayTask(request: Request, task: ReplayTask): void { erroredReplay( request, task.blockedBoundary, - request.aborted ? request.fatalError : x, + request.status === ABORTING ? request.fatalError : x, errorInfo, task.replay.nodes, task.replay.slots, @@ -5410,7 +5260,6 @@ function retryReplayTask(request: Request, task: ReplayTask): void { } return; } finally { - request.currentTask = prevTask; if (__DEV__) { setCurrentTaskInDEV(prevTaskInDEV); } @@ -5418,7 +5267,7 @@ function retryReplayTask(request: Request, task: ReplayTask): void { } export function performWork(request: Request): void { - if (request.aborted || request.status > OPEN) { + if (request.status === CLOSED || request.status === CLOSING) { return; } const prevContext = getActiveContext(); @@ -6180,48 +6029,39 @@ function flushCompletedQueues( } export function startWork(request: Request): void { - // $FlowFixMe[constant-condition] - if (isWorkLoopExternallyDriven) { - return; + request.flushScheduled = request.destination !== null; + // When prerendering we use microtasks for pinging work + if (supportsRequestStorage) { + scheduleMicrotask(() => requestStorage.run(request, performWork, request)); } else { - request.flushScheduled = request.destination !== null; - // When prerendering we use microtasks for pinging work - // $FlowFixMe[constant-condition] - if (supportsRequestStorage) { - scheduleMicrotask(() => - requestStorage.run(request, performWork, request), - ); - } else { - scheduleMicrotask(() => performWork(request)); - } - scheduleWork(() => { - if (request.status === OPENING) { - request.status = OPEN; - } - - if (request.trackedPostpones === null) { - // this is either a regular render or a resume. For regular render we want - // to call emitEarlyPreloads after the first performWork because we want - // are responding to a live request and need to balance sending something early - // (i.e. don't want for the shell to finish) but we need something to send. - // The only implementation of this is for DOM at the moment and during resumes nothing - // actually emits but the code paths here are the same. - // During a prerender we don't want to be too aggressive in emitting early preloads - // because we aren't responding to a live request and we can wait for the prerender to - // postpone before we emit anything. - // $FlowFixMe[constant-condition] - if (supportsRequestStorage) { - requestStorage.run( - request, - enqueueEarlyPreloadsAfterInitialWork, - request, - ); - } else { - enqueueEarlyPreloadsAfterInitialWork(request); - } + scheduleMicrotask(() => performWork(request)); + } + scheduleWork(() => { + if (request.status === OPENING) { + request.status = OPEN; + } + + if (request.trackedPostpones === null) { + // this is either a regular render or a resume. For regular render we want + // to call emitEarlyPreloads after the first performWork because we want + // are responding to a live request and need to balance sending something early + // (i.e. don't want for the shell to finish) but we need something to send. + // The only implementation of this is for DOM at the moment and during resumes nothing + // actually emits but the code paths here are the same. + // During a prerender we don't want to be too aggressive in emitting early preloads + // because we aren't responding to a live request and we can wait for the prerender to + // postpone before we emit anything. + if (supportsRequestStorage) { + requestStorage.run( + request, + enqueueEarlyPreloadsAfterInitialWork, + request, + ); + } else { + enqueueEarlyPreloadsAfterInitialWork(request); } - }); - } + } + }); } function enqueueEarlyPreloadsAfterInitialWork(request: Request) { @@ -6301,16 +6141,30 @@ export function stopFlowing(request: Request): void { request.destination = null; } -function finishAbort(request: Request, abortableTasks: Set<Task>): void { +// This is called to early terminate a request. It puts all pending boundaries in client rendered state. +export function abort(request: Request, reason: mixed): void { + if (request.status === OPEN || request.status === OPENING) { + request.status = ABORTING; + } + try { + const abortableTasks = request.abortableTasks; if (abortableTasks.size > 0) { - const error = request.fatalError; + const error = + reason === undefined + ? new Error('The render was aborted by the server without a reason.') + : typeof reason === 'object' && + reason !== null && + typeof reason.then === 'function' + ? new Error('The render was aborted by the server with a promise.') + : reason; + // This error isn't necessarily fatal in this case but we need to stash it + // so we can use it to abort any pending work + request.fatalError = error; if (__DEV__) { - abortableTasks.forEach(task => - finishAbortedTaskDEV(task, request, error), - ); + abortableTasks.forEach(task => abortTaskDEV(task, request, error)); } else { - abortableTasks.forEach(task => finishAbortedTask(task, request, error)); + abortableTasks.forEach(task => abortTask(task, request, error)); } abortableTasks.clear(); } @@ -6324,40 +6178,6 @@ function finishAbort(request: Request, abortableTasks: Set<Task>): void { } } -// This is called to early terminate a request. It puts all pending boundaries in client rendered state. -export function abort(request: Request, reason: mixed): void { - if ( - request.aborted || - (request.status !== OPEN && request.status !== OPENING) - ) { - // Only requests that are not already complete or in the process of aborting - // can be aborted. in practice this makes abort callable at most once per render. - return; - } - request.aborted = true; - const error = - reason === undefined - ? new Error('The render was aborted by the server without a reason.') - : typeof reason === 'object' && - reason !== null && - typeof reason.then === 'function' - ? new Error('The render was aborted by the server with a promise.') - : reason; - // This error isn't necessarily fatal in this case but we need to stash it - // so we can use it to abort any pending work. - request.fatalError = error; - const abortableTasks = request.abortableTasks; - if (__DEV__) { - abortableTasks.forEach(task => abortTaskDEV(task, request)); - } else { - abortableTasks.forEach(task => abortTask(task, request)); - } - // Even though this looks async some renderers schedule work sync - // So it is important that the finish step does not assume the stack - // has unwinded yet - scheduleWork(() => finishAbort(request, abortableTasks)); -} - export function flushResources(request: Request): void { enqueueFlush(request); } @@ -6387,12 +6207,12 @@ function addToReplayParent( const workingMap = trackedPostpones.workingMap; let parentNode = workingMap.get(parentKeyPath); if (parentNode === undefined) { - parentNode = [ + parentNode = ([ parentKeyPath[1], parentKeyPath[2], - [] as Array<ReplayNode>, + ([]: Array<ReplayNode>), null, - ] as ReplayNode; + ]: ReplayNode); workingMap.set(parentKeyPath, parentNode); addToReplayParent(parentNode, parentKeyPath[0], trackedPostpones); } diff --git a/packages/react-server/src/ReactFizzThenable.js b/packages/react-server/src/ReactFizzThenable.js index ac00ee754529..ea6fbec7a5db 100644 --- a/packages/react-server/src/ReactFizzThenable.js +++ b/packages/react-server/src/ReactFizzThenable.js @@ -103,19 +103,19 @@ export function trackUsedThenable<T>( // happen. Flight lazily parses JSON when the value is actually awaited. thenable.then(noop, noop); } else { - const pendingThenable: PendingThenable<T> = thenable as any; + const pendingThenable: PendingThenable<T> = (thenable: any); pendingThenable.status = 'pending'; pendingThenable.then( fulfilledValue => { if (thenable.status === 'pending') { - const fulfilledThenable: FulfilledThenable<T> = thenable as any; + const fulfilledThenable: FulfilledThenable<T> = (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = fulfilledValue; } }, (error: mixed) => { if (thenable.status === 'pending') { - const rejectedThenable: RejectedThenable<T> = thenable as any; + const rejectedThenable: RejectedThenable<T> = (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; } @@ -124,13 +124,13 @@ export function trackUsedThenable<T>( } // Check one more time in case the thenable resolved synchronously - switch ((thenable as Thenable<T>).status) { + switch ((thenable: Thenable<T>).status) { case 'fulfilled': { - const fulfilledThenable: FulfilledThenable<T> = thenable as any; + const fulfilledThenable: FulfilledThenable<T> = (thenable: any); return fulfilledThenable.value; } case 'rejected': { - const rejectedThenable: RejectedThenable<T> = thenable as any; + const rejectedThenable: RejectedThenable<T> = (thenable: any); throw rejectedThenable.reason; } } @@ -160,7 +160,7 @@ export function readPreviousThenable<T>( return undefined; } else { // We assume this has been resolved already. - return (previous as any).value; + return (previous: any).value; } } @@ -277,10 +277,10 @@ export function ensureSuspendableThenableStateDEV( switch (lastThenable.status) { case 'fulfilled': { const previousThenableValue = lastThenable.value; - // $FlowFixMe[method-unbinding] We rebind .then immediately. + // $FlowIgnore[method-unbinding] We rebind .then immediately. const previousThenableThen = lastThenable.then.bind(lastThenable); delete lastThenable.value; - delete (lastThenable as any).status; + delete (lastThenable: any).status; // We'll call .then again if we resuspend. Since we potentially corrupted // the internal state of unknown classes, we need to diffuse the potential // crash by replacing the .then method with a noop. @@ -295,10 +295,10 @@ export function ensureSuspendableThenableStateDEV( } case 'rejected': { const previousThenableReason = lastThenable.reason; - // $FlowFixMe[method-unbinding] We rebind .then immediately. + // $FlowIgnore[method-unbinding] We rebind .then immediately. const previousThenableThen = lastThenable.then.bind(lastThenable); delete lastThenable.reason; - delete (lastThenable as any).status; + delete (lastThenable: any).status; // We'll call .then again if we resuspend. Since we potentially corrupted // the internal state of unknown classes, we need to diffuse the potential // crash by replacing the .then method with a noop. diff --git a/packages/react-server/src/ReactFizzViewTransitionComponent.js b/packages/react-server/src/ReactFizzViewTransitionComponent.js index 739e4318ee4b..d2e6c0e120f3 100644 --- a/packages/react-server/src/ReactFizzViewTransitionComponent.js +++ b/packages/react-server/src/ReactFizzViewTransitionComponent.js @@ -36,7 +36,6 @@ function getClassNameByType(classByType: ?ViewTransitionClass): ?string { for (let i = 0; i < activeTypes.length; i++) { const match = classByType[activeTypes[i]]; if (match != null) { - // $FlowFixMe[invalid-compare] if (match === 'none') { // If anything matches "none" that takes precedence over any other // type that also matches. diff --git a/packages/react-server/src/ReactFlightActionServer.js b/packages/react-server/src/ReactFlightActionServer.js index 68fdc464ca9b..a3a47a9f9793 100644 --- a/packages/react-server/src/ReactFlightActionServer.js +++ b/packages/react-server/src/ReactFlightActionServer.js @@ -52,7 +52,7 @@ function loadServerReference<T>( ): Promise<T> { const id: ServerReferenceId = metaData.id; if (typeof id !== 'string') { - return null as any; + return (null: any); } const serverReference: ServerReference<T> = resolveServerReference<$FlowFixMe>(bundlerConfig, id); @@ -62,7 +62,7 @@ function loadServerReference<T>( const preloadPromise = preloadModule(serverReference); const bound = metaData.bound; if (bound instanceof Promise) { - return Promise.all([bound as any, preloadPromise]).then( + return Promise.all([(bound: any), preloadPromise]).then( ([args]: Array<any>) => bindArgs(requireModule(serverReference), args), ); } else if (preloadPromise) { @@ -95,10 +95,10 @@ function decodeBoundActionMetaData( bound: null | Promise<Array<any>>, }>(actionResponse); // Force it to initialize - // $FlowFixMe[incompatible-type] + // $FlowFixMe refPromise.then(() => {}); if (refPromise.status !== 'fulfilled') { - // $FlowFixMe[prop-missing] + // $FlowFixMe throw refPromise.reason; } return refPromise.value; @@ -118,7 +118,7 @@ export function decodeAction<T>( // $FlowFixMe[prop-missing] body.forEach((value: string | File, key: string) => { if (!key.startsWith('$ACTION_')) { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] formData.append(key, value); return; } diff --git a/packages/react-server/src/ReactFlightCallUserSpace.js b/packages/react-server/src/ReactFlightCallUserSpace.js index 87b54c20ae3a..becd0e1d71ab 100644 --- a/packages/react-server/src/ReactFlightCallUserSpace.js +++ b/packages/react-server/src/ReactFlightCallUserSpace.js @@ -41,8 +41,8 @@ export const callComponentInDEV: <Props, R>( componentDebugInfo: ReactComponentInfo, ) => R = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. - (callComponent.react_stack_bottom_frame.bind(callComponent) as any) - : (null as any); + (callComponent.react_stack_bottom_frame.bind(callComponent): any) + : (null: any); const callLazyInit = { react_stack_bottom_frame: function (lazy: LazyComponent<any, any>): any { @@ -54,8 +54,8 @@ const callLazyInit = { export const callLazyInitInDEV: (lazy: LazyComponent<any, any>) => any = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. - (callLazyInit.react_stack_bottom_frame.bind(callLazyInit) as any) - : (null as any); + (callLazyInit.react_stack_bottom_frame.bind(callLazyInit): any) + : (null: any); const callIterator = { react_stack_bottom_frame: function ( @@ -81,5 +81,5 @@ export const callIteratorInDEV: ( error: (reason: mixed) => void, ) => void = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. - (callIterator.react_stack_bottom_frame.bind(callIterator) as any) - : (null as any); + (callIterator.react_stack_bottom_frame.bind(callIterator): any) + : (null: any); diff --git a/packages/react-server/src/ReactFlightHooks.js b/packages/react-server/src/ReactFlightHooks.js index 23d15618a885..d814de462982 100644 --- a/packages/react-server/src/ReactFlightHooks.js +++ b/packages/react-server/src/ReactFlightHooks.js @@ -50,7 +50,7 @@ export function getThenableStateAfterSuspending(): ThenableState { if (__DEV__) { // This is a hack but we stash the debug info here so that we don't need a completely // different data structure just for this in DEV. Not too happy about it. - (state as any)._componentDebugInfo = currentComponentDebugInfo; + (state: any)._componentDebugInfo = currentComponentDebugInfo; currentComponentDebugInfo = null; } thenableState = null; @@ -64,32 +64,32 @@ export function getTrackedThenablesAfterRendering(): null | Array< } export const HooksDispatcher: Dispatcher = { - readContext: unsupportedContext as any, + readContext: (unsupportedContext: any), use, useCallback<T>(callback: T): T { return callback; }, - useContext: unsupportedContext as any, - useEffect: unsupportedHook as any, - useImperativeHandle: unsupportedHook as any, - useLayoutEffect: unsupportedHook as any, - useInsertionEffect: unsupportedHook as any, + useContext: (unsupportedContext: any), + useEffect: (unsupportedHook: any), + useImperativeHandle: (unsupportedHook: any), + useLayoutEffect: (unsupportedHook: any), + useInsertionEffect: (unsupportedHook: any), useMemo<T>(nextCreate: () => T): T { return nextCreate(); }, - useReducer: unsupportedHook as any, - useRef: unsupportedHook as any, - useState: unsupportedHook as any, + useReducer: (unsupportedHook: any), + useRef: (unsupportedHook: any), + useState: (unsupportedHook: any), useDebugValue(): void {}, - useDeferredValue: unsupportedHook as any, - useTransition: unsupportedHook as any, - useSyncExternalStore: unsupportedHook as any, + useDeferredValue: (unsupportedHook: any), + useTransition: (unsupportedHook: any), + useSyncExternalStore: (unsupportedHook: any), useId, - useHostTransitionStatus: unsupportedHook as any, - useFormState: unsupportedHook as any, - useActionState: unsupportedHook as any, - useOptimistic: unsupportedHook as any, + useHostTransitionStatus: (unsupportedHook: any), + useFormState: (unsupportedHook: any), + useActionState: (unsupportedHook: any), + useOptimistic: (unsupportedHook: any), useMemoCache(size: number): Array<any> { const data = new Array<any>(size); for (let i = 0; i < size; i++) { @@ -100,7 +100,7 @@ export const HooksDispatcher: Dispatcher = { useCacheRefresh(): <T>(?() => T, ?T) => void { return unsupportedRefresh; }, - useEffectEvent: unsupportedHook as any, + useEffectEvent: (unsupportedHook: any), }; function unsupportedHook(): void { @@ -128,14 +128,13 @@ function useId(): string { function use<T>(usable: Usable<T>): T { if ( - // $FlowFixMe[invalid-compare] (usable !== null && typeof usable === 'object') || typeof usable === 'function' ) { // $FlowFixMe[method-unbinding] if (typeof usable.then === 'function') { // This is a thenable. - const thenable: Thenable<T> = usable as any; + const thenable: Thenable<T> = (usable: any); // Track the position of the thenable within this fiber. const index = thenableIndexCounter; diff --git a/packages/react-server/src/ReactFlightReplyServer.js b/packages/react-server/src/ReactFlightReplyServer.js index 866584ff82a0..7248dc537678 100644 --- a/packages/react-server/src/ReactFlightReplyServer.js +++ b/packages/react-server/src/ReactFlightReplyServer.js @@ -70,7 +70,7 @@ const ERRORED = 'rejected'; const __PROTO__ = '__proto__'; type RESPONSE_SYMBOL_TYPE = 'RESPONSE_SYMBOL'; // Fake symbol type. -const RESPONSE_SYMBOL: RESPONSE_SYMBOL_TYPE = Symbol() as any; +const RESPONSE_SYMBOL: RESPONSE_SYMBOL_TYPE = (Symbol(): any); type PendingChunk<T> = { status: 'pending', @@ -124,7 +124,7 @@ function ReactPromise(status: any, value: any, reason: any) { this.reason = reason; } // We subclass Promise.prototype so that we get other methods like .catch -ReactPromise.prototype = Object.create(Promise.prototype) as any; +ReactPromise.prototype = (Object.create(Promise.prototype): any); // TODO: This doesn't return a new Promise chain unlike the real .then ReactPromise.prototype.then = function <T>( this: SomeChunk<T>, @@ -151,7 +151,6 @@ ReactPromise.prototype.then = function <T>( while (inspectedValue instanceof ReactPromise) { cycleProtection++; if ( - // $FlowFixMe[invalid-compare] inspectedValue === chunk || visited.has(inspectedValue) || cycleProtection > 1000 @@ -162,7 +161,6 @@ ReactPromise.prototype.then = function <T>( return; } visited.add(inspectedValue); - // $FlowFixMe[invalid-compare] if (inspectedValue.status === INITIALIZED) { inspectedValue = inspectedValue.value; } else { @@ -178,15 +176,15 @@ ReactPromise.prototype.then = function <T>( case BLOCKED: if (typeof resolve === 'function') { if (chunk.value === null) { - chunk.value = [] as Array<InitializationReference | (T => mixed)>; + chunk.value = ([]: Array<InitializationReference | (T => mixed)>); } chunk.value.push(resolve); } if (typeof reject === 'function') { if (chunk.reason === null) { - chunk.reason = [] as Array< + chunk.reason = ([]: Array< InitializationReference | (mixed => mixed), - >; + >); } chunk.reason.push(reject); } @@ -216,7 +214,7 @@ export type Response = { export function getRoot<T>(response: Response): Thenable<T> { const chunk = getChunk(response, 0); - return chunk as any; + return (chunk: any); } function createPendingChunk<T>(response: Response): PendingChunk<T> { @@ -301,14 +299,14 @@ function triggerErrorOnChunk<T>( if (chunk.status !== PENDING && chunk.status !== BLOCKED) { // If we get more data to an already resolved ID, we assume that it's // a stream chunk since any other row shouldn't have more than one entry. - const streamChunk: InitializedStreamChunk<any> = chunk as any; + const streamChunk: InitializedStreamChunk<any> = (chunk: any); const controller = streamChunk.reason; - // $FlowFixMe[incompatible-type]: The error method should accept mixed. + // $FlowFixMe[incompatible-call]: The error method should accept mixed. controller.error(error); return; } const listeners = chunk.reason; - const erroredChunk: ErroredChunk<T> = chunk as any; + const erroredChunk: ErroredChunk<T> = (chunk: any); erroredChunk.status = ERRORED; erroredChunk.reason = error; if (listeners !== null) { @@ -345,7 +343,7 @@ function resolveModelChunk<T>( if (chunk.status !== PENDING) { // If we get more data to an already resolved ID, we assume that it's // a stream chunk since any other row shouldn't have more than one entry. - const streamChunk: InitializedStreamChunk<any> = chunk as any; + const streamChunk: InitializedStreamChunk<any> = (chunk: any); const controller = streamChunk.reason; if (value[0] === 'C') { controller.close(value === 'C' ? '"$undefined"' : value.slice(1)); @@ -356,7 +354,7 @@ function resolveModelChunk<T>( } const resolveListeners = chunk.value; const rejectListeners = chunk.reason; - const resolvedChunk: ResolvedModelChunk<T> = chunk as any; + const resolvedChunk: ResolvedModelChunk<T> = (chunk: any); resolvedChunk.status = RESOLVED_MODEL; resolvedChunk.value = value; resolvedChunk.reason = {id, [RESPONSE_SYMBOL]: response}; @@ -421,27 +419,27 @@ function loadServerReference<A: Iterable<any>, T>( ): (...A) => Promise<T> { const id: ServerReferenceId = metaData.id; if (typeof id !== 'string') { - return null as any; + return (null: any); } if (key === 'then') { // This should never happen because we always serialize objects with then-functions // as "thenable" which reduces to ReactPromise with no other fields. - return null as any; + return (null: any); } // Check for a cached promise from a previous call with the same metadata. // This handles deduplication when the same server reference appears multiple // times in the payload. - const cachedPromise: SomeChunk<T> | void = (metaData as any).$$promise; + const cachedPromise: SomeChunk<T> | void = (metaData: any).$$promise; if (cachedPromise !== undefined) { if (cachedPromise.status === INITIALIZED) { // The value was already resolved by a previous call. const resolvedValue: T = cachedPromise.value; if (key === __PROTO__) { - return null as any; + return (null: any); } parentObject[key] = resolvedValue; - return resolvedValue as any; + return (resolvedValue: any); } // The promise is still blocked. Increment the handler dependency count ... @@ -465,14 +463,14 @@ function loadServerReference<A: Iterable<any>, T>( ); // Return a place holder value for now. - return null as any; + return (null: any); } // This is the first call for this server reference metadata. Create a cached // promise to be used for subsequent calls. // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors const blockedPromise: BlockedChunk<T> = new ReactPromise(BLOCKED, null, null); - (metaData as any).$$promise = blockedPromise; + (metaData: any).$$promise = blockedPromise; const serverReference: ServerReference<T> = resolveServerReference<$FlowFixMe>(response._bundlerConfig, id); @@ -486,9 +484,9 @@ function loadServerReference<A: Iterable<any>, T>( if (bound instanceof ReactPromise) { serverReferencePromise = Promise.resolve(bound); } else { - const resolvedValue = requireModule(serverReference) as any; + const resolvedValue = (requireModule(serverReference): any); // Resolve the cached promise synchronously. - const initializedPromise: InitializedChunk<T> = blockedPromise as any; + const initializedPromise: InitializedChunk<T> = (blockedPromise: any); initializedPromise.status = INITIALIZED; initializedPromise.value = resolvedValue; initializedPromise.reason = null; @@ -513,11 +511,11 @@ function loadServerReference<A: Iterable<any>, T>( } function fulfill(): void { - let resolvedValue = requireModule(serverReference) as any; + let resolvedValue = (requireModule(serverReference): any); if (metaData.bound) { // This promise is coming from us and should have initialized by now. - const promiseValue = (metaData.bound as any).value; + const promiseValue = (metaData.bound: any).value; const boundArgs: Array<any> = isArray(promiseValue) ? promiseValue.slice(0) : []; @@ -539,7 +537,7 @@ function loadServerReference<A: Iterable<any>, T>( // Resolve the cached promise so subsequent references can use the value. const resolveListeners = blockedPromise.value; - const initializedPromise: InitializedChunk<T> = blockedPromise as any; + const initializedPromise: InitializedChunk<T> = (blockedPromise: any); initializedPromise.status = INITIALIZED; initializedPromise.value = resolvedValue; initializedPromise.reason = null; @@ -555,7 +553,7 @@ function loadServerReference<A: Iterable<any>, T>( function reject(error: mixed): void { // Mark the cached promise as errored so subsequent references fail too. const rejectListeners = blockedPromise.reason; - const erroredPromise: ErroredChunk<T> = blockedPromise as any; + const erroredPromise: ErroredChunk<T> = (blockedPromise: any); erroredPromise.status = ERRORED; erroredPromise.value = null; erroredPromise.reason = error; @@ -571,7 +569,7 @@ function loadServerReference<A: Iterable<any>, T>( serverReferencePromise.then(fulfill, reject); // Return a place holder value for now. - return null as any; + return (null: any); } function reviveModel( @@ -608,10 +606,10 @@ function reviveModel( if (isArray(value)) { let childContext: NestedArrayContext; if (arrayRoot === null) { - childContext = { + childContext = ({ count: 0, fork: false, - } as NestedArrayContext; + }: NestedArrayContext); response._rootArrayContexts.set(value, childContext); } else { childContext = arrayRoot; @@ -731,7 +729,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void { // We go to the BLOCKED state until we've fully resolved this. // We do this before parsing in case we try to initialize the same chunk // while parsing the model. Such as in a cyclic reference. - const cyclicChunk: BlockedChunk<T> = chunk as any; + const cyclicChunk: BlockedChunk<T> = (chunk: any); cyclicChunk.status = BLOCKED; cyclicChunk.value = null; cyclicChunk.reason = null; @@ -783,12 +781,12 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void { return; } } - const initializedChunk: InitializedChunk<T> = chunk as any; + const initializedChunk: InitializedChunk<T> = (chunk: any); initializedChunk.status = INITIALIZED; initializedChunk.value = value; initializedChunk.reason = arrayRoot; } catch (error) { - const erroredChunk: ErroredChunk<T> = chunk as any; + const erroredChunk: ErroredChunk<T> = (chunk: any); erroredChunk.status = ERRORED; erroredChunk.reason = error; } finally { @@ -810,7 +808,7 @@ export function reportGlobalError(response: Response, error: Error): void { } else if (chunk.status === INITIALIZED) { const initializedChunk: | InitializedChunk<any> - | InitializedStreamChunk<any> = chunk as any; + | InitializedStreamChunk<any> = (chunk: any); if (initializedChunk.reason !== null) { const maybeController = initializedChunk.reason; // $FlowFixMe[method-unbinding] Just doing a typeof check @@ -947,7 +945,7 @@ function resolveReference( return; } const resolveListeners = chunk.value; - const initializedChunk: InitializedChunk<any> = chunk as any; + const initializedChunk: InitializedChunk<any> = (chunk: any); initializedChunk.status = INITIALIZED; initializedChunk.value = handler.value; initializedChunk.reason = @@ -1026,7 +1024,7 @@ function waitForReference<T>( } // Return a place holder value for now. - return null as any; + return (null: any); } function getOutlinedModel<T>( @@ -1043,8 +1041,8 @@ function getOutlinedModel<T>( switch (chunk.status) { case RESOLVED_MODEL: initializeModelChunk(chunk); - // $FlowFixMe[incompatible-type] We just initialized this chunk so it can't be a ResolvedModelChunk anymore. - chunk = chunk as Exclude<SomeChunk<T>, ResolvedModelChunk<T>>; + // $FlowFixMe[incompatible-cast] We just initialized this chunk so it can't be a ResolvedModelChunk anymore. + chunk = (chunk: Exclude<SomeChunk<T>, ResolvedModelChunk<T>>); break; } // The status might have changed after initialization. @@ -1069,7 +1067,6 @@ function getOutlinedModel<T>( const name = path[i]; if ( typeof value === 'object' && - // $FlowFixMe[invalid-compare] This check is still needed at runtime. value !== null && (getPrototypeOf(value) === ObjectPrototype || getPrototypeOf(value) === ArrayPrototype) && @@ -1080,8 +1077,8 @@ function getOutlinedModel<T>( localLength = 0; arrayRoot = rootArrayContexts.get( - // $FlowFixMe[incompatible-type] Our `isArray` typing can't narrow `mixed` - value as $ReadOnlyArray<mixed>, + // $FlowFixMe[incompatible-cast] Our `isArray` typing can't narrow `mixed` + (value: $ReadOnlyArray<mixed>), ) || arrayRoot; } else { arrayRoot = null; @@ -1155,7 +1152,7 @@ function getOutlinedModel<T>( }; } // Placeholder - return null as any; + return (null: any); } } @@ -1241,7 +1238,7 @@ function parseTypedArray<T: $ArrayBufferView | ArrayBuffer>( // We should have this backingEntry in the store already because we emitted // it before referencing it. It should be a Blob. - const backingEntry: Blob = getBackingEntry(response._formData, key) as any; + const backingEntry: Blob = (getBackingEntry(response._formData, key): any); const promise: Promise<ArrayBuffer> = backingEntry.arrayBuffer(); @@ -1270,8 +1267,8 @@ function parseTypedArray<T: $ArrayBufferView | ArrayBuffer>( const resolvedValue: T = constructor === ArrayBuffer - ? (buffer as any) - : (new constructor(buffer) as any); + ? (buffer: any) + : (new constructor(buffer): any); if (key !== __PROTO__) { parentObject[parentKey] = resolvedValue; @@ -1295,7 +1292,7 @@ function parseTypedArray<T: $ArrayBufferView | ArrayBuffer>( return; } const resolveListeners = chunk.value; - const initializedChunk: InitializedChunk<T> = chunk as any; + const initializedChunk: InitializedChunk<T> = (chunk: any); initializedChunk.status = INITIALIZED; initializedChunk.value = handler.value; // We don't keep an array count for this since it won't be referenced again. @@ -1367,7 +1364,7 @@ function parseReadableStream<T>( throw new Error('Already initialized stream.'); } - let controller: ReadableStreamController = null as any; + let controller: ReadableStreamController = (null: any); let closed = false; const stream = new ReadableStream({ type: type, @@ -1437,13 +1434,13 @@ function parseReadableStream<T>( } closed = true; if (previousBlockedChunk === null) { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] controller.error(error); } else { const blockedChunk = previousBlockedChunk; // We shouldn't get any more enqueues after this so we can set it back to null. previousBlockedChunk = null; - blockedChunk.then(() => controller.error(error as any)); + blockedChunk.then(() => controller.error((error: any))); } }, }; @@ -1461,7 +1458,7 @@ function FlightIterator( // TODO: The iterator could inherit the AsyncIterator prototype which is not exposed as // a global but exists as a prototype of an AsyncGenerator. However, it's not needed // to satisfy the iterable protocol. -FlightIterator.prototype = {} as any; +FlightIterator.prototype = ({}: any); FlightIterator.prototype[ASYNC_ITERATOR] = function asyncIterator( this: $AsyncIterator<any, any, void>, ) { @@ -1667,7 +1664,7 @@ function parseModelString( ); const referencedFormDataKey = formDataKey.slice(formPrefix.length); for (let i = 0; i < referencedFormDataValue.length; i++) { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] data.append(referencedFormDataKey, referencedFormDataValue[i]); } consumeBackingEntry(backingFormData, formDataKey); @@ -1868,10 +1865,10 @@ function parseModelString( const blobKey = prefix + id; // We should have this backingEntry in the store already because we emitted // it before referencing it. It should be a Blob. - const backingEntry: Blob = getBackingEntry( + const backingEntry: Blob = (getBackingEntry( response._formData, blobKey, - ) as any; + ): any); if (!(backingEntry instanceof Blob)) { throw new Error('Referenced Blob is not a Blob.'); } diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 9e46915fee44..7f6376821b9b 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -23,7 +23,6 @@ import { scheduleMicrotask, flushBuffered, beginWriting, - writeChunk, writeChunkAndReturn, stringToChunk, typedArrayToBinaryChunk, @@ -166,7 +165,7 @@ import { } from './ReactFlightAsyncSequence'; // DEV-only set containing internal objects that should not be limited and turned into getters. -const doNotLimit: WeakSet<Reference> = __DEV__ ? new WeakSet() : (null as any); +const doNotLimit: WeakSet<Reference> = __DEV__ ? new WeakSet() : (null: any); function defaultFilterStackFrame( filename: string, @@ -287,7 +286,7 @@ function filterStackTrace( if (filterStackFrame(url, functionName, lineNumber, columnNumber)) { // Use a clone because the Flight protocol isn't yet resilient to deduping // objects in the debug info. TODO: Support deduping stacks. - const clone: ReactCallSite = callsite.slice(0) as any; + const clone: ReactCallSite = (callsite.slice(0): any); clone[1] = url; filteredStack.push(clone); } @@ -393,7 +392,7 @@ function patchConsole(consoleInst: typeof console, methodName: string) { ) { const originalMethod = descriptor.value; const originalName = Object.getOwnPropertyDescriptor( - // $FlowFixMe[incompatible-type]: We should be able to get descriptors from any function. + // $FlowFixMe[incompatible-call]: We should be able to get descriptors from any function. originalMethod, 'name', ); @@ -422,7 +421,6 @@ function patchConsole(consoleInst: typeof console, methodName: string) { emitConsoleChunk(request, methodName, owner, env, stack, args); } // $FlowFixMe[incompatible-call] - // $FlowFixMe[incompatible-type] return originalMethod.apply(this, arguments); }; if (originalName) { @@ -439,7 +437,6 @@ function patchConsole(consoleInst: typeof console, methodName: string) { } } -// $FlowFixMe[invalid-compare] if (__DEV__ && typeof console === 'object' && console !== null) { // Instrument console to capture logs for replaying on the client. patchConsole(console, 'assert'); @@ -563,12 +560,6 @@ const CLOSED = 14; const RENDER = 20; const PRERENDER = 21; -// Marker pushed before a [headerChunk, contentChunk] pair in -// completedRegularChunks / completedDebugChunks to signal that the next two -// entries must be written atomically — see emitTextChunk and -// emitTypedArrayChunk for why, and flushCompletedChunks for how it's read. -const NEXT_TWO_CHUNKS_ARE_ATOMIC: symbol = Symbol(); - export type Request = { status: 10 | 11 | 12 | 13 | 14, type: 20 | 21, @@ -585,13 +576,7 @@ export type Request = { pingedTasks: Array<Task>, completedImportChunks: Array<Chunk>, completedHintChunks: Array<Chunk>, - // Text and TypedArray rows are pushed as a NEXT_TWO_CHUNKS_ARE_ATOMIC - // sentinel followed by their [headerChunk, contentChunk] pair, so that - // flushCompletedChunks can write the pair atomically and never strand the - // content chunk on a backpressure break. - completedRegularChunks: Array< - Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC, - >, + completedRegularChunks: Array<Chunk | BinaryChunk>, completedErrorChunks: Array<Chunk>, writtenSymbols: Map<symbol, number>, writtenClientReferences: Map<ClientReferenceKey, number>, @@ -609,11 +594,7 @@ export type Request = { abortTime: number, // DEV-only pendingDebugChunks: number, - // See completedRegularChunks for why some entries are preceded by the - // NEXT_TWO_CHUNKS_ARE_ATOMIC sentinel. - completedDebugChunks: Array< - Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC, - >, + completedDebugChunks: Array<Chunk | BinaryChunk>, debugDestination: null | Destination, environmentName: () => string, filterStackFrame: ( @@ -712,12 +693,10 @@ function RequestInstance( this.hints = hints; this.abortableTasks = abortSet; this.pingedTasks = pingedTasks; - this.completedImportChunks = [] as Array<Chunk>; - this.completedHintChunks = [] as Array<Chunk>; - this.completedRegularChunks = [] as Array< - Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC, - >; - this.completedErrorChunks = [] as Array<Chunk>; + this.completedImportChunks = ([]: Array<Chunk>); + this.completedHintChunks = ([]: Array<Chunk>); + this.completedRegularChunks = ([]: Array<Chunk | BinaryChunk>); + this.completedErrorChunks = ([]: Array<Chunk>); this.writtenSymbols = new Map(); this.writtenClientReferences = new Map(); this.writtenServerReferences = new Map(); @@ -732,9 +711,7 @@ function RequestInstance( if (__DEV__) { this.pendingDebugChunks = 0; - this.completedDebugChunks = [] as Array< - Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC, - >; + this.completedDebugChunks = ([]: Array<Chunk>); this.debugDestination = null; this.environmentName = environmentName === undefined @@ -872,7 +849,6 @@ let currentRequest: null | Request = null; export function resolveRequest(): null | Request { if (currentRequest) return currentRequest; - // $FlowFixMe[constant-condition] if (supportsRequestStorage) { const store = requestStorage.getStore(); if (store) return store; @@ -960,7 +936,7 @@ function serializeDebugThenable( // safe to defer them. This also ensures that we don't eagerly call .then() on a Promise that // otherwise wouldn't have initialized. It also ensures that we don't "handle" a rejection // that otherwise would have triggered unhandled rejection. - deferredDebugObjects.retained.set(id, thenable as any); + deferredDebugObjects.retained.set(id, (thenable: any)); const deferredRef = '$Y@' + id.toString(16); // We can now refer to the deferred object in the future. request.writtenDebugObjects.set(thenable, deferredRef); @@ -1023,8 +999,8 @@ function serializeDebugThenable( emitDebugHaltChunk(request, id); enqueueFlush(request); // Clean up the request so we don't leak this forever. - request = null as any; - counter = null as any; + request = (null: any); + counter = (null: any); }); return ref; @@ -1067,7 +1043,7 @@ function serializeThenable( ): number { const newTask = createTask( request, - thenable as any, // will be replaced by the value before we retry. used for debug info. + (thenable: any), // will be replaced by the value before we retry. used for debug info. task.keyPath, // the server component sequence continues through Promise-as-a-child. task.implicitSlot, task.formatContext, @@ -1103,7 +1079,7 @@ function serializeThenable( haltTask(newTask, request); finishHaltedTask(newTask, request); } else { - const errorId: number = request.fatalError as any; + const errorId: number = (request.fatalError: any); abortTask(newTask, request, errorId); finishAbortedTask(newTask, request, errorId); } @@ -1115,19 +1091,19 @@ function serializeThenable( // some custom userspace implementation. We treat it as "pending". break; } - const pendingThenable: PendingThenable<mixed> = thenable as any; + const pendingThenable: PendingThenable<mixed> = (thenable: any); pendingThenable.status = 'pending'; pendingThenable.then( fulfilledValue => { if (thenable.status === 'pending') { - const fulfilledThenable: FulfilledThenable<mixed> = thenable as any; + const fulfilledThenable: FulfilledThenable<mixed> = (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = fulfilledValue; } }, (error: mixed) => { if (thenable.status === 'pending') { - const rejectedThenable: RejectedThenable<mixed> = thenable as any; + const rejectedThenable: RejectedThenable<mixed> = (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; } @@ -1173,7 +1149,7 @@ function serializeReadableStream( // receiving side. It also implies that different chunks can be split up or merged as opposed // to a readable stream that happens to have Uint8Array as the type which might expect it to be // received in the same slices. - // $FlowFixMe[prop-missing]: This is a Node.js extension. + // $FlowFixMe: This is a Node.js extension. let supportsBYOB: void | boolean = stream.supportsBYOB; if (supportsBYOB === undefined) { try { @@ -1231,7 +1207,7 @@ function serializeReadableStream( streamTask.model = entry.value; if (isByteStream) { // Chunks of byte streams are always Uint8Array instances. - const chunk: Uint8Array = streamTask.model as any; + const chunk: Uint8Array = (streamTask.model: any); emitTypedArrayChunk(request, streamTask.id, 'b', chunk, false); } else { tryStreamTask(request, streamTask); @@ -1251,8 +1227,7 @@ function serializeReadableStream( erroredTask(request, streamTask, reason); enqueueFlush(request); - // $FlowFixMe[incompatible-type] should be able to pass mixed - // $FlowFixMe[incompatible-use] + // $FlowFixMe should be able to pass mixed reader.cancel(reason).then(error, error); } function abortStream() { @@ -1271,7 +1246,7 @@ function serializeReadableStream( erroredTask(request, streamTask, reason); enqueueFlush(request); } - // $FlowFixMe[incompatible-use] should be able to pass mixed + // $FlowFixMe should be able to pass mixed reader.cancel(reason).then(error, error); } @@ -1310,7 +1285,7 @@ function serializeAsyncIterable( ); if (__DEV__) { - const debugInfo: ?ReactDebugInfo = (iterable as any)._debugInfo; + const debugInfo: ?ReactDebugInfo = (iterable: any)._debugInfo; if (debugInfo) { forwardDebugInfo(request, streamTask, debugInfo); } @@ -1383,9 +1358,9 @@ function serializeAsyncIterable( request.cacheController.signal.removeEventListener('abort', abortIterable); erroredTask(request, streamTask, reason); enqueueFlush(request); - if (typeof (iterator as any).throw === 'function') { + if (typeof (iterator: any).throw === 'function') { // The iterator protocol doesn't necessarily include this but a generator do. - // $FlowFixMe[prop-missing] should be able to pass mixed + // $FlowFixMe should be able to pass mixed iterator.throw(reason).then(error, error); } } @@ -1405,9 +1380,9 @@ function serializeAsyncIterable( erroredTask(request, streamTask, signal.reason); enqueueFlush(request); } - if (typeof (iterator as any).throw === 'function') { + if (typeof (iterator: any).throw === 'function') { // The iterator protocol doesn't necessarily include this but a generator do. - // $FlowFixMe[prop-missing] should be able to pass mixed + // $FlowFixMe should be able to pass mixed iterator.throw(reason).then(error, error); } } @@ -1453,7 +1428,7 @@ function createLazyWrapperAroundWakeable( ) { // This is a temporary fork of the `use` implementation until we accept // promises everywhere. - const thenable: Thenable<mixed> = wakeable as any; + const thenable: Thenable<mixed> = (wakeable: any); switch (thenable.status) { case 'fulfilled': { forwardDebugInfoFromThenable(request, task, thenable, null, null); @@ -1469,13 +1444,13 @@ function createLazyWrapperAroundWakeable( // some custom userspace implementation. We treat it as "pending". break; } - const pendingThenable: PendingThenable<mixed> = thenable as any; + const pendingThenable: PendingThenable<mixed> = (thenable: any); pendingThenable.status = 'pending'; pendingThenable.then( fulfilledValue => { forwardDebugInfoFromCurrentContext(request, task, thenable); if (thenable.status === 'pending') { - const fulfilledThenable: FulfilledThenable<mixed> = thenable as any; + const fulfilledThenable: FulfilledThenable<mixed> = (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = fulfilledValue; } @@ -1483,7 +1458,7 @@ function createLazyWrapperAroundWakeable( (error: mixed) => { forwardDebugInfoFromCurrentContext(request, task, thenable); if (thenable.status === 'pending') { - const rejectedThenable: RejectedThenable<mixed> = thenable as any; + const rejectedThenable: RejectedThenable<mixed> = (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; } @@ -1581,11 +1556,11 @@ function processServerComponentReturnValue( } if (__DEV__) { - if ((result as any).$$typeof === REACT_ELEMENT_TYPE) { + if ((result: any).$$typeof === REACT_ELEMENT_TYPE) { // If the server component renders to an element, then it was in a static position. // That doesn't need further validation of keys. The Server Component itself would // have had a key. - (result as any)._store.validated = 1; + (result: any)._store.validated = 1; } } @@ -1607,10 +1582,10 @@ function processServerComponentReturnValue( // tempting to try to return the value from a generator. if (iterator === iterableChild) { const isGeneratorComponent = - // $FlowFixMe[method-unbinding] + // $FlowIgnore[method-unbinding] Object.prototype.toString.call(Component) === '[object GeneratorFunction]' && - // $FlowFixMe[method-unbinding] + // $FlowIgnore[method-unbinding] Object.prototype.toString.call(iterableChild) === '[object Generator]'; if (!isGeneratorComponent) { @@ -1623,23 +1598,23 @@ function processServerComponentReturnValue( } } } - return iterator as any; + return (iterator: any); }, }; if (__DEV__) { - (multiShot as any)._debugInfo = iterableChild._debugInfo; + (multiShot: any)._debugInfo = iterableChild._debugInfo; } return multiShot; } if ( - typeof (result as any)[ASYNC_ITERATOR] === 'function' && + typeof (result: any)[ASYNC_ITERATOR] === 'function' && (typeof ReadableStream !== 'function' || !(result instanceof ReadableStream)) ) { const iterableChild = result; const multishot = { [ASYNC_ITERATOR]: function () { - const iterator = (iterableChild as any)[ASYNC_ITERATOR](); + const iterator = (iterableChild: any)[ASYNC_ITERATOR](); if (__DEV__) { // If this was an AsyncIterator but not an AsyncGeneratorFunction we warn because // it might have been a mistake. Technically you can make this mistake with @@ -1647,10 +1622,10 @@ function processServerComponentReturnValue( // tempting to try to return the value from a generator. if (iterator === iterableChild) { const isGeneratorComponent = - // $FlowFixMe[method-unbinding] + // $FlowIgnore[method-unbinding] Object.prototype.toString.call(Component) === '[object AsyncGeneratorFunction]' && - // $FlowFixMe[method-unbinding] + // $FlowIgnore[method-unbinding] Object.prototype.toString.call(iterableChild) === '[object AsyncGenerator]'; if (!isGeneratorComponent) { @@ -1667,7 +1642,7 @@ function processServerComponentReturnValue( }, }; if (__DEV__) { - (multishot as any)._debugInfo = iterableChild._debugInfo; + (multishot: any)._debugInfo = iterableChild._debugInfo; } return multishot; } @@ -1700,20 +1675,20 @@ function renderFunctionComponent<Props>( // This is a replay and we've already emitted the debug info of this component // in the first pass. We skip emitting a duplicate line. // As a hack we stashed the previous component debug info on this object in DEV. - componentDebugInfo = (prevThenableState as any)._componentDebugInfo; + componentDebugInfo = (prevThenableState: any)._componentDebugInfo; } else { // This is a new component in the same task so we can emit more debug info. const componentDebugID = task.id; const componentName = - (Component as any).displayName || Component.name || ''; + (Component: any).displayName || Component.name || ''; const componentEnv = (0, request.environmentName)(); request.pendingChunks++; - componentDebugInfo = { + componentDebugInfo = ({ name: componentName, env: componentEnv, key: key, owner: task.debugOwner, - } as ReactComponentInfo; + }: ReactComponentInfo); // $FlowFixMe[cannot-write] componentDebugInfo.stack = task.debugStack === null @@ -1750,7 +1725,6 @@ function renderFunctionComponent<Props>( } } prepareToUseHooksForComponent(prevThenableState, componentDebugInfo); - // $FlowFixMe[constant-condition] if (supportsComponentStorage) { // Run the component in an Async Context that tracks the current owner. if (task.debugTask) { @@ -1784,7 +1758,7 @@ function renderFunctionComponent<Props>( } } } else { - componentDebugInfo = null as any; + componentDebugInfo = (null: any); prepareToUseHooksForComponent(prevThenableState, null); // The secondArg is always undefined in Server Components since refs error early. const secondArg = undefined; @@ -1794,7 +1768,6 @@ function renderFunctionComponent<Props>( if (request.status === ABORTING) { if ( typeof result === 'object' && - // $FlowFixMe[invalid-compare] result !== null && typeof result.then === 'function' && !isClientReference(result) @@ -1815,9 +1788,9 @@ function renderFunctionComponent<Props>( if (trackedThenables !== null) { const stacks: Array<Error> = __DEV__ && enableAsyncDebugInfo - ? (trackedThenables as any)._stacks || - ((trackedThenables as any)._stacks = []) - : (null as any); + ? (trackedThenables: any)._stacks || + ((trackedThenables: any)._stacks = []) + : (null: any); for (let i = 0; i < trackedThenables.length; i++) { const stack = __DEV__ && enableAsyncDebugInfo ? stacks[i] : null; forwardDebugInfoFromThenable( @@ -1900,7 +1873,6 @@ function warnForMissingKey( ); }; - // $FlowFixMe[constant-condition] if (supportsComponentStorage) { // Run the component in an Async Context that tracks the current owner. if (debugTask) { @@ -1949,7 +1921,7 @@ function renderFragment( typeof child === 'object' && child.$$typeof === REACT_ELEMENT_TYPE ) { - const element: ReactElement = child as any; + const element: ReactElement = (child: any); if (element.key === null && !element._store.validated) { element._store.validated = 2; } @@ -1994,7 +1966,7 @@ function renderFragment( // be recursive serialization, we need to reset the keyPath and implicitSlot, // before recursing here. if (__DEV__) { - const debugInfo: ?ReactDebugInfo = (children as any)._debugInfo; + const debugInfo: ?ReactDebugInfo = (children: any)._debugInfo; if (debugInfo) { // If this came from Flight, forward any debug info into this new row. if (!canEmitDebugInfo) { @@ -2595,7 +2567,7 @@ function visitAsyncNodeImpl( // Then emit a reference to us awaiting it in the current task. request.pendingChunks++; emitDebugChunk(request, task.id, { - awaited: ioNode as any as ReactIOInfo, // This is deduped by this reference. + awaited: ((ioNode: any): ReactIOInfo), // This is deduped by this reference. env: env, owner: node.owner, stack: @@ -2665,7 +2637,7 @@ function emitAsyncSequence( // If we don't have any thing awaited, the time we started awaiting was internal // when we yielded after rendering. The current task time is basically that. const debugInfo: ReactAsyncInfo = { - awaited: awaitedNode as any as ReactIOInfo, // This is deduped by this reference. + awaited: ((awaitedNode: any): ReactIOInfo), // This is deduped by this reference. env: env, }; if (__DEV__) { @@ -2753,7 +2725,7 @@ function createTask( request.writtenObjects.set(model, serializeByValueID(id)); } } - const task: Task = { + const task: Task = (({ id, status: PENDING, model, @@ -2811,7 +2783,7 @@ function createTask( return renderModel(request, task, parent, parentPropertyName, value); }, thenableState: null, - } as Omit< + }: Omit< Task, | 'timed' | 'time' @@ -2819,7 +2791,7 @@ function createTask( | 'debugOwner' | 'debugStack' | 'debugTask', - > as any; + >): any); if ( enableProfilerTimer && (enableComponentPerformanceTrack || enableAsyncDebugInfo) @@ -3081,7 +3053,6 @@ function serializeServerReference( request.bundlerConfig, serverReference, ); - // $FlowFixMe[constant-condition] if (error) { const frames = parseStackTrace(error, 1); if (frames.length > 0) { @@ -3153,7 +3124,7 @@ function serializeMap( function serializeFormData(request: Request, formData: FormData): string { const entries = Array.from(formData.entries()); - const id = outlineModel(request, entries as any); + const id = outlineModel(request, (entries: any)); return '$K' + id.toString(16); } @@ -3162,7 +3133,7 @@ function serializeDebugFormData(request: Request, formData: FormData): string { const id = outlineDebugModel( request, {objectLimit: entries.length * 2 + 1}, - entries as any, + (entries: any), ); return '$K' + id.toString(16); } @@ -3275,17 +3246,17 @@ function serializeDebugBlob(request: Request, blob: Blob): string { } // TODO: Emit the chunk early and refer to it later by dedupe. model.push(entry.value); - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] return reader.read().then(progress).catch(error); } function error(reason: mixed) { const digest = ''; emitErrorChunk(request, id, digest, reason, true, null); enqueueFlush(request); - // $FlowFixMe[incompatible-type] should be able to pass mixed + // $FlowFixMe should be able to pass mixed reader.cancel(reason).then(noop, noop); } - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] reader.read().then(progress).catch(error); return '$B' + id.toString(16); } @@ -3323,7 +3294,7 @@ function serializeBlob(request: Request, blob: Blob): string { } // TODO: Emit the chunk early and refer to it later by dedupe. model.push(entry.value); - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] return reader.read().then(progress).catch(error); } function error(reason: mixed) { @@ -3333,8 +3304,7 @@ function serializeBlob(request: Request, blob: Blob): string { request.cacheController.signal.removeEventListener('abort', abortBlob); erroredTask(request, newTask, reason); enqueueFlush(request); - // $FlowFixMe[incompatible-type] should be able to pass mixed - // $FlowFixMe[incompatible-use] + // $FlowFixMe should be able to pass mixed reader.cancel(reason).then(error, error); } function abortBlob() { @@ -3353,13 +3323,13 @@ function serializeBlob(request: Request, blob: Blob): string { erroredTask(request, newTask, reason); enqueueFlush(request); } - // $FlowFixMe[incompatible-use] should be able to pass mixed + // $FlowFixMe should be able to pass mixed reader.cancel(reason).then(error, error); } request.cacheController.signal.addEventListener('abort', abortBlob); - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] reader.read().then(progress).catch(error); return '$B' + newTask.id.toString(16); @@ -3400,8 +3370,8 @@ function renderModel( const wasReactNode = typeof model === 'object' && model !== null && - ((model as any).$$typeof === REACT_ELEMENT_TYPE || - (model as any).$$typeof === REACT_LAZY_TYPE); + ((model: any).$$typeof === REACT_ELEMENT_TYPE || + (model: any).$$typeof === REACT_LAZY_TYPE); if (request.status === ABORTING) { task.status = ABORTED; @@ -3410,7 +3380,7 @@ function renderModel( // the new task won't be retried because we are aborting return outlineHaltedTask(request, task, wasReactNode); } - const errorId = request.fatalError as any; + const errorId = (request.fatalError: any); if (wasReactNode) { return serializeLazyID(errorId); } @@ -3427,7 +3397,6 @@ function renderModel( getSuspendedThenable() : thrownValue; - // $FlowFixMe[invalid-compare] if (typeof x === 'object' && x !== null) { // $FlowFixMe[method-unbinding] if (typeof x.then === 'function') { @@ -3448,7 +3417,7 @@ function renderModel( __DEV__ ? task.debugTask : null, ); const ping = newTask.ping; - (x as any).then(ping, ping); + (x: any).then(ping, ping); newTask.thenableState = getThenableStateAfterSuspending(); // Restore the context. We assume that this will be restored by the inner @@ -3524,7 +3493,7 @@ function renderModelDestructive( } if (typeof value === 'object') { - switch ((value as any).$$typeof) { + switch ((value: any).$$typeof) { case REACT_ELEMENT_TYPE: { let elementReference = null; const writtenObjects = request.writtenObjects; @@ -3561,14 +3530,14 @@ function renderModelDestructive( } } - const element: ReactElement = value as any; + const element: ReactElement = (value: any); if (serializedSize > MAX_ROW_SIZE) { return deferTask(request, task); } if (__DEV__) { - const debugInfo: ?ReactDebugInfo = (value as any)._debugInfo; + const debugInfo: ?ReactDebugInfo = (value: any)._debugInfo; if (debugInfo) { // If this came from Flight, forward any debug info into this new row. if (!canEmitDebugInfo) { @@ -3654,7 +3623,7 @@ function renderModelDestructive( // from suspending the lazy before. task.thenableState = null; - const lazy: LazyComponent<any, any> = value as any; + const lazy: LazyComponent<any, any> = (value: any); let resolvedModel; if (__DEV__) { resolvedModel = callLazyInitInDEV(lazy); @@ -3710,7 +3679,7 @@ function renderModelDestructive( request, parent, parentPropertyName, - value as any, + (value: any), ); } @@ -3740,7 +3709,7 @@ function renderModelDestructive( // If we're in some kind of context we can't reuse the result of this render or // previous renders of this element. We only reuse Promises if they're not wrapped // by another Server Component. - const promiseId = serializeThenable(request, task, value as any); + const promiseId = serializeThenable(request, task, (value: any)); return serializePromiseID(promiseId); } else if (modelRoot === value) { // This is the ID we're currently emitting so we need to write it @@ -3753,7 +3722,7 @@ function renderModelDestructive( } // We assume that any object with a .then property is a "Thenable" type, // or a Promise type. Either of which can be represented by a Promise. - const promiseId = serializeThenable(request, task, value as any); + const promiseId = serializeThenable(request, task, (value: any)); const promiseReference = serializePromiseID(promiseId); writtenObjects.set(value, promiseReference); return promiseReference; @@ -3883,9 +3852,9 @@ function renderModelDestructive( const iterator = iteratorFn.call(value); if (iterator === value) { // Iterator, not Iterable - return serializeIterator(request, iterator as any); + return serializeIterator(request, (iterator: any)); } - return renderFragment(request, task, Array.from(iterator as any)); + return renderFragment(request, task, Array.from((iterator: any))); } // TODO: Blob is not available in old Node. Remove the typeof check later. @@ -3895,12 +3864,11 @@ function renderModelDestructive( ) { return serializeReadableStream(request, task, value); } - const getAsyncIterator: void | (() => $AsyncIterator<any, any, any>) = ( - value as any - )[ASYNC_ITERATOR]; + const getAsyncIterator: void | (() => $AsyncIterator<any, any, any>) = + (value: any)[ASYNC_ITERATOR]; if (typeof getAsyncIterator === 'function') { // We treat AsyncIterables as a Fragment and as such we might need to key them. - return renderAsyncFragment(request, task, value as any, getAsyncIterator); + return renderAsyncFragment(request, task, (value: any), getAsyncIterator); } // We put the Date check low b/c most of the time Date's will already have been serialized @@ -3956,7 +3924,7 @@ function renderModelDestructive( } } - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-return] return value; } @@ -3977,7 +3945,6 @@ function renderModelDestructive( return serializeDateFromDateJSON(value); } } - // $FlowFixMe[invalid-compare] if (value.length >= 1024 && byteLengthOfChunk !== null) { // For large strings, we encode them outside the JSON payload so that we // don't have to double encode and double parse the strings. This can also @@ -4005,11 +3972,11 @@ function renderModelDestructive( request, parent, parentPropertyName, - value as any, + (value: any), ); } if (isServerReference(value)) { - return serializeServerReference(request, value as any); + return serializeServerReference(request, (value: any)); } if (request.temporaryReferences !== undefined) { const tempRef = resolveTemporaryReference( @@ -4122,7 +4089,6 @@ function logRecoverableError( try { const onError = request.onError; if (__DEV__ && task !== null) { - // $FlowFixMe[constant-condition] if (supportsRequestStorage) { errorDigest = requestStorage.run( undefined, @@ -4135,7 +4101,6 @@ function logRecoverableError( } else { errorDigest = callWithDebugContextInDEV(request, task, onError, error); } - // $FlowFixMe[constant-condition] } else if (supportsRequestStorage) { // Exit the request context while running callbacks. errorDigest = requestStorage.run(undefined, onError, error); @@ -4188,7 +4153,7 @@ function serializeErrorValue(request: Request, error: Error): string { // eslint-disable-next-line react-internal/safe-string-coercion message = String(error.message); stack = filterStackTrace(request, parseStackTrace(error, 0)); - const errorEnv = (error as any).environmentName; + const errorEnv = (error: any).environmentName; if (typeof errorEnv === 'string') { // This probably came from another FlightClient as a pass through. // Keep the environment name. @@ -4200,7 +4165,7 @@ function serializeErrorValue(request: Request, error: Error): string { } const errorInfo: ReactErrorInfoDev = {name, message, stack, env}; if ('cause' in error) { - const cause: ReactClientValue = error.cause as any; + const cause: ReactClientValue = (error.cause: any); const causeId = outlineModel(request, cause); errorInfo.cause = serializeByValueID(causeId); } @@ -4208,7 +4173,7 @@ function serializeErrorValue(request: Request, error: Error): string { typeof AggregateError !== 'undefined' && error instanceof AggregateError ) { - const errors: ReactClientValue = error.errors as any; + const errors: ReactClientValue = (error.errors: any); const errorsId = outlineModel(request, errors); errorInfo.errors = serializeByValueID(errorsId); } @@ -4237,7 +4202,7 @@ function serializeDebugErrorValue( // eslint-disable-next-line react-internal/safe-string-coercion message = String(error.message); stack = filterStackTrace(request, parseStackTrace(error, 0)); - const errorEnv = (error as any).environmentName; + const errorEnv = (error: any).environmentName; if (typeof errorEnv === 'string') { // This probably came from another FlightClient as a pass through. // Keep the environment name. @@ -4250,7 +4215,7 @@ function serializeDebugErrorValue( const errorInfo: ReactErrorInfoDev = {name, message, stack, env}; if ('cause' in error) { counter.objectLimit--; - const cause: ReactClientValue = error.cause as any; + const cause: ReactClientValue = (error.cause: any); const causeId = outlineDebugModel(request, counter, cause); errorInfo.cause = serializeByValueID(causeId); } @@ -4259,7 +4224,7 @@ function serializeDebugErrorValue( error instanceof AggregateError ) { counter.objectLimit--; - const errors: ReactClientValue = error.errors as any; + const errors: ReactClientValue = (error.errors: any); const errorsId = outlineDebugModel(request, counter, errors); errorInfo.errors = serializeByValueID(errorsId); } @@ -4299,14 +4264,14 @@ function emitErrorChunk( // eslint-disable-next-line react-internal/safe-string-coercion message = String(error.message); stack = filterStackTrace(request, parseStackTrace(error, 0)); - const errorEnv = (error as any).environmentName; + const errorEnv = (error: any).environmentName; if (typeof errorEnv === 'string') { // This probably came from another FlightClient as a pass through. // Keep the environment name. env = errorEnv; } if ('cause' in error) { - const cause: ReactClientValue = error.cause as any; + const cause: ReactClientValue = (error.cause: any); const causeId = debug ? outlineDebugModel(request, {objectLimit: 5}, cause) : outlineModel(request, cause); @@ -4316,7 +4281,7 @@ function emitErrorChunk( typeof AggregateError !== 'undefined' && error instanceof AggregateError ) { - const errors: ReactClientValue = error.errors as any; + const errors: ReactClientValue = (error.errors: any); const errorsId = debug ? outlineDebugModel(request, {objectLimit: 5}, errors) : outlineModel(request, errors); @@ -4338,10 +4303,10 @@ function emitErrorChunk( owner == null ? null : outlineComponentInfo(request, owner); errorInfo = {digest, name, message, stack, env, owner: ownerRef}; if (causeReference !== null) { - (errorInfo as ReactErrorInfoDev).cause = causeReference; + (errorInfo: ReactErrorInfoDev).cause = causeReference; } if (errorsReference !== null) { - (errorInfo as ReactErrorInfoDev).errors = errorsReference; + (errorInfo: ReactErrorInfoDev).errors = errorsReference; } } else { errorInfo = {digest}; @@ -4726,25 +4691,10 @@ function emitTypedArrayChunk( const binaryLength = byteLengthOfBinaryChunk(binaryChunk); const row = id.toString(16) + ':' + tag + binaryLength.toString(16) + ','; const headerChunk = stringToChunk(row); - // Push a NEXT_TWO_CHUNKS_ARE_ATOMIC sentinel before the header so that - // flushCompletedChunks can write the header and binary chunks atomically. - // Otherwise, if the destination's backpressure flips between the two writes, - // the content chunk would be stranded at the front of the queue and the next - // drain would emit Import or Hint chunks between the header and the content — - // and the Flight Client would frame those intervening bytes as this row's - // content. if (__DEV__ && debug) { - request.completedDebugChunks.push( - NEXT_TWO_CHUNKS_ARE_ATOMIC, - headerChunk, - binaryChunk, - ); + request.completedDebugChunks.push(headerChunk, binaryChunk); } else { - request.completedRegularChunks.push( - NEXT_TWO_CHUNKS_ARE_ATOMIC, - headerChunk, - binaryChunk, - ); + request.completedRegularChunks.push(headerChunk, binaryChunk); } } @@ -4754,7 +4704,6 @@ function emitTextChunk( text: string, debug: boolean, ): void { - // $FlowFixMe[invalid-compare] if (byteLengthOfChunk === null) { // eslint-disable-next-line react-internal/prod-error-codes throw new Error( @@ -4770,19 +4719,10 @@ function emitTextChunk( const binaryLength = byteLengthOfChunk(textChunk); const row = id.toString(16) + ':T' + binaryLength.toString(16) + ','; const headerChunk = stringToChunk(row); - // See emitTypedArrayChunk for why the pair is preceded by a sentinel. if (__DEV__ && debug) { - request.completedDebugChunks.push( - NEXT_TWO_CHUNKS_ARE_ATOMIC, - headerChunk, - textChunk, - ); + request.completedDebugChunks.push(headerChunk, textChunk); } else { - request.completedRegularChunks.push( - NEXT_TWO_CHUNKS_ARE_ATOMIC, - headerChunk, - textChunk, - ); + request.completedRegularChunks.push(headerChunk, textChunk); } } @@ -4797,7 +4737,7 @@ function serializeEval(source: string): string { return '$E' + source; } -const CONSTRUCTOR_MARKER: symbol = __DEV__ ? Symbol() : (null as any); +const CONSTRUCTOR_MARKER: symbol = __DEV__ ? Symbol() : (null: any); let debugModelRoot: mixed = null; let debugNoOutline: mixed = null; @@ -4831,11 +4771,11 @@ function renderDebugModel( request, parent, parentPropertyName, - value as any, + (value: any), ); } if (value.$$typeof === CONSTRUCTOR_MARKER) { - const constructor: Function = (value as any).constructor; + const constructor: Function = (value: any).constructor; let ref = request.writtenDebugObjects.get(constructor); if (ref === undefined) { const id = outlineDebugModel(request, counter, constructor); @@ -4904,7 +4844,7 @@ function renderDebugModel( // $FlowFixMe[method-unbinding] if (typeof value.then === 'function') { // If this is a Promise we're going to assign it an external ID anyway which can be deduped. - const thenable: Thenable<any> = value as any; + const thenable: Thenable<any> = (value: any); return serializeDebugThenable(request, counter, thenable); } else { const outlinedId = outlineDebugModel(request, counter, value); @@ -4942,9 +4882,9 @@ function renderDebugModel( } } - switch ((value as any).$$typeof) { + switch ((value: any).$$typeof) { case REACT_ELEMENT_TYPE: { - const element: ReactElement = value as any; + const element: ReactElement = (value: any); if (element._owner != null) { outlineComponentInfo(request, element._owner); @@ -4989,7 +4929,7 @@ function renderDebugModel( // some assumptions about the structure of the payload even though // that's not really part of the contract. In practice, this is really // just coming from React.lazy helper or Flight. - const lazy: LazyComponent<any, any> = value as any; + const lazy: LazyComponent<any, any> = (value: any); const payload = lazy._payload; if (payload !== null && typeof payload === 'object') { @@ -5052,7 +4992,7 @@ function renderDebugModel( // $FlowFixMe[method-unbinding] if (typeof value.then === 'function') { - const thenable: Thenable<any> = value as any; + const thenable: Thenable<any> = (value: any); return serializeDebugThenable(request, counter, thenable); } @@ -5139,7 +5079,7 @@ function renderDebugModel( const iteratorFn = getIteratorFn(value); if (iteratorFn) { - return Array.from(value as any); + return Array.from((value: any)); } const proto = getPrototypeOf(value); @@ -5170,22 +5110,11 @@ function renderDebugModel( return instanceDescription; } - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-return] return value; } if (typeof value === 'string') { - if (value.length > 1000000) { - // Reconstructing a multi-megabyte string on the client blocks the main - // thread for too long. We omit the actual value and send a placeholder - // instead. - return ( - 'This string of length ' + - value.length + - ' has been omitted by React to avoid sending too much data from the ' + - 'server.' - ); - } if (value.length >= 1024) { // Large strings are counted towards the object limit. if (counter.objectLimit <= 0) { @@ -5220,7 +5149,7 @@ function renderDebugModel( request, parent, parentPropertyName, - value as any, + (value: any), ); } if (request.temporaryReferences !== undefined) { @@ -5322,13 +5251,12 @@ function serializeDebugModel( debugNoOutline = model; try { // $FlowFixMe[incompatible-cast] stringify can return null - // $FlowFixMe[incompatible-type] - return stringify(model, replacer) as string; + return (stringify(model, replacer): string); } catch (x) { // $FlowFixMe[incompatible-cast] stringify can return null - return stringify( + return (stringify( 'Unknown Value: React could not send it from the server.\n' + x.message, - ) as string; + ): string); } finally { debugNoOutline = prevNoOutline; } @@ -5386,13 +5314,13 @@ function emitOutlinedDebugModelChunk( } let json: string; try { - // $FlowFixMe[incompatible-type] stringify can return null - json = stringify(model, replacer) as string; + // $FlowFixMe[incompatible-cast] stringify can return null + json = (stringify(model, replacer): string); } catch (x) { - // $FlowFixMe[incompatible-type] stringify can return null - json = stringify( + // $FlowFixMe[incompatible-cast] stringify can return null + json = (stringify( 'Unknown Value: React could not send it from the server.\n' + x.message, - ) as string; + ): string); } finally { debugModelRoot = prevModelRoot; } @@ -5495,7 +5423,7 @@ function forwardDebugInfo( // We outline this model eagerly so that we can refer to by reference as an owner. // If we had a smarter way to dedupe we might not have to do this if there ends up // being no references to this as an owner. - outlineComponentInfo(request, info as any); + outlineComponentInfo(request, (info: any)); // Emit a reference to the outlined one. request.pendingChunks++; emitDebugChunk(request, id, info); @@ -5620,7 +5548,7 @@ function forwardDebugInfoFromAbortedTask(request: Request, task: Task): void { if (enableProfilerTimer && enableAsyncDebugInfo) { let thenable: null | Thenable<any> = null; if (typeof model.then === 'function') { - thenable = model as any; + thenable = (model: any); } else if (model.$$typeof === REACT_LAZY_TYPE) { const payload = model._payload; if (typeof payload.then === 'function') { @@ -5644,7 +5572,7 @@ function forwardDebugInfoFromAbortedTask(request: Request, task: Task): void { request.pendingChunks++; const env = (0, request.environmentName)(); const asyncInfo: ReactAsyncInfo = { - awaited: node as any as ReactIOInfo, // This is deduped by this reference. + awaited: ((node: any): ReactIOInfo), // This is deduped by this reference. env: env, }; // We don't have a start time for this await but in case there was no start time emitted @@ -5740,7 +5668,6 @@ function emitChunk( const id = task.id; // For certain types we have special types, we typically outlined them but // we can emit them directly for this row instead of through an indirection. - // $FlowFixMe[invalid-compare] if (typeof value === 'string' && byteLengthOfChunk !== null) { if (enableTaint) { const tainted = TaintRegistryValues.get(value); @@ -5943,7 +5870,7 @@ function retryTask(request: Request, task: Task): void { finishHaltedTask(task, request); } else { // Otherwise we emit an error chunk into the task slot. - const errorId: number = request.fatalError as any; + const errorId: number = (request.fatalError: any); abortTask(task, request, errorId); finishAbortedTask(task, request, errorId); } @@ -5959,7 +5886,6 @@ function retryTask(request: Request, task: Task): void { // later, once we deprecate the old API in favor of `use`. getSuspendedThenable() : thrownValue; - // $FlowFixMe[invalid-compare] if (typeof x === 'object' && x !== null) { // $FlowFixMe[method-unbinding] if (typeof x.then === 'function') { @@ -6088,27 +6014,9 @@ function flushCompletedChunks(request: Request): void { const debugChunks = request.completedDebugChunks; let i = 0; for (; i < debugChunks.length; i++) { - const item = debugChunks[i]; - if (item === NEXT_TWO_CHUNKS_ARE_ATOMIC) { - if (i + 2 >= debugChunks.length) { - throw new Error( - 'A chunk pair is incomplete. This is a bug in React.', - ); - } - request.pendingDebugChunks -= 2; - writeChunk( - debugDestination, - debugChunks[i + 1] as any as Chunk | BinaryChunk, - ); - writeChunk( - debugDestination, - debugChunks[i + 2] as any as Chunk | BinaryChunk, - ); - i += 2; - } else { - request.pendingDebugChunks--; - writeChunk(debugDestination, item as any as Chunk | BinaryChunk); - } + request.pendingDebugChunks--; + const chunk = debugChunks[i]; + writeChunkAndReturn(debugDestination, chunk); } debugChunks.splice(0, i); } finally { @@ -6156,31 +6064,9 @@ function flushCompletedChunks(request: Request): void { const debugChunks = request.completedDebugChunks; i = 0; for (; i < debugChunks.length; i++) { - const item = debugChunks[i]; - let keepWriting: boolean; - if (item === NEXT_TWO_CHUNKS_ARE_ATOMIC) { - if (i + 2 >= debugChunks.length) { - throw new Error( - 'A chunk pair is incomplete. This is a bug in React.', - ); - } - request.pendingDebugChunks -= 2; - writeChunk( - destination, - debugChunks[i + 1] as any as Chunk | BinaryChunk, - ); - keepWriting = writeChunkAndReturn( - destination, - debugChunks[i + 2] as any as Chunk | BinaryChunk, - ); - i += 2; - } else { - request.pendingDebugChunks--; - keepWriting = writeChunkAndReturn( - destination, - item as any as Chunk | BinaryChunk, - ); - } + request.pendingDebugChunks--; + const chunk = debugChunks[i]; + const keepWriting: boolean = writeChunkAndReturn(destination, chunk); if (!keepWriting) { request.destination = null; i++; @@ -6194,31 +6080,9 @@ function flushCompletedChunks(request: Request): void { const regularChunks = request.completedRegularChunks; i = 0; for (; i < regularChunks.length; i++) { - const item = regularChunks[i]; - let keepWriting: boolean; - if (item === NEXT_TWO_CHUNKS_ARE_ATOMIC) { - if (i + 2 >= regularChunks.length) { - throw new Error( - 'A chunk pair is incomplete. This is a bug in React.', - ); - } - request.pendingChunks -= 2; - writeChunk( - destination, - regularChunks[i + 1] as any as Chunk | BinaryChunk, - ); - keepWriting = writeChunkAndReturn( - destination, - regularChunks[i + 2] as any as Chunk | BinaryChunk, - ); - i += 2; - } else { - request.pendingChunks--; - keepWriting = writeChunkAndReturn( - destination, - item as any as Chunk | BinaryChunk, - ); - } + request.pendingChunks--; + const chunk = regularChunks[i]; + const keepWriting: boolean = writeChunkAndReturn(destination, chunk); if (!keepWriting) { request.destination = null; i++; @@ -6300,7 +6164,6 @@ function flushCompletedChunks(request: Request): void { export function startWork(request: Request): void { request.flushScheduled = request.destination !== null; - // $FlowFixMe[constant-condition] if (supportsRequestStorage) { scheduleMicrotask(() => { requestStorage.run(request, performWork, request); @@ -6545,7 +6408,7 @@ export function resolveDebugMessage(request: Request, message: string): void { request, id, counter, - retainedValue as any, + (retainedValue: any), ); } } diff --git a/packages/react-server/src/ReactFlightServerConfigDebugNode.js b/packages/react-server/src/ReactFlightServerConfigDebugNode.js index e17cba314add..9bb521be4065 100644 --- a/packages/react-server/src/ReactFlightServerConfigDebugNode.js +++ b/packages/react-server/src/ReactFlightServerConfigDebugNode.js @@ -35,7 +35,7 @@ import {parseStackTracePrivate} from './ReactFlightServerConfig'; const getAsyncId = AsyncResource.prototype.asyncId; const pendingOperations: Map<number, AsyncSequence> = - __DEV__ && enableAsyncDebugInfo ? new Map() : (null as any); + __DEV__ && enableAsyncDebugInfo ? new Map() : (null: any); // Keep the last resolved await as a workaround for async functions missing data. let lastRanAwait: null | AwaitNode = null; @@ -44,10 +44,10 @@ function resolvePromiseOrAwaitNode( unresolvedNode: UnresolvedAwaitNode | UnresolvedPromiseNode, endTime: number, ): AwaitNode | PromiseNode { - const resolvedNode: AwaitNode | PromiseNode = unresolvedNode as any; - resolvedNode.tag = ( - unresolvedNode.tag === UNRESOLVED_PROMISE_NODE ? PROMISE_NODE : AWAIT_NODE - ) as any; + const resolvedNode: AwaitNode | PromiseNode = (unresolvedNode: any); + resolvedNode.tag = ((unresolvedNode.tag === UNRESOLVED_PROMISE_NODE + ? PROMISE_NODE + : AWAIT_NODE): any); resolvedNode.end = endTime; return resolvedNode; } @@ -93,13 +93,13 @@ export function initAsyncDebugInfo(): void { stack = emptyStack; if (resource._debugInfo !== undefined) { // We may need to forward this debug info at the end so we need to retain this promise. - promiseRef = new WeakRef(resource as Promise<any>); + promiseRef = new WeakRef((resource: Promise<any>)); } else { // Otherwise, we can just refer to the inner one since that's the one we'll log anyway. promiseRef = trigger.promise; } } else { - promiseRef = new WeakRef(resource as Promise<any>); + promiseRef = new WeakRef((resource: Promise<any>)); const request = resolveRequest(); if (request === null) { // We don't collect stacks for awaits that weren't in the scope of a specific render. @@ -114,7 +114,7 @@ export function initAsyncDebugInfo(): void { } } const current = pendingOperations.get(currentAsyncId); - node = { + node = ({ tag: UNRESOLVED_AWAIT_NODE, owner: resolveOwner(), stack: stack, @@ -123,23 +123,23 @@ export function initAsyncDebugInfo(): void { promise: promiseRef, awaited: trigger, // The thing we're awaiting on. Might get overrriden when we resolve. previous: current === undefined ? null : current, // The path that led us here. - } as UnresolvedAwaitNode; + }: UnresolvedAwaitNode); } else { const owner = resolveOwner(); - node = { + node = ({ tag: UNRESOLVED_PROMISE_NODE, owner: owner, stack: owner === null ? null : parseStackTracePrivate(new Error(), 5), start: performance.now(), end: -1.1, // Set when we resolve. - promise: new WeakRef(resource as Promise<any>), + promise: new WeakRef((resource: Promise<any>)), awaited: trigger === undefined ? null // It might get overridden when we resolve. : trigger, previous: null, - } as UnresolvedPromiseNode; + }: UnresolvedPromiseNode); } } else if ( // bound-anonymous-fn is the default name for snapshots and .bind() without a name. @@ -167,7 +167,7 @@ export function initAsyncDebugInfo(): void { if (trigger === undefined) { // We have begun a new I/O sequence. const owner = resolveOwner(); - node = { + node = ({ tag: IO_NODE, owner: owner, stack: @@ -177,14 +177,14 @@ export function initAsyncDebugInfo(): void { promise: null, awaited: null, previous: null, - } as IONode; + }: IONode); } else if ( trigger.tag === AWAIT_NODE || trigger.tag === UNRESOLVED_AWAIT_NODE ) { // We have begun a new I/O sequence after the await. const owner = resolveOwner(); - node = { + node = ({ tag: IO_NODE, owner: owner, stack: @@ -194,7 +194,7 @@ export function initAsyncDebugInfo(): void { promise: null, awaited: null, previous: trigger, - } as IONode; + }: IONode); } else { // Otherwise, this is just a continuation of the same I/O sequence. node = trigger; @@ -209,7 +209,7 @@ export function initAsyncDebugInfo(): void { case IO_NODE: { lastRanAwait = null; // Log the end time when we resolved the I/O. - const ioNode: IONode = node as any; + const ioNode: IONode = (node: any); if (ioNode.end < 0) { ioNode.end = performance.now(); } else { @@ -237,7 +237,7 @@ export function initAsyncDebugInfo(): void { // If we begin before we resolve, that means that this is actually already resolved but // the promiseResolve hook is called at the end of the execution. So we track the time // in the before call instead. - // $FlowFixMe[incompatible-type] + // $FlowFixMe lastRanAwait = resolvePromiseOrAwaitNode(node, performance.now()); break; } diff --git a/packages/react-server/src/ReactFlightServerTemporaryReferences.js b/packages/react-server/src/ReactFlightServerTemporaryReferences.js index ccf11b2fb815..9195afa494c9 100644 --- a/packages/react-server/src/ReactFlightServerTemporaryReferences.js +++ b/packages/react-server/src/ReactFlightServerTemporaryReferences.js @@ -96,13 +96,13 @@ export function createTemporaryReference<T>( id: string, ): TemporaryReference<T> { const reference: TemporaryReference<any> = Object.defineProperties( - function () { + (function () { throw new Error( `Attempted to call a temporary Client Reference from the server but it is on the client. ` + `It's not possible to invoke a client function from the server, it can ` + `only be rendered as a Component or passed to props of a Client Component.`, ); - } as any, + }: any), { $$typeof: {value: TEMPORARY_REFERENCE_TAG}, }, diff --git a/packages/react-server/src/ReactFlightStackConfigV8.js b/packages/react-server/src/ReactFlightStackConfigV8.js index a42b18bbb98f..1aa97cb8e6b7 100644 --- a/packages/react-server/src/ReactFlightStackConfigV8.js +++ b/packages/react-server/src/ReactFlightStackConfigV8.js @@ -93,12 +93,12 @@ function collectStackTracePrivate( const enclosingLine: number = // $FlowFixMe[prop-missing] typeof callSite.getEnclosingLineNumber === 'function' - ? (callSite as any).getEnclosingLineNumber() || 0 + ? (callSite: any).getEnclosingLineNumber() || 0 : 0; const enclosingCol: number = // $FlowFixMe[prop-missing] typeof callSite.getEnclosingColumnNumber === 'function' - ? (callSite as any).getEnclosingColumnNumber() || 0 + ? (callSite: any).getEnclosingColumnNumber() || 0 : 0; // $FlowFixMe[prop-missing] const isAsync = callSite.isAsync(); @@ -149,7 +149,7 @@ const frameRegExp = // DEV-only cache of parsed and filtered stack frames. const stackTraceCache: WeakMap<Error, ReactStackTrace> = __DEV__ ? new WeakMap() - : (null as any); + : (null: any); // This version is only used when React fully owns the Error object and there's no risk of it having // been already initialized and no risky that anyone else will initialize it later. diff --git a/packages/react-server/src/ReactFlightThenable.js b/packages/react-server/src/ReactFlightThenable.js index 87ce47673a83..9c0c8082facb 100644 --- a/packages/react-server/src/ReactFlightThenable.js +++ b/packages/react-server/src/ReactFlightThenable.js @@ -54,7 +54,7 @@ export function trackUsedThenable<T>( thenableState.push(thenable); if (__DEV__ && enableAsyncDebugInfo) { const stacks: Array<Error> = - (thenableState as any)._stacks || ((thenableState as any)._stacks = []); + (thenableState: any)._stacks || ((thenableState: any)._stacks = []); stacks.push(new Error()); } } else { @@ -111,19 +111,19 @@ export function trackUsedThenable<T>( // happen. Flight lazily parses JSON when the value is actually awaited. thenable.then(noop, noop); } else { - const pendingThenable: PendingThenable<T> = thenable as any; + const pendingThenable: PendingThenable<T> = (thenable: any); pendingThenable.status = 'pending'; pendingThenable.then( fulfilledValue => { if (thenable.status === 'pending') { - const fulfilledThenable: FulfilledThenable<T> = thenable as any; + const fulfilledThenable: FulfilledThenable<T> = (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = fulfilledValue; } }, (error: mixed) => { if (thenable.status === 'pending') { - const rejectedThenable: RejectedThenable<T> = thenable as any; + const rejectedThenable: RejectedThenable<T> = (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; } @@ -132,13 +132,13 @@ export function trackUsedThenable<T>( } // Check one more time in case the thenable resolved synchronously - switch ((thenable as Thenable<T>).status) { + switch ((thenable: Thenable<T>).status) { case 'fulfilled': { - const fulfilledThenable: FulfilledThenable<T> = thenable as any; + const fulfilledThenable: FulfilledThenable<T> = (thenable: any); return fulfilledThenable.value; } case 'rejected': { - const rejectedThenable: RejectedThenable<T> = thenable as any; + const rejectedThenable: RejectedThenable<T> = (thenable: any); throw rejectedThenable.reason; } } diff --git a/packages/react-server/src/ReactServerConsoleConfigBrowser.js b/packages/react-server/src/ReactServerConsoleConfigBrowser.js index 19e03a14756e..be8bf9534670 100644 --- a/packages/react-server/src/ReactServerConsoleConfigBrowser.js +++ b/packages/react-server/src/ReactServerConsoleConfigBrowser.js @@ -36,7 +36,7 @@ export function unbadgeConsole( case 'table': { // These methods cannot be colorized because they don't take a formatting string. // So we wouldn't have added any badge in the first place. - // $FlowFixMe[incompatible-type] + // $FlowFixMe return null; } case 'assert': { diff --git a/packages/react-server/src/ReactServerConsoleConfigPlain.js b/packages/react-server/src/ReactServerConsoleConfigPlain.js index 0208af4c975b..d93e5f1a0ddf 100644 --- a/packages/react-server/src/ReactServerConsoleConfigPlain.js +++ b/packages/react-server/src/ReactServerConsoleConfigPlain.js @@ -27,7 +27,7 @@ export function unbadgeConsole( case 'table': { // These methods cannot be colorized because they don't take a formatting string. // So we wouldn't have added any badge in the first place. - // $FlowFixMe[incompatible-type] + // $FlowFixMe return null; } case 'assert': { diff --git a/packages/react-server/src/ReactServerConsoleConfigServer.js b/packages/react-server/src/ReactServerConsoleConfigServer.js index bee6d7ad3975..7987b9b262fa 100644 --- a/packages/react-server/src/ReactServerConsoleConfigServer.js +++ b/packages/react-server/src/ReactServerConsoleConfigServer.js @@ -35,7 +35,7 @@ export function unbadgeConsole( case 'table': { // These methods cannot be colorized because they don't take a formatting string. // So we wouldn't have added any badge in the first place. - // $FlowFixMe[incompatible-type] + // $FlowFixMe return null; } case 'assert': { diff --git a/packages/react-server/src/ReactServerStreamConfigBrowser.js b/packages/react-server/src/ReactServerStreamConfigBrowser.js index 4c0a48fe8c18..41d9aedd0053 100644 --- a/packages/react-server/src/ReactServerStreamConfigBrowser.js +++ b/packages/react-server/src/ReactServerStreamConfigBrowser.js @@ -71,7 +71,7 @@ export function writeChunk( if (writtenBytes > 0) { destination.enqueue( new Uint8Array( - (currentView as any as Uint8Array).buffer, + ((currentView: any): Uint8Array).buffer, 0, writtenBytes, ), @@ -84,8 +84,7 @@ export function writeChunk( } let bytesToWrite = chunk; - const allowableBytes = - (currentView as any as Uint8Array).length - writtenBytes; + const allowableBytes = ((currentView: any): Uint8Array).length - writtenBytes; if (allowableBytes < bytesToWrite.byteLength) { // this chunk would overflow the current view. We enqueue a full view // and start a new view with the remaining chunk @@ -95,7 +94,7 @@ export function writeChunk( } else { // fill up the current view and apply the remaining chunk bytes // to a new view. - (currentView as any as Uint8Array).set( + ((currentView: any): Uint8Array).set( bytesToWrite.subarray(0, allowableBytes), writtenBytes, ); @@ -106,7 +105,7 @@ export function writeChunk( currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; } - (currentView as any as Uint8Array).set(bytesToWrite, writtenBytes); + ((currentView: any): Uint8Array).set(bytesToWrite, writtenBytes); writtenBytes += bytesToWrite.byteLength; } @@ -179,7 +178,7 @@ export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number { export function closeWithError(destination: Destination, error: mixed): void { // $FlowFixMe[method-unbinding] if (typeof destination.error === 'function') { - // $FlowFixMe[incompatible-type]: This is an Error object or the destination accepts other types. + // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types. destination.error(error); } else { // Earlier implementations doesn't support this method. In that environment you're @@ -197,7 +196,7 @@ export {createFastHashJS as createFastHash} from 'react-server/src/createFastHas export function readAsDataURL(blob: Blob): Promise<string> { return new Promise((resolve, reject) => { const reader = new FileReader(); - // $FlowFixMe[incompatible-type]: We always expect a string result with readAsDataURL. + // $FlowFixMe[incompatible-call]: We always expect a string result with readAsDataURL. reader.onloadend = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); diff --git a/packages/react-server/src/ReactServerStreamConfigBun.js b/packages/react-server/src/ReactServerStreamConfigBun.js index ce52a5b521e8..a9079bee43a6 100644 --- a/packages/react-server/src/ReactServerStreamConfigBun.js +++ b/packages/react-server/src/ReactServerStreamConfigBun.js @@ -55,7 +55,7 @@ export function writeChunk( return; } - // $FlowFixMe[incompatible-type]: write() is compatible with both types in Bun + // $FlowFixMe[incompatible-call]: write() is compatible with both types in Bun destination.write(chunk); } @@ -63,7 +63,7 @@ export function writeChunkAndReturn( destination: Destination, chunk: PrecomputedChunk | Chunk | BinaryChunk, ): boolean { - // $FlowFixMe[incompatible-type]: write() is compatible with both types in Bun + // $FlowFixMe[incompatible-call]: write() is compatible with both types in Bun return !!destination.write(chunk); } @@ -100,13 +100,13 @@ export function closeWithError(destination: Destination, error: mixed): void { // $FlowFixMe[incompatible-use] // $FlowFixMe[method-unbinding] if (typeof destination.error === 'function') { - // $FlowFixMe[incompatible-type]: This is an Error object or the destination accepts other types. + // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types. destination.error(error); // $FlowFixMe[incompatible-use] // $FlowFixMe[method-unbinding] } else if (typeof destination.destroy === 'function') { - // $FlowFixMe[incompatible-type]: This is an Error object or the destination accepts other types. + // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types. destination.destroy(error); // $FlowFixMe[incompatible-use] diff --git a/packages/react-server/src/ReactServerStreamConfigEdge.js b/packages/react-server/src/ReactServerStreamConfigEdge.js index b0a8fdaab089..90affdc6b8ac 100644 --- a/packages/react-server/src/ReactServerStreamConfigEdge.js +++ b/packages/react-server/src/ReactServerStreamConfigEdge.js @@ -65,7 +65,7 @@ export function writeChunk( if (writtenBytes > 0) { destination.enqueue( new Uint8Array( - (currentView as any as Uint8Array).buffer, + ((currentView: any): Uint8Array).buffer, 0, writtenBytes, ), @@ -78,8 +78,7 @@ export function writeChunk( } let bytesToWrite = chunk; - const allowableBytes = - (currentView as any as Uint8Array).length - writtenBytes; + const allowableBytes = ((currentView: any): Uint8Array).length - writtenBytes; if (allowableBytes < bytesToWrite.byteLength) { // this chunk would overflow the current view. We enqueue a full view // and start a new view with the remaining chunk @@ -89,7 +88,7 @@ export function writeChunk( } else { // fill up the current view and apply the remaining chunk bytes // to a new view. - (currentView as any as Uint8Array).set( + ((currentView: any): Uint8Array).set( bytesToWrite.subarray(0, allowableBytes), writtenBytes, ); @@ -100,7 +99,7 @@ export function writeChunk( currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; } - (currentView as any as Uint8Array).set(bytesToWrite, writtenBytes); + ((currentView: any): Uint8Array).set(bytesToWrite, writtenBytes); writtenBytes += bytesToWrite.byteLength; } @@ -166,7 +165,7 @@ export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number { export function closeWithError(destination: Destination, error: mixed): void { // $FlowFixMe[method-unbinding] if (typeof destination.error === 'function') { - // $FlowFixMe[incompatible-type]: This is an Error object or the destination accepts other types. + // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types. destination.error(error); } else { // Earlier implementations doesn't support this method. In that environment you're diff --git a/packages/react-server/src/ReactServerStreamConfigFB.js b/packages/react-server/src/ReactServerStreamConfigFB.js index e956286fa4f1..61d678ed6958 100644 --- a/packages/react-server/src/ReactServerStreamConfigFB.js +++ b/packages/react-server/src/ReactServerStreamConfigFB.js @@ -18,22 +18,10 @@ export opaque type PrecomputedChunk = string; export opaque type Chunk = string; export opaque type BinaryChunk = string; -export function scheduleMicrotask(callback: () => void) { - // TODO: Consider unifying this with the FB Flight stream config and - // adopting its microtask scheduling implementation. - callback(); -} - -export function scheduleWork(callback: () => void) { - // TODO: Consider unifying this with the FB Flight stream config and - // adopting its work scheduling implementation. - callback(); -} - export function flushBuffered(destination: Destination) {} export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export function beginWriting(destination: Destination) {} diff --git a/packages/react-server/src/ReactServerStreamConfigNode.js b/packages/react-server/src/ReactServerStreamConfigNode.js index c50619e66efd..90609da2c45d 100644 --- a/packages/react-server/src/ReactServerStreamConfigNode.js +++ b/packages/react-server/src/ReactServerStreamConfigNode.js @@ -62,7 +62,7 @@ function writeStringChunk(destination: Destination, stringChunk: string) { if (writtenBytes > 0) { writeToDestination( destination, - (currentView as any as Uint8Array).subarray(0, writtenBytes), + ((currentView: any): Uint8Array).subarray(0, writtenBytes), ); currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; @@ -72,9 +72,9 @@ function writeStringChunk(destination: Destination, stringChunk: string) { return; } - let target: Uint8Array = currentView as any; + let target: Uint8Array = (currentView: any); if (writtenBytes > 0) { - target = (currentView as any as Uint8Array).subarray(writtenBytes); + target = ((currentView: any): Uint8Array).subarray(writtenBytes); } const {read, written} = textEncoder.encodeInto(stringChunk, target); writtenBytes += written; @@ -82,17 +82,17 @@ function writeStringChunk(destination: Destination, stringChunk: string) { if (read < stringChunk.length) { writeToDestination( destination, - (currentView as any).subarray(0, writtenBytes), + (currentView: any).subarray(0, writtenBytes), ); currentView = new Uint8Array(VIEW_SIZE); writtenBytes = textEncoder.encodeInto( stringChunk.slice(read), - currentView as any, + (currentView: any), ).written; } if (writtenBytes === VIEW_SIZE) { - writeToDestination(destination, currentView as any); + writeToDestination(destination, (currentView: any)); currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; } @@ -112,7 +112,7 @@ function writeViewChunk( if (writtenBytes > 0) { writeToDestination( destination, - (currentView as any as Uint8Array).subarray(0, writtenBytes), + ((currentView: any): Uint8Array).subarray(0, writtenBytes), ); currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; @@ -122,33 +122,32 @@ function writeViewChunk( } let bytesToWrite = chunk; - const allowableBytes = - (currentView as any as Uint8Array).length - writtenBytes; + const allowableBytes = ((currentView: any): Uint8Array).length - writtenBytes; if (allowableBytes < bytesToWrite.byteLength) { // this chunk would overflow the current view. We enqueue a full view // and start a new view with the remaining chunk if (allowableBytes === 0) { // the current view is already full, send it - writeToDestination(destination, currentView as any); + writeToDestination(destination, (currentView: any)); } else { // fill up the current view and apply the remaining chunk bytes // to a new view. - (currentView as any as Uint8Array).set( + ((currentView: any): Uint8Array).set( bytesToWrite.subarray(0, allowableBytes), writtenBytes, ); writtenBytes += allowableBytes; - writeToDestination(destination, currentView as any); + writeToDestination(destination, (currentView: any)); bytesToWrite = bytesToWrite.subarray(allowableBytes); } currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; } - (currentView as any as Uint8Array).set(bytesToWrite, writtenBytes); + ((currentView: any): Uint8Array).set(bytesToWrite, writtenBytes); writtenBytes += bytesToWrite.byteLength; if (writtenBytes === VIEW_SIZE) { - writeToDestination(destination, currentView as any); + writeToDestination(destination, (currentView: any)); currentView = new Uint8Array(VIEW_SIZE); writtenBytes = 0; } @@ -161,7 +160,7 @@ export function writeChunk( if (typeof chunk === 'string') { writeStringChunk(destination, chunk); } else { - writeViewChunk(destination, chunk as any as PrecomputedChunk | BinaryChunk); + writeViewChunk(destination, ((chunk: any): PrecomputedChunk | BinaryChunk)); } } @@ -232,7 +231,7 @@ export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number { } export function closeWithError(destination: Destination, error: mixed): void { - // $FlowFixMe[incompatible-type]: This is an Error object or the destination accepts other types. + // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types. destination.destroy(error); } diff --git a/packages/react-server/src/ReactSharedInternalsServer.js b/packages/react-server/src/ReactSharedInternalsServer.js index 0c2bb73a2b61..efc3588a6452 100644 --- a/packages/react-server/src/ReactSharedInternalsServer.js +++ b/packages/react-server/src/ReactSharedInternalsServer.js @@ -12,8 +12,7 @@ import type {SharedStateServer} from 'react/src/ReactSharedInternalsServer'; import * as React from 'react'; const ReactSharedInternalsServer: SharedStateServer = - // $FlowFixMe[incompatible-type]: It's defined in the one we resolve to. - // $FlowFixMe[missing-export] + // $FlowFixMe: It's defined in the one we resolve to. React.__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; if (!ReactSharedInternalsServer) { diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js index f659c2bba839..5107fc0bfaea 100644 --- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js +++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js @@ -3669,267 +3669,4 @@ describe('ReactFlightAsyncDebugInfo', () => { await finishLoadingStream(readable); }); - - it('omits large debug strings to avoid blocking the main thread when parsing', async () => { - async function Component() { - // This promise's value is expected to show up in the debug info below. - const small = await new Promise(resolve => { - setTimeout(() => resolve('hello'), 1); - }); - - // This promise's value exceeds the threshold for debug string length and - // is expected to show up as a placeholder in the debug info below. - // Reconstructing a multi-megabyte string on the client would block the - // main thread for too long. - const large = await new Promise(resolve => { - setTimeout(() => resolve('x'.repeat(1000001)), 1); - }); - - return small + ' ' + large.length; - } - - const stream = ReactServerDOMServer.renderToPipeableStream( - ReactServer.createElement(Component), - {}, - {filterStackFrame}, - ); - - const readable = new Stream.PassThrough(streamOptions); - - const result = ReactServerDOMClient.createFromNodeStream(readable, { - moduleMap: {}, - moduleLoading: {}, - }); - stream.pipe(readable); - - expect(await result).toBe('hello 1000001'); - - await finishLoadingStream(readable); - if ( - __DEV__ && - gate( - flags => - flags.enableComponentPerformanceTrack && flags.enableAsyncDebugInfo, - ) - ) { - expect(getDebugInfo(result)).toMatchInlineSnapshot(` - [ - { - "time": 0, - }, - { - "env": "Server", - "key": null, - "name": "Component", - "props": {}, - "stack": [ - [ - "Object.<anonymous>", - "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3692, - 19, - 3673, - 82, - ], - [ - "new Promise", - "", - 0, - 0, - 0, - 0, - ], - ], - }, - { - "time": 0, - }, - { - "awaited": { - "end": 0, - "env": "Server", - "name": "Component", - "owner": { - "env": "Server", - "key": null, - "name": "Component", - "props": {}, - "stack": [ - [ - "Object.<anonymous>", - "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3692, - 19, - 3673, - 82, - ], - [ - "new Promise", - "", - 0, - 0, - 0, - 0, - ], - ], - }, - "stack": [ - [ - "Component", - "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3676, - 25, - 3674, - 5, - ], - ], - "start": 0, - "value": { - "value": "hello", - }, - }, - "env": "Server", - "owner": { - "env": "Server", - "key": null, - "name": "Component", - "props": {}, - "stack": [ - [ - "Object.<anonymous>", - "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3692, - 19, - 3673, - 82, - ], - [ - "new Promise", - "", - 0, - 0, - 0, - 0, - ], - ], - }, - "stack": [ - [ - "Component", - "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3676, - 25, - 3674, - 5, - ], - ], - }, - { - "time": 0, - }, - { - "time": 0, - }, - { - "awaited": { - "end": 0, - "env": "Server", - "name": "Component", - "owner": { - "env": "Server", - "key": null, - "name": "Component", - "props": {}, - "stack": [ - [ - "Object.<anonymous>", - "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3692, - 19, - 3673, - 82, - ], - [ - "new Promise", - "", - 0, - 0, - 0, - 0, - ], - ], - }, - "stack": [ - [ - "Component", - "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3684, - 25, - 3674, - 5, - ], - ], - "start": 0, - "value": { - "value": "This string of length 1000001 has been omitted by React to avoid sending too much data from the server.", - }, - }, - "env": "Server", - "owner": { - "env": "Server", - "key": null, - "name": "Component", - "props": {}, - "stack": [ - [ - "Object.<anonymous>", - "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3692, - 19, - 3673, - 82, - ], - [ - "new Promise", - "", - 0, - 0, - 0, - 0, - ], - ], - }, - "stack": [ - [ - "Component", - "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 3684, - 25, - 3674, - 5, - ], - ], - }, - { - "time": 0, - }, - { - "time": 0, - }, - { - "awaited": { - "byteSize": 0, - "end": 0, - "name": "rsc stream", - "owner": null, - "start": 0, - "value": { - "value": "stream", - }, - }, - }, - ] - `); - } - }); }); diff --git a/packages/react-server/src/flight/ReactFlightAsyncDispatcher.js b/packages/react-server/src/flight/ReactFlightAsyncDispatcher.js index e261b3709b87..958b92c9cc6c 100644 --- a/packages/react-server/src/flight/ReactFlightAsyncDispatcher.js +++ b/packages/react-server/src/flight/ReactFlightAsyncDispatcher.js @@ -20,10 +20,10 @@ function resolveCache(): Map<Function, mixed> { return new Map(); } -export const DefaultAsyncDispatcher: AsyncDispatcher = { +export const DefaultAsyncDispatcher: AsyncDispatcher = ({ getCacheForType<T>(resourceType: () => T): T { const cache = resolveCache(); - let entry: T | void = cache.get(resourceType) as any; + let entry: T | void = (cache.get(resourceType): any); if (entry === undefined) { entry = resourceType(); // TODO: Warn if undefined? @@ -38,7 +38,7 @@ export const DefaultAsyncDispatcher: AsyncDispatcher = { } return null; }, -} as any; +}: any); if (__DEV__) { DefaultAsyncDispatcher.getOwner = resolveOwner; diff --git a/packages/react-server/src/flight/ReactFlightCurrentOwner.js b/packages/react-server/src/flight/ReactFlightCurrentOwner.js index e2eb66c19d73..fec9e86829ba 100644 --- a/packages/react-server/src/flight/ReactFlightCurrentOwner.js +++ b/packages/react-server/src/flight/ReactFlightCurrentOwner.js @@ -22,7 +22,6 @@ export function setCurrentOwner(componentInfo: null | ReactComponentInfo) { export function resolveOwner(): null | ReactComponentInfo { if (currentOwner) return currentOwner; - // $FlowFixMe[constant-condition] if (supportsComponentStorage) { const owner = componentStorage.getStore(); if (owner) return owner; diff --git a/packages/react-server/src/forks/ReactFizzConfig.custom.js b/packages/react-server/src/forks/ReactFizzConfig.custom.js index e7ed031010b4..aa8ea94b5791 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.custom.js +++ b/packages/react-server/src/forks/ReactFizzConfig.custom.js @@ -40,10 +40,8 @@ export const isPrimaryRenderer = false; export const supportsClientAPIs = true; -export const isWorkLoopExternallyDriven = - $$$config.isWorkLoopExternallyDriven === true; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const bindToConsole = $$$config.bindToConsole; diff --git a/packages/react-server/src/forks/ReactFizzConfig.dom-edge.js b/packages/react-server/src/forks/ReactFizzConfig.dom-edge.js index e568d41c08bc..244202002edd 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.dom-edge.js +++ b/packages/react-server/src/forks/ReactFizzConfig.dom-edge.js @@ -12,9 +12,7 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; export * from 'react-client/src/ReactClientConsoleConfigServer'; -export const isWorkLoopExternallyDriven = false; - // For now, we get this from the global scope, but this will likely move to a module. export const supportsRequestStorage = typeof AsyncLocalStorage === 'function'; export const requestStorage: AsyncLocalStorage<Request | void> = - supportsRequestStorage ? new AsyncLocalStorage() : (null as any); + supportsRequestStorage ? new AsyncLocalStorage() : (null: any); diff --git a/packages/react-server/src/forks/ReactFizzConfig.dom-fb.js b/packages/react-server/src/forks/ReactFizzConfig.dom-fb.js deleted file mode 100644 index 8aa48acba5f9..000000000000 --- a/packages/react-server/src/forks/ReactFizzConfig.dom-fb.js +++ /dev/null @@ -1,20 +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. - * - * @flow - */ -import type {Request} from 'react-server/src/ReactFizzServer'; - -export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; - -export * from 'react-client/src/ReactClientConsoleConfigBrowser'; - -// This renderer is pulled from the outside through renderNextChunk. Promises -// can ping tasks, but the outer caller decides when to process them. -export const isWorkLoopExternallyDriven = true; - -export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; diff --git a/packages/react-server/src/forks/ReactFizzConfig.dom-legacy.js b/packages/react-server/src/forks/ReactFizzConfig.dom-legacy.js index 3e8cb539844c..5695669839f8 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.dom-legacy.js +++ b/packages/react-server/src/forks/ReactFizzConfig.dom-legacy.js @@ -12,6 +12,5 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy'; export * from 'react-client/src/ReactClientConsoleConfigPlain'; -export const isWorkLoopExternallyDriven = false; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); diff --git a/packages/react-server/src/forks/ReactFizzConfig.dom-node.js b/packages/react-server/src/forks/ReactFizzConfig.dom-node.js index 042cb6d74dd2..5ee4566ad09b 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.dom-node.js +++ b/packages/react-server/src/forks/ReactFizzConfig.dom-node.js @@ -15,7 +15,6 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; export * from 'react-client/src/ReactClientConsoleConfigServer'; -export const isWorkLoopExternallyDriven = false; export const supportsRequestStorage = true; export const requestStorage: AsyncLocalStorage<Request | void> = new AsyncLocalStorage(); diff --git a/packages/react-server/src/forks/ReactFizzConfig.dom.js b/packages/react-server/src/forks/ReactFizzConfig.dom.js index a772196e1205..17ddc166a792 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.dom.js +++ b/packages/react-server/src/forks/ReactFizzConfig.dom.js @@ -12,6 +12,5 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; export * from 'react-client/src/ReactClientConsoleConfigBrowser'; -export const isWorkLoopExternallyDriven = false; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); diff --git a/packages/react-server/src/forks/ReactFizzConfig.markup.js b/packages/react-server/src/forks/ReactFizzConfig.markup.js index 4a91bd130add..ed3fa90ff153 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.markup.js +++ b/packages/react-server/src/forks/ReactFizzConfig.markup.js @@ -12,6 +12,5 @@ export * from 'react-markup/src/ReactFizzConfigMarkup.js'; export * from 'react-client/src/ReactClientConsoleConfigPlain'; -export const isWorkLoopExternallyDriven = false; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); diff --git a/packages/react-server/src/forks/ReactFizzConfig.noop.js b/packages/react-server/src/forks/ReactFizzConfig.noop.js index d39c1d2290a7..791c402bf773 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.noop.js +++ b/packages/react-server/src/forks/ReactFizzConfig.noop.js @@ -40,10 +40,8 @@ export const isPrimaryRenderer = false; export const supportsClientAPIs = true; -export const isWorkLoopExternallyDriven = - $$$config.isWorkLoopExternallyDriven === true; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const bindToConsole = $$$config.bindToConsole; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.custom.js b/packages/react-server/src/forks/ReactFlightServerConfig.custom.js index 526b78ea281d..a7f0ee3d991b 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.custom.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.custom.js @@ -23,11 +23,11 @@ export type HintCode = any; export type HintModel<T: any> = any; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const supportsComponentStorage = false; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - null as any; + (null: any); export function createHints(): any { return null; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js index 4995c15f5b48..f7abb24acfc7 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-esm.js @@ -14,11 +14,11 @@ export * from 'react-server-dom-esm/src/server/ReactFlightServerConfigESMBundler export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const supportsComponentStorage = false; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - null as any; + (null: any); export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-fb.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-fb.js index dfd59b7eb6b3..9dbd288d3da5 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-fb.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-fb.js @@ -14,11 +14,11 @@ export * from 'react-flight-server-fb/src/server/ReactFlightServerConfigFBBundle export * from 'react-flight-server-fb/src/server/ReactFlightServerConfigDOMFB'; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const supportsComponentStorage = false; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - null as any; + (null: any); export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js index 5040c4fc0742..d4ab5fe86cf0 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js @@ -14,11 +14,11 @@ export * from 'react-server-dom-parcel/src/server/ReactFlightServerConfigParcelB export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const supportsComponentStorage = false; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - null as any; + (null: any); export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js index d415d976c3ce..a28bae93cac5 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-turbopack.js @@ -14,11 +14,11 @@ export * from 'react-server-dom-turbopack/src/server/ReactFlightServerConfigTurb export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const supportsComponentStorage = false; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - null as any; + (null: any); export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js index acea01b64e0c..a34bbf28c532 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser.js @@ -14,11 +14,11 @@ export * from 'react-server-dom-webpack/src/server/ReactFlightServerConfigWebpac export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const supportsComponentStorage = false; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - null as any; + (null: any); export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js index e5c512fbb98c..04fa6348123a 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-bun.js @@ -14,11 +14,11 @@ export * from '../ReactFlightServerConfigBundlerCustom'; export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const supportsComponentStorage = false; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - null as any; + (null: any); export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js index 9599b96c842e..0cd224a54055 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js @@ -15,12 +15,12 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; // For now, we get this from the global scope, but this will likely move to a module. export const supportsRequestStorage = typeof AsyncLocalStorage === 'function'; export const requestStorage: AsyncLocalStorage<Request | void> = - supportsRequestStorage ? new AsyncLocalStorage() : (null as any); + supportsRequestStorage ? new AsyncLocalStorage() : (null: any); export const supportsComponentStorage: boolean = __DEV__ && supportsRequestStorage; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - supportsComponentStorage ? new AsyncLocalStorage() : (null as any); + supportsComponentStorage ? new AsyncLocalStorage() : (null: any); export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js index 8d334fe0a1ce..67f82f59a6a2 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-turbopack.js @@ -15,12 +15,12 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; // For now, we get this from the global scope, but this will likely move to a module. export const supportsRequestStorage = typeof AsyncLocalStorage === 'function'; export const requestStorage: AsyncLocalStorage<Request | void> = - supportsRequestStorage ? new AsyncLocalStorage() : (null as any); + supportsRequestStorage ? new AsyncLocalStorage() : (null: any); export const supportsComponentStorage: boolean = __DEV__ && supportsRequestStorage; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - supportsComponentStorage ? new AsyncLocalStorage() : (null as any); + supportsComponentStorage ? new AsyncLocalStorage() : (null: any); export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js index 3c966a9fd6cc..c7605c0fcbc7 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js @@ -16,12 +16,12 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; // For now, we get this from the global scope, but this will likely move to a module. export const supportsRequestStorage = typeof AsyncLocalStorage === 'function'; export const requestStorage: AsyncLocalStorage<Request | void> = - supportsRequestStorage ? new AsyncLocalStorage() : (null as any); + supportsRequestStorage ? new AsyncLocalStorage() : (null: any); export const supportsComponentStorage: boolean = __DEV__ && supportsRequestStorage; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - supportsComponentStorage ? new AsyncLocalStorage() : (null as any); + supportsComponentStorage ? new AsyncLocalStorage() : (null: any); export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js index 81ec18bf3c60..2b4d2e1e809e 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js @@ -23,11 +23,11 @@ export type HintCode = any; export type HintModel<T: any> = any; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const supportsComponentStorage = false; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - null as any; + (null: any); export function createHints(): any { return null; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js index d59cfc893921..04ece77cc4a4 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js @@ -21,7 +21,7 @@ export const requestStorage: AsyncLocalStorage<Request | void> = export const supportsComponentStorage = __DEV__; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - supportsComponentStorage ? new AsyncLocalStorage() : (null as any); + supportsComponentStorage ? new AsyncLocalStorage() : (null: any); export * from '../ReactFlightServerConfigDebugNode'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-fb.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-fb.js index 0c6546d832c7..2a69070d027e 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-fb.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-fb.js @@ -14,11 +14,11 @@ export * from 'react-flight-server-fb/src/server/ReactFlightServerConfigFBBundle export * from 'react-flight-server-fb/src/server/ReactFlightServerConfigDOMFB'; export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const supportsComponentStorage = false; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - null as any; + (null: any); export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js index 5a72d34098f4..b4f0f98a2db4 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js @@ -21,7 +21,7 @@ export const requestStorage: AsyncLocalStorage<Request | void> = export const supportsComponentStorage = __DEV__; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - supportsComponentStorage ? new AsyncLocalStorage() : (null as any); + supportsComponentStorage ? new AsyncLocalStorage() : (null: any); export * from '../ReactFlightServerConfigDebugNode'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js index 67e57fd84f30..03975f6bf5fe 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-turbopack.js @@ -21,7 +21,7 @@ export const requestStorage: AsyncLocalStorage<Request | void> = export const supportsComponentStorage = __DEV__; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - supportsComponentStorage ? new AsyncLocalStorage() : (null as any); + supportsComponentStorage ? new AsyncLocalStorage() : (null: any); export * from '../ReactFlightServerConfigDebugNode'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-unbundled.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-unbundled.js index 72c66133d30a..661b756fc82f 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-unbundled.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-unbundled.js @@ -21,7 +21,7 @@ export const requestStorage: AsyncLocalStorage<Request | void> = export const supportsComponentStorage = __DEV__; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - supportsComponentStorage ? new AsyncLocalStorage() : (null as any); + supportsComponentStorage ? new AsyncLocalStorage() : (null: any); export * from '../ReactFlightServerConfigDebugNode'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js index 590cb0065de6..f60f0f0242a2 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node.js @@ -21,7 +21,7 @@ export const requestStorage: AsyncLocalStorage<Request | void> = export const supportsComponentStorage = __DEV__; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - supportsComponentStorage ? new AsyncLocalStorage() : (null as any); + supportsComponentStorage ? new AsyncLocalStorage() : (null: any); export * from '../ReactFlightServerConfigDebugNode'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.markup.js b/packages/react-server/src/forks/ReactFlightServerConfig.markup.js index 4f0684cb5847..ca8c4670834f 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.markup.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.markup.js @@ -34,11 +34,11 @@ export function getChildFormatContext( } export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const supportsComponentStorage = false; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - null as any; + (null: any); export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.noop.js b/packages/react-server/src/forks/ReactFlightServerConfig.noop.js index 7ce0f277ae51..2d1fb68a07a7 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.noop.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.noop.js @@ -22,11 +22,11 @@ export type HintCode = string; export type HintModel<T: HintCode> = null; // eslint-disable-line no-unused-vars export const supportsRequestStorage = false; -export const requestStorage: AsyncLocalStorage<Request | void> = null as any; +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any); export const supportsComponentStorage = false; export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> = - null as any; + (null: any); export function createHints(): Hints { return null; diff --git a/packages/react-server/src/forks/ReactServerStreamConfig.dom-fb.js b/packages/react-server/src/forks/ReactServerStreamConfig.dom-fb.js index f90c75933874..12ed6ba59852 100644 --- a/packages/react-server/src/forks/ReactServerStreamConfig.dom-fb.js +++ b/packages/react-server/src/forks/ReactServerStreamConfig.dom-fb.js @@ -8,3 +8,11 @@ */ export * from '../ReactServerStreamConfigFB'; + +export function scheduleMicrotask(callback: () => void) { + // We don't schedule work in this model, and instead expect performWork to always be called repeatedly. +} + +export function scheduleWork(callback: () => void) { + // We don't schedule work in this model, and instead expect performWork to always be called repeatedly. +} diff --git a/packages/react-suspense-test-utils/package.json b/packages/react-suspense-test-utils/package.json index 56bb5eb50840..b23c3fc2e31c 100644 --- a/packages/react-suspense-test-utils/package.json +++ b/packages/react-suspense-test-utils/package.json @@ -4,7 +4,7 @@ "private": true, "repository": { "type" : "git", - "url" : "https://github.com/react/react.git", + "url" : "https://github.com/facebook/react.git", "directory": "packages/react-suspense-test-utils" }, "license": "MIT", diff --git a/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js index 65a54504d570..0e8e5c5b687d 100644 --- a/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js +++ b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js @@ -14,7 +14,7 @@ export function waitForSuspense<T>(fn: () => T): Promise<T> { const cache: Map<Function, mixed> = new Map(); const testDispatcher: AsyncDispatcher = { getCacheForType<R>(resourceType: () => R): R { - let entry: R | void = cache.get(resourceType) as any; + let entry: R | void = (cache.get(resourceType): any); if (entry === undefined) { entry = resourceType(); // TODO: Warn if undefined? diff --git a/packages/react-test-renderer/package.json b/packages/react-test-renderer/package.json index 080d498bb3e3..b09c371c0100 100644 --- a/packages/react-test-renderer/package.json +++ b/packages/react-test-renderer/package.json @@ -5,7 +5,7 @@ "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react-test-renderer" }, "keywords": [ @@ -15,7 +15,7 @@ ], "license": "MIT", "bugs": { - "url": "https://github.com/react/react/issues" + "url": "https://github.com/facebook/react/issues" }, "homepage": "https://react.dev/", "dependencies": { diff --git a/packages/react-test-renderer/src/ReactFiberConfigTestHost.js b/packages/react-test-renderer/src/ReactFiberConfigTestHost.js index 9614e0504691..6b04a36d297a 100644 --- a/packages/react-test-renderer/src/ReactFiberConfigTestHost.js +++ b/packages/react-test-renderer/src/ReactFiberConfigTestHost.js @@ -612,8 +612,8 @@ export function getSuspendedCommitReason( export const NotPendingTransition: TransitionStatus = null; export const HostTransitionContext: ReactContext<TransitionStatus> = { $$typeof: REACT_CONTEXT_TYPE, - Provider: null as any, - Consumer: null as any, + Provider: (null: any), + Consumer: (null: any), _currentValue: NotPendingTransition, _currentValue2: NotPendingTransition, _threadCount: 0, diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index 2533af3b4a9f..08676503fa49 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -67,7 +67,6 @@ import type {WorkTag} from 'react-reconciler/src/ReactWorkTags'; const defaultOnDefaultTransitionIndicator: () => void | (() => void) = noop; // $FlowFixMe[prop-missing]: This is only in the development export. -// $FlowFixMe[missing-export] const act = React.act; // TODO: Remove from public bundle @@ -180,7 +179,7 @@ function flatten(arr) { // $FlowFixMe[incompatible-use] n.i += 1; if (isArray(el)) { - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] stack.push(n); stack.push({i: 0, array: el}); break; @@ -288,10 +287,10 @@ function getChildren(parent: Fiber) { if (node.return === startingNode) { break outer; } - node = node.return as any; + node = (node.return: any); } - (node.sibling as any).return = node.return; - node = node.sibling as any; + (node.sibling: any).return = node.return; + node = (node.sibling: any); } return children; } @@ -495,7 +494,6 @@ function create( global.IS_REACT_NATIVE_TEST_ENVIRONMENT !== true; let isConcurrent = isConcurrentOnly; let isStrictMode = false; - // $FlowFixMe[invalid-compare] if (typeof options === 'object' && options !== null) { if (typeof options.createNodeMock === 'function') { // $FlowFixMe[incompatible-type] found when upgrading Flow @@ -509,7 +507,7 @@ function create( } } let container: Container = { - children: [] as Array<Instance | TextInstance>, + children: ([]: Array<Instance | TextInstance>), createNodeMock, tag: 'CONTAINER', }; @@ -531,7 +529,6 @@ function create( throw new Error('something went wrong'); } - // $FlowFixMe[incompatible-type] updateContainer(element, root, null, null); const entry = { @@ -583,7 +580,6 @@ function create( if (root == null || root.current == null) { return; } - // $FlowFixMe[incompatible-type] updateContainer(newElement, root, null, null); }, unmount() { @@ -605,27 +601,31 @@ function create( unstable_flushSync: flushSyncFromReconciler, }; - Object.defineProperty(entry, 'root', { - configurable: true, - enumerable: true, - get: function () { - if (root === null) { - throw new Error("Can't access .root on unmounted test renderer"); - } - const children = getChildren(root.current); - if (children.length === 0) { - throw new Error("Can't access .root on unmounted test renderer"); - } else if (children.length === 1) { - // Normally, we skip the root and just give you the child. - return children[0]; - } else { - // However, we give you the root if there's more than one root child. - // We could make this the behavior for all cases but it would be a breaking change. - // $FlowFixMe[incompatible-use] found when upgrading Flow - return wrapFiber(root.current); - } - }, - } as Object); + Object.defineProperty( + entry, + 'root', + ({ + configurable: true, + enumerable: true, + get: function () { + if (root === null) { + throw new Error("Can't access .root on unmounted test renderer"); + } + const children = getChildren(root.current); + if (children.length === 0) { + throw new Error("Can't access .root on unmounted test renderer"); + } else if (children.length === 1) { + // Normally, we skip the root and just give you the child. + return children[0]; + } else { + // However, we give you the root if there's more than one root child. + // We could make this the behavior for all cases but it would be a breaking change. + // $FlowFixMe[incompatible-use] found when upgrading Flow + return wrapFiber(root.current); + } + }, + }: Object), + ); return entry; } diff --git a/packages/react/index.development.js b/packages/react/index.development.js index 5ee39427fdde..ed4a5a325d95 100644 --- a/packages/react/index.development.js +++ b/packages/react/index.development.js @@ -12,13 +12,11 @@ export type ElementType = React$ElementType; export type Element<+C> = React$Element<C>; export type Key = React$Key; export type Node = React$Node; -// eslint-disable-next-line no-undef -export type Context<T> = React.Context<T>; +export type Context<T> = React$Context<T>; export type Portal = React$Portal; -export type ElementProps<C> = React$ElementConfig<C>; +export type ElementProps<C> = React$ElementProps<C>; export type ElementConfig<C> = React$ElementConfig<C>; -// eslint-disable-next-line no-undef -export type ElementRef<C> = React.ElementRef<C>; +export type ElementRef<C> = React$ElementRef<C>; export type ChildrenArray<+T> = $ReadOnlyArray<ChildrenArray<T>> | T; // Export all exports so that they're available in tests. diff --git a/packages/react/index.js b/packages/react/index.js index 140174ec01de..78b11b809e75 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -13,15 +13,12 @@ export type Element<+C> = React$Element<C>; export type MixedElement = React$Element<ElementType>; export type Key = React$Key; export type Node = React$Node; -// eslint-disable-next-line no-undef -export type Context<T> = React.Context<T>; +export type Context<T> = React$Context<T>; export type Portal = React$Portal; -// eslint-disable-next-line no-undef -export type RefSetter<-I> = React.RefSetter<I>; -export type ElementProps<C> = React$ElementConfig<C>; +export type RefSetter<-I> = React$RefSetter<I>; +export type ElementProps<C> = React$ElementProps<C>; export type ElementConfig<C> = React$ElementConfig<C>; -// eslint-disable-next-line no-undef -export type ElementRef<C> = React.ElementRef<C>; +export type ElementRef<C> = React$ElementRef<C>; export type ChildrenArray<+T> = $ReadOnlyArray<ChildrenArray<T>> | T; export { diff --git a/packages/react/package.json b/packages/react/package.json index cc5169a93739..0705be8aa063 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -6,7 +6,7 @@ ], "version": "19.3.0", "homepage": "https://react.dev/", - "bugs": "https://github.com/react/react/issues", + "bugs": "https://github.com/facebook/react/issues", "license": "MIT", "files": [ "LICENSE", @@ -43,7 +43,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/react" }, "engines": { diff --git a/packages/react/src/ReactAct.js b/packages/react/src/ReactAct.js index 40d87d6840f2..1e74d222a29a 100644 --- a/packages/react/src/ReactAct.js +++ b/packages/react/src/ReactAct.js @@ -102,7 +102,6 @@ export function act<T>(callback: () => T | Thenable<T>): Thenable<T> { } if ( - // $FlowFixMe[invalid-compare] result !== null && typeof result === 'object' && // $FlowFixMe[method-unbinding] @@ -114,7 +113,7 @@ export function act<T>(callback: () => T | Thenable<T>): Thenable<T> { // If `act` were implemented as an async function, this whole block could // be a single `await` call. That's really the only difference between // this branch and the next one. - const thenable = result as any as Thenable<T>; + const thenable = ((result: any): Thenable<T>); // Warn if the an `act` call with an async scope is not awaited. In a // future release, consider making this an error. @@ -178,7 +177,7 @@ export function act<T>(callback: () => T | Thenable<T>): Thenable<T> { }, }; } else { - const returnValue: T = result as any; + const returnValue: T = (result: any); // The callback is not an async function. Exit the current // scope immediately. popActScope(prevActQueue, prevActScopeDepth); diff --git a/packages/react/src/ReactCacheClient.js b/packages/react/src/ReactCacheClient.js index 69bfdfc7559f..ef1e8d6d1da5 100644 --- a/packages/react/src/ReactCacheClient.js +++ b/packages/react/src/ReactCacheClient.js @@ -27,7 +27,7 @@ function noopCache<A: Iterable<mixed>, T>(fn: (...A) => T): (...A) => T { // preserved, the length of the new function is 0, etc. That way apps can't // accidentally depend on those details. return function () { - // $FlowFixMe[incompatible-type]: We don't want to use rest arguments since we transpile the code. + // $FlowFixMe[incompatible-call]: We don't want to use rest arguments since we transpile the code. return fn.apply(null, arguments); }; } diff --git a/packages/react/src/ReactCacheImpl.js b/packages/react/src/ReactCacheImpl.js index e4854e1023e9..2ff7431fc519 100644 --- a/packages/react/src/ReactCacheImpl.js +++ b/packages/react/src/ReactCacheImpl.js @@ -57,7 +57,7 @@ export function cache<A: Iterable<mixed>, T>(fn: (...A) => T): (...A) => T { const dispatcher = ReactSharedInternals.A; if (!dispatcher) { // If there is no dispatcher, then we treat this as not being cached. - // $FlowFixMe[incompatible-type]: We don't want to use rest arguments since we transpile the code. + // $FlowFixMe[incompatible-call]: We don't want to use rest arguments since we transpile the code. return fn.apply(null, arguments); } const fnMap: WeakMap<any, CacheNode<T>> = dispatcher.getCacheForType( @@ -75,7 +75,6 @@ export function cache<A: Iterable<mixed>, T>(fn: (...A) => T): (...A) => T { const arg = arguments[i]; if ( typeof arg === 'function' || - // $FlowFixMe[invalid-compare] (typeof arg === 'object' && arg !== null) ) { // Objects go into a WeakMap @@ -112,15 +111,15 @@ export function cache<A: Iterable<mixed>, T>(fn: (...A) => T): (...A) => T { throw cacheNode.v; } try { - // $FlowFixMe[incompatible-type]: We don't want to use rest arguments since we transpile the code. + // $FlowFixMe[incompatible-call]: We don't want to use rest arguments since we transpile the code. const result = fn.apply(null, arguments); - const terminatedNode: TerminatedCacheNode<T> = cacheNode as any; + const terminatedNode: TerminatedCacheNode<T> = (cacheNode: any); terminatedNode.s = TERMINATED; terminatedNode.v = result; return result; } catch (error) { // We store the first error that's thrown and rethrow it. - const erroredNode: ErroredCacheNode<T> = cacheNode as any; + const erroredNode: ErroredCacheNode<T> = (cacheNode: any); erroredNode.s = ERRORED; erroredNode.v = error; throw error; diff --git a/packages/react/src/ReactChildren.js b/packages/react/src/ReactChildren.js index 99ad9a7971cb..d4c41d6669a3 100644 --- a/packages/react/src/ReactChildren.js +++ b/packages/react/src/ReactChildren.js @@ -115,19 +115,19 @@ function resolveThenable<T>(thenable: Thenable<T>): T { // TODO: Detect infinite ping loops caused by uncached promises. - const pendingThenable: PendingThenable<T> = thenable as any; + const pendingThenable: PendingThenable<T> = (thenable: any); pendingThenable.status = 'pending'; pendingThenable.then( fulfilledValue => { if (thenable.status === 'pending') { - const fulfilledThenable: FulfilledThenable<T> = thenable as any; + const fulfilledThenable: FulfilledThenable<T> = (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = fulfilledValue; } }, (error: mixed) => { if (thenable.status === 'pending') { - const rejectedThenable: RejectedThenable<T> = thenable as any; + const rejectedThenable: RejectedThenable<T> = (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; } @@ -136,13 +136,13 @@ function resolveThenable<T>(thenable: Thenable<T>): T { } // Check one more time in case the thenable resolved synchronously. - switch ((thenable as Thenable<T>).status) { + switch ((thenable: Thenable<T>).status) { case 'fulfilled': { - const fulfilledThenable: FulfilledThenable<T> = thenable as any; + const fulfilledThenable: FulfilledThenable<T> = (thenable: any); return fulfilledThenable.value; } case 'rejected': { - const rejectedThenable: RejectedThenable<T> = thenable as any; + const rejectedThenable: RejectedThenable<T> = (thenable: any); const rejectedError = rejectedThenable.reason; throw rejectedError; } @@ -178,14 +178,14 @@ function mapIntoArray( invokeCallback = true; break; case 'object': - switch ((children as any).$$typeof) { + switch ((children: any).$$typeof) { case REACT_ELEMENT_TYPE: case REACT_PORTAL_TYPE: invokeCallback = true; break; case REACT_LAZY_TYPE: - const payload = (children as any)._payload; - const init = (children as any)._init; + const payload = (children: any)._payload; + const init = (children: any)._init; return mapIntoArray( init(payload), array, @@ -287,7 +287,7 @@ function mapIntoArray( if (typeof iteratorFn === 'function') { const iterableChildren: Iterable<React$Node> & { entries: any, - } = children as any; + } = (children: any); if (__DEV__) { // Warn about using Maps as children @@ -318,9 +318,9 @@ function mapIntoArray( ); } } else if (type === 'object') { - if (typeof (children as any).then === 'function') { + if (typeof (children: any).then === 'function') { return mapIntoArray( - resolveThenable(children as any), + resolveThenable((children: any)), array, escapedPrefix, nameSoFar, @@ -329,13 +329,13 @@ function mapIntoArray( } // eslint-disable-next-line react-internal/safe-string-coercion - const childrenString = String(children as any); + const childrenString = String((children: any)); throw new Error( `Objects are not valid as a React child (found: ${ childrenString === '[object Object]' ? 'object with keys {' + - Object.keys(children as any).join(', ') + + Object.keys((children: any)).join(', ') + '}' : childrenString }). ` + @@ -369,7 +369,7 @@ function mapChildren( context: mixed, ): ?Array<React$Node> { if (children == null) { - // $FlowFixMe[incompatible-type] limitation refining abstract types in Flow + // $FlowFixMe limitation refining abstract types in Flow return children; } const result: Array<React$Node> = []; diff --git a/packages/react/src/ReactContext.js b/packages/react/src/ReactContext.js index ca105aa952a6..d5dbb433dfe3 100644 --- a/packages/react/src/ReactContext.js +++ b/packages/react/src/ReactContext.js @@ -28,8 +28,8 @@ export function createContext<T>(defaultValue: T): ReactContext<T> { // supports within in a single renderer. Such as parallel server rendering. _threadCount: 0, // These are circular - Provider: null as any, - Consumer: null as any, + Provider: (null: any), + Consumer: (null: any), }; context.Provider = context; diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index bdb382ca2cfd..ff86130baa05 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -38,7 +38,7 @@ function resolveDispatcher() { // Will result in a null access error if accessed outside render phase. We // intentionally don't throw our own error because this is in a hot path. // Also helps ensure this is inlined. - return dispatcher as any as Dispatcher; + return ((dispatcher: any): Dispatcher); } export function getCacheForType<T>(resourceType: () => T): T { diff --git a/packages/react/src/ReactLazy.js b/packages/react/src/ReactLazy.js index f782758b99d3..b380750ca10a 100644 --- a/packages/react/src/ReactLazy.js +++ b/packages/react/src/ReactLazy.js @@ -69,8 +69,8 @@ export type LazyComponent<T, P> = { function lazyInitializer<T>(payload: Payload<T>): T { if (payload._status === Uninitialized) { - let resolveDebugValue: (void | T) => void = null as any; - let rejectDebugValue: mixed => void = null as any; + let resolveDebugValue: (void | T) => void = (null: any); + let rejectDebugValue: mixed => void = (null: any); if (__DEV__ && enableAsyncDebugInfo) { const ioInfo = payload._ioInfo; if (ioInfo != null) { @@ -95,11 +95,11 @@ function lazyInitializer<T>(payload: Payload<T>): T { thenable.then( moduleObject => { if ( - (payload as Payload<T>)._status === Pending || + (payload: Payload<T>)._status === Pending || payload._status === Uninitialized ) { // Transition to the next state. - const resolved: ResolvedPayload<T> = payload as any; + const resolved: ResolvedPayload<T> = (payload: any); resolved._status = Resolved; resolved._result = moduleObject; if (__DEV__ && enableAsyncDebugInfo) { @@ -112,11 +112,9 @@ function lazyInitializer<T>(payload: Payload<T>): T { const debugValue = moduleObject == null ? undefined : moduleObject.default; resolveDebugValue(debugValue); - // $FlowFixMe[incompatible-use] - // $FlowFixMe[prop-missing] + // $FlowFixMe ioInfo.value.status = 'fulfilled'; - // $FlowFixMe[incompatible-use] - // $FlowFixMe[prop-missing] + // $FlowFixMe ioInfo.value.value = debugValue; } } @@ -125,7 +123,7 @@ function lazyInitializer<T>(payload: Payload<T>): T { // impl or make suspendedThenable be able to be a lazy itself if (thenable.status === undefined) { const fulfilledThenable: FulfilledThenable<{default: T, ...}> = - thenable as any; + (thenable: any); fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = moduleObject; } @@ -133,11 +131,11 @@ function lazyInitializer<T>(payload: Payload<T>): T { }, error => { if ( - (payload as Payload<T>)._status === Pending || + (payload: Payload<T>)._status === Pending || payload._status === Uninitialized ) { // Transition to the next state. - const rejected: RejectedPayload = payload as any; + const rejected: RejectedPayload = (payload: any); rejected._status = Rejected; rejected._result = error; if (__DEV__ && enableAsyncDebugInfo) { @@ -147,14 +145,12 @@ function lazyInitializer<T>(payload: Payload<T>): T { // $FlowFixMe[cannot-write] ioInfo.end = performance.now(); // Hide unhandled rejections. - // $FlowFixMe[incompatible-use] + // $FlowFixMe ioInfo.value.then(noop, noop); rejectDebugValue(error); - // $FlowFixMe[incompatible-use] - // $FlowFixMe[prop-missing] + // $FlowFixMe ioInfo.value.status = 'rejected'; - // $FlowFixMe[incompatible-use] - // $FlowFixMe[prop-missing] + // $FlowFixMe ioInfo.value.reason = error; } } @@ -163,7 +159,7 @@ function lazyInitializer<T>(payload: Payload<T>): T { // impl or make suspendedThenable be able to be a lazy itself if (thenable.status === undefined) { const rejectedThenable: RejectedThenable<{default: T, ...}> = - thenable as any; + (thenable: any); rejectedThenable.status = 'rejected'; rejectedThenable.reason = error; } @@ -183,7 +179,7 @@ function lazyInitializer<T>(payload: Payload<T>): T { if (payload._status === Uninitialized) { // In case, we're still uninitialized, then we're waiting for the thenable // to resolve. Set it as pending in the meantime. - const pending: PendingPayload = payload as any; + const pending: PendingPayload = (payload: any); pending._status = Pending; pending._result = thenable; } diff --git a/packages/react/src/ReactSharedInternalsClient.js b/packages/react/src/ReactSharedInternalsClient.js index 1599e57a6f36..cb957a5dfd32 100644 --- a/packages/react/src/ReactSharedInternalsClient.js +++ b/packages/react/src/ReactSharedInternalsClient.js @@ -57,12 +57,12 @@ export type SharedStateClient = { export type RendererTask = boolean => RendererTask | null; -const ReactSharedInternals: SharedStateClient = { +const ReactSharedInternals: SharedStateClient = ({ H: null, A: null, T: null, S: null, -} as any; +}: any); if (enableGestureTransition) { ReactSharedInternals.G = null; } @@ -75,7 +75,7 @@ if (__DEV__) { ReactSharedInternals.didUsePromise = false; ReactSharedInternals.thrownErrors = []; // Stack implementation injected by the current renderer. - ReactSharedInternals.getCurrentStack = null as null | (() => string); + ReactSharedInternals.getCurrentStack = (null: null | (() => string)); ReactSharedInternals.recentlyCreatedOwnerStacks = 0; } diff --git a/packages/react/src/ReactSharedInternalsServer.js b/packages/react/src/ReactSharedInternalsServer.js index ab8900f9578f..c411b77b59db 100644 --- a/packages/react/src/ReactSharedInternalsServer.js +++ b/packages/react/src/ReactSharedInternalsServer.js @@ -46,10 +46,10 @@ export type SharedStateServer = { export type RendererTask = boolean => RendererTask | null; -const ReactSharedInternals: SharedStateServer = { +const ReactSharedInternals: SharedStateServer = ({ H: null, A: null, -} as any; +}: any); if (enableTaint) { ReactSharedInternals.TaintRegistryObjects = TaintRegistryObjects; @@ -61,7 +61,7 @@ if (enableTaint) { if (__DEV__) { // Stack implementation injected by the current renderer. - ReactSharedInternals.getCurrentStack = null as null | (() => string); + ReactSharedInternals.getCurrentStack = (null: null | (() => string)); ReactSharedInternals.recentlyCreatedOwnerStacks = 0; } diff --git a/packages/react/src/ReactStartTransition.js b/packages/react/src/ReactStartTransition.js index ec6c4b86aa2c..3e353a3b6153 100644 --- a/packages/react/src/ReactStartTransition.js +++ b/packages/react/src/ReactStartTransition.js @@ -47,7 +47,7 @@ export function startTransition( options?: StartTransitionOptions, ): void { const prevTransition = ReactSharedInternals.T; - const currentTransition: Transition = {} as any; + const currentTransition: Transition = ({}: any); if (enableViewTransition) { currentTransition.types = prevTransition !== null @@ -80,7 +80,6 @@ export function startTransition( } if ( typeof returnValue === 'object' && - // $FlowFixMe[invalid-compare] returnValue !== null && typeof returnValue.then === 'function' ) { @@ -137,11 +136,10 @@ export function startGestureTransition( ); } const prevTransition = ReactSharedInternals.T; - const currentTransition: Transition = {} as any; + const currentTransition: Transition = ({}: any); if (enableViewTransition) { currentTransition.types = null; } - // $FlowFixMe[constant-condition] if (enableGestureTransition) { currentTransition.gesture = provider; } @@ -160,7 +158,6 @@ export function startGestureTransition( if (__DEV__) { if ( typeof returnValue === 'object' && - // $FlowFixMe[invalid-compare] returnValue !== null && typeof returnValue.then === 'function' ) { diff --git a/packages/react/src/ReactTaint.js b/packages/react/src/ReactTaint.js index c80a18a597b2..99bf73ac2544 100644 --- a/packages/react/src/ReactTaint.js +++ b/packages/react/src/ReactTaint.js @@ -63,7 +63,6 @@ export function taintUniqueValue( // eslint-disable-next-line react-internal/safe-string-coercion message = '' + (message || defaultMessage); if ( - // $FlowFixMe[invalid-compare] lifetime === null || (typeof lifetime !== 'object' && typeof lifetime !== 'function') ) { @@ -87,7 +86,6 @@ export function taintUniqueValue( TaintRegistryByteLengths.add(value.byteLength); entryValue = binaryToComparableString(value); } else { - // $FlowFixMe[invalid-compare] const kind = value === null ? 'null' : typeof value; if (kind === 'object' || kind === 'function') { throw new Error( @@ -129,7 +127,6 @@ export function taintObjectReference( ); } if ( - // $FlowFixMe[invalid-compare] object === null || (typeof object !== 'object' && typeof object !== 'function') ) { diff --git a/packages/scheduler/package.json b/packages/scheduler/package.json index e6fb0c375c2c..9c94ef2b3572 100644 --- a/packages/scheduler/package.json +++ b/packages/scheduler/package.json @@ -4,7 +4,7 @@ "description": "Cooperative scheduler for the browser environment.", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/scheduler" }, "license": "MIT", @@ -12,7 +12,7 @@ "react" ], "bugs": { - "url": "https://github.com/react/react/issues" + "url": "https://github.com/facebook/react/issues" }, "homepage": "https://react.dev/", "files": [ diff --git a/packages/scheduler/src/SchedulerMinHeap.js b/packages/scheduler/src/SchedulerMinHeap.js index 4601bb056084..da997bad3577 100644 --- a/packages/scheduler/src/SchedulerMinHeap.js +++ b/packages/scheduler/src/SchedulerMinHeap.js @@ -33,7 +33,7 @@ export function pop<T: Node>(heap: Heap<T>): T | null { if (last !== first) { // $FlowFixMe[incompatible-type] heap[0] = last; - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-call] siftDown(heap, last, 0); } return first; diff --git a/packages/scheduler/src/SchedulerProfiling.js b/packages/scheduler/src/SchedulerProfiling.js index 22d1d71748bf..9fdd5906af62 100644 --- a/packages/scheduler/src/SchedulerProfiling.js +++ b/packages/scheduler/src/SchedulerProfiling.js @@ -47,7 +47,7 @@ function logEvent(entries: Array<number | PriorityLevel>) { return; } const newEventLog = new Int32Array(eventLogSize * 4); - // $FlowFixMe[incompatible-type] found when upgrading Flow + // $FlowFixMe[incompatible-call] found when upgrading Flow newEventLog.set(eventLog); eventLogBuffer = newEventLog.buffer; eventLog = newEventLog; @@ -80,7 +80,6 @@ export function markTaskStart( }, ms: number, ) { - // $FlowFixMe[constant-condition] if (enableProfiling) { if (eventLog !== null) { // performance.now returns a float, representing milliseconds. When the @@ -99,7 +98,6 @@ export function markTaskCompleted( }, ms: number, ) { - // $FlowFixMe[constant-condition] if (enableProfiling) { if (eventLog !== null) { logEvent([TaskCompleteEvent, ms * 1000, task.id]); @@ -115,7 +113,6 @@ export function markTaskCanceled( }, ms: number, ) { - // $FlowFixMe[constant-condition] if (enableProfiling) { if (eventLog !== null) { logEvent([TaskCancelEvent, ms * 1000, task.id]); @@ -131,7 +128,6 @@ export function markTaskErrored( }, ms: number, ) { - // $FlowFixMe[constant-condition] if (enableProfiling) { if (eventLog !== null) { logEvent([TaskErrorEvent, ms * 1000, task.id]); @@ -147,7 +143,6 @@ export function markTaskRun( }, ms: number, ) { - // $FlowFixMe[constant-condition] if (enableProfiling) { runIdCounter++; @@ -158,7 +153,6 @@ export function markTaskRun( } export function markTaskYield(task: {id: number, ...}, ms: number) { - // $FlowFixMe[constant-condition] if (enableProfiling) { if (eventLog !== null) { logEvent([TaskYieldEvent, ms * 1000, task.id, runIdCounter]); @@ -167,7 +161,6 @@ export function markTaskYield(task: {id: number, ...}, ms: number) { } export function markSchedulerSuspended(ms: number) { - // $FlowFixMe[constant-condition] if (enableProfiling) { mainThreadIdCounter++; @@ -178,7 +171,6 @@ export function markSchedulerSuspended(ms: number) { } export function markSchedulerUnsuspended(ms: number) { - // $FlowFixMe[constant-condition] if (enableProfiling) { if (eventLog !== null) { logEvent([SchedulerResumeEvent, ms * 1000, mainThreadIdCounter]); diff --git a/packages/scheduler/src/forks/Scheduler.js b/packages/scheduler/src/forks/Scheduler.js index 0ea8753fc2ba..88239b710676 100644 --- a/packages/scheduler/src/forks/Scheduler.js +++ b/packages/scheduler/src/forks/Scheduler.js @@ -112,7 +112,6 @@ function advanceTimers(currentTime: number) { pop(timerQueue); timer.sortIndex = timer.expirationTime; push(taskQueue, timer); - // $FlowFixMe[constant-condition] if (enableProfiling) { markTaskStart(timer, currentTime); timer.isQueued = true; @@ -143,7 +142,6 @@ function handleTimeout(currentTime: number) { } function flushWork(initialTime: number) { - // $FlowFixMe[constant-condition] if (enableProfiling) { markSchedulerUnsuspended(initialTime); } @@ -159,7 +157,6 @@ function flushWork(initialTime: number) { isPerformingWork = true; const previousPriorityLevel = currentPriorityLevel; try { - // $FlowFixMe[constant-condition] if (enableProfiling) { try { return workLoop(initialTime); @@ -167,7 +164,6 @@ function flushWork(initialTime: number) { if (currentTask !== null) { const currentTime = getCurrentTime(); // $FlowFixMe[incompatible-call] found when upgrading Flow - // $FlowFixMe[incompatible-type] markTaskErrored(currentTask, currentTime); // $FlowFixMe[incompatible-use] found when upgrading Flow currentTask.isQueued = false; @@ -182,7 +178,6 @@ function flushWork(initialTime: number) { currentTask = null; currentPriorityLevel = previousPriorityLevel; isPerformingWork = false; - // $FlowFixMe[constant-condition] if (enableProfiling) { const currentTime = getCurrentTime(); markSchedulerSuspended(currentTime); @@ -210,9 +205,8 @@ function workLoop(initialTime: number) { currentPriorityLevel = currentTask.priorityLevel; // $FlowFixMe[incompatible-use] found when upgrading Flow const didUserCallbackTimeout = currentTask.expirationTime <= currentTime; - // $FlowFixMe[constant-condition] if (enableProfiling) { - // $FlowFixMe[incompatible-type] found when upgrading Flow + // $FlowFixMe[incompatible-call] found when upgrading Flow markTaskRun(currentTask, currentTime); } const continuationCallback = callback(didUserCallbackTimeout); @@ -222,17 +216,15 @@ function workLoop(initialTime: number) { // regardless of how much time is left in the current time slice. // $FlowFixMe[incompatible-use] found when upgrading Flow currentTask.callback = continuationCallback; - // $FlowFixMe[constant-condition] if (enableProfiling) { - // $FlowFixMe[incompatible-type] found when upgrading Flow + // $FlowFixMe[incompatible-call] found when upgrading Flow markTaskYield(currentTask, currentTime); } advanceTimers(currentTime); return true; } else { - // $FlowFixMe[constant-condition] if (enableProfiling) { - // $FlowFixMe[incompatible-type] found when upgrading Flow + // $FlowFixMe[incompatible-call] found when upgrading Flow markTaskCompleted(currentTask, currentTime); // $FlowFixMe[incompatible-use] found when upgrading Flow currentTask.isQueued = false; @@ -317,7 +309,7 @@ function unstable_next<T>(eventHandler: () => T): T { function unstable_wrapCallback<T: (...Array<mixed>) => mixed>(callback: T): T { var parentPriorityLevel = currentPriorityLevel; - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-return] // $FlowFixMe[missing-this-annot] return function () { // This is a fork of runWithPriority, inlined for performance. @@ -340,7 +332,6 @@ function unstable_scheduleCallback( var currentTime = getCurrentTime(); var startTime; - // $FlowFixMe[invalid-compare] if (typeof options === 'object' && options !== null) { var delay = options.delay; if (typeof delay === 'number' && delay > 0) { @@ -387,7 +378,6 @@ function unstable_scheduleCallback( expirationTime, sortIndex: -1, }; - // $FlowFixMe[constant-condition] if (enableProfiling) { newTask.isQueued = false; } @@ -410,7 +400,6 @@ function unstable_scheduleCallback( } else { newTask.sortIndex = expirationTime; push(taskQueue, newTask); - // $FlowFixMe[constant-condition] if (enableProfiling) { markTaskStart(newTask, currentTime); newTask.isQueued = true; @@ -427,7 +416,6 @@ function unstable_scheduleCallback( } function unstable_cancelCallback(task: Task) { - // $FlowFixMe[constant-condition] if (enableProfiling) { if (task.isQueued) { const currentTime = getCurrentTime(); @@ -447,7 +435,7 @@ function unstable_getCurrentPriorityLevel(): PriorityLevel { } let isMessageLoopRunning = false; -let taskTimeoutID: TimeoutID = -1 as any; +let taskTimeoutID: TimeoutID = (-1: any); // Scheduler periodically yields in case there is other work on the main // thread, like user events. By default, it yields multiple times per frame. @@ -472,7 +460,6 @@ function shouldYieldToHost(): boolean { } function requestPaint() { - // $FlowFixMe[constant-condition] if (enableRequestPaint) { needsPaint = true; } @@ -496,7 +483,6 @@ function forceFrameRate(fps: number) { } const performWorkUntilDeadline = () => { - // $FlowFixMe[constant-condition] if (enableRequestPaint) { needsPaint = false; } @@ -580,7 +566,7 @@ function requestHostTimeout( function cancelHostTimeout() { // $FlowFixMe[not-a-function] nullable value localClearTimeout(taskTimeoutID); - taskTimeoutID = -1 as any as TimeoutID; + taskTimeoutID = ((-1: any): TimeoutID); } export { @@ -604,7 +590,6 @@ export { export const unstable_Profiling: { startLoggingProfilingEvents(): void, stopLoggingProfilingEvents(): ArrayBuffer | null, - // $FlowFixMe[constant-condition] } | null = enableProfiling ? { startLoggingProfilingEvents, diff --git a/packages/scheduler/src/forks/SchedulerMock.js b/packages/scheduler/src/forks/SchedulerMock.js index 50f0988e1e69..cf76126410e6 100644 --- a/packages/scheduler/src/forks/SchedulerMock.js +++ b/packages/scheduler/src/forks/SchedulerMock.js @@ -112,7 +112,6 @@ function advanceTimers(currentTime: number) { pop(timerQueue); timer.sortIndex = timer.expirationTime; push(taskQueue, timer); - // $FlowFixMe[constant-condition] if (enableProfiling) { markTaskStart(timer, currentTime); timer.isQueued = true; @@ -143,7 +142,6 @@ function handleTimeout(currentTime: number) { } function flushWork(hasTimeRemaining: boolean, initialTime: number) { - // $FlowFixMe[constant-condition] if (enableProfiling) { markSchedulerUnsuspended(initialTime); } @@ -159,7 +157,6 @@ function flushWork(hasTimeRemaining: boolean, initialTime: number) { isPerformingWork = true; const previousPriorityLevel = currentPriorityLevel; try { - // $FlowFixMe[constant-condition] if (enableProfiling) { try { return workLoop(hasTimeRemaining, initialTime); @@ -167,7 +164,6 @@ function flushWork(hasTimeRemaining: boolean, initialTime: number) { if (currentTask !== null) { const currentTime = getCurrentTime(); // $FlowFixMe[incompatible-call] found when upgrading Flow - // $FlowFixMe[incompatible-type] markTaskErrored(currentTask, currentTime); // $FlowFixMe[incompatible-use] found when upgrading Flow currentTask.isQueued = false; @@ -182,7 +178,6 @@ function flushWork(hasTimeRemaining: boolean, initialTime: number) { currentTask = null; currentPriorityLevel = previousPriorityLevel; isPerformingWork = false; - // $FlowFixMe[constant-condition] if (enableProfiling) { const currentTime = getCurrentTime(); markSchedulerSuspended(currentTime); @@ -211,10 +206,8 @@ function workLoop(hasTimeRemaining: boolean, initialTime: number): boolean { currentPriorityLevel = currentTask.priorityLevel; // $FlowFixMe[incompatible-use] found when upgrading Flow const didUserCallbackTimeout = currentTask.expirationTime <= currentTime; - // $FlowFixMe[constant-condition] if (enableProfiling) { // $FlowFixMe[incompatible-call] found when upgrading Flow - // $FlowFixMe[incompatible-type] markTaskRun(currentTask, currentTime); } const continuationCallback = callback(didUserCallbackTimeout); @@ -224,10 +217,8 @@ function workLoop(hasTimeRemaining: boolean, initialTime: number): boolean { // regardless of how much time is left in the current time slice. // $FlowFixMe[incompatible-use] found when upgrading Flow currentTask.callback = continuationCallback; - // $FlowFixMe[constant-condition] if (enableProfiling) { // $FlowFixMe[incompatible-call] found when upgrading Flow - // $FlowFixMe[incompatible-type] markTaskYield(currentTask, currentTime); } advanceTimers(currentTime); @@ -241,10 +232,8 @@ function workLoop(hasTimeRemaining: boolean, initialTime: number): boolean { // `toFlushAndYield` and `toFlushAndYieldThrough` testing helpers . } } else { - // $FlowFixMe[constant-condition] if (enableProfiling) { // $FlowFixMe[incompatible-call] found when upgrading Flow - // $FlowFixMe[incompatible-type] markTaskCompleted(currentTask, currentTime); // $FlowFixMe[incompatible-use] found when upgrading Flow currentTask.isQueued = false; @@ -325,7 +314,6 @@ function unstable_wrapCallback<T: (...Array<mixed>) => mixed>(callback: T): T { var parentPriorityLevel = currentPriorityLevel; // $FlowFixMe[incompatible-return] // $FlowFixMe[missing-this-annot] - // $FlowFixMe[incompatible-type] return function () { // This is a fork of runWithPriority, inlined for performance. var previousPriorityLevel = currentPriorityLevel; @@ -347,7 +335,6 @@ function unstable_scheduleCallback( var currentTime = getCurrentTime(); var startTime; - // $FlowFixMe[invalid-compare] if (typeof options === 'object' && options !== null) { var delay = options.delay; if (typeof delay === 'number' && delay > 0) { @@ -389,7 +376,6 @@ function unstable_scheduleCallback( expirationTime, sortIndex: -1, }; - // $FlowFixMe[constant-condition] if (enableProfiling) { newTask.isQueued = false; } @@ -412,7 +398,6 @@ function unstable_scheduleCallback( } else { newTask.sortIndex = expirationTime; push(taskQueue, newTask); - // $FlowFixMe[constant-condition] if (enableProfiling) { markTaskStart(newTask, currentTime); newTask.isQueued = true; @@ -429,7 +414,6 @@ function unstable_scheduleCallback( } function unstable_cancelCallback(task: Task) { - // $FlowFixMe[constant-condition] if (enableProfiling) { if (task.isQueued) { const currentTime = getCurrentTime(); @@ -585,7 +569,6 @@ function unstable_flushAllWithoutAsserting(): boolean { do { hasMoreWork = cb(true, currentMockTime); } while (hasMoreWork); - // $FlowFixMe[constant-condition] if (!hasMoreWork) { scheduledCallback = null; } @@ -689,7 +672,6 @@ export { export const unstable_Profiling: { startLoggingProfilingEvents(): void, stopLoggingProfilingEvents(): ArrayBuffer | null, - // $FlowFixMe[constant-condition] } | null = enableProfiling ? { startLoggingProfilingEvents, diff --git a/packages/scheduler/src/forks/SchedulerNative.js b/packages/scheduler/src/forks/SchedulerNative.js index 4d993c768022..1acee1f5f499 100644 --- a/packages/scheduler/src/forks/SchedulerNative.js +++ b/packages/scheduler/src/forks/SchedulerNative.js @@ -107,4 +107,4 @@ function throwNotImplemented() { // Flow magic to verify the exports of this file match the original version. export type {Callback, Task}; -null as any as SchedulerExportsType as SchedulerNativeExportsType as SchedulerExportsType; +((((null: any): SchedulerExportsType): SchedulerNativeExportsType): SchedulerExportsType); diff --git a/packages/scheduler/src/forks/SchedulerPostTask.js b/packages/scheduler/src/forks/SchedulerPostTask.js index 2d3f85b605ae..700ff2e5d2c5 100644 --- a/packages/scheduler/src/forks/SchedulerPostTask.js +++ b/packages/scheduler/src/forks/SchedulerPostTask.js @@ -96,7 +96,6 @@ export function unstable_scheduleCallback<T>( const controller = new TaskController({priority: postTaskPriority}); const postTaskOptions = { - // $FlowFixMe[invalid-compare] delay: typeof options === 'object' && options !== null ? options.delay : 0, signal: controller.signal, }; @@ -128,7 +127,7 @@ function runTask<T>( const result = callback(didTimeout_DEPRECATED); if (typeof result === 'function') { // Assume this is a continuation - const continuation: SchedulerCallback<T> = result as any; + const continuation: SchedulerCallback<T> = (result: any); const continuationOptions = { signal: node._controller.signal, }; diff --git a/packages/shared/CheckStringCoercion.js b/packages/shared/CheckStringCoercion.js index 8ddf846cdd66..a186d6755d99 100644 --- a/packages/shared/CheckStringCoercion.js +++ b/packages/shared/CheckStringCoercion.js @@ -17,21 +17,21 @@ * of the `value` object). */ -// $FlowFixMe[incompatible-type] only called in DEV, so void return is not possible. +// $FlowFixMe[incompatible-return] only called in DEV, so void return is not possible. function typeName(value: mixed): string { if (__DEV__) { // toStringTag is needed for namespaced types like Temporal.Instant const hasToStringTag = typeof Symbol === 'function' && Symbol.toStringTag; const type = - (hasToStringTag && (value as any)[Symbol.toStringTag]) || - (value as any).constructor.name || + (hasToStringTag && (value: any)[Symbol.toStringTag]) || + (value: any).constructor.name || 'Object'; - // $FlowFixMe[incompatible-type] + // $FlowFixMe[incompatible-return] return type; } } -// $FlowFixMe[incompatible-type] only called in DEV, so void return is not possible. +// $FlowFixMe[incompatible-return] only called in DEV, so void return is not possible. function willCoercionThrow(value: mixed): boolean { if (__DEV__) { try { @@ -68,7 +68,7 @@ function testStringCoercion(value: mixed) { // ancestor components where the exception happened. // // eslint-disable-next-line react-internal/safe-string-coercion - return '' + (value as any); + return '' + (value: any); } export function checkAttributeStringCoercion( diff --git a/packages/shared/DefaultPrepareStackTrace.js b/packages/shared/DefaultPrepareStackTrace.js index 85d2c328d094..5e0e3dc0da9d 100644 --- a/packages/shared/DefaultPrepareStackTrace.js +++ b/packages/shared/DefaultPrepareStackTrace.js @@ -9,4 +9,4 @@ // This is forked in server builds where the default stack frame may be source mapped. -export default undefined as any as (Error, Array<CallSite>) => string; +export default ((undefined: any): (Error, CallSite[]) => string); diff --git a/packages/shared/ReactPerformanceTrackProperties.js b/packages/shared/ReactPerformanceTrackProperties.js index 8bc3a29e2dc7..29aba7282f04 100644 --- a/packages/shared/ReactPerformanceTrackProperties.js +++ b/packages/shared/ReactPerformanceTrackProperties.js @@ -166,7 +166,7 @@ export function addValueToProperties( const objectToString = Object.prototype.toString.call(value); let objectName = objectToString.slice(8, objectToString.length - 1); if (objectName === 'Array') { - const array: Array<any> = value as any; + const array: Array<any> = (value: any); const didTruncate = array.length > OBJECT_WIDTH_LIMIT; const kind = getArrayKind(array); if (kind === PRIMITIVE_ARRAY || kind === EMPTY_ARRAY) { @@ -275,11 +275,7 @@ export function addValueToProperties( if (value === OMITTED_PROP_ERROR) { desc = '\u2026'; // ellipsis } else { - desc = JSON.stringify( - value.length >= 1024 - ? value.slice(0, 1023) + '\u2026' // ellipsis - : value, - ); + desc = JSON.stringify(value); } break; case 'undefined': diff --git a/packages/shared/ReactSerializationErrors.js b/packages/shared/ReactSerializationErrors.js index 6a754679f7ac..36342412885a 100644 --- a/packages/shared/ReactSerializationErrors.js +++ b/packages/shared/ReactSerializationErrors.js @@ -122,10 +122,10 @@ export function describeValueForErrorMessage(value: mixed): string { return name; } case 'function': { - if ((value as any).$$typeof === CLIENT_REFERENCE_TAG) { + if ((value: any).$$typeof === CLIENT_REFERENCE_TAG) { return describeClientReference(value); } - const name = (value as any).displayName || value.name; + const name = (value: any).displayName || value.name; return name ? 'function ' + name : 'function'; } default: @@ -155,7 +155,7 @@ function describeElementType(type: any): string { case REACT_MEMO_TYPE: return describeElementType(type.type); case REACT_LAZY_TYPE: { - const lazyComponent: LazyComponent<any, any> = type as any; + const lazyComponent: LazyComponent<any, any> = (type: any); const payload = lazyComponent._payload; const init = lazyComponent._init; try { diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js index 72d8bc020e2c..bb67b5389306 100644 --- a/packages/shared/ReactSymbols.js +++ b/packages/shared/ReactSymbols.js @@ -64,9 +64,9 @@ export function getIteratorFn(maybeIterable: ?any): ?() => ?Iterator<any> { export const ASYNC_ITERATOR = Symbol.asyncIterator; -export const REACT_OPTIMISTIC_KEY: ReactOptimisticKey = Symbol.for( +export const REACT_OPTIMISTIC_KEY: ReactOptimisticKey = (Symbol.for( 'react.optimistic_key', -) as any; +): any); // This is actually a symbol but Flow doesn't support comparison of symbols to refine. // We use a boolean since in our code we often expect string (key) or number (index), diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index dcbc667ad77b..96d13e9ec461 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -14,7 +14,7 @@ import typeof * as DynamicExportsType from './ReactFeatureFlags.native-fb-dynami // Re-export dynamic flags from the internal module. // Intentionally using * because this import is compiled to a `require` call. import * as dynamicFlagsUntyped from 'ReactNativeInternalFeatureFlags'; -const dynamicFlags: DynamicExportsType = dynamicFlagsUntyped as any; +const dynamicFlags: DynamicExportsType = (dynamicFlagsUntyped: any); // We destructure each value before re-exporting to avoid a dynamic look-up on // the exports object every time a flag is read. @@ -95,4 +95,4 @@ export const eprh_enableExhaustiveEffectDependenciesCompilerLint: | 'missing-only' = 'off'; // Flow magic to verify the exports of this file match the original version. -null as any as ExportsType as FeatureFlagsType as ExportsType; +((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index b2460eeaf9de..3c452162ab98 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -95,4 +95,4 @@ export const eprh_enableExhaustiveEffectDependenciesCompilerLint: | 'missing-only' = 'off'; // Flow magic to verify the exports of this file match the original version. -null as any as ExportsType as FeatureFlagsType as ExportsType; +((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 8db84c96f90b..309de96b4951 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -104,4 +104,4 @@ export const eprh_enableExhaustiveEffectDependenciesCompilerLint: | 'missing-only' = 'off'; // Flow magic to verify the exports of this file match the original version. -null as any as ExportsType as FeatureFlagsType as ExportsType; +((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index 498b34a28d43..af8dd955d0ba 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -81,4 +81,4 @@ export const eprh_enableExhaustiveEffectDependenciesCompilerLint: | 'missing-only' = 'off'; // Flow magic to verify the exports of this file match the original version. -null as any as ExportsType as FeatureFlagsType as ExportsType; +((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index ee598ab4a77f..e57495ed4e53 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -96,4 +96,4 @@ export const eprh_enableExhaustiveEffectDependenciesCompilerLint: | 'missing-only' = 'off'; // Flow magic to verify the exports of this file match the original version. -null as any as ExportsType as FeatureFlagsType as ExportsType; +((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index e3b877c07c3f..062cbaa4268a 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -127,4 +127,4 @@ export const eprh_enableExhaustiveEffectDependenciesCompilerLint: | 'missing-only' = 'off'; // Flow magic to verify the exports of this file match the original version. -null as any as ExportsType as FeatureFlagsType as ExportsType; +((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/getComponentNameFromType.js b/packages/shared/getComponentNameFromType.js index 9a333a5bef83..d9ed331660fe 100644 --- a/packages/shared/getComponentNameFromType.js +++ b/packages/shared/getComponentNameFromType.js @@ -38,7 +38,7 @@ function getWrappedName( innerType: any, wrapperName: string, ): string { - const displayName = (outerType as any).displayName; + const displayName = (outerType: any).displayName; if (displayName) { return displayName; } @@ -60,11 +60,11 @@ export default function getComponentNameFromType(type: mixed): string | null { return null; } if (typeof type === 'function') { - if ((type as any).$$typeof === REACT_CLIENT_REFERENCE) { + if ((type: any).$$typeof === REACT_CLIENT_REFERENCE) { // TODO: Create a convention for naming client references with debug info. return null; } - return (type as any).displayName || type.name || null; + return (type: any).displayName || type.name || null; } if (typeof type === 'string') { return type; @@ -94,7 +94,7 @@ export default function getComponentNameFromType(type: mixed): string | null { } if (typeof type === 'object') { if (__DEV__) { - if (typeof (type as any).tag === 'number') { + if (typeof (type: any).tag === 'number') { console.error( 'Received an unexpected object in getComponentNameFromType(). ' + 'This is likely a bug in React. Please file an issue.', @@ -105,21 +105,21 @@ export default function getComponentNameFromType(type: mixed): string | null { case REACT_PORTAL_TYPE: return 'Portal'; case REACT_CONTEXT_TYPE: - const context: ReactContext<any> = type as any; + const context: ReactContext<any> = (type: any); return getContextName(context); case REACT_CONSUMER_TYPE: - const consumer: ReactConsumerType<any> = type as any; + const consumer: ReactConsumerType<any> = (type: any); return getContextName(consumer._context) + '.Consumer'; case REACT_FORWARD_REF_TYPE: return getWrappedName(type, type.render, 'ForwardRef'); case REACT_MEMO_TYPE: - const outerName = (type as any).displayName || null; + const outerName = (type: any).displayName || null; if (outerName !== null) { return outerName; } return getComponentNameFromType(type.type) || 'Memo'; case REACT_LAZY_TYPE: { - const lazyComponent: LazyComponent<any, any> = type as any; + const lazyComponent: LazyComponent<any, any> = (type: any); const payload = lazyComponent._payload; const init = lazyComponent._init; try { diff --git a/packages/use-subscription/package.json b/packages/use-subscription/package.json index 4ac02a7b1317..a784ea751e7a 100644 --- a/packages/use-subscription/package.json +++ b/packages/use-subscription/package.json @@ -4,7 +4,7 @@ "version": "1.13.0", "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/use-subscription" }, "files": [ diff --git a/packages/use-sync-external-store/package.json b/packages/use-sync-external-store/package.json index 487af2a4d927..5ca1f42860f1 100644 --- a/packages/use-sync-external-store/package.json +++ b/packages/use-sync-external-store/package.json @@ -20,7 +20,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/react/react.git", + "url": "https://github.com/facebook/react.git", "directory": "packages/use-sync-external-store" }, "files": [ diff --git a/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js b/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js index 23fd22ec9a38..ddbd77c69822 100644 --- a/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js +++ b/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js @@ -77,8 +77,8 @@ export function useSyncExternalStoreWithSelector<Snapshot, Selection>( } // We may be able to reuse the previous invocation's result. - const prevSnapshot: Snapshot = memoizedSnapshot as any; - const prevSelection: Selection = memoizedSelection as any; + const prevSnapshot: Snapshot = (memoizedSnapshot: any); + const prevSelection: Selection = (memoizedSelection: any); if (is(prevSnapshot, nextSnapshot)) { // The snapshot is the same as last time. Reuse the previous selection. diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index f475821238c6..d0169c6ef2c3 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -585,6 +585,5 @@ "597": "The module \"%s\" is marked as an async ESM module but was loaded as a CJS proxy. This is probably a bug in the React Server Components bundler.", "598": "Maximum update depth exceeded. This could be an infinite loop. This can happen when a component repeatedly calls setState during render phase or inside useLayoutEffect, causing infinite render loop. React limits the number of nested updates to prevent infinite loops.", "599": "Expected an initialized chunk but got an initialized stream chunk instead. This payload may have been submitted by an older version of React.", - "600": "A rejected Promise was passed to React without a `reason` property. React threw a generic error from where the Promise was used to assist in identifying the problematic Promise. Make sure that instrumented Promises correctly set the `reason` property when setting `status` to `'rejected'`.", - "601": "A chunk pair is incomplete. This is a bug in React." + "600": "A rejected Promise was passed to React without a `reason` property. React threw a generic error from where the Promise was used to assist in identifying the problematic Promise. Make sure that instrumented Promises correctly set the `reason` property when setting `status` to `'rejected'`." } diff --git a/scripts/flags/flags.js b/scripts/flags/flags.js index f176f714ccc1..7a5c9730f22a 100644 --- a/scripts/flags/flags.js +++ b/scripts/flags/flags.js @@ -12,10 +12,7 @@ const Module = require('module'); const path = require('path'); const fs = require('fs'); babel({ - plugins: [ - 'babel-plugin-syntax-hermes-parser', - '@babel/plugin-transform-modules-commonjs', - ], + plugins: ['@babel/plugin-transform-modules-commonjs'], }); const yargs = require('yargs'); @@ -93,10 +90,7 @@ function getReactFeatureFlagsMajor() { 'const __NEXT_MAJOR__ = "next";' ), { - plugins: [ - 'babel-plugin-syntax-hermes-parser', - '@babel/plugin-transform-modules-commonjs', - ], + plugins: ['@babel/plugin-transform-modules-commonjs'], } ).code; @@ -131,10 +125,7 @@ function getReactNativeFeatureFlagsMajor() { 'const __TODO_NEXT_RN_MAJOR__ = "next-todo";' ), { - plugins: [ - 'babel-plugin-syntax-hermes-parser', - '@babel/plugin-transform-modules-commonjs', - ], + plugins: ['@babel/plugin-transform-modules-commonjs'], } ).code; diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js index 04dcd6356176..180ee3838dee 100644 --- a/scripts/flow/environment.js +++ b/scripts/flow/environment.js @@ -85,7 +85,7 @@ declare class ScrollTimeline extends AnimationTimeline { // $FlowFixMe[libdef-override] declare opaque type React$Element< +ElementType: React$ElementType, - +P = React$ElementConfig<ElementType>, + +P = React$ElementProps<ElementType>, >: { +type: ElementType, +props: P, @@ -197,44 +197,56 @@ declare module 'busboy' { addListener<Event: $Keys<BusboyEvents>>( event: Event, listener: BusboyEvents[Event], - ): this; - addListener(event: string, listener: Function): this; + ): Busboy; + addListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): Busboy; on<Event: $Keys<BusboyEvents>>( event: Event, listener: BusboyEvents[Event], - ): this; - on(event: string, listener: Function): this; + ): Busboy; + on(event: string | symbol, listener: (...args: any[]) => void): Busboy; once<Event: $Keys<BusboyEvents>>( event: Event, listener: BusboyEvents[Event], - ): this; - once(event: string, listener: Function): this; + ): Busboy; + once(event: string | symbol, listener: (...args: any[]) => void): Busboy; removeListener<Event: $Keys<BusboyEvents>>( event: Event, listener: BusboyEvents[Event], - ): this; - removeListener(event: string, listener: Function): this; + ): Busboy; + removeListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): Busboy; off<Event: $Keys<BusboyEvents>>( event: Event, listener: BusboyEvents[Event], - ): this; - off(event: string, listener: Function): this; + ): Busboy; + off(event: string | symbol, listener: (...args: any[]) => void): Busboy; prependListener<Event: $Keys<BusboyEvents>>( event: Event, listener: BusboyEvents[Event], - ): this; - prependListener(event: string, listener: Function): this; + ): Busboy; + prependListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): Busboy; prependOnceListener<Event: $Keys<BusboyEvents>>( event: Event, listener: BusboyEvents[Event], - ): this; - prependOnceListener(event: string, listener: Function): this; + ): Busboy; + prependOnceListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): Busboy; } } diff --git a/scripts/jest/TestFlags.js b/scripts/jest/TestFlags.js index 067c3b80ae29..e2d34cc516d5 100644 --- a/scripts/jest/TestFlags.js +++ b/scripts/jest/TestFlags.js @@ -81,6 +81,8 @@ function getTestFlags() { fb: www || xplat, // These aren't flags, just a useful aliases for tests. + // TODO: Clean this up. + enableActivity: true, enableSuspenseList: releaseChannel === 'experimental' || www || xplat, enableLegacyHidden: www, // TODO: Suspending the work loop during the render phase is currently diff --git a/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js b/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js index d1cbc6c95ef4..75e646e1b39d 100644 --- a/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js +++ b/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js @@ -16,8 +16,8 @@ import invariant from 'invariant'; // Event configs export const customBubblingEventTypes: { - [eventName: string]: Readonly<{ - phasedRegistrationNames: Readonly<{ + [eventName: string]: $ReadOnly<{ + phasedRegistrationNames: $ReadOnly<{ captured: string, bubbled: string, skipBubbling?: ?boolean, @@ -25,7 +25,7 @@ export const customBubblingEventTypes: { }>, } = {}; export const customDirectEventTypes: { - [eventName: string]: Readonly<{ + [eventName: string]: $ReadOnly<{ registrationName: string, }>, } = {}; diff --git a/yarn.lock b/yarn.lock index faaf6ea65296..ddcbd689a91b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5508,12 +5508,12 @@ babel-plugin-polyfill-regenerator@^0.6.1: dependencies: "@babel/helper-define-polyfill-provider" "^0.6.3" -babel-plugin-syntax-hermes-parser@^0.36.1: - version "0.36.1" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.1.tgz#970277618fbec67b400bf8b01e4f61ba96ef5a40" - integrity sha512-ycduwJbvdvIMmVvlAZqGggS+pm5Eu4Bk9pcV9Sm2Z4PJNRVsKkv0g7vHj+LeuC1gHTeF67sJXFOq61IlqCa2hA== +babel-plugin-syntax-hermes-parser@^0.32.0: + version "0.32.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz#06f7452bf91adf6cafd7c98e7467404d4eb65cec" + integrity sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg== dependencies: - hermes-parser "0.36.1" + hermes-parser "0.32.0" babel-plugin-syntax-trailing-function-commas@^6.5.0: version "6.22.0" @@ -9247,17 +9247,17 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== -flow-bin@^0.317.0: - version "0.317.0" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.317.0.tgz#91f640a26a61c984cf8197d083d0bbfd144fb3be" - integrity sha512-3BoVN4+oqRPKNnJ6LTOLMRmJGrB0iW1fkVJ1Pu6E3lMH0F2JwN53I83IqLdrvg+5cpgQkeATmZRL/zHvSNAMiA== +flow-bin@^0.279.0: + version "0.279.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.279.0.tgz#06e502a51d735083d715ef769f43bdcb0fc2bb61" + integrity sha512-Xf0T82atOcEf5auHvJfUF+wWIxieBuUJZBu2hlAizdhAzwqSJic74ZLaL6N5SsE0SY9PxPf3Z/lBU7iRpRa9Lw== -flow-remove-types@^2.317.0: - version "2.317.0" - resolved "https://registry.yarnpkg.com/flow-remove-types/-/flow-remove-types-2.317.0.tgz#5ad5e628ed59770f54888995eb7b7d866bf32550" - integrity sha512-XWjTvwcW2atcCT5vJSDMjq7J0GhIhHLHjuhkb3zotwjaS0/D2XlYNXb52wPluJQmqo6gNzUUqcpBnXycI1iUtw== +flow-remove-types@^2.279.0: + version "2.279.0" + resolved "https://registry.yarnpkg.com/flow-remove-types/-/flow-remove-types-2.279.0.tgz#3a3388d9158eba0f82c40d80d31d9640b883a3f5" + integrity sha512-bPFloMR/A2b/r/sIsf7Ix0LaMicCJNjwhXc4xEEQVzJCIz5u7C7XDaEOXOiqveKlCYK7DcBNn6R01Cbbc9gsYA== dependencies: - hermes-parser "0.36.1" + hermes-parser "0.29.1" pirates "^3.0.2" vlq "^0.2.1" @@ -10125,31 +10125,43 @@ hasown@^2.0.0, hasown@^2.0.2: dependencies: function-bind "^1.1.2" -hermes-eslint@^0.36.1: - version "0.36.1" - resolved "https://registry.yarnpkg.com/hermes-eslint/-/hermes-eslint-0.36.1.tgz#c7263e3a3db38621f16b9b122fee14c062faf4ea" - integrity sha512-aX8LUlLRHLAkELYWdz4IhRnZ/+Sbmmo5TzDEtsDiDuPIplaStzMnX84aTmPufQOyxZir5GjGxAiqdt+y4GcooA== +hermes-eslint@^0.32.0: + version "0.32.0" + resolved "https://registry.yarnpkg.com/hermes-eslint/-/hermes-eslint-0.32.0.tgz#a23bcaece522f356cb1b8e990e57117dca13852d" + integrity sha512-f/gnFD3Nl7QNrclG6otkHnHsUbwYrJGO76AMtoDeIYs2+i7fFgqJgSg7DKwejTtAKBoXQg51hAQuo9cgcp1R1w== dependencies: esrecurse "^4.3.0" - hermes-estree "0.36.1" - hermes-parser "0.36.1" + hermes-estree "0.32.0" + hermes-parser "0.32.0" hermes-estree@0.25.1: version "0.25.1" resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== -hermes-estree@0.36.1: - version "0.36.1" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.36.1.tgz#71368d9e78238728e11ef1f458a8921d0564a572" - integrity sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw== +hermes-estree@0.29.1: + version "0.29.1" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.29.1.tgz#043c7db076e0e8ef8c5f6ed23828d1ba463ebcc5" + integrity sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ== -hermes-parser@0.36.1, hermes-parser@^0.36.1: - version "0.36.1" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.36.1.tgz#f619b9f99bf34e80fb6f7024b1c62944d2beb14a" - integrity sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ== +hermes-estree@0.32.0: + version "0.32.0" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.32.0.tgz#bb7da6613ab8e67e334a1854ea1e209f487d307b" + integrity sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ== + +hermes-parser@0.29.1: + version "0.29.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.29.1.tgz#436b24bcd7bb1e71f92a04c396ccc0716c288d56" + integrity sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA== + dependencies: + hermes-estree "0.29.1" + +hermes-parser@0.32.0, hermes-parser@^0.32.0: + version "0.32.0" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.32.0.tgz#7916984ef6fdce62e7415d354cf35392061cd303" + integrity sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw== dependencies: - hermes-estree "0.36.1" + hermes-estree "0.32.0" hermes-parser@^0.25.1: version "0.25.1" @@ -14225,11 +14237,6 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -prettier-plugin-hermes-parser@^0.36.1: - version "0.36.1" - resolved "https://registry.yarnpkg.com/prettier-plugin-hermes-parser/-/prettier-plugin-hermes-parser-0.36.1.tgz#827935227ba28d8da6a12d97022516d305953c95" - integrity sha512-i1wirokdDStTGNtYGlE9Y4Mf4W7VYo43Uz4j/Fhm63FsPdxVFgSeeaSPDYDbXMRDYpEKyNtB/k3FlcH4VKYlnQ== - prettier@*, prettier@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105"