Skip to main content

react_compiler/entrypoint/
pipeline.rs

1// Copyright (c) Meta Platforms, Inc. and affiliates.
2//
3// This source code is licensed under the MIT license found in the
4// LICENSE file in the root directory of this source tree.
5
6//! Compilation pipeline for a single function.
7//!
8//! Analogous to TS `Pipeline.ts` (`compileFn` → `run` → `runWithEnvironment`).
9//! Currently runs BuildHIR (lowering) and PruneMaybeThrows.
10
11use react_compiler_ast::scope::ScopeInfo;
12use react_compiler_diagnostics::CompilerError;
13use react_compiler_hir::ReactFunctionType;
14use react_compiler_hir::environment::Environment;
15use react_compiler_hir::environment::OutputMode;
16use react_compiler_hir::environment_config::EnvironmentConfig;
17use react_compiler_lowering::FunctionNode;
18
19use super::compile_result::CodegenFunction;
20use super::compile_result::CompilerErrorDetailInfo;
21use super::compile_result::CompilerErrorItemInfo;
22use super::compile_result::DebugLogEntry;
23use super::compile_result::LoggerPosition;
24use super::compile_result::LoggerSourceLocation;
25use super::compile_result::OutlinedFunction;
26use super::imports::ProgramContext;
27use super::plugin_options::CompilerOutputMode;
28use crate::debug_print;
29
30/// Run the compilation pipeline on a single function.
31///
32/// Currently: creates an Environment, runs BuildHIR (lowering), and produces
33/// debug output via the context. Returns a CodegenFunction with zeroed memo
34/// stats on success (codegen is not yet implemented).
35pub fn compile_fn(
36    func: &FunctionNode<'_>,
37    fn_name: Option<&str>,
38    scope_info: &ScopeInfo,
39    fn_type: ReactFunctionType,
40    mode: CompilerOutputMode,
41    env_config: &EnvironmentConfig,
42    context: &mut ProgramContext,
43) -> Result<CodegenFunction, CompilerError> {
44    let mut env = Environment::with_config(env_config.clone());
45    env.fn_type = fn_type;
46    env.output_mode = match mode {
47        CompilerOutputMode::Ssr => OutputMode::Ssr,
48        CompilerOutputMode::Client => OutputMode::Client,
49        CompilerOutputMode::Lint => OutputMode::Lint,
50    };
51    env.code = context.code.clone();
52    env.filename = context.filename.clone();
53    env.instrument_fn_name = context.instrument_fn_name.clone();
54    env.instrument_gating_name = context.instrument_gating_name.clone();
55    env.hook_guard_name = context.hook_guard_name.clone();
56    env.seed_uid_known_names(&context.known_referenced_names());
57
58    env.reference_node_ids = scope_info.ref_node_id_to_binding.keys().copied().collect();
59
60    context.timing.start("lower");
61    let mut hir = react_compiler_lowering::lower(func, fn_name, scope_info, &mut env)?;
62    context.timing.stop();
63
64    // Copy renames from lowering to context (keep on env for codegen to apply to type annotations)
65    if !env.renames.is_empty() {
66        context.renames.extend(env.renames.iter().cloned());
67    }
68
69    // Check for Invariant errors after lowering, before logging HIR.
70    // In TS, Invariant errors throw from recordError(), aborting lower() before
71    // the HIR entry is logged. The thrown error contains ONLY the Invariant error,
72    // not other recorded (non-Invariant) errors.
73    if env.has_invariant_errors() {
74        return Err(env.take_invariant_errors());
75    }
76
77    if context.debug_enabled {
78        context.timing.start("debug_print:HIR");
79        let debug_hir = debug_print::debug_hir(&hir, &env);
80        context.log_debug(DebugLogEntry::new("HIR", debug_hir));
81        context.timing.stop();
82    }
83
84    context.timing.start("PruneMaybeThrows");
85    react_compiler_optimization::prune_maybe_throws(&mut hir, &mut env.functions)?;
86    context.timing.stop();
87
88    if context.debug_enabled {
89        context.timing.start("debug_print:PruneMaybeThrows");
90        let debug_prune = debug_print::debug_hir(&hir, &env);
91        context.log_debug(DebugLogEntry::new("PruneMaybeThrows", debug_prune));
92        context.timing.stop();
93    }
94
95    context.timing.start("ValidateContextVariableLValues");
96    react_compiler_validation::validate_context_variable_lvalues(&hir, &mut env)?;
97    if context.debug_enabled {
98        context.log_debug(DebugLogEntry::new(
99            "ValidateContextVariableLValues",
100            "ok".to_string(),
101        ));
102    }
103    context.timing.stop();
104
105    context.timing.start("ValidateUseMemo");
106    let void_memo_errors = react_compiler_validation::validate_use_memo(&hir, &mut env);
107    log_errors_as_events(&void_memo_errors, context);
108    if context.debug_enabled {
109        context.log_debug(DebugLogEntry::new("ValidateUseMemo", "ok".to_string()));
110    }
111    context.timing.stop();
112
113    context.timing.start("DropManualMemoization");
114    react_compiler_optimization::drop_manual_memoization(&mut hir, &mut env)?;
115    context.timing.stop();
116
117    if context.debug_enabled {
118        context.timing.start("debug_print:DropManualMemoization");
119        let debug_drop_memo = debug_print::debug_hir(&hir, &env);
120        context.log_debug(DebugLogEntry::new("DropManualMemoization", debug_drop_memo));
121        context.timing.stop();
122    }
123
124    context
125        .timing
126        .start("InlineImmediatelyInvokedFunctionExpressions");
127    react_compiler_optimization::inline_immediately_invoked_function_expressions(
128        &mut hir, &mut env,
129    );
130    context.timing.stop();
131
132    if context.debug_enabled {
133        context
134            .timing
135            .start("debug_print:InlineImmediatelyInvokedFunctionExpressions");
136        let debug_inline_iifes = debug_print::debug_hir(&hir, &env);
137        context.log_debug(DebugLogEntry::new(
138            "InlineImmediatelyInvokedFunctionExpressions",
139            debug_inline_iifes,
140        ));
141        context.timing.stop();
142    }
143
144    context.timing.start("MergeConsecutiveBlocks");
145    react_compiler_optimization::merge_consecutive_blocks::merge_consecutive_blocks(
146        &mut hir,
147        &mut env.functions,
148    );
149    context.timing.stop();
150
151    if context.debug_enabled {
152        context.timing.start("debug_print:MergeConsecutiveBlocks");
153        let debug_merge = debug_print::debug_hir(&hir, &env);
154        context.log_debug(DebugLogEntry::new("MergeConsecutiveBlocks", debug_merge));
155        context.timing.stop();
156    }
157
158    // TODO: port assertConsistentIdentifiers
159    if context.debug_enabled {
160        context.log_debug(DebugLogEntry::new(
161            "AssertConsistentIdentifiers",
162            "ok".to_string(),
163        ));
164    }
165    // TODO: port assertTerminalSuccessorsExist
166    if context.debug_enabled {
167        context.log_debug(DebugLogEntry::new(
168            "AssertTerminalSuccessorsExist",
169            "ok".to_string(),
170        ));
171    }
172
173    context.timing.start("EnterSSA");
174    react_compiler_ssa::enter_ssa(&mut hir, &mut env).map_err(|diag| {
175        let loc = diag.primary_location().cloned();
176        let mut err = CompilerError::new();
177        err.push_error_detail(react_compiler_diagnostics::CompilerErrorDetail {
178            category: diag.category,
179            reason: diag.reason,
180            description: diag.description,
181            loc,
182            suggestions: diag.suggestions,
183        });
184        err
185    })?;
186    context.timing.stop();
187
188    if context.debug_enabled {
189        context.timing.start("debug_print:SSA");
190        let debug_ssa = debug_print::debug_hir(&hir, &env);
191        context.log_debug(DebugLogEntry::new("SSA", debug_ssa));
192        context.timing.stop();
193    }
194
195    context.timing.start("EliminateRedundantPhi");
196    react_compiler_ssa::eliminate_redundant_phi(&mut hir, &mut env);
197    context.timing.stop();
198
199    if context.debug_enabled {
200        context.timing.start("debug_print:EliminateRedundantPhi");
201        let debug_eliminate_phi = debug_print::debug_hir(&hir, &env);
202        context.log_debug(DebugLogEntry::new(
203            "EliminateRedundantPhi",
204            debug_eliminate_phi,
205        ));
206        context.timing.stop();
207    }
208
209    // TODO: port assertConsistentIdentifiers
210    if context.debug_enabled {
211        context.log_debug(DebugLogEntry::new(
212            "AssertConsistentIdentifiers",
213            "ok".to_string(),
214        ));
215    }
216
217    context.timing.start("ConstantPropagation");
218    react_compiler_optimization::constant_propagation(&mut hir, &mut env);
219    context.timing.stop();
220
221    if context.debug_enabled {
222        context.timing.start("debug_print:ConstantPropagation");
223        let debug_const_prop = debug_print::debug_hir(&hir, &env);
224        context.log_debug(DebugLogEntry::new("ConstantPropagation", debug_const_prop));
225        context.timing.stop();
226    }
227
228    context.timing.start("InferTypes");
229    react_compiler_typeinference::infer_types(&mut hir, &mut env)?;
230    context.timing.stop();
231
232    if context.debug_enabled {
233        context.timing.start("debug_print:InferTypes");
234        let debug_infer_types = debug_print::debug_hir(&hir, &env);
235        context.log_debug(DebugLogEntry::new("InferTypes", debug_infer_types));
236        context.timing.stop();
237    }
238
239    if env.enable_validations() {
240        if env.config.validate_hooks_usage {
241            context.timing.start("ValidateHooksUsage");
242            react_compiler_validation::validate_hooks_usage(&hir, &mut env)?;
243            if context.debug_enabled {
244                context.log_debug(DebugLogEntry::new("ValidateHooksUsage", "ok".to_string()));
245            }
246            context.timing.stop();
247        }
248
249        if env.config.validate_no_capitalized_calls.is_some() {
250            context.timing.start("ValidateNoCapitalizedCalls");
251            react_compiler_validation::validate_no_capitalized_calls(&hir, &mut env)?;
252            if context.debug_enabled {
253                context.log_debug(DebugLogEntry::new(
254                    "ValidateNoCapitalizedCalls",
255                    "ok".to_string(),
256                ));
257            }
258            context.timing.stop();
259        }
260    }
261
262    context.timing.start("OptimizePropsMethodCalls");
263    react_compiler_optimization::optimize_props_method_calls(&mut hir, &env);
264    context.timing.stop();
265
266    if context.debug_enabled {
267        context.timing.start("debug_print:OptimizePropsMethodCalls");
268        let debug_optimize_props = debug_print::debug_hir(&hir, &env);
269        context.log_debug(DebugLogEntry::new(
270            "OptimizePropsMethodCalls",
271            debug_optimize_props,
272        ));
273        context.timing.stop();
274    }
275
276    context.timing.start("AnalyseFunctions");
277    let mut inner_logs: Vec<String> = Vec::new();
278    let debug_inner = context.debug_enabled;
279    let analyse_result = react_compiler_inference::analyse_functions(
280        &mut hir,
281        &mut env,
282        &mut |inner_func, inner_env| {
283            if debug_inner {
284                inner_logs.push(debug_print::debug_hir(inner_func, inner_env));
285            }
286        },
287    );
288    context.timing.stop();
289
290    // Always flush inner logs before propagating errors
291    if context.debug_enabled {
292        for inner_log in inner_logs {
293            context.log_debug(DebugLogEntry::new("AnalyseFunction (inner)", inner_log));
294        }
295    }
296
297    analyse_result?;
298
299    if env.has_invariant_errors() {
300        return Err(env.take_invariant_errors());
301    }
302
303    if context.debug_enabled {
304        context.timing.start("debug_print:AnalyseFunctions");
305        let debug_analyse_functions = debug_print::debug_hir(&hir, &env);
306        context.log_debug(DebugLogEntry::new(
307            "AnalyseFunctions",
308            debug_analyse_functions,
309        ));
310        context.timing.stop();
311    }
312
313    context.timing.start("InferMutationAliasingEffects");
314    react_compiler_inference::infer_mutation_aliasing_effects(&mut hir, &mut env, false)?;
315    context.timing.stop();
316
317    if context.debug_enabled {
318        context
319            .timing
320            .start("debug_print:InferMutationAliasingEffects");
321        let debug_infer_effects = debug_print::debug_hir(&hir, &env);
322        context.log_debug(DebugLogEntry::new(
323            "InferMutationAliasingEffects",
324            debug_infer_effects,
325        ));
326        context.timing.stop();
327    }
328
329    if env.output_mode == OutputMode::Ssr {
330        context.timing.start("OptimizeForSSR");
331        react_compiler_optimization::optimize_for_ssr(&mut hir, &env);
332        context.timing.stop();
333
334        if context.debug_enabled {
335            context.timing.start("debug_print:OptimizeForSSR");
336            let debug_ssr = debug_print::debug_hir(&hir, &env);
337            context.log_debug(DebugLogEntry::new("OptimizeForSSR", debug_ssr));
338            context.timing.stop();
339        }
340    }
341
342    context.timing.start("DeadCodeElimination");
343    react_compiler_optimization::dead_code_elimination(&mut hir, &env);
344    context.timing.stop();
345
346    if context.debug_enabled {
347        context.timing.start("debug_print:DeadCodeElimination");
348        let debug_dce = debug_print::debug_hir(&hir, &env);
349        context.log_debug(DebugLogEntry::new("DeadCodeElimination", debug_dce));
350        context.timing.stop();
351    }
352
353    context.timing.start("PruneMaybeThrows2");
354    react_compiler_optimization::prune_maybe_throws(&mut hir, &mut env.functions)?;
355    context.timing.stop();
356
357    if context.debug_enabled {
358        context.timing.start("debug_print:PruneMaybeThrows2");
359        let debug_prune2 = debug_print::debug_hir(&hir, &env);
360        context.log_debug(DebugLogEntry::new("PruneMaybeThrows", debug_prune2));
361        context.timing.stop();
362    }
363
364    context.timing.start("InferMutationAliasingRanges");
365    react_compiler_inference::infer_mutation_aliasing_ranges(&mut hir, &mut env, false)?;
366    context.timing.stop();
367
368    if context.debug_enabled {
369        context
370            .timing
371            .start("debug_print:InferMutationAliasingRanges");
372        let debug_infer_ranges = debug_print::debug_hir(&hir, &env);
373        context.log_debug(DebugLogEntry::new(
374            "InferMutationAliasingRanges",
375            debug_infer_ranges,
376        ));
377        context.timing.stop();
378    }
379
380    if env.enable_validations() {
381        context
382            .timing
383            .start("ValidateLocalsNotReassignedAfterRender");
384        react_compiler_validation::validate_locals_not_reassigned_after_render(&hir, &mut env);
385        if context.debug_enabled {
386            context.log_debug(DebugLogEntry::new(
387                "ValidateLocalsNotReassignedAfterRender",
388                "ok".to_string(),
389            ));
390        }
391        context.timing.stop();
392
393        if env.config.validate_ref_access_during_render {
394            context.timing.start("ValidateNoRefAccessInRender");
395            react_compiler_validation::validate_no_ref_access_in_render(&hir, &mut env);
396            if context.debug_enabled {
397                context.log_debug(DebugLogEntry::new(
398                    "ValidateNoRefAccessInRender",
399                    "ok".to_string(),
400                ));
401            }
402            context.timing.stop();
403        }
404
405        if env.config.validate_no_set_state_in_render {
406            context.timing.start("ValidateNoSetStateInRender");
407            react_compiler_validation::validate_no_set_state_in_render(&hir, &mut env)?;
408            if context.debug_enabled {
409                context.log_debug(DebugLogEntry::new(
410                    "ValidateNoSetStateInRender",
411                    "ok".to_string(),
412                ));
413            }
414            context.timing.stop();
415        }
416
417        if env.config.validate_no_derived_computations_in_effects_exp
418            && env.output_mode == OutputMode::Lint
419        {
420            context
421                .timing
422                .start("ValidateNoDerivedComputationsInEffects");
423            let errors =
424                react_compiler_validation::validate_no_derived_computations_in_effects_exp(
425                    &hir, &env,
426                )?;
427            log_errors_as_events(&errors, context);
428            if context.debug_enabled {
429                context.log_debug(DebugLogEntry::new(
430                    "ValidateNoDerivedComputationsInEffects",
431                    "ok".to_string(),
432                ));
433            }
434            context.timing.stop();
435        } else if env.config.validate_no_derived_computations_in_effects {
436            context
437                .timing
438                .start("ValidateNoDerivedComputationsInEffects");
439            react_compiler_validation::validate_no_derived_computations_in_effects(&hir, &mut env)?;
440            if context.debug_enabled {
441                context.log_debug(DebugLogEntry::new(
442                    "ValidateNoDerivedComputationsInEffects",
443                    "ok".to_string(),
444                ));
445            }
446            context.timing.stop();
447        }
448
449        if env.config.validate_no_set_state_in_effects && env.output_mode == OutputMode::Lint {
450            context.timing.start("ValidateNoSetStateInEffects");
451            let errors = react_compiler_validation::validate_no_set_state_in_effects(&hir, &env)?;
452            log_errors_as_events(&errors, context);
453            if context.debug_enabled {
454                context.log_debug(DebugLogEntry::new(
455                    "ValidateNoSetStateInEffects",
456                    "ok".to_string(),
457                ));
458            }
459            context.timing.stop();
460        }
461
462        if env.config.validate_no_jsx_in_try_statements && env.output_mode == OutputMode::Lint {
463            context.timing.start("ValidateNoJSXInTryStatement");
464            let errors = react_compiler_validation::validate_no_jsx_in_try_statement(&hir);
465            log_errors_as_events(&errors, context);
466            if context.debug_enabled {
467                context.log_debug(DebugLogEntry::new(
468                    "ValidateNoJSXInTryStatement",
469                    "ok".to_string(),
470                ));
471            }
472            context.timing.stop();
473        }
474
475        context
476            .timing
477            .start("ValidateNoFreezingKnownMutableFunctions");
478        react_compiler_validation::validate_no_freezing_known_mutable_functions(&hir, &mut env);
479        if context.debug_enabled {
480            context.log_debug(DebugLogEntry::new(
481                "ValidateNoFreezingKnownMutableFunctions",
482                "ok".to_string(),
483            ));
484        }
485        context.timing.stop();
486    }
487
488    context.timing.start("InferReactivePlaces");
489    react_compiler_inference::infer_reactive_places(&mut hir, &mut env)?;
490    context.timing.stop();
491
492    if context.debug_enabled {
493        context.timing.start("debug_print:InferReactivePlaces");
494        let debug_reactive_places = debug_print::debug_hir(&hir, &env);
495        context.log_debug(DebugLogEntry::new(
496            "InferReactivePlaces",
497            debug_reactive_places,
498        ));
499        context.timing.stop();
500    }
501
502    if env.enable_validations() {
503        context.timing.start("ValidateExhaustiveDependencies");
504        react_compiler_validation::validate_exhaustive_dependencies(&mut hir, &mut env)?;
505        if context.debug_enabled {
506            context.log_debug(DebugLogEntry::new(
507                "ValidateExhaustiveDependencies",
508                "ok".to_string(),
509            ));
510        }
511        context.timing.stop();
512    }
513
514    context
515        .timing
516        .start("RewriteInstructionKindsBasedOnReassignment");
517    react_compiler_ssa::rewrite_instruction_kinds_based_on_reassignment(&mut hir, &env)?;
518    context.timing.stop();
519
520    if context.debug_enabled {
521        context
522            .timing
523            .start("debug_print:RewriteInstructionKindsBasedOnReassignment");
524        let debug_rewrite = debug_print::debug_hir(&hir, &env);
525        context.log_debug(DebugLogEntry::new(
526            "RewriteInstructionKindsBasedOnReassignment",
527            debug_rewrite,
528        ));
529        context.timing.stop();
530    }
531
532    if env.enable_validations()
533        && env.config.validate_static_components
534        && env.output_mode == OutputMode::Lint
535    {
536        context.timing.start("ValidateStaticComponents");
537        let errors = react_compiler_validation::validate_static_components(&hir);
538        log_errors_as_events(&errors, context);
539        if context.debug_enabled {
540            context.log_debug(DebugLogEntry::new(
541                "ValidateStaticComponents",
542                "ok".to_string(),
543            ));
544        }
545        context.timing.stop();
546    }
547
548    if env.enable_memoization() {
549        context.timing.start("InferReactiveScopeVariables");
550        react_compiler_inference::infer_reactive_scope_variables(&mut hir, &mut env)?;
551        context.timing.stop();
552
553        if context.debug_enabled {
554            context
555                .timing
556                .start("debug_print:InferReactiveScopeVariables");
557            let debug_infer_scopes = debug_print::debug_hir(&hir, &env);
558            context.log_debug(DebugLogEntry::new(
559                "InferReactiveScopeVariables",
560                debug_infer_scopes,
561            ));
562            context.timing.stop();
563        }
564    }
565
566    context
567        .timing
568        .start("MemoizeFbtAndMacroOperandsInSameScope");
569    let fbt_operands =
570        react_compiler_inference::memoize_fbt_and_macro_operands_in_same_scope(&hir, &mut env);
571    context.timing.stop();
572
573    if context.debug_enabled {
574        context
575            .timing
576            .start("debug_print:MemoizeFbtAndMacroOperandsInSameScope");
577        let debug_fbt = debug_print::debug_hir(&hir, &env);
578        context.log_debug(DebugLogEntry::new(
579            "MemoizeFbtAndMacroOperandsInSameScope",
580            debug_fbt,
581        ));
582        context.timing.stop();
583    }
584
585    if env.config.enable_jsx_outlining {
586        context.timing.start("OutlineJsx");
587        react_compiler_optimization::outline_jsx(&mut hir, &mut env);
588        context.timing.stop();
589    }
590
591    if env.config.enable_name_anonymous_functions {
592        context.timing.start("NameAnonymousFunctions");
593        react_compiler_optimization::name_anonymous_functions(&mut hir, &mut env);
594        context.timing.stop();
595
596        if context.debug_enabled {
597            context.timing.start("debug_print:NameAnonymousFunctions");
598            let debug_name_anon = debug_print::debug_hir(&hir, &env);
599            context.log_debug(DebugLogEntry::new(
600                "NameAnonymousFunctions",
601                debug_name_anon,
602            ));
603            context.timing.stop();
604        }
605    }
606
607    if env.config.enable_function_outlining {
608        context.timing.start("OutlineFunctions");
609        react_compiler_optimization::outline_functions(&mut hir, &mut env, &fbt_operands);
610        context.timing.stop();
611
612        if context.debug_enabled {
613            context.timing.start("debug_print:OutlineFunctions");
614            let debug_outline = debug_print::debug_hir(&hir, &env);
615            context.log_debug(DebugLogEntry::new("OutlineFunctions", debug_outline));
616            context.timing.stop();
617        }
618    }
619
620    context.timing.start("AlignMethodCallScopes");
621    react_compiler_inference::align_method_call_scopes(&mut hir, &mut env);
622    context.timing.stop();
623
624    if context.debug_enabled {
625        context.timing.start("debug_print:AlignMethodCallScopes");
626        let debug_align = debug_print::debug_hir(&hir, &env);
627        context.log_debug(DebugLogEntry::new("AlignMethodCallScopes", debug_align));
628        context.timing.stop();
629    }
630
631    context.timing.start("AlignObjectMethodScopes");
632    react_compiler_inference::align_object_method_scopes(&mut hir, &mut env);
633    context.timing.stop();
634
635    if context.debug_enabled {
636        context.timing.start("debug_print:AlignObjectMethodScopes");
637        let debug_align_obj = debug_print::debug_hir(&hir, &env);
638        context.log_debug(DebugLogEntry::new(
639            "AlignObjectMethodScopes",
640            debug_align_obj,
641        ));
642        context.timing.stop();
643    }
644
645    context.timing.start("PruneUnusedLabelsHIR");
646    react_compiler_optimization::prune_unused_labels_hir(&mut hir);
647    context.timing.stop();
648
649    if context.debug_enabled {
650        context.timing.start("debug_print:PruneUnusedLabelsHIR");
651        let debug_prune_labels = debug_print::debug_hir(&hir, &env);
652        context.log_debug(DebugLogEntry::new(
653            "PruneUnusedLabelsHIR",
654            debug_prune_labels,
655        ));
656        context.timing.stop();
657    }
658
659    context.timing.start("AlignReactiveScopesToBlockScopesHIR");
660    react_compiler_inference::align_reactive_scopes_to_block_scopes_hir(&mut hir, &mut env);
661    context.timing.stop();
662
663    if context.debug_enabled {
664        context
665            .timing
666            .start("debug_print:AlignReactiveScopesToBlockScopesHIR");
667        let debug_align_block_scopes = debug_print::debug_hir(&hir, &env);
668        context.log_debug(DebugLogEntry::new(
669            "AlignReactiveScopesToBlockScopesHIR",
670            debug_align_block_scopes,
671        ));
672        context.timing.stop();
673    }
674
675    context.timing.start("MergeOverlappingReactiveScopesHIR");
676    react_compiler_inference::merge_overlapping_reactive_scopes_hir(&mut hir, &mut env);
677    context.timing.stop();
678
679    if context.debug_enabled {
680        context
681            .timing
682            .start("debug_print:MergeOverlappingReactiveScopesHIR");
683        let debug_merge_overlapping = debug_print::debug_hir(&hir, &env);
684        context.log_debug(DebugLogEntry::new(
685            "MergeOverlappingReactiveScopesHIR",
686            debug_merge_overlapping,
687        ));
688        context.timing.stop();
689    }
690
691    // TODO: port assertValidBlockNesting
692    if context.debug_enabled {
693        context.log_debug(DebugLogEntry::new(
694            "AssertValidBlockNesting",
695            "ok".to_string(),
696        ));
697    }
698
699    context.timing.start("BuildReactiveScopeTerminalsHIR");
700    react_compiler_inference::build_reactive_scope_terminals_hir(&mut hir, &mut env);
701    context.timing.stop();
702
703    if context.debug_enabled {
704        context
705            .timing
706            .start("debug_print:BuildReactiveScopeTerminalsHIR");
707        let debug_build_scope_terminals = debug_print::debug_hir(&hir, &env);
708        context.log_debug(DebugLogEntry::new(
709            "BuildReactiveScopeTerminalsHIR",
710            debug_build_scope_terminals,
711        ));
712        context.timing.stop();
713    }
714
715    // TODO: port assertValidBlockNesting
716    if context.debug_enabled {
717        context.log_debug(DebugLogEntry::new(
718            "AssertValidBlockNesting",
719            "ok".to_string(),
720        ));
721    }
722
723    context.timing.start("FlattenReactiveLoopsHIR");
724    react_compiler_inference::flatten_reactive_loops_hir(&mut hir);
725    context.timing.stop();
726
727    if context.debug_enabled {
728        context.timing.start("debug_print:FlattenReactiveLoopsHIR");
729        let debug_flatten_loops = debug_print::debug_hir(&hir, &env);
730        context.log_debug(DebugLogEntry::new(
731            "FlattenReactiveLoopsHIR",
732            debug_flatten_loops,
733        ));
734        context.timing.stop();
735    }
736
737    context.timing.start("FlattenScopesWithHooksOrUseHIR");
738    react_compiler_inference::flatten_scopes_with_hooks_or_use_hir(&mut hir, &env)?;
739    context.timing.stop();
740
741    if context.debug_enabled {
742        context
743            .timing
744            .start("debug_print:FlattenScopesWithHooksOrUseHIR");
745        let debug_flatten_hooks = debug_print::debug_hir(&hir, &env);
746        context.log_debug(DebugLogEntry::new(
747            "FlattenScopesWithHooksOrUseHIR",
748            debug_flatten_hooks,
749        ));
750        context.timing.stop();
751    }
752
753    // TODO: port assertTerminalSuccessorsExist
754    if context.debug_enabled {
755        context.log_debug(DebugLogEntry::new(
756            "AssertTerminalSuccessorsExist",
757            "ok".to_string(),
758        ));
759    }
760    // TODO: port assertTerminalPredsExist
761    if context.debug_enabled {
762        context.log_debug(DebugLogEntry::new(
763            "AssertTerminalPredsExist",
764            "ok".to_string(),
765        ));
766    }
767
768    context.timing.start("PropagateScopeDependenciesHIR");
769    react_compiler_inference::propagate_scope_dependencies_hir(&mut hir, &mut env);
770    context.timing.stop();
771
772    if context.debug_enabled {
773        context
774            .timing
775            .start("debug_print:PropagateScopeDependenciesHIR");
776        let debug_propagate_deps = debug_print::debug_hir(&hir, &env);
777        context.log_debug(DebugLogEntry::new(
778            "PropagateScopeDependenciesHIR",
779            debug_propagate_deps,
780        ));
781        context.timing.stop();
782    }
783
784    context.timing.start("BuildReactiveFunction");
785    let mut reactive_fn = react_compiler_reactive_scopes::build_reactive_function(&hir, &env)?;
786    context.timing.stop();
787
788    let hir_formatter = |fmt: &mut react_compiler_hir::print::PrintFormatter,
789                         func: &react_compiler_hir::HirFunction| {
790        debug_print::format_hir_function_into(fmt, func);
791    };
792
793    if context.debug_enabled {
794        context.timing.start("debug_print:BuildReactiveFunction");
795        let debug_reactive = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
796            &reactive_fn, &env, Some(&hir_formatter),
797        );
798        context.log_debug(DebugLogEntry::new("BuildReactiveFunction", debug_reactive));
799        context.timing.stop();
800    }
801
802    context.timing.start("AssertWellFormedBreakTargets");
803    react_compiler_reactive_scopes::assert_well_formed_break_targets(&reactive_fn, &env);
804    if context.debug_enabled {
805        context.log_debug(DebugLogEntry::new(
806            "AssertWellFormedBreakTargets",
807            "ok".to_string(),
808        ));
809    }
810    context.timing.stop();
811
812    context.timing.start("PruneUnusedLabels");
813    react_compiler_reactive_scopes::prune_unused_labels(&mut reactive_fn, &env)?;
814    context.timing.stop();
815
816    if context.debug_enabled {
817        context.timing.start("debug_print:PruneUnusedLabels");
818        let debug_prune_labels_reactive = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
819            &reactive_fn, &env, Some(&hir_formatter),
820        );
821        context.log_debug(DebugLogEntry::new(
822            "PruneUnusedLabels",
823            debug_prune_labels_reactive,
824        ));
825        context.timing.stop();
826    }
827
828    context.timing.start("AssertScopeInstructionsWithinScopes");
829    react_compiler_reactive_scopes::assert_scope_instructions_within_scopes(&reactive_fn, &env)?;
830    if context.debug_enabled {
831        context.log_debug(DebugLogEntry::new(
832            "AssertScopeInstructionsWithinScopes",
833            "ok".to_string(),
834        ));
835    }
836    context.timing.stop();
837
838    context.timing.start("PruneNonEscapingScopes");
839    react_compiler_reactive_scopes::prune_non_escaping_scopes(&mut reactive_fn, &mut env)?;
840    context.timing.stop();
841
842    if context.debug_enabled {
843        context.timing.start("debug_print:PruneNonEscapingScopes");
844        let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
845            &reactive_fn, &env, Some(&hir_formatter),
846        );
847        context.log_debug(DebugLogEntry::new("PruneNonEscapingScopes", debug));
848        context.timing.stop();
849    }
850
851    context.timing.start("PruneNonReactiveDependencies");
852    react_compiler_reactive_scopes::prune_non_reactive_dependencies(&mut reactive_fn, &mut env);
853    context.timing.stop();
854
855    if context.debug_enabled {
856        context
857            .timing
858            .start("debug_print:PruneNonReactiveDependencies");
859        let debug_prune_non_reactive = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
860            &reactive_fn, &env, Some(&hir_formatter),
861        );
862        context.log_debug(DebugLogEntry::new(
863            "PruneNonReactiveDependencies",
864            debug_prune_non_reactive,
865        ));
866        context.timing.stop();
867    }
868
869    context.timing.start("PruneUnusedScopes");
870    react_compiler_reactive_scopes::prune_unused_scopes(&mut reactive_fn, &env)?;
871    context.timing.stop();
872
873    if context.debug_enabled {
874        context.timing.start("debug_print:PruneUnusedScopes");
875        let debug_prune_unused_scopes = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
876            &reactive_fn, &env, Some(&hir_formatter),
877        );
878        context.log_debug(DebugLogEntry::new(
879            "PruneUnusedScopes",
880            debug_prune_unused_scopes,
881        ));
882        context.timing.stop();
883    }
884
885    context
886        .timing
887        .start("MergeReactiveScopesThatInvalidateTogether");
888    react_compiler_reactive_scopes::merge_reactive_scopes_that_invalidate_together(
889        &mut reactive_fn,
890        &mut env,
891    )?;
892    context.timing.stop();
893
894    if context.debug_enabled {
895        context
896            .timing
897            .start("debug_print:MergeReactiveScopesThatInvalidateTogether");
898        let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
899            &reactive_fn, &env, Some(&hir_formatter),
900        );
901        context.log_debug(DebugLogEntry::new(
902            "MergeReactiveScopesThatInvalidateTogether",
903            debug,
904        ));
905        context.timing.stop();
906    }
907
908    context.timing.start("PruneAlwaysInvalidatingScopes");
909    react_compiler_reactive_scopes::prune_always_invalidating_scopes(&mut reactive_fn, &env)?;
910    context.timing.stop();
911
912    if context.debug_enabled {
913        context
914            .timing
915            .start("debug_print:PruneAlwaysInvalidatingScopes");
916        let debug_prune_always_inv = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
917            &reactive_fn, &env, Some(&hir_formatter),
918        );
919        context.log_debug(DebugLogEntry::new(
920            "PruneAlwaysInvalidatingScopes",
921            debug_prune_always_inv,
922        ));
923        context.timing.stop();
924    }
925
926    context.timing.start("PropagateEarlyReturns");
927    react_compiler_reactive_scopes::propagate_early_returns(&mut reactive_fn, &mut env);
928    context.timing.stop();
929
930    if context.debug_enabled {
931        context.timing.start("debug_print:PropagateEarlyReturns");
932        let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
933            &reactive_fn, &env, Some(&hir_formatter),
934        );
935        context.log_debug(DebugLogEntry::new("PropagateEarlyReturns", debug));
936        context.timing.stop();
937    }
938
939    context.timing.start("PruneUnusedLValues");
940    react_compiler_reactive_scopes::prune_unused_lvalues(&mut reactive_fn, &env);
941    context.timing.stop();
942
943    if context.debug_enabled {
944        context.timing.start("debug_print:PruneUnusedLValues");
945        let debug_prune_lvalues = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
946            &reactive_fn, &env, Some(&hir_formatter),
947        );
948        context.log_debug(DebugLogEntry::new(
949            "PruneUnusedLValues",
950            debug_prune_lvalues,
951        ));
952        context.timing.stop();
953    }
954
955    context.timing.start("PromoteUsedTemporaries");
956    react_compiler_reactive_scopes::promote_used_temporaries(&mut reactive_fn, &mut env);
957    context.timing.stop();
958
959    if context.debug_enabled {
960        context.timing.start("debug_print:PromoteUsedTemporaries");
961        let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
962            &reactive_fn, &env, Some(&hir_formatter),
963        );
964        context.log_debug(DebugLogEntry::new("PromoteUsedTemporaries", debug));
965        context.timing.stop();
966    }
967
968    context
969        .timing
970        .start("ExtractScopeDeclarationsFromDestructuring");
971    react_compiler_reactive_scopes::extract_scope_declarations_from_destructuring(
972        &mut reactive_fn,
973        &mut env,
974    )?;
975    context.timing.stop();
976
977    if context.debug_enabled {
978        context
979            .timing
980            .start("debug_print:ExtractScopeDeclarationsFromDestructuring");
981        let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
982            &reactive_fn, &env, Some(&hir_formatter),
983        );
984        context.log_debug(DebugLogEntry::new(
985            "ExtractScopeDeclarationsFromDestructuring",
986            debug,
987        ));
988        context.timing.stop();
989    }
990
991    context.timing.start("StabilizeBlockIds");
992    react_compiler_reactive_scopes::stabilize_block_ids(&mut reactive_fn, &mut env);
993    context.timing.stop();
994
995    if context.debug_enabled {
996        context.timing.start("debug_print:StabilizeBlockIds");
997        let debug_stabilize = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
998            &reactive_fn, &env, Some(&hir_formatter),
999        );
1000        context.log_debug(DebugLogEntry::new("StabilizeBlockIds", debug_stabilize));
1001        context.timing.stop();
1002    }
1003
1004    context.timing.start("RenameVariables");
1005    let unique_identifiers =
1006        react_compiler_reactive_scopes::rename_variables(&mut reactive_fn, &mut env);
1007    context.timing.stop();
1008
1009    for name in &unique_identifiers {
1010        context.add_new_reference(name.clone());
1011    }
1012
1013    if context.debug_enabled {
1014        context.timing.start("debug_print:RenameVariables");
1015        let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
1016            &reactive_fn, &env, Some(&hir_formatter),
1017        );
1018        context.log_debug(DebugLogEntry::new("RenameVariables", debug));
1019        context.timing.stop();
1020    }
1021
1022    context.timing.start("PruneHoistedContexts");
1023    react_compiler_reactive_scopes::prune_hoisted_contexts(&mut reactive_fn, &mut env)?;
1024    context.timing.stop();
1025
1026    if context.debug_enabled {
1027        context.timing.start("debug_print:PruneHoistedContexts");
1028        let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
1029            &reactive_fn, &env, Some(&hir_formatter),
1030        );
1031        context.log_debug(DebugLogEntry::new("PruneHoistedContexts", debug));
1032        context.timing.stop();
1033    }
1034
1035    if env.config.enable_preserve_existing_memoization_guarantees
1036        || env.config.validate_preserve_existing_memoization_guarantees
1037    {
1038        context.timing.start("ValidatePreservedManualMemoization");
1039        react_compiler_validation::validate_preserved_manual_memoization(&reactive_fn, &mut env);
1040        if context.debug_enabled {
1041            context.log_debug(DebugLogEntry::new(
1042                "ValidatePreservedManualMemoization",
1043                "ok".to_string(),
1044            ));
1045        }
1046        context.timing.stop();
1047    }
1048
1049    context.timing.start("codegen");
1050    let codegen_result = react_compiler_reactive_scopes::codegen_function(
1051        &reactive_fn,
1052        &mut env,
1053        unique_identifiers,
1054        fbt_operands,
1055    )?;
1056    context.timing.stop();
1057
1058    // NOTE: we intentionally do NOT register the memo cache import here.
1059    // The import is registered in apply_compiled_functions() only for functions
1060    // that are actually applied to the output. Registering it here would cause
1061    // a spurious `import { c as _c }` when a function compiles with memo slots
1062    // but is later discarded (e.g., due to "use no memo" opt-out or errors),
1063    // while other functions in the same file compile to 0 memo slots.
1064
1065    if env.config.validate_source_locations {
1066        super::validate_source_locations::validate_source_locations(
1067            func,
1068            &codegen_result,
1069            &mut env,
1070        );
1071    }
1072
1073    // Simulate unexpected exception for testing (matches TS Pipeline.ts)
1074    if env.config.throw_unknown_exception_testonly {
1075        let mut err = CompilerError::new();
1076        err.push_error_detail(react_compiler_diagnostics::CompilerErrorDetail {
1077            category: react_compiler_diagnostics::ErrorCategory::Invariant,
1078            reason: "unexpected error".to_string(),
1079            description: None,
1080            loc: None,
1081            suggestions: None,
1082        });
1083        return Err(err);
1084    }
1085
1086    // Check for accumulated errors at the end of the pipeline
1087    // (matches TS Pipeline.ts: env.hasErrors() → Err at the end)
1088    if env.has_errors() {
1089        // Merge UIDs even on error: in TS, Babel's scope.generateUid() permanently
1090        // registers names in the scope's `uids` map regardless of whether the function
1091        // compilation succeeds or fails. Without this merge, failed compilations would
1092        // "leak" _temp names that subsequent successful compilations wouldn't see,
1093        // causing numbering mismatches vs TS.
1094        if let Some(uid_names) = env.take_uid_known_names() {
1095            context.merge_uid_known_names(&uid_names);
1096        }
1097        return Err(env.take_errors());
1098    }
1099
1100    // Re-compile outlined functions through the full pipeline.
1101    // This mirrors TS behavior where outlined functions from JSX outlining
1102    // are pushed back onto the compilation queue and compiled as components.
1103    let mut compiled_outlined: Vec<OutlinedFunction> = Vec::new();
1104    for o in codegen_result.outlined {
1105        let outlined_codegen = CodegenFunction {
1106            loc: o.func.loc,
1107            id: o.func.id,
1108            name_hint: o.func.name_hint,
1109            params: o.func.params,
1110            body: o.func.body,
1111            generator: o.func.generator,
1112            is_async: o.func.is_async,
1113            memo_slots_used: o.func.memo_slots_used,
1114            memo_blocks: o.func.memo_blocks,
1115            memo_values: o.func.memo_values,
1116            pruned_memo_blocks: o.func.pruned_memo_blocks,
1117            pruned_memo_values: o.func.pruned_memo_values,
1118            outlined: Vec::new(),
1119        };
1120        if let Some(fn_type) = o.fn_type {
1121            let fn_name = outlined_codegen.id.as_ref().map(|id| id.name.clone());
1122            match compile_outlined_fn(
1123                outlined_codegen,
1124                fn_name.as_deref(),
1125                fn_type,
1126                mode,
1127                env_config,
1128                context,
1129            ) {
1130                Ok(compiled) => {
1131                    compiled_outlined.push(OutlinedFunction {
1132                        func: compiled,
1133                        fn_type: Some(fn_type),
1134                    });
1135                }
1136                Err(_err) => {
1137                    // If re-compilation fails, skip the outlined function
1138                }
1139            }
1140        } else {
1141            compiled_outlined.push(OutlinedFunction {
1142                func: outlined_codegen,
1143                fn_type: o.fn_type,
1144            });
1145        }
1146    }
1147
1148    if let Some(uid_names) = env.take_uid_known_names() {
1149        context.merge_uid_known_names(&uid_names);
1150    }
1151
1152    Ok(CodegenFunction {
1153        loc: codegen_result.loc,
1154        id: codegen_result.id,
1155        name_hint: codegen_result.name_hint,
1156        params: codegen_result.params,
1157        body: codegen_result.body,
1158        generator: codegen_result.generator,
1159        is_async: codegen_result.is_async,
1160        memo_slots_used: codegen_result.memo_slots_used,
1161        memo_blocks: codegen_result.memo_blocks,
1162        memo_values: codegen_result.memo_values,
1163        pruned_memo_blocks: codegen_result.pruned_memo_blocks,
1164        pruned_memo_values: codegen_result.pruned_memo_values,
1165        outlined: compiled_outlined,
1166    })
1167}
1168
1169/// Compile an outlined function's codegen AST through the full pipeline.
1170///
1171/// Creates a fresh Environment, builds a synthetic ScopeInfo with unique fake
1172/// positions for identifier resolution, lowers from AST to HIR, then runs
1173/// the full compilation pipeline. This mirrors the TS behavior where outlined
1174/// functions are inserted into the program AST and re-compiled from scratch.
1175pub fn compile_outlined_fn(
1176    mut codegen_fn: CodegenFunction,
1177    fn_name: Option<&str>,
1178    fn_type: ReactFunctionType,
1179    mode: CompilerOutputMode,
1180    env_config: &EnvironmentConfig,
1181    context: &mut ProgramContext,
1182) -> Result<CodegenFunction, CompilerError> {
1183    let mut env = Environment::with_config(env_config.clone());
1184    env.fn_type = fn_type;
1185    env.output_mode = match mode {
1186        CompilerOutputMode::Ssr => OutputMode::Ssr,
1187        CompilerOutputMode::Client => OutputMode::Client,
1188        CompilerOutputMode::Lint => OutputMode::Lint,
1189    };
1190
1191    // Build a FunctionDeclaration from the codegen output
1192    let mut outlined_decl = react_compiler_ast::statements::FunctionDeclaration {
1193        base: react_compiler_ast::common::BaseNode::typed("FunctionDeclaration"),
1194        id: codegen_fn.id.take(),
1195        params: std::mem::take(&mut codegen_fn.params),
1196        body: std::mem::replace(
1197            &mut codegen_fn.body,
1198            react_compiler_ast::statements::BlockStatement {
1199                base: react_compiler_ast::common::BaseNode::typed("BlockStatement"),
1200                body: Vec::new(),
1201                directives: Vec::new(),
1202            },
1203        ),
1204        generator: codegen_fn.generator,
1205        is_async: codegen_fn.is_async,
1206        declare: None,
1207        return_type: None,
1208        type_parameters: None,
1209        predicate: None,
1210        component_declaration: false,
1211        hook_declaration: false,
1212    };
1213
1214    // Build scope info by assigning fake positions to all identifiers
1215    let scope_info = build_outlined_scope_info(&mut outlined_decl);
1216
1217    let func_node = react_compiler_lowering::FunctionNode::FunctionDeclaration(&outlined_decl);
1218    let mut hir = react_compiler_lowering::lower(&func_node, fn_name, &scope_info, &mut env)?;
1219
1220    if env.has_invariant_errors() {
1221        return Err(env.take_invariant_errors());
1222    }
1223
1224    run_pipeline_passes(&mut hir, &mut env, context)
1225}
1226
1227/// Build a ScopeInfo for an outlined function declaration by assigning unique
1228/// fake positions to all Identifier nodes and building the binding/reference maps.
1229fn build_outlined_scope_info(
1230    func: &mut react_compiler_ast::statements::FunctionDeclaration,
1231) -> react_compiler_ast::scope::ScopeInfo {
1232    use std::collections::HashMap;
1233
1234    use react_compiler_ast::scope::*;
1235
1236    let mut pos: u32 = 1; // reserve 0 for the function itself
1237    func.base.start = Some(0);
1238
1239    let mut fn_bindings: HashMap<String, BindingId> = HashMap::new();
1240    let mut bindings_list: Vec<BindingData> = Vec::new();
1241    let mut ref_to_binding: indexmap::IndexMap<u32, BindingId> = indexmap::IndexMap::new();
1242
1243    // Helper to add a binding
1244    let _add_binding =
1245        |name: &str,
1246         kind: BindingKind,
1247         p: u32,
1248         fn_bindings: &mut HashMap<String, BindingId>,
1249         bindings_list: &mut Vec<BindingData>,
1250         ref_to_binding: &mut indexmap::IndexMap<u32, BindingId>| {
1251            if fn_bindings.contains_key(name) {
1252                // Already exists, just add reference
1253                let bid = fn_bindings[name];
1254                ref_to_binding.insert(p, bid);
1255                return;
1256            }
1257            let binding_id = BindingId(bindings_list.len() as u32);
1258            fn_bindings.insert(name.to_string(), binding_id);
1259            bindings_list.push(BindingData {
1260                id: binding_id,
1261                name: name.to_string(),
1262                kind,
1263                scope: ScopeId(1),
1264                declaration_type: "VariableDeclarator".to_string(),
1265                declaration_start: Some(p),
1266                declaration_node_id: None,
1267                import: None,
1268            });
1269            ref_to_binding.insert(p, binding_id);
1270        };
1271
1272    // Process params - add as Param bindings
1273    for param in &mut func.params {
1274        outlined_assign_pattern_positions(
1275            param,
1276            &mut pos,
1277            BindingKind::Param,
1278            &mut fn_bindings,
1279            &mut bindings_list,
1280            &mut ref_to_binding,
1281        );
1282    }
1283
1284    // Process body - walk all statements to assign positions and collect variable declarations
1285    for stmt in &mut func.body.body {
1286        outlined_assign_stmt_positions(
1287            stmt,
1288            &mut pos,
1289            &mut fn_bindings,
1290            &mut bindings_list,
1291            &mut ref_to_binding,
1292        );
1293    }
1294
1295    let program_scope = ScopeData {
1296        id: ScopeId(0),
1297        parent: None,
1298        kind: ScopeKind::Program,
1299        bindings: HashMap::new(),
1300    };
1301    let fn_scope = ScopeData {
1302        id: ScopeId(1),
1303        parent: Some(ScopeId(0)),
1304        kind: ScopeKind::Function,
1305        bindings: fn_bindings,
1306    };
1307
1308    let mut node_to_scope: HashMap<u32, ScopeId> = HashMap::new();
1309    node_to_scope.insert(0, ScopeId(1));
1310
1311    // Mirror position maps into node-ID maps for outlined functions
1312    let mut node_id_to_scope: HashMap<u32, ScopeId> = HashMap::new();
1313    node_id_to_scope.insert(0, ScopeId(1));
1314    let ref_node_id_to_binding: indexmap::IndexMap<u32, BindingId> =
1315        ref_to_binding.iter().map(|(&k, &v)| (k, v)).collect();
1316
1317    ScopeInfo {
1318        scopes: vec![program_scope, fn_scope],
1319        bindings: bindings_list,
1320        node_to_scope,
1321        node_to_scope_end: HashMap::new(),
1322        reference_to_binding: indexmap::IndexMap::new(),
1323        ref_node_id_to_binding,
1324        node_id_to_scope,
1325        program_scope: ScopeId(0),
1326    }
1327}
1328
1329/// Assign positions to identifiers in a pattern and register as bindings.
1330fn outlined_assign_pattern_positions(
1331    pattern: &mut react_compiler_ast::patterns::PatternLike,
1332    pos: &mut u32,
1333    kind: react_compiler_ast::scope::BindingKind,
1334    fn_bindings: &mut std::collections::HashMap<String, react_compiler_ast::scope::BindingId>,
1335    bindings_list: &mut Vec<react_compiler_ast::scope::BindingData>,
1336    ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>,
1337) {
1338    use react_compiler_ast::patterns::PatternLike;
1339    use react_compiler_ast::scope::*;
1340
1341    match pattern {
1342        PatternLike::Identifier(id) => {
1343            let p = *pos;
1344            *pos += 1;
1345            id.base.start = Some(p);
1346            id.base.node_id = Some(p);
1347            // Add as a binding
1348            if !fn_bindings.contains_key(&id.name) {
1349                let binding_id = BindingId(bindings_list.len() as u32);
1350                fn_bindings.insert(id.name.clone(), binding_id);
1351                bindings_list.push(BindingData {
1352                    id: binding_id,
1353                    name: id.name.clone(),
1354                    kind: kind.clone(),
1355                    scope: ScopeId(1),
1356                    declaration_type: "VariableDeclarator".to_string(),
1357                    declaration_start: Some(p),
1358                    declaration_node_id: Some(p),
1359                    import: None,
1360                });
1361                ref_to_binding.insert(p, binding_id);
1362            } else {
1363                let bid = fn_bindings[&id.name];
1364                ref_to_binding.insert(p, bid);
1365            }
1366        }
1367        PatternLike::ObjectPattern(obj) => {
1368            for prop in &mut obj.properties {
1369                match prop {
1370                    react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(
1371                        p_inner,
1372                    ) => {
1373                        outlined_assign_pattern_positions(
1374                            &mut p_inner.value,
1375                            pos,
1376                            kind.clone(),
1377                            fn_bindings,
1378                            bindings_list,
1379                            ref_to_binding,
1380                        );
1381                    }
1382                    react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => {
1383                        outlined_assign_pattern_positions(
1384                            &mut r.argument,
1385                            pos,
1386                            kind.clone(),
1387                            fn_bindings,
1388                            bindings_list,
1389                            ref_to_binding,
1390                        );
1391                    }
1392                }
1393            }
1394        }
1395        PatternLike::ArrayPattern(arr) => {
1396            for elem in arr.elements.iter_mut().flatten() {
1397                outlined_assign_pattern_positions(
1398                    elem,
1399                    pos,
1400                    kind.clone(),
1401                    fn_bindings,
1402                    bindings_list,
1403                    ref_to_binding,
1404                );
1405            }
1406        }
1407        PatternLike::AssignmentPattern(assign) => {
1408            outlined_assign_pattern_positions(
1409                &mut assign.left,
1410                pos,
1411                kind.clone(),
1412                fn_bindings,
1413                bindings_list,
1414                ref_to_binding,
1415            );
1416        }
1417        PatternLike::RestElement(rest) => {
1418            outlined_assign_pattern_positions(
1419                &mut rest.argument,
1420                pos,
1421                kind.clone(),
1422                fn_bindings,
1423                bindings_list,
1424                ref_to_binding,
1425            );
1426        }
1427        _ => {}
1428    }
1429}
1430
1431/// Assign positions to identifiers in a statement body.
1432fn outlined_assign_stmt_positions(
1433    stmt: &mut react_compiler_ast::statements::Statement,
1434    pos: &mut u32,
1435    fn_bindings: &mut std::collections::HashMap<String, react_compiler_ast::scope::BindingId>,
1436    bindings_list: &mut Vec<react_compiler_ast::scope::BindingData>,
1437    ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>,
1438) {
1439    use react_compiler_ast::statements::Statement;
1440
1441    match stmt {
1442        Statement::VariableDeclaration(decl) => {
1443            for declarator in &mut decl.declarations {
1444                // Process init first (references)
1445                if let Some(init) = &mut declarator.init {
1446                    outlined_assign_expr_positions(init, pos, fn_bindings, ref_to_binding);
1447                }
1448                // Process pattern (declarations)
1449                outlined_assign_pattern_positions(
1450                    &mut declarator.id,
1451                    pos,
1452                    react_compiler_ast::scope::BindingKind::Let,
1453                    fn_bindings,
1454                    bindings_list,
1455                    ref_to_binding,
1456                );
1457            }
1458        }
1459        Statement::ReturnStatement(ret) => {
1460            if let Some(arg) = &mut ret.argument {
1461                outlined_assign_expr_positions(arg, pos, fn_bindings, ref_to_binding);
1462            }
1463        }
1464        Statement::ExpressionStatement(expr_stmt) => {
1465            outlined_assign_expr_positions(
1466                &mut expr_stmt.expression,
1467                pos,
1468                fn_bindings,
1469                ref_to_binding,
1470            );
1471        }
1472        _ => {}
1473    }
1474}
1475
1476/// Assign positions to identifiers in an expression.
1477fn outlined_assign_expr_positions(
1478    expr: &mut react_compiler_ast::expressions::Expression,
1479    pos: &mut u32,
1480    fn_bindings: &std::collections::HashMap<String, react_compiler_ast::scope::BindingId>,
1481    ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>,
1482) {
1483    use react_compiler_ast::expressions::*;
1484
1485    match expr {
1486        Expression::Identifier(id) => {
1487            let p = *pos;
1488            *pos += 1;
1489            id.base.start = Some(p);
1490            id.base.node_id = Some(p);
1491            if let Some(&bid) = fn_bindings.get(&id.name) {
1492                ref_to_binding.insert(p, bid);
1493            }
1494        }
1495        Expression::JSXElement(jsx) => {
1496            // Opening tag
1497            outlined_assign_jsx_name_positions(
1498                &mut jsx.opening_element.name,
1499                pos,
1500                fn_bindings,
1501                ref_to_binding,
1502            );
1503            for attr in &mut jsx.opening_element.attributes {
1504                match attr {
1505                    react_compiler_ast::jsx::JSXAttributeItem::JSXAttribute(a) => {
1506                        if let Some(val) = &mut a.value {
1507                            outlined_assign_jsx_val_positions(
1508                                val,
1509                                pos,
1510                                fn_bindings,
1511                                ref_to_binding,
1512                            );
1513                        }
1514                    }
1515                    react_compiler_ast::jsx::JSXAttributeItem::JSXSpreadAttribute(s) => {
1516                        outlined_assign_expr_positions(
1517                            &mut s.argument,
1518                            pos,
1519                            fn_bindings,
1520                            ref_to_binding,
1521                        );
1522                    }
1523                }
1524            }
1525            for child in &mut jsx.children {
1526                outlined_assign_jsx_child_positions(child, pos, fn_bindings, ref_to_binding);
1527            }
1528        }
1529        Expression::JSXFragment(frag) => {
1530            for child in &mut frag.children {
1531                outlined_assign_jsx_child_positions(child, pos, fn_bindings, ref_to_binding);
1532            }
1533        }
1534        _ => {}
1535    }
1536}
1537
1538fn outlined_assign_jsx_name_positions(
1539    name: &mut react_compiler_ast::jsx::JSXElementName,
1540    pos: &mut u32,
1541    fn_bindings: &std::collections::HashMap<String, react_compiler_ast::scope::BindingId>,
1542    ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>,
1543) {
1544    match name {
1545        react_compiler_ast::jsx::JSXElementName::JSXIdentifier(id) => {
1546            let p = *pos;
1547            *pos += 1;
1548            id.base.start = Some(p);
1549            id.base.node_id = Some(p);
1550            if let Some(&bid) = fn_bindings.get(&id.name) {
1551                ref_to_binding.insert(p, bid);
1552            }
1553        }
1554        react_compiler_ast::jsx::JSXElementName::JSXMemberExpression(m) => {
1555            outlined_assign_jsx_member_positions(m, pos, fn_bindings, ref_to_binding);
1556        }
1557        _ => {}
1558    }
1559}
1560
1561fn outlined_assign_jsx_member_positions(
1562    member: &mut react_compiler_ast::jsx::JSXMemberExpression,
1563    pos: &mut u32,
1564    fn_bindings: &std::collections::HashMap<String, react_compiler_ast::scope::BindingId>,
1565    ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>,
1566) {
1567    match &mut *member.object {
1568        react_compiler_ast::jsx::JSXMemberExprObject::JSXIdentifier(id) => {
1569            let p = *pos;
1570            *pos += 1;
1571            id.base.start = Some(p);
1572            id.base.node_id = Some(p);
1573            if let Some(&bid) = fn_bindings.get(&id.name) {
1574                ref_to_binding.insert(p, bid);
1575            }
1576        }
1577        react_compiler_ast::jsx::JSXMemberExprObject::JSXMemberExpression(inner) => {
1578            outlined_assign_jsx_member_positions(inner, pos, fn_bindings, ref_to_binding);
1579        }
1580    }
1581}
1582
1583fn outlined_assign_jsx_val_positions(
1584    val: &mut react_compiler_ast::jsx::JSXAttributeValue,
1585    pos: &mut u32,
1586    fn_bindings: &std::collections::HashMap<String, react_compiler_ast::scope::BindingId>,
1587    ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>,
1588) {
1589    match val {
1590        react_compiler_ast::jsx::JSXAttributeValue::JSXExpressionContainer(c) => {
1591            if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(e) =
1592                &mut c.expression
1593            {
1594                outlined_assign_expr_positions(e, pos, fn_bindings, ref_to_binding);
1595            }
1596        }
1597        react_compiler_ast::jsx::JSXAttributeValue::JSXElement(el) => {
1598            let mut expr = react_compiler_ast::expressions::Expression::JSXElement(el.clone());
1599            outlined_assign_expr_positions(&mut expr, pos, fn_bindings, ref_to_binding);
1600            if let react_compiler_ast::expressions::Expression::JSXElement(new_el) = expr {
1601                **el = *new_el;
1602            }
1603        }
1604        _ => {}
1605    }
1606}
1607
1608fn outlined_assign_jsx_child_positions(
1609    child: &mut react_compiler_ast::jsx::JSXChild,
1610    pos: &mut u32,
1611    fn_bindings: &std::collections::HashMap<String, react_compiler_ast::scope::BindingId>,
1612    ref_to_binding: &mut indexmap::IndexMap<u32, react_compiler_ast::scope::BindingId>,
1613) {
1614    match child {
1615        react_compiler_ast::jsx::JSXChild::JSXExpressionContainer(c) => {
1616            if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(e) =
1617                &mut c.expression
1618            {
1619                outlined_assign_expr_positions(e, pos, fn_bindings, ref_to_binding);
1620            }
1621        }
1622        react_compiler_ast::jsx::JSXChild::JSXElement(el) => {
1623            let mut expr =
1624                react_compiler_ast::expressions::Expression::JSXElement(Box::new(*el.clone()));
1625            outlined_assign_expr_positions(&mut expr, pos, fn_bindings, ref_to_binding);
1626            if let react_compiler_ast::expressions::Expression::JSXElement(new_el) = expr {
1627                **el = *new_el;
1628            }
1629        }
1630        react_compiler_ast::jsx::JSXChild::JSXFragment(frag) => {
1631            for inner in &mut frag.children {
1632                outlined_assign_jsx_child_positions(inner, pos, fn_bindings, ref_to_binding);
1633            }
1634        }
1635        _ => {}
1636    }
1637}
1638// end of outlined function helpers
1639
1640/// Run the compilation pipeline passes on an HIR function (everything after lowering).
1641///
1642/// This is extracted from `compile_fn` to allow reuse for outlined functions.
1643/// Returns the compiled CodegenFunction on success.
1644fn run_pipeline_passes(
1645    hir: &mut react_compiler_hir::HirFunction,
1646    env: &mut Environment,
1647    context: &mut ProgramContext,
1648) -> Result<CodegenFunction, CompilerError> {
1649    react_compiler_optimization::prune_maybe_throws(hir, &mut env.functions)?;
1650
1651    react_compiler_optimization::drop_manual_memoization(hir, env)?;
1652
1653    react_compiler_optimization::inline_immediately_invoked_function_expressions(hir, env);
1654
1655    react_compiler_optimization::merge_consecutive_blocks::merge_consecutive_blocks(
1656        hir,
1657        &mut env.functions,
1658    );
1659
1660    react_compiler_ssa::enter_ssa(hir, env).map_err(|diag| {
1661        let loc = diag.primary_location().cloned();
1662        let mut err = CompilerError::new();
1663        err.push_error_detail(react_compiler_diagnostics::CompilerErrorDetail {
1664            category: diag.category,
1665            reason: diag.reason,
1666            description: diag.description,
1667            loc,
1668            suggestions: diag.suggestions,
1669        });
1670        err
1671    })?;
1672
1673    react_compiler_ssa::eliminate_redundant_phi(hir, env);
1674
1675    react_compiler_optimization::constant_propagation(hir, env);
1676
1677    react_compiler_typeinference::infer_types(hir, env)?;
1678
1679    if env.enable_validations() {
1680        if env.config.validate_hooks_usage {
1681            react_compiler_validation::validate_hooks_usage(hir, env)?;
1682        }
1683    }
1684
1685    react_compiler_optimization::optimize_props_method_calls(hir, env);
1686
1687    react_compiler_inference::analyse_functions(hir, env, &mut |_inner_func, _inner_env| {})?;
1688
1689    if env.has_invariant_errors() {
1690        return Err(env.take_invariant_errors());
1691    }
1692
1693    react_compiler_inference::infer_mutation_aliasing_effects(hir, env, false)?;
1694
1695    if env.output_mode == OutputMode::Ssr {
1696        react_compiler_optimization::optimize_for_ssr(hir, env);
1697    }
1698
1699    react_compiler_optimization::dead_code_elimination(hir, env);
1700
1701    react_compiler_optimization::prune_maybe_throws(hir, &mut env.functions)?;
1702
1703    react_compiler_inference::infer_mutation_aliasing_ranges(hir, env, false)?;
1704
1705    if env.enable_validations() {
1706        react_compiler_validation::validate_locals_not_reassigned_after_render(hir, env);
1707
1708        if env.config.validate_ref_access_during_render {
1709            react_compiler_validation::validate_no_ref_access_in_render(hir, env);
1710        }
1711
1712        if env.config.validate_no_set_state_in_render {
1713            react_compiler_validation::validate_no_set_state_in_render(hir, env)?;
1714        }
1715
1716        react_compiler_validation::validate_no_freezing_known_mutable_functions(hir, env);
1717    }
1718
1719    react_compiler_inference::infer_reactive_places(hir, env)?;
1720
1721    if env.enable_validations() {
1722        react_compiler_validation::validate_exhaustive_dependencies(hir, env)?;
1723    }
1724
1725    react_compiler_ssa::rewrite_instruction_kinds_based_on_reassignment(hir, env)?;
1726
1727    if env.enable_memoization() {
1728        react_compiler_inference::infer_reactive_scope_variables(hir, env)?;
1729    }
1730
1731    let fbt_operands =
1732        react_compiler_inference::memoize_fbt_and_macro_operands_in_same_scope(hir, env);
1733
1734    // Don't run outline_jsx on outlined functions (they're already outlined)
1735
1736    if env.config.enable_name_anonymous_functions {
1737        react_compiler_optimization::name_anonymous_functions(hir, env);
1738    }
1739
1740    if env.config.enable_function_outlining {
1741        react_compiler_optimization::outline_functions(hir, env, &fbt_operands);
1742    }
1743
1744    react_compiler_inference::align_method_call_scopes(hir, env);
1745    react_compiler_inference::align_object_method_scopes(hir, env);
1746
1747    react_compiler_optimization::prune_unused_labels_hir(hir);
1748
1749    react_compiler_inference::align_reactive_scopes_to_block_scopes_hir(hir, env);
1750    react_compiler_inference::merge_overlapping_reactive_scopes_hir(hir, env);
1751
1752    react_compiler_inference::build_reactive_scope_terminals_hir(hir, env);
1753    react_compiler_inference::flatten_reactive_loops_hir(hir);
1754    react_compiler_inference::flatten_scopes_with_hooks_or_use_hir(hir, env)?;
1755    react_compiler_inference::propagate_scope_dependencies_hir(hir, env);
1756    let mut reactive_fn = react_compiler_reactive_scopes::build_reactive_function(hir, env)?;
1757
1758    react_compiler_reactive_scopes::assert_well_formed_break_targets(&reactive_fn, env);
1759
1760    react_compiler_reactive_scopes::prune_unused_labels(&mut reactive_fn, env)?;
1761
1762    react_compiler_reactive_scopes::assert_scope_instructions_within_scopes(&reactive_fn, env)?;
1763
1764    react_compiler_reactive_scopes::prune_non_escaping_scopes(&mut reactive_fn, env)?;
1765    react_compiler_reactive_scopes::prune_non_reactive_dependencies(&mut reactive_fn, env);
1766    react_compiler_reactive_scopes::prune_unused_scopes(&mut reactive_fn, env)?;
1767    react_compiler_reactive_scopes::merge_reactive_scopes_that_invalidate_together(
1768        &mut reactive_fn,
1769        env,
1770    )?;
1771    react_compiler_reactive_scopes::prune_always_invalidating_scopes(&mut reactive_fn, env)?;
1772    react_compiler_reactive_scopes::propagate_early_returns(&mut reactive_fn, env);
1773    react_compiler_reactive_scopes::prune_unused_lvalues(&mut reactive_fn, env);
1774    react_compiler_reactive_scopes::promote_used_temporaries(&mut reactive_fn, env);
1775    react_compiler_reactive_scopes::extract_scope_declarations_from_destructuring(
1776        &mut reactive_fn,
1777        env,
1778    )?;
1779    react_compiler_reactive_scopes::stabilize_block_ids(&mut reactive_fn, env);
1780
1781    let unique_identifiers =
1782        react_compiler_reactive_scopes::rename_variables(&mut reactive_fn, env);
1783    for name in &unique_identifiers {
1784        context.add_new_reference(name.clone());
1785    }
1786
1787    react_compiler_reactive_scopes::prune_hoisted_contexts(&mut reactive_fn, env)?;
1788
1789    if env.config.enable_preserve_existing_memoization_guarantees
1790        || env.config.validate_preserve_existing_memoization_guarantees
1791    {
1792        react_compiler_validation::validate_preserved_manual_memoization(&reactive_fn, env);
1793    }
1794
1795    let codegen_result = react_compiler_reactive_scopes::codegen_function(
1796        &reactive_fn,
1797        env,
1798        unique_identifiers,
1799        fbt_operands,
1800    )?;
1801
1802    Ok(CodegenFunction {
1803        loc: codegen_result.loc,
1804        id: codegen_result.id,
1805        name_hint: codegen_result.name_hint,
1806        params: codegen_result.params,
1807        body: codegen_result.body,
1808        generator: codegen_result.generator,
1809        is_async: codegen_result.is_async,
1810        memo_slots_used: codegen_result.memo_slots_used,
1811        memo_blocks: codegen_result.memo_blocks,
1812        memo_values: codegen_result.memo_values,
1813        pruned_memo_blocks: codegen_result.pruned_memo_blocks,
1814        pruned_memo_values: codegen_result.pruned_memo_values,
1815        outlined: codegen_result
1816            .outlined
1817            .into_iter()
1818            .map(|o| OutlinedFunction {
1819                func: CodegenFunction {
1820                    loc: o.func.loc,
1821                    id: o.func.id,
1822                    name_hint: o.func.name_hint,
1823                    params: o.func.params,
1824                    body: o.func.body,
1825                    generator: o.func.generator,
1826                    is_async: o.func.is_async,
1827                    memo_slots_used: o.func.memo_slots_used,
1828                    memo_blocks: o.func.memo_blocks,
1829                    memo_values: o.func.memo_values,
1830                    pruned_memo_blocks: o.func.pruned_memo_blocks,
1831                    pruned_memo_values: o.func.pruned_memo_values,
1832                    outlined: Vec::new(),
1833                },
1834                fn_type: o.fn_type,
1835            })
1836            .collect(),
1837    })
1838}
1839
1840/// Log CompilerError diagnostics as CompileError events, matching TS `env.logErrors()` behavior.
1841/// These are logged for telemetry/lint output but not accumulated as compile errors.
1842fn log_errors_as_events(errors: &CompilerError, context: &mut ProgramContext) {
1843    // Use the source_filename from the AST (set by parser's sourceFilename option).
1844    // This is stored on the Environment during lowering.
1845    let source_filename = context.source_filename();
1846    for detail in &errors.details {
1847        let detail_info = match detail {
1848            react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => {
1849                let items: Option<Vec<CompilerErrorItemInfo>> = {
1850                    let v: Vec<CompilerErrorItemInfo> = d
1851                        .details
1852                        .iter()
1853                        .map(|item| match item {
1854                            react_compiler_diagnostics::CompilerDiagnosticDetail::Error {
1855                                loc,
1856                                message,
1857                                identifier_name,
1858                            } => CompilerErrorItemInfo {
1859                                kind: "error".to_string(),
1860                                loc: loc.as_ref().map(|l| LoggerSourceLocation {
1861                                    start: LoggerPosition {
1862                                        line: l.start.line,
1863                                        column: l.start.column,
1864                                        index: l.start.index,
1865                                    },
1866                                    end: LoggerPosition {
1867                                        line: l.end.line,
1868                                        column: l.end.column,
1869                                        index: l.end.index,
1870                                    },
1871                                    filename: source_filename.clone(),
1872                                    identifier_name: identifier_name.clone(),
1873                                }),
1874                                message: message.clone(),
1875                            },
1876                            react_compiler_diagnostics::CompilerDiagnosticDetail::Hint {
1877                                message,
1878                            } => CompilerErrorItemInfo {
1879                                kind: "hint".to_string(),
1880                                loc: None,
1881                                message: Some(message.clone()),
1882                            },
1883                        })
1884                        .collect();
1885                    if v.is_empty() { None } else { Some(v) }
1886                };
1887                CompilerErrorDetailInfo {
1888                    category: format!("{:?}", d.category),
1889                    reason: d.reason.clone(),
1890                    description: d.description.clone(),
1891                    severity: format!("{:?}", d.logged_severity()),
1892                    suggestions: None,
1893                    details: items,
1894                    loc: None,
1895                }
1896            }
1897            react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => {
1898                CompilerErrorDetailInfo {
1899                    category: format!("{:?}", d.category),
1900                    reason: d.reason.clone(),
1901                    description: d.description.clone(),
1902                    severity: format!("{:?}", d.logged_severity()),
1903                    suggestions: None,
1904                    details: None,
1905                    loc: None,
1906                }
1907            }
1908        };
1909        context.log_event(super::compile_result::LoggerEvent::CompileError {
1910            fn_loc: None,
1911            detail: detail_info,
1912        });
1913    }
1914}