| 1 | # Rust port: e2e parity TODO |
| 2 | |
| 3 | Status snapshot (after the current stack lands): |
| 4 | |
| 5 | | Variant | Score | Failures | |
| 6 | | ------- | ------------ | -------- | |
| 7 | | Babel | 1792 / 1802 | 10 | |
| 8 | | SWC | 1786 / 1802 | 16 | |
| 9 | | OXC | 1704 / 1795 | 91 | |
| 10 | |
| 11 | The corpus grew by the three `ts-*` module-interop fixtures (1799 → |
| 12 | 1802 on this branch). The Babel/SWC rows are measured on this branch |
| 13 | and their failure sets are byte-identical to the pre-stack baseline; |
| 14 | the OXC row predates the fixtures and has not been re-measured. |
| 15 | |
| 16 | `cargo test --workspace`: 84 passed, 0 failed. |
| 17 | |
| 18 | ## SWC |
| 19 | |
| 20 | (Historical, pre-ts-interop-stack triage on the old staging base; current |
| 21 | snapshot at top.) |
| 22 | |
| 23 | The 15 remaining SWC e2e failures fall into three groups. Each line names the |
| 24 | fixture and the failure mode; the group it sits in dictates the appropriate |
| 25 | fix. |
| 26 | |
| 27 | ### Group A: Fixture maintenance, not Rust bugs |
| 28 | |
| 29 | SWC compiles code that TS rejects, or vice versa, in ways where Rust's |
| 30 | behavior is arguably correct. The fix is to rename the fixture (drop the |
| 31 | `error.` prefix) and update the `.expect.md` snapshot so the suite stops |
| 32 | asserting the TS-specific output. |
| 33 | |
| 34 | - `error.bug-invariant-local-or-context-references.js` — TS fires |
| 35 | `CompilerError::invariant` ("expected all references ... consistently |
| 36 | local or context"). Rust handles the same code without tripping the |
| 37 | invariant. |
| 38 | - `error.todo-jsx-intrinsic-tag-matches-local-binding.js` — SWC pipeline |
| 39 | emits a Todo bailout (`[hoisting] EnterSSA: Expected identifier to be |
| 40 | defined before being used`) that the Babel path does not. |
| 41 | - `error.todo-repro-named-function-with-shadowed-local-same-name.js` — |
| 42 | Babel errors; SWC compiles. |
| 43 | - `new-mutability/error.todo-repro-named-function-with-shadowed-local-same-name.js` |
| 44 | — same as above with the new mutation-aliasing model enabled. |
| 45 | - `error.todo-rust-as-expression-assignment-target.tsx` — Babel errors; |
| 46 | SWC compiles. |
| 47 | - `fbt/error.todo-locally-require-fbt.js` — Babel emits the |
| 48 | `Invariant: <fbt> tags should be module-level imports` shape; SWC emits |
| 49 | `Todo: Local variables named 'fbt' may conflict with the fbt plugin`. |
| 50 | Different categories, both reasonable. |
| 51 | |
| 52 | ### Group B: External dependency |
| 53 | |
| 54 | - `use-no-forget-multiple-with-eslint-suppression.js` — spurious |
| 55 | `import { c as _c }` in the TS reference output. Fixed on `main` by |
| 56 | [react#36500](https://github.com/facebook/react/pull/36500) (merged). |
| 57 | Will pass automatically once `pr-36173` rebases onto `main`; until then |
| 58 | the TS dist built from `pr-36173` still emits the unused import. |
| 59 | |
| 60 | ### Group C: Real SWC frontend bugs |
| 61 | |
| 62 | Each line names the failure mode and a sketch of where to look. |
| 63 | |
| 64 | - `fbt/fbt-param-with-quotes.js` — SWC codegen emits double quotes |
| 65 | (`"fbt"`) and reformats multi-line JSX into a single line; Babel uses |
| 66 | single quotes and preserves the source layout. Semantically equivalent |
| 67 | output; the fix is either an SWC codegen flag for quote style or a |
| 68 | post-emit pass. Low impact, high effort. |
| 69 | |
| 70 | - `lone-surrogate-string-values.js` — TS preserves lone surrogates |
| 71 | (`\uD83E`); SWC emits `\uFFFD` because `Wtf8Atom::to_string_lossy()` in |
| 72 | `react_compiler_swc/src/convert_ast.rs::wtf8_to_string` replaces invalid |
| 73 | UTF-8 sequences. Real WTF-8 handling work that touches every call site |
| 74 | using that helper. Probably needs to detect lone surrogates and emit |
| 75 | `\uXXXX` escapes before they hit `String`. |
| 76 | |
| 77 | - `many-scopes-no-stack-overflow.js` — TS memoizes the function |
| 78 | (`const $ = _c(401);` with 401 memo slots); SWC pipeline bails out and |
| 79 | returns the uncompiled source. The fixture exists to test that the |
| 80 | compiler handles many sequential reactive scopes without stack overflow, |
| 81 | so the SWC variant should compile. Root cause unclear — needs |
| 82 | investigation in the SWC pipeline or the compiler core to see where the |
| 83 | bail happens. |
| 84 | |
| 85 | - `pattern4_bare_type.js` — Two unrelated bugs in one fixture: |
| 86 | 1. Operator-precedence stripping. `Math.round((x - y) * 1000)` becomes |
| 87 | `Math.round(x - y * 1000)`. SWC codegen drops the parentheses around |
| 88 | the subtraction. Probably in `convert_ast_reverse.rs`'s |
| 89 | BinaryExpression handling. |
| 90 | 2. Method return type annotation. `formatMetrics(): Metrics` becomes |
| 91 | `formatMetrics()`. The TS-type-on-binding-ident fix in commit |
| 92 | cc1ba1e1 only covered binding identifiers; class method signatures |
| 93 | are a separate code path. Same shape of fix; different |
| 94 | `convert_binding_ident`-equivalent call site. |
| 95 | |
| 96 | - `reduce-reactive-deps/hoist-deps-diff-ssa-instance1.tsx` — |
| 97 | `(x as HasA).a.value + 2` becomes `(x as HasA.a.value) + 2`. The member |
| 98 | expression's property chain gets absorbed into the type annotation when |
| 99 | `convert_ast_reverse` emits the cast. Likely a parenthesization / |
| 100 | precedence bug in the reverse converter or the SWC printer's handling |
| 101 | of `TSAsExpression` as the object of a `MemberExpression`. |
| 102 | |
| 103 | - `todo-round2_unicode_string.js` (prefixed `todo-`) — Hex escape format |
| 104 | (`\xC5`) vs unicode escape (`\u00C5`) for bytes 0x80-0xFF. Both valid JS |
| 105 | literals; codegen format choice in SWC's string printer. |
| 106 | |
| 107 | - `todo-round3_promote_used_temps.js` (prefixed `todo-`) — Class body |
| 108 | codegen. TS emits the class with fields and constructor; SWC emits an |
| 109 | empty class body and pulls fields/methods out into separate assignments. |
| 110 | Likely an interaction between SWC codegen and the compiler's |
| 111 | `promote_used_temps` pass. |
| 112 | |
| 113 | - `ts-non-null-expression-default-value.tsx` — Generic type parameter |
| 114 | support. `const x: ReadonlyMap<string, string> = ...` becomes |
| 115 | `const x = ...` (annotation dropped entirely). Our |
| 116 | `convert_ts_type_to_json` helper in cc1ba1e1 explicitly guards against |
| 117 | `TsTypeRef` with `type_params` to avoid silently emitting |
| 118 | `ReadonlyMap` without the params. The proper fix needs serialization of |
| 119 | `TSTypeParameterInstantiation` in `convert_ast.rs` AND deserialization |
| 120 | in `convert_ast_reverse.rs::convert_ts_type_from_json`. |
| 121 | |
| 122 | ## Cross-frontend: TypeScript module interop statements |
| 123 | |
| 124 | Three `ts-*` fixtures pin how TS module-interop statements |
| 125 | (`import x = require(...)`, `export = x`, `export as namespace X`) must |
| 126 | behave: the statement is preserved in output and the file's functions |
| 127 | still compile. |
| 128 | |
| 129 | - **Babel/NAPI** and **SWC** now preserve these end to end. Both flow |
| 130 | the statements through `Statement::Unknown` (the raw Babel-shaped |
| 131 | carrier in `react_compiler_ast`); the SWC frontend rebuilds the swc |
| 132 | module declarations in `convert_ast_reverse.rs` and works around an |
| 133 | upstream swc_ecma_codegen bug that prints `TsNamespaceExportDecl` |
| 134 | as `export = X` (`react_compiler_swc/src/ts_namespace_export_fixup.rs`, |
| 135 | which also carries the guard test that flags when the upstream fix |
| 136 | lands). Fixtures renamed from `todo-ts-*` to `ts-*`; the |
| 137 | `SproutTodoFilter` entry for the namespace fixture remains (sprout's |
| 138 | evaluator cannot evaluate `export as namespace`). |
| 139 | - **OXC** remains deferred: `todo!()` panics in |
| 140 | `react_compiler_oxc/src/convert_ast.rs` (arms |
| 141 | `TSImportEqualsDeclaration` / `TSExportAssignment` / |
| 142 | `TSNamespaceExportDeclaration`; the sibling `TSGlobalDeclaration` |
| 143 | arm is also unmodeled but unreachable from Babel-parsed fixtures, |
| 144 | which represent `declare global` as `TSModuleDeclaration`). |
| 145 | |
| 146 | - `ts-import-equals-declaration.ts` |
| 147 | - `ts-export-assignment.ts` |
| 148 | - `ts-namespace-export-declaration.ts` |
| 149 | |
| 150 | ## Babel |
| 151 | |
| 152 | (Historical, pre-ts-interop-stack numbers; current snapshot at top.) |
| 153 | |
| 154 | **TODO: scope this out.** Babel is at 1788 / 1795 (7 failures). These have |
| 155 | been the baseline throughout the SWC parity stack and were not touched, so the |
| 156 | failure list is whatever was on `pr-36173` before this work landed. |
| 157 | |
| 158 | Next step is to enumerate the failures by fixture and bucket them the same |
| 159 | way as SWC (fixture maintenance / external dependency / real bugs). Run: |
| 160 | |
| 161 | ```bash |
| 162 | bash compiler/scripts/test-e2e.sh --no-color --variant babel |
| 163 | ``` |
| 164 | |
| 165 | …and triage the resulting failures into A/B/C groups under this section. |
| 166 | |
| 167 | ## OXC |
| 168 | |
| 169 | (Historical, pre-ts-interop-stack numbers; current snapshot at top.) |
| 170 | |
| 171 | **TODO: scope this out.** OXC is at 1704 / 1795 (91 failures). The CLI |
| 172 | `filename` fix in commit c30f0d6f bumped this by +2 from the 1702 baseline, |
| 173 | but everything else is unaddressed. |
| 174 | |
| 175 | Next step is to enumerate failures and identify OXC-specific clusters |
| 176 | (likely AST conversion gaps in `react_compiler_oxc` analogous to the SWC |
| 177 | work in this stack). Run: |
| 178 | |
| 179 | ```bash |
| 180 | bash compiler/scripts/test-e2e.sh --no-color --variant oxc |
| 181 | ``` |
| 182 | |
| 183 | …and bucket the resulting failures into A/B/C groups under this section. |
| 184 | Expect significant overlap with the SWC Group C bugs (cast wrappers, |
| 185 | type annotations, UTF-16/WTF-8 handling) since both frontends share the |
| 186 | post-conversion pipeline. |
| 187 | |
| 188 | ## How this stack got here |
| 189 | |
| 190 | (Historical, pre-ts-interop-stack numbers; current snapshot at top.) |
| 191 | |
| 192 | - `compiler/scripts/test-e2e.sh --variant swc` baseline was 1742 / 1795 |
| 193 | (53 failures) before this stack. |
| 194 | - 9 commits in the current stack reduce that to 1780 / 1795 (15 failures, |
| 195 | -38 fixtures, 72% reduction). |
| 196 | - Babel variant: 1788 / 1795 throughout (no regressions). |
| 197 | - OXC variant: 1702 → 1704 (the CLI filename commit also benefited OXC). |
| 198 | - `cargo test --workspace`: 56 passed, 0 failed throughout. |