[compiler] WIP port of React Compiler to Rust#36173
Open
josephsavona wants to merge 292 commits intofacebook:mainfrom
Open
[compiler] WIP port of React Compiler to Rust#36173josephsavona wants to merge 292 commits intofacebook:mainfrom
josephsavona wants to merge 292 commits intofacebook:mainfrom
Conversation
added 30 commits
March 16, 2026 09:36
…expressions M7: ConditionalExpression, LogicalExpression (&&/||/??), AssignmentExpression (simple =, compound +=/-= etc). M8: ObjectExpression, ArrayExpression, TemplateLiteral, TaggedTemplateExpression, UpdateExpression, AwaitExpression, RegExpLiteral, MetaProperty, lower_object_property_key helper. Todo errors for BigInt, yield, class expressions, dynamic import, private names.
…ering, and JSX M9: ArrowFunctionExpression, FunctionExpression, FunctionDeclaration, ObjectMethod lowering with recursive lower_inner(). gather_captured_context walks scope_info references. capture_scopes helper. lower_function_to_value, lower_function_declaration, lower_object_method. HirBuilder.scope_info_and_env_mut() for disjoint borrow. M10: JSXElement, JSXFragment, lower_jsx_element_name (builtin/identifier/member), lower_jsx_element (text/element/fragment/expression/spread children), trim_jsx_text whitespace normalization, JSX attribute handling.
… chaining, remaining statements SwitchStatement with case discrimination and fallthrough. TryStatement with handler blocks and enter_try_catch. ForOfStatement with GetIterator/IteratorNext protocol. ForInStatement with NextPropertyOf. OptionalCallExpression and OptionalMemberExpression with optional chaining control flow. lower_reorderable_expression and is_reorderable_expression. WithStatement error, ClassDeclaration todo, export declarations, remaining type declarations.
Implements lower_assignment() for all pattern types: Identifier (StoreLocal/StoreContext/StoreGlobal), MemberExpression (PropertyStore/ComputedStore), ArrayPattern (Destructure with items, holes, spreads, nested followups), ObjectPattern (Destructure with properties, rest, nested followups), AssignmentPattern (default values with undefined check using Ternary/Branch control flow), RestElement. Also simplifies VariableDeclaration to delegate to lower_assignment for all patterns.
Replaces all remaining todo!() stubs with proper error handling: delete unary operator (PropertyDelete/ComputedDelete), throw unary (unsupported error), logical assignment operators (todo error), compound member assignment (todo error), AssignmentPattern in expression position (todo error), Pipeline operator (panic), export declarations (skip in function bodies), lower_type (returns Poly).
All milestones implemented. No todo!() stubs remain.
…rovements Fixes from thorough review against plan and TS source: lowerValueToTemporary optimization (skip extra instruction for unnamed temporaries), VariableDeclaration now handles var-kind errors, no-init declarations (DeclareLocal/DeclareContext), destructure-style detection. TypeCast expressions now emit TypeCastExpression instruction instead of silently unwrapping. Environment gains fn_type field for ReactFunctionType classification (Component/Hook/Other). ObjectMethod getter/setter error check. is_reorderable_expression now handles TS/Flow type wrapper expressions.
…compound member assignment, enum errors Implements UpdateExpression with MemberExpression arguments (read value, compute increment/decrement, store back via PropertyStore/ComputedStore). Implements compound assignment (+=, -= etc.) to MemberExpression targets (read, compute binary op, store back). Enum declarations now emit UnsupportedNode with error instead of silently skipping. Adds TS/Flow type wrapper handling to is_reorderable_expression.
Add DebugLogEntry type to CompileResult for returning debug log entries (kind: "debug") from Rust to JS. The compile_program function now logs the EnvironmentConfig, matching the TS compiler's behavior. The JS shim forwards debug logs to logger.debugLogIRs when available.
Distill key architectural constraints from rust-port-research.md and rust-port-notes.md into a concise reference covering arenas, ID types, error handling, pass structure, side maps, and structural similarity.
Wire the Babel plugin entrypoint (program.rs) to the HIR lowering pipeline. Add FunctionNode enum to pass discovered functions directly to lower(), removing the redundant extract_function traversal from build_hir.rs. Create entrypoint/pipeline.rs (analogous to TS Pipeline.ts) that orchestrates Environment creation and BuildHIR. Functions are now lowered through the full entrypoint path with debug HIR output in compile results.
Add compiler-review agent and skill for reviewing Rust port code against the original TypeScript source. Update existing skills/rules/agents to reference rust-port-architecture.md instead of rust-port-notes.md and rust-port-research.md. Add compiler-review as a step in /compiler-commit.
Unify error types by removing local CompileError and TryCompileResult enums in favor of CompilerError from react_compiler_diagnostics. Add CodegenFunction placeholder, flow debug logs via callback instead of return value, and apply panicThreshold logic in process_fn's error path.
Add a new DebugPrintHIR.ts with an exhaustive debug printer for HIR that prints all fields of every type (Identifier, Place, InstructionValue, Terminal, AliasingEffect, ReactiveScope, Type, etc.) with first-seen deduplication for identifiers and scopes. This enables pass-by-pass comparison of TS and Rust compiler output to verify port correctness.
Rewrite debug_print.rs to use a DebugPrinter struct with indent/dedent, first-seen identifier/scope deduplication, inline type expansion, and Environment/Errors section matching the TS output format.
…ecture Make ProgramContext mutable and accumulate events/debug_logs directly on it instead of returning them from process_fn. Add CompilerOutputMode enum derived from PluginOptions, thread it through compile_program → process_fn → try_compile_function → compile_fn → Environment. Change process_fn to return Option<CodegenFunction> matching TS, collect compiled functions for future AST rewriting, and align handle_error/log_error to take &mut ProgramContext.
Replace the standalone-binary test approach with a unified TS script (test-rust-port.ts) that runs both TS and Rust compilers through their real Babel plugins, captures debug log entries via the logger API, and diffs output for a specific pass. Update pipeline.rs to use DebugLogEntry::new() (kind: "debug") for pre-formatted string output. The shell wrapper now builds the native module and delegates to the TS script.
Error with a helpful message if the TypeScript compiler produces no log entries for the given pass name across all fixtures, indicating the pass name is likely incorrect.
Move the cargo build + dylib copy into the TS script so the native module is always rebuilt before testing, avoiding stale binary issues. The shell wrapper is now a simple passthrough to the TS script.
Capture CompileError, CompileSkip, CompileUnexpectedThrow, and PipelineError events from both compilers via logEvent and diff them alongside debug entries. Also throw a TODO if a reactive/ast log entry matches the target pass, so unsupported kinds surface immediately.
… BuildHIR/HIRBuilder Fix multiple port fidelity issues identified by review against the TypeScript source. Key changes: implement BlockStatement hoisting logic with DeclareContext emission, add fbt/fbs depth tracking for JSX whitespace handling, fix import/export error reporting, handle pipeline operator gracefully instead of panicking, fix JSXNamespacedName to produce Primitive string (matching TS), add JSX attribute colon validation, fix ClassDeclaration error category, add MemberExpression Reassign invariant check, add context variable kind validation, handle \r\n line endings in trim_jsx_text, and add hoisted identifier tracking to Environment.
Create skill and agent for automating the TS-to-Rust pass porting workflow. The skill orchestrates context gathering, planning, implementation via subagent, test-fix loop, and review. Also makes lowering crate helper functions public so optimization passes can reuse them.
Point to rust-port-architecture.md for translation guidelines and data modeling rules instead of inlining a translation table.
…5→772 tests passing) Fix multiple issues in the Rust HIR lowering to improve test-rust-port HIR pass results from 55 to 772 passing out of 1717 fixtures: - Add Display traits for Effect, BlockKind, BinaryOperator, UnaryOperator, LogicalOperator, UpdateOperator, ObjectPropertyType to match TS formatting - Fix scope.ts to register program scope explicitly (program.traverse doesn't visit the Program node) so local bindings aren't misidentified as module-level - Map constantViolations (assignment LHS, update exprs, for-of/for-in) in scope extraction so reassigned variables are properly resolved - Propagate source locations through resolve_identifier to set identifier locs - Pre-allocate 28 built-in type slots to match TS global type counter offset - Skip prefilter when compilationMode is 'all' in BabelPlugin.ts - Remove inner function printing from debug_hir (TS prints inline) - Add ID normalization in test-rust-port.ts for opaque counter differences
…tion id, and debug print (772→951 tests passing) Fix source locations throughout HIR lowering to match TypeScript behavior: use sub-node locs (consequent, body, left-side, identifier) instead of parent statement locs for if/for/while/do-while/for-in/for-of/labeled statements, assignment expressions, compound member assignments, and update expressions. Extract type annotations from Babel AST JSON for DeclareLocal/StoreLocal. Use the AST function's own id (null for arrow functions) instead of the inferred name. Fix UpdateOperator debug format to output ++/-- instead of Increment/Decrement.
…n error diffing Merge entries and events into a single ordered log, stopping capture once the target pass is reached. CompileError events now include severity, category, and all diagnostic detail objects (error locs/messages and hints) for exact matching between TS and Rust. The EnvironmentConfig debug entry is skipped since its formatting differs between implementations.
…rs, and name dedup (1190→1467 tests passing) Fix assignment expression lowering to return temporary results matching TS behavior, use correct locs for logical expressions and return-without-value, add context identifier pre-computation (findContextIdentifiers equivalent) via ScopeInfo, share used_names across function scope boundaries for name deduplication, remove incorrect promote_temporary for AssignmentPattern, and handle member expression assignments inline.
…nstead of JS Add a generic AST visitor (visitor.rs) with scope tracking, and use it to implement FindContextIdentifiers in Rust (find_context_identifiers.rs). Remove referenceToScope and reassignments from the serialized scope info — context identifiers are now computed entirely from the AST and scope tree.
Create react_compiler_optimization crate and port PruneMaybeThrows and MergeConsecutiveBlocks from TypeScript. Wire PruneMaybeThrows into the Rust pipeline after lowering. Update test-rust-port.ts to handle passes that appear multiple times in the TS pipeline via stride-based entry matching (774/780 HIR-passing fixtures also pass PruneMaybeThrows).
Creates a new react_compiler_validation crate with ports of both validation passes. Adds NonLocalBinding::name() helper to react_compiler_hir and wires both passes into pipeline.rs after PruneMaybeThrows. Validation invariant errors are suppressed until lowering is complete.
added 21 commits
March 29, 2026 13:43
Add missing Branch terminal handling in visit_block (TS converts to reactive if terminals). Add missing invariant error for scheduled alternate in if handler. Remove extra emitted.contains guards on loop fallthroughs that suppressed what should be invariant errors.
…sWithinScopes The error was concatenating reason and description into a single string. Split into separate fields matching the TS CompilerError.invariant structure.
Convert silent returns on missing nodes to .expect() panics matching the TS CompilerError.invariant() behavior in visit and force_memoize_scope_dependencies.
…alues The TS uses Map<DeclarationId, ReactiveInstruction> but the instruction ref is only used for JS-style mutation. Rust's two-phase approach doesn't need the value side, so HashSet is the idiomatic equivalent.
…lMemoization PruneHoistedContexts: remove redundant insert before remove in StoreContext Function branch. ValidatePreservedManualMemoization: add missing StartMemoize nesting invariant, FinishMemoize id matching, fix record_temporaries ordering, remove incorrect LoadLocal/LoadContext entries in record_deps_in_value, add missing invariant in validate_inferred_dep.
…nal debug_print Add timing instrumentation across the Rust compiler pipeline and JS/Rust bridge, controlled by a `__profiling` flag in plugin options. Add a standalone profiling script (profile-rust-port.ts) that compares TS vs Rust compiler performance with fine-grained per-pass timing. Make debug_print calls conditional on a `__debug` flag, eliminating ~71% of overhead when no logger is configured — reducing the Rust/TS ratio from 3.16x to ~1.2x.
Change CompileResult.ast from Option<serde_json::Value> to Option<Box<RawValue>>, serializing the AST directly to a JSON string in compile_program rather than going through an intermediate Value. This avoids a full extra serialization pass (File→Value→String becomes File→String) when the NAPI layer serializes the final result.
Stop cloning every debug entry into both debug_logs and ordered_log. Remove the debug_logs field from CompileResult and ProgramContext entirely, using ordered_log as the single source of truth. The JS side already prefers ordered_log when available, so this is a no-op for consumers. This halves the serialization cost for debug data.
Remove the debugLogs optional field from CompileSuccess and CompileError interfaces in bridge.ts, and remove the dead fallback code path in BabelPlugin.ts that read from debugLogs. orderedLog is now the single source for debug entries.
…void per-function EnvironmentConfig clone Replace regex compilation in find_program_suppressions with simple string matching (strip_prefix + starts_with), eliminating ~240ms of per-fixture regex compilation overhead. Also hoist the EnvironmentConfig clone from try_compile_function (called per function) to compile_program (called once per file), reducing allocation overhead.
…gistry Replace HashMap type aliases with newtype structs supporting a base+overlay pattern. Built-in shapes and globals are initialized once via LazyLock and shared across all Environments. Custom hooks and module types go into per- environment overlay maps. Cloning for outlined functions now copies only the small extras map. ~18% overall speedup across 1717 fixtures.
- Migrate react_compiler_oxc convert_ast.rs to OXC v0.121 API (~341 errors fixed) - Wire up OXC transform() to actually call the compiler - Fix RawValue deserialization in both SWC and OXC frontends - Fix SWC convert_ast_reverse and emit to produce correct output - Update e2e CLI to use new TransformResult.file field
Add a --rust flag to the snap test runner so fixtures can be run through the Rust compiler backend (babel-plugin-react-compiler-rust) for side-by-side comparison. Includes buildRust() for cargo build + native module copy, watch mode support for .rs file changes, TS type fixes in the Rust babel plugin, and removal of debug eprintln! lines from pipeline.rs.
Format CompilerDiagnostic/CompilerErrorDetail class instances into plain
objects before passing to logEvent(). Both TS and Rust now emit the same
flat structure: {category, reason, description, severity, suggestions,
details/loc} with no nested `options` wrapper or getter-based properties.
Also adds index/filename to source locations in logger events from Rust,
and propagates source locations to codegen AST nodes for blank line handling.
- Add source locations to JSX elements, text nodes, identifiers, and namespaced names in the Rust codegen, matching the TS codegen's withLoc pattern - Switch BabelPlugin from direct AST assignment to prog.replaceWith() so subsequent Babel plugins (fbt, idx) properly traverse the new AST - Add re-entry guard for replaceWith re-traversal - Add formatCompilerError for proper error message formatting - Fix source_filename in error info construction Snap: 841→1605/1718 passing (+764).
Propagate identifierName from Babel AST source locations through HIR diagnostics and into logger event serialization. This field appears on CompilerDiagnosticDetail::Error locations for identifier-related errors (e.g., validateUseMemo, validateNoRefAccessInRender). Snap: 1605→1606/1718.
…, identifierName Fix 38 snap test failures across several categories: - Fix FBT plugin crashes by ensuring JSX attribute value nodes have loc (19 tests) - Fix preserve-memo error descriptions to include inferred/source dependency details (18 tests) - Fix categoryToHeading: UnsupportedSyntax maps to 'Compilation Skipped' not 'Todo' - Add identifierName to diagnostics from validate_use_memo and validate_no_set_state_in_effects - Add loc to codegen primitive values for downstream plugin compatibility Snap tests: 1644/1718 passed (was 1606), 74 failed. Pass-level: 1717/1717.
…r formatting - Add identifier_name extraction in validate_no_derived_computations_in_effects and validate_no_set_state_in_effects using source code byte offsets - Add identifier_name_for_id helper to Environment - Fix BabelPlugin error formatting: scope extraction errors, raw exception messages - Add raw_message field to CompilerErrorInfo for unknown exception passthrough Snap: 1644→1661/1718 (+17).
…egen - Add "Inferred dependencies" hint text to validateExhaustiveDependencies, matching TS output format for dependency mismatch errors - Fix invariant error formatting in codegen: separate reason from message in MethodCall and unnamed temporary errors - Add logged_severity() to CompilerDiagnostic for PreserveManualMemo - Fix code frame message in validate_no_derived_computations_in_effects Snap: 1661→1672/1718 (+11).
… and prefilter Fix snap test failures (46→16 remaining) across multiple categories: validation error suppression (has_invalid_deps on StartMemoize), type provider and knownIncompatible checks, JSON log field ordering and severity, code-frame abbreviation, codegen error formatting and loc propagation, React.memo/forwardRef prefilter detection, toString() return type inference, ref-like name detection, and component-syntax ref parameter binding resolution. Also auto-enables sync mode for yarn snap --rust.
Auto-formatted Options.ts, Program.ts, runner-watch.ts, and runner-worker.ts via yarn prettier-all.
added 3 commits
March 30, 2026 14:53
FBT loc propagation on identifier/place/declarator/JSXAttribute nodes, component/hook declaration syntax via __componentDeclaration and __hookDeclaration AST fields, 13 missing BuiltInMixedReadonly methods, identifierName in effect-derived-computation diagnostics, and ValidateSourceLocations skip. Only remaining failure is the intentional error.todo-missing-source-locations (pass not ported to Rust).
Comprehensive comparison of TypeScript and Rust compiler implementations covering all major subsystems, generated from systematic review.
…dNode codegen Fix two critical gaps identified in the Rust port gap analysis: 1. Transitive freeze: Added recursive freeze_function_captures_transitive() that freezes through arbitrarily nested FunctionExpression captures, matching the TS freezeValue → freeze → freezeValue recursion chain. Added test fixture that verifies the fix (previously failed in Rust). 2. UnsupportedNode expression codegen: Expression-level handler now attempts to deserialize the original AST node from JSON (like the statement-level handler) instead of emitting a broken __unsupported_ placeholder. Updated gap analysis doc to remove fixed items and annotate Gap 5 (early return) with investigation findings — simply removing it causes downstream panics, requiring a more careful fix.
… use Err returns Per the architecture guide, invariant and todo errors should return Err(CompilerDiagnostic) instead of recording on env. Fixed all error sites in infer_mutation_aliasing_effects (invariant uninitialized access, known_incompatible throws, todo spread syntax) to return Err, matching the TS behavior where these are CompilerError.invariant()/throwTodo()/throw calls that abort compilation. This allowed removing the pipeline's early return guard after inferMutationAliasingEffects since the ? operator now naturally short-circuits on these errors.
|
you should be able to write it much faster with |
|
And replacing |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is an experimental, work-in-progress port of React Compiler to Rust. Key points:
correctness:
development:
yarn snap --rustis the primary test suite, testing that we error or compile as expected. It does not test the inner state of the compiler along the way, though, making it less suitable for finding subtle logic gaps btw the TS and Rust versions. It's also Babel based, making it less easy to test OXC and SWC integrations.compiler/scripts/test-e2e.shis an e2e test of all 3 variants (babel wrapper around Rust, OXC/SWC integrations) against the TS implementation. This does a partial comparison, focused on final output code only (doesn't test error details etc). Useful for getting the swc and oxc integrations closer to parity.compiler/script/test-rust-port.shdoes detailed testing of the internal compiler state after each pass, in addition to checking the final output code. This is the key script used to port the compiler, ensuring not just that the output was the same but that each pass was capturing all the same detail. This script can be pointed at any directory of JS files, which we expect to use for internal testing at Meta.