main
rs 1,914 lines 70.8 KB
Raw
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
11 use indexmap::IndexMap;
12 use react_compiler_ast::scope::ScopeInfo;
13 use react_compiler_diagnostics::CompilerError;
14 use react_compiler_hir::ReactFunctionType;
15 use react_compiler_hir::environment::Environment;
16 use react_compiler_hir::environment::OutputMode;
17 use react_compiler_hir::environment_config::EnvironmentConfig;
18 use react_compiler_lowering::FunctionNode;
19 use rustc_hash::{FxBuildHasher, FxHashMap};
20
21 use super::compile_result::CodegenFunction;
22 use super::compile_result::CompilerErrorDetailInfo;
23 use super::compile_result::CompilerErrorItemInfo;
24 use super::compile_result::DebugLogEntry;
25 use super::compile_result::LoggerPosition;
26 use super::compile_result::LoggerSourceLocation;
27 use super::compile_result::OutlinedFunction;
28 use super::imports::ProgramContext;
29 use super::plugin_options::CompilerOutputMode;
30 use crate::debug_print;
31
32 /// Run the compilation pipeline on a single function.
33 ///
34 /// Currently: creates an Environment, runs BuildHIR (lowering), and produces
35 /// debug output via the context. Returns a CodegenFunction with zeroed memo
36 /// stats on success (codegen is not yet implemented).
37 pub fn compile_fn(
38 func: &FunctionNode<'_>,
39 fn_name: Option<&str>,
40 scope_info: &ScopeInfo,
41 fn_type: ReactFunctionType,
42 mode: CompilerOutputMode,
43 env_config: &EnvironmentConfig,
44 context: &mut ProgramContext,
45 ) -> Result<CodegenFunction, CompilerError> {
46 let mut env = Environment::with_config(env_config.clone());
47 env.fn_type = fn_type;
48 env.output_mode = match mode {
49 CompilerOutputMode::Ssr => OutputMode::Ssr,
50 CompilerOutputMode::Client => OutputMode::Client,
51 CompilerOutputMode::Lint => OutputMode::Lint,
52 };
53 env.code = context.code.clone();
54 env.filename = context.filename.clone();
55 env.instrument_fn_name = context.instrument_fn_name.clone();
56 env.instrument_gating_name = context.instrument_gating_name.clone();
57 env.hook_guard_name = context.hook_guard_name.clone();
58 env.seed_uid_known_names(&context.known_referenced_names());
59
60 env.reference_node_ids = scope_info.ref_node_id_to_binding.keys().copied().collect();
61
62 context.timing.start("lower");
63 let mut hir = react_compiler_lowering::lower(func, fn_name, scope_info, &mut env)?;
64 context.timing.stop();
65
66 // Copy renames from lowering to context (keep on env for codegen to apply to type annotations)
67 if !env.renames.is_empty() {
68 context.renames.extend(env.renames.iter().cloned());
69 }
70
71 // Check for Invariant errors after lowering, before logging HIR.
72 // In TS, Invariant errors throw from recordError(), aborting lower() before
73 // the HIR entry is logged. The thrown error contains ONLY the Invariant error,
74 // not other recorded (non-Invariant) errors.
75 if env.has_invariant_errors() {
76 return Err(env.take_invariant_errors());
77 }
78
79 if context.debug_enabled {
80 context.timing.start("debug_print:HIR");
81 let debug_hir = debug_print::debug_hir(&hir, &env);
82 context.log_debug(DebugLogEntry::new("HIR", debug_hir));
83 context.timing.stop();
84 }
85
86 context.timing.start("PruneMaybeThrows");
87 react_compiler_optimization::prune_maybe_throws(&mut hir, &mut env.functions)?;
88 context.timing.stop();
89
90 if context.debug_enabled {
91 context.timing.start("debug_print:PruneMaybeThrows");
92 let debug_prune = debug_print::debug_hir(&hir, &env);
93 context.log_debug(DebugLogEntry::new("PruneMaybeThrows", debug_prune));
94 context.timing.stop();
95 }
96
97 context.timing.start("ValidateContextVariableLValues");
98 react_compiler_validation::validate_context_variable_lvalues(&hir, &mut env)?;
99 if context.debug_enabled {
100 context.log_debug(DebugLogEntry::new(
101 "ValidateContextVariableLValues",
102 "ok".to_string(),
103 ));
104 }
105 context.timing.stop();
106
107 context.timing.start("ValidateUseMemo");
108 let void_memo_errors = react_compiler_validation::validate_use_memo(&hir, &mut env);
109 log_errors_as_events(&void_memo_errors, context);
110 if context.debug_enabled {
111 context.log_debug(DebugLogEntry::new("ValidateUseMemo", "ok".to_string()));
112 }
113 context.timing.stop();
114
115 context.timing.start("DropManualMemoization");
116 react_compiler_optimization::drop_manual_memoization(&mut hir, &mut env)?;
117 context.timing.stop();
118
119 if context.debug_enabled {
120 context.timing.start("debug_print:DropManualMemoization");
121 let debug_drop_memo = debug_print::debug_hir(&hir, &env);
122 context.log_debug(DebugLogEntry::new("DropManualMemoization", debug_drop_memo));
123 context.timing.stop();
124 }
125
126 context
127 .timing
128 .start("InlineImmediatelyInvokedFunctionExpressions");
129 react_compiler_optimization::inline_immediately_invoked_function_expressions(
130 &mut hir, &mut env,
131 );
132 context.timing.stop();
133
134 if context.debug_enabled {
135 context
136 .timing
137 .start("debug_print:InlineImmediatelyInvokedFunctionExpressions");
138 let debug_inline_iifes = debug_print::debug_hir(&hir, &env);
139 context.log_debug(DebugLogEntry::new(
140 "InlineImmediatelyInvokedFunctionExpressions",
141 debug_inline_iifes,
142 ));
143 context.timing.stop();
144 }
145
146 context.timing.start("MergeConsecutiveBlocks");
147 react_compiler_optimization::merge_consecutive_blocks::merge_consecutive_blocks(
148 &mut hir,
149 &mut env.functions,
150 );
151 context.timing.stop();
152
153 if context.debug_enabled {
154 context.timing.start("debug_print:MergeConsecutiveBlocks");
155 let debug_merge = debug_print::debug_hir(&hir, &env);
156 context.log_debug(DebugLogEntry::new("MergeConsecutiveBlocks", debug_merge));
157 context.timing.stop();
158 }
159
160 // TODO: port assertConsistentIdentifiers
161 if context.debug_enabled {
162 context.log_debug(DebugLogEntry::new(
163 "AssertConsistentIdentifiers",
164 "ok".to_string(),
165 ));
166 }
167 // TODO: port assertTerminalSuccessorsExist
168 if context.debug_enabled {
169 context.log_debug(DebugLogEntry::new(
170 "AssertTerminalSuccessorsExist",
171 "ok".to_string(),
172 ));
173 }
174
175 context.timing.start("EnterSSA");
176 react_compiler_ssa::enter_ssa(&mut hir, &mut env).map_err(|diag| {
177 let loc = diag.primary_location().cloned();
178 let mut err = CompilerError::new();
179 err.push_error_detail(react_compiler_diagnostics::CompilerErrorDetail {
180 category: diag.category,
181 reason: diag.reason,
182 description: diag.description,
183 loc,
184 suggestions: diag.suggestions,
185 });
186 err
187 })?;
188 context.timing.stop();
189
190 if context.debug_enabled {
191 context.timing.start("debug_print:SSA");
192 let debug_ssa = debug_print::debug_hir(&hir, &env);
193 context.log_debug(DebugLogEntry::new("SSA", debug_ssa));
194 context.timing.stop();
195 }
196
197 context.timing.start("EliminateRedundantPhi");
198 react_compiler_ssa::eliminate_redundant_phi(&mut hir, &mut env);
199 context.timing.stop();
200
201 if context.debug_enabled {
202 context.timing.start("debug_print:EliminateRedundantPhi");
203 let debug_eliminate_phi = debug_print::debug_hir(&hir, &env);
204 context.log_debug(DebugLogEntry::new(
205 "EliminateRedundantPhi",
206 debug_eliminate_phi,
207 ));
208 context.timing.stop();
209 }
210
211 // TODO: port assertConsistentIdentifiers
212 if context.debug_enabled {
213 context.log_debug(DebugLogEntry::new(
214 "AssertConsistentIdentifiers",
215 "ok".to_string(),
216 ));
217 }
218
219 context.timing.start("ConstantPropagation");
220 react_compiler_optimization::constant_propagation(&mut hir, &mut env);
221 context.timing.stop();
222
223 if context.debug_enabled {
224 context.timing.start("debug_print:ConstantPropagation");
225 let debug_const_prop = debug_print::debug_hir(&hir, &env);
226 context.log_debug(DebugLogEntry::new("ConstantPropagation", debug_const_prop));
227 context.timing.stop();
228 }
229
230 context.timing.start("InferTypes");
231 react_compiler_typeinference::infer_types(&mut hir, &mut env)?;
232 context.timing.stop();
233
234 if context.debug_enabled {
235 context.timing.start("debug_print:InferTypes");
236 let debug_infer_types = debug_print::debug_hir(&hir, &env);
237 context.log_debug(DebugLogEntry::new("InferTypes", debug_infer_types));
238 context.timing.stop();
239 }
240
241 if env.enable_validations() {
242 if env.config.validate_hooks_usage {
243 context.timing.start("ValidateHooksUsage");
244 react_compiler_validation::validate_hooks_usage(&hir, &mut env)?;
245 if context.debug_enabled {
246 context.log_debug(DebugLogEntry::new("ValidateHooksUsage", "ok".to_string()));
247 }
248 context.timing.stop();
249 }
250
251 if env.config.validate_no_capitalized_calls.is_some() {
252 context.timing.start("ValidateNoCapitalizedCalls");
253 react_compiler_validation::validate_no_capitalized_calls(&hir, &mut env)?;
254 if context.debug_enabled {
255 context.log_debug(DebugLogEntry::new(
256 "ValidateNoCapitalizedCalls",
257 "ok".to_string(),
258 ));
259 }
260 context.timing.stop();
261 }
262 }
263
264 context.timing.start("OptimizePropsMethodCalls");
265 react_compiler_optimization::optimize_props_method_calls(&mut hir, &env);
266 context.timing.stop();
267
268 if context.debug_enabled {
269 context.timing.start("debug_print:OptimizePropsMethodCalls");
270 let debug_optimize_props = debug_print::debug_hir(&hir, &env);
271 context.log_debug(DebugLogEntry::new(
272 "OptimizePropsMethodCalls",
273 debug_optimize_props,
274 ));
275 context.timing.stop();
276 }
277
278 context.timing.start("AnalyseFunctions");
279 let mut inner_logs: Vec<String> = Vec::new();
280 let debug_inner = context.debug_enabled;
281 let analyse_result = react_compiler_inference::analyse_functions(
282 &mut hir,
283 &mut env,
284 &mut |inner_func, inner_env| {
285 if debug_inner {
286 inner_logs.push(debug_print::debug_hir(inner_func, inner_env));
287 }
288 },
289 );
290 context.timing.stop();
291
292 // Always flush inner logs before propagating errors
293 if context.debug_enabled {
294 for inner_log in inner_logs {
295 context.log_debug(DebugLogEntry::new("AnalyseFunction (inner)", inner_log));
296 }
297 }
298
299 analyse_result?;
300
301 if env.has_invariant_errors() {
302 return Err(env.take_invariant_errors());
303 }
304
305 if context.debug_enabled {
306 context.timing.start("debug_print:AnalyseFunctions");
307 let debug_analyse_functions = debug_print::debug_hir(&hir, &env);
308 context.log_debug(DebugLogEntry::new(
309 "AnalyseFunctions",
310 debug_analyse_functions,
311 ));
312 context.timing.stop();
313 }
314
315 context.timing.start("InferMutationAliasingEffects");
316 react_compiler_inference::infer_mutation_aliasing_effects(&mut hir, &mut env, false)?;
317 context.timing.stop();
318
319 if context.debug_enabled {
320 context
321 .timing
322 .start("debug_print:InferMutationAliasingEffects");
323 let debug_infer_effects = debug_print::debug_hir(&hir, &env);
324 context.log_debug(DebugLogEntry::new(
325 "InferMutationAliasingEffects",
326 debug_infer_effects,
327 ));
328 context.timing.stop();
329 }
330
331 if env.output_mode == OutputMode::Ssr {
332 context.timing.start("OptimizeForSSR");
333 react_compiler_optimization::optimize_for_ssr(&mut hir, &env);
334 context.timing.stop();
335
336 if context.debug_enabled {
337 context.timing.start("debug_print:OptimizeForSSR");
338 let debug_ssr = debug_print::debug_hir(&hir, &env);
339 context.log_debug(DebugLogEntry::new("OptimizeForSSR", debug_ssr));
340 context.timing.stop();
341 }
342 }
343
344 context.timing.start("DeadCodeElimination");
345 react_compiler_optimization::dead_code_elimination(&mut hir, &env);
346 context.timing.stop();
347
348 if context.debug_enabled {
349 context.timing.start("debug_print:DeadCodeElimination");
350 let debug_dce = debug_print::debug_hir(&hir, &env);
351 context.log_debug(DebugLogEntry::new("DeadCodeElimination", debug_dce));
352 context.timing.stop();
353 }
354
355 context.timing.start("PruneMaybeThrows2");
356 react_compiler_optimization::prune_maybe_throws(&mut hir, &mut env.functions)?;
357 context.timing.stop();
358
359 if context.debug_enabled {
360 context.timing.start("debug_print:PruneMaybeThrows2");
361 let debug_prune2 = debug_print::debug_hir(&hir, &env);
362 context.log_debug(DebugLogEntry::new("PruneMaybeThrows", debug_prune2));
363 context.timing.stop();
364 }
365
366 context.timing.start("InferMutationAliasingRanges");
367 react_compiler_inference::infer_mutation_aliasing_ranges(&mut hir, &mut env, false)?;
368 context.timing.stop();
369
370 if context.debug_enabled {
371 context
372 .timing
373 .start("debug_print:InferMutationAliasingRanges");
374 let debug_infer_ranges = debug_print::debug_hir(&hir, &env);
375 context.log_debug(DebugLogEntry::new(
376 "InferMutationAliasingRanges",
377 debug_infer_ranges,
378 ));
379 context.timing.stop();
380 }
381
382 if env.enable_validations() {
383 context
384 .timing
385 .start("ValidateLocalsNotReassignedAfterRender");
386 react_compiler_validation::validate_locals_not_reassigned_after_render(&hir, &mut env);
387 if context.debug_enabled {
388 context.log_debug(DebugLogEntry::new(
389 "ValidateLocalsNotReassignedAfterRender",
390 "ok".to_string(),
391 ));
392 }
393 context.timing.stop();
394
395 if env.config.validate_ref_access_during_render {
396 context.timing.start("ValidateNoRefAccessInRender");
397 react_compiler_validation::validate_no_ref_access_in_render(&hir, &mut env);
398 if context.debug_enabled {
399 context.log_debug(DebugLogEntry::new(
400 "ValidateNoRefAccessInRender",
401 "ok".to_string(),
402 ));
403 }
404 context.timing.stop();
405 }
406
407 if env.config.validate_no_set_state_in_render {
408 context.timing.start("ValidateNoSetStateInRender");
409 react_compiler_validation::validate_no_set_state_in_render(&hir, &mut env)?;
410 if context.debug_enabled {
411 context.log_debug(DebugLogEntry::new(
412 "ValidateNoSetStateInRender",
413 "ok".to_string(),
414 ));
415 }
416 context.timing.stop();
417 }
418
419 if env.config.validate_no_derived_computations_in_effects_exp
420 && env.output_mode == OutputMode::Lint
421 {
422 context
423 .timing
424 .start("ValidateNoDerivedComputationsInEffects");
425 let errors =
426 react_compiler_validation::validate_no_derived_computations_in_effects_exp(
427 &hir, &env,
428 )?;
429 log_errors_as_events(&errors, context);
430 if context.debug_enabled {
431 context.log_debug(DebugLogEntry::new(
432 "ValidateNoDerivedComputationsInEffects",
433 "ok".to_string(),
434 ));
435 }
436 context.timing.stop();
437 } else if env.config.validate_no_derived_computations_in_effects {
438 context
439 .timing
440 .start("ValidateNoDerivedComputationsInEffects");
441 react_compiler_validation::validate_no_derived_computations_in_effects(&hir, &mut env)?;
442 if context.debug_enabled {
443 context.log_debug(DebugLogEntry::new(
444 "ValidateNoDerivedComputationsInEffects",
445 "ok".to_string(),
446 ));
447 }
448 context.timing.stop();
449 }
450
451 if env.config.validate_no_set_state_in_effects && env.output_mode == OutputMode::Lint {
452 context.timing.start("ValidateNoSetStateInEffects");
453 let errors = react_compiler_validation::validate_no_set_state_in_effects(&hir, &env)?;
454 log_errors_as_events(&errors, context);
455 if context.debug_enabled {
456 context.log_debug(DebugLogEntry::new(
457 "ValidateNoSetStateInEffects",
458 "ok".to_string(),
459 ));
460 }
461 context.timing.stop();
462 }
463
464 if env.config.validate_no_jsx_in_try_statements && env.output_mode == OutputMode::Lint {
465 context.timing.start("ValidateNoJSXInTryStatement");
466 let errors = react_compiler_validation::validate_no_jsx_in_try_statement(&hir);
467 log_errors_as_events(&errors, context);
468 if context.debug_enabled {
469 context.log_debug(DebugLogEntry::new(
470 "ValidateNoJSXInTryStatement",
471 "ok".to_string(),
472 ));
473 }
474 context.timing.stop();
475 }
476
477 context
478 .timing
479 .start("ValidateNoFreezingKnownMutableFunctions");
480 react_compiler_validation::validate_no_freezing_known_mutable_functions(&hir, &mut env);
481 if context.debug_enabled {
482 context.log_debug(DebugLogEntry::new(
483 "ValidateNoFreezingKnownMutableFunctions",
484 "ok".to_string(),
485 ));
486 }
487 context.timing.stop();
488 }
489
490 context.timing.start("InferReactivePlaces");
491 react_compiler_inference::infer_reactive_places(&mut hir, &mut env)?;
492 context.timing.stop();
493
494 if context.debug_enabled {
495 context.timing.start("debug_print:InferReactivePlaces");
496 let debug_reactive_places = debug_print::debug_hir(&hir, &env);
497 context.log_debug(DebugLogEntry::new(
498 "InferReactivePlaces",
499 debug_reactive_places,
500 ));
501 context.timing.stop();
502 }
503
504 if env.enable_validations() {
505 context.timing.start("ValidateExhaustiveDependencies");
506 react_compiler_validation::validate_exhaustive_dependencies(&mut hir, &mut env)?;
507 if context.debug_enabled {
508 context.log_debug(DebugLogEntry::new(
509 "ValidateExhaustiveDependencies",
510 "ok".to_string(),
511 ));
512 }
513 context.timing.stop();
514 }
515
516 context
517 .timing
518 .start("RewriteInstructionKindsBasedOnReassignment");
519 react_compiler_ssa::rewrite_instruction_kinds_based_on_reassignment(&mut hir, &env)?;
520 context.timing.stop();
521
522 if context.debug_enabled {
523 context
524 .timing
525 .start("debug_print:RewriteInstructionKindsBasedOnReassignment");
526 let debug_rewrite = debug_print::debug_hir(&hir, &env);
527 context.log_debug(DebugLogEntry::new(
528 "RewriteInstructionKindsBasedOnReassignment",
529 debug_rewrite,
530 ));
531 context.timing.stop();
532 }
533
534 if env.enable_validations()
535 && env.config.validate_static_components
536 && env.output_mode == OutputMode::Lint
537 {
538 context.timing.start("ValidateStaticComponents");
539 let errors = react_compiler_validation::validate_static_components(&hir);
540 log_errors_as_events(&errors, context);
541 if context.debug_enabled {
542 context.log_debug(DebugLogEntry::new(
543 "ValidateStaticComponents",
544 "ok".to_string(),
545 ));
546 }
547 context.timing.stop();
548 }
549
550 if env.enable_memoization() {
551 context.timing.start("InferReactiveScopeVariables");
552 react_compiler_inference::infer_reactive_scope_variables(&mut hir, &mut env)?;
553 context.timing.stop();
554
555 if context.debug_enabled {
556 context
557 .timing
558 .start("debug_print:InferReactiveScopeVariables");
559 let debug_infer_scopes = debug_print::debug_hir(&hir, &env);
560 context.log_debug(DebugLogEntry::new(
561 "InferReactiveScopeVariables",
562 debug_infer_scopes,
563 ));
564 context.timing.stop();
565 }
566 }
567
568 context
569 .timing
570 .start("MemoizeFbtAndMacroOperandsInSameScope");
571 let fbt_operands =
572 react_compiler_inference::memoize_fbt_and_macro_operands_in_same_scope(&hir, &mut env);
573 context.timing.stop();
574
575 if context.debug_enabled {
576 context
577 .timing
578 .start("debug_print:MemoizeFbtAndMacroOperandsInSameScope");
579 let debug_fbt = debug_print::debug_hir(&hir, &env);
580 context.log_debug(DebugLogEntry::new(
581 "MemoizeFbtAndMacroOperandsInSameScope",
582 debug_fbt,
583 ));
584 context.timing.stop();
585 }
586
587 if env.config.enable_jsx_outlining {
588 context.timing.start("OutlineJsx");
589 react_compiler_optimization::outline_jsx(&mut hir, &mut env);
590 context.timing.stop();
591 }
592
593 if env.config.enable_name_anonymous_functions {
594 context.timing.start("NameAnonymousFunctions");
595 react_compiler_optimization::name_anonymous_functions(&mut hir, &mut env);
596 context.timing.stop();
597
598 if context.debug_enabled {
599 context.timing.start("debug_print:NameAnonymousFunctions");
600 let debug_name_anon = debug_print::debug_hir(&hir, &env);
601 context.log_debug(DebugLogEntry::new(
602 "NameAnonymousFunctions",
603 debug_name_anon,
604 ));
605 context.timing.stop();
606 }
607 }
608
609 if env.config.enable_function_outlining {
610 context.timing.start("OutlineFunctions");
611 react_compiler_optimization::outline_functions(&mut hir, &mut env, &fbt_operands);
612 context.timing.stop();
613
614 if context.debug_enabled {
615 context.timing.start("debug_print:OutlineFunctions");
616 let debug_outline = debug_print::debug_hir(&hir, &env);
617 context.log_debug(DebugLogEntry::new("OutlineFunctions", debug_outline));
618 context.timing.stop();
619 }
620 }
621
622 context.timing.start("AlignMethodCallScopes");
623 react_compiler_inference::align_method_call_scopes(&mut hir, &mut env);
624 context.timing.stop();
625
626 if context.debug_enabled {
627 context.timing.start("debug_print:AlignMethodCallScopes");
628 let debug_align = debug_print::debug_hir(&hir, &env);
629 context.log_debug(DebugLogEntry::new("AlignMethodCallScopes", debug_align));
630 context.timing.stop();
631 }
632
633 context.timing.start("AlignObjectMethodScopes");
634 react_compiler_inference::align_object_method_scopes(&mut hir, &mut env);
635 context.timing.stop();
636
637 if context.debug_enabled {
638 context.timing.start("debug_print:AlignObjectMethodScopes");
639 let debug_align_obj = debug_print::debug_hir(&hir, &env);
640 context.log_debug(DebugLogEntry::new(
641 "AlignObjectMethodScopes",
642 debug_align_obj,
643 ));
644 context.timing.stop();
645 }
646
647 context.timing.start("PruneUnusedLabelsHIR");
648 react_compiler_optimization::prune_unused_labels_hir(&mut hir);
649 context.timing.stop();
650
651 if context.debug_enabled {
652 context.timing.start("debug_print:PruneUnusedLabelsHIR");
653 let debug_prune_labels = debug_print::debug_hir(&hir, &env);
654 context.log_debug(DebugLogEntry::new(
655 "PruneUnusedLabelsHIR",
656 debug_prune_labels,
657 ));
658 context.timing.stop();
659 }
660
661 context.timing.start("AlignReactiveScopesToBlockScopesHIR");
662 react_compiler_inference::align_reactive_scopes_to_block_scopes_hir(&mut hir, &mut env);
663 context.timing.stop();
664
665 if context.debug_enabled {
666 context
667 .timing
668 .start("debug_print:AlignReactiveScopesToBlockScopesHIR");
669 let debug_align_block_scopes = debug_print::debug_hir(&hir, &env);
670 context.log_debug(DebugLogEntry::new(
671 "AlignReactiveScopesToBlockScopesHIR",
672 debug_align_block_scopes,
673 ));
674 context.timing.stop();
675 }
676
677 context.timing.start("MergeOverlappingReactiveScopesHIR");
678 react_compiler_inference::merge_overlapping_reactive_scopes_hir(&mut hir, &mut env);
679 context.timing.stop();
680
681 if context.debug_enabled {
682 context
683 .timing
684 .start("debug_print:MergeOverlappingReactiveScopesHIR");
685 let debug_merge_overlapping = debug_print::debug_hir(&hir, &env);
686 context.log_debug(DebugLogEntry::new(
687 "MergeOverlappingReactiveScopesHIR",
688 debug_merge_overlapping,
689 ));
690 context.timing.stop();
691 }
692
693 // TODO: port assertValidBlockNesting
694 if context.debug_enabled {
695 context.log_debug(DebugLogEntry::new(
696 "AssertValidBlockNesting",
697 "ok".to_string(),
698 ));
699 }
700
701 context.timing.start("BuildReactiveScopeTerminalsHIR");
702 react_compiler_inference::build_reactive_scope_terminals_hir(&mut hir, &mut env);
703 context.timing.stop();
704
705 if context.debug_enabled {
706 context
707 .timing
708 .start("debug_print:BuildReactiveScopeTerminalsHIR");
709 let debug_build_scope_terminals = debug_print::debug_hir(&hir, &env);
710 context.log_debug(DebugLogEntry::new(
711 "BuildReactiveScopeTerminalsHIR",
712 debug_build_scope_terminals,
713 ));
714 context.timing.stop();
715 }
716
717 // TODO: port assertValidBlockNesting
718 if context.debug_enabled {
719 context.log_debug(DebugLogEntry::new(
720 "AssertValidBlockNesting",
721 "ok".to_string(),
722 ));
723 }
724
725 context.timing.start("FlattenReactiveLoopsHIR");
726 react_compiler_inference::flatten_reactive_loops_hir(&mut hir);
727 context.timing.stop();
728
729 if context.debug_enabled {
730 context.timing.start("debug_print:FlattenReactiveLoopsHIR");
731 let debug_flatten_loops = debug_print::debug_hir(&hir, &env);
732 context.log_debug(DebugLogEntry::new(
733 "FlattenReactiveLoopsHIR",
734 debug_flatten_loops,
735 ));
736 context.timing.stop();
737 }
738
739 context.timing.start("FlattenScopesWithHooksOrUseHIR");
740 react_compiler_inference::flatten_scopes_with_hooks_or_use_hir(&mut hir, &env)?;
741 context.timing.stop();
742
743 if context.debug_enabled {
744 context
745 .timing
746 .start("debug_print:FlattenScopesWithHooksOrUseHIR");
747 let debug_flatten_hooks = debug_print::debug_hir(&hir, &env);
748 context.log_debug(DebugLogEntry::new(
749 "FlattenScopesWithHooksOrUseHIR",
750 debug_flatten_hooks,
751 ));
752 context.timing.stop();
753 }
754
755 // TODO: port assertTerminalSuccessorsExist
756 if context.debug_enabled {
757 context.log_debug(DebugLogEntry::new(
758 "AssertTerminalSuccessorsExist",
759 "ok".to_string(),
760 ));
761 }
762 // TODO: port assertTerminalPredsExist
763 if context.debug_enabled {
764 context.log_debug(DebugLogEntry::new(
765 "AssertTerminalPredsExist",
766 "ok".to_string(),
767 ));
768 }
769
770 context.timing.start("PropagateScopeDependenciesHIR");
771 react_compiler_inference::propagate_scope_dependencies_hir(&mut hir, &mut env);
772 context.timing.stop();
773
774 if context.debug_enabled {
775 context
776 .timing
777 .start("debug_print:PropagateScopeDependenciesHIR");
778 let debug_propagate_deps = debug_print::debug_hir(&hir, &env);
779 context.log_debug(DebugLogEntry::new(
780 "PropagateScopeDependenciesHIR",
781 debug_propagate_deps,
782 ));
783 context.timing.stop();
784 }
785
786 context.timing.start("BuildReactiveFunction");
787 let mut reactive_fn = react_compiler_reactive_scopes::build_reactive_function(&hir, &env)?;
788 context.timing.stop();
789
790 let hir_formatter = |fmt: &mut react_compiler_hir::print::PrintFormatter,
791 func: &react_compiler_hir::HirFunction| {
792 debug_print::format_hir_function_into(fmt, func);
793 };
794
795 if context.debug_enabled {
796 context.timing.start("debug_print:BuildReactiveFunction");
797 let debug_reactive = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
798 &reactive_fn, &env, Some(&hir_formatter),
799 );
800 context.log_debug(DebugLogEntry::new("BuildReactiveFunction", debug_reactive));
801 context.timing.stop();
802 }
803
804 context.timing.start("AssertWellFormedBreakTargets");
805 react_compiler_reactive_scopes::assert_well_formed_break_targets(&reactive_fn, &env);
806 if context.debug_enabled {
807 context.log_debug(DebugLogEntry::new(
808 "AssertWellFormedBreakTargets",
809 "ok".to_string(),
810 ));
811 }
812 context.timing.stop();
813
814 context.timing.start("PruneUnusedLabels");
815 react_compiler_reactive_scopes::prune_unused_labels(&mut reactive_fn, &env)?;
816 context.timing.stop();
817
818 if context.debug_enabled {
819 context.timing.start("debug_print:PruneUnusedLabels");
820 let debug_prune_labels_reactive = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
821 &reactive_fn, &env, Some(&hir_formatter),
822 );
823 context.log_debug(DebugLogEntry::new(
824 "PruneUnusedLabels",
825 debug_prune_labels_reactive,
826 ));
827 context.timing.stop();
828 }
829
830 context.timing.start("AssertScopeInstructionsWithinScopes");
831 react_compiler_reactive_scopes::assert_scope_instructions_within_scopes(&reactive_fn, &env)?;
832 if context.debug_enabled {
833 context.log_debug(DebugLogEntry::new(
834 "AssertScopeInstructionsWithinScopes",
835 "ok".to_string(),
836 ));
837 }
838 context.timing.stop();
839
840 context.timing.start("PruneNonEscapingScopes");
841 react_compiler_reactive_scopes::prune_non_escaping_scopes(&mut reactive_fn, &mut env)?;
842 context.timing.stop();
843
844 if context.debug_enabled {
845 context.timing.start("debug_print:PruneNonEscapingScopes");
846 let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
847 &reactive_fn, &env, Some(&hir_formatter),
848 );
849 context.log_debug(DebugLogEntry::new("PruneNonEscapingScopes", debug));
850 context.timing.stop();
851 }
852
853 context.timing.start("PruneNonReactiveDependencies");
854 react_compiler_reactive_scopes::prune_non_reactive_dependencies(&mut reactive_fn, &mut env);
855 context.timing.stop();
856
857 if context.debug_enabled {
858 context
859 .timing
860 .start("debug_print:PruneNonReactiveDependencies");
861 let debug_prune_non_reactive = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
862 &reactive_fn, &env, Some(&hir_formatter),
863 );
864 context.log_debug(DebugLogEntry::new(
865 "PruneNonReactiveDependencies",
866 debug_prune_non_reactive,
867 ));
868 context.timing.stop();
869 }
870
871 context.timing.start("PruneUnusedScopes");
872 react_compiler_reactive_scopes::prune_unused_scopes(&mut reactive_fn, &env)?;
873 context.timing.stop();
874
875 if context.debug_enabled {
876 context.timing.start("debug_print:PruneUnusedScopes");
877 let debug_prune_unused_scopes = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
878 &reactive_fn, &env, Some(&hir_formatter),
879 );
880 context.log_debug(DebugLogEntry::new(
881 "PruneUnusedScopes",
882 debug_prune_unused_scopes,
883 ));
884 context.timing.stop();
885 }
886
887 context
888 .timing
889 .start("MergeReactiveScopesThatInvalidateTogether");
890 react_compiler_reactive_scopes::merge_reactive_scopes_that_invalidate_together(
891 &mut reactive_fn,
892 &mut env,
893 )?;
894 context.timing.stop();
895
896 if context.debug_enabled {
897 context
898 .timing
899 .start("debug_print:MergeReactiveScopesThatInvalidateTogether");
900 let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
901 &reactive_fn, &env, Some(&hir_formatter),
902 );
903 context.log_debug(DebugLogEntry::new(
904 "MergeReactiveScopesThatInvalidateTogether",
905 debug,
906 ));
907 context.timing.stop();
908 }
909
910 context.timing.start("PruneAlwaysInvalidatingScopes");
911 react_compiler_reactive_scopes::prune_always_invalidating_scopes(&mut reactive_fn, &env)?;
912 context.timing.stop();
913
914 if context.debug_enabled {
915 context
916 .timing
917 .start("debug_print:PruneAlwaysInvalidatingScopes");
918 let debug_prune_always_inv = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
919 &reactive_fn, &env, Some(&hir_formatter),
920 );
921 context.log_debug(DebugLogEntry::new(
922 "PruneAlwaysInvalidatingScopes",
923 debug_prune_always_inv,
924 ));
925 context.timing.stop();
926 }
927
928 context.timing.start("PropagateEarlyReturns");
929 react_compiler_reactive_scopes::propagate_early_returns(&mut reactive_fn, &mut env);
930 context.timing.stop();
931
932 if context.debug_enabled {
933 context.timing.start("debug_print:PropagateEarlyReturns");
934 let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
935 &reactive_fn, &env, Some(&hir_formatter),
936 );
937 context.log_debug(DebugLogEntry::new("PropagateEarlyReturns", debug));
938 context.timing.stop();
939 }
940
941 context.timing.start("PruneUnusedLValues");
942 react_compiler_reactive_scopes::prune_unused_lvalues(&mut reactive_fn, &env);
943 context.timing.stop();
944
945 if context.debug_enabled {
946 context.timing.start("debug_print:PruneUnusedLValues");
947 let debug_prune_lvalues = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
948 &reactive_fn, &env, Some(&hir_formatter),
949 );
950 context.log_debug(DebugLogEntry::new(
951 "PruneUnusedLValues",
952 debug_prune_lvalues,
953 ));
954 context.timing.stop();
955 }
956
957 context.timing.start("PromoteUsedTemporaries");
958 react_compiler_reactive_scopes::promote_used_temporaries(&mut reactive_fn, &mut env);
959 context.timing.stop();
960
961 if context.debug_enabled {
962 context.timing.start("debug_print:PromoteUsedTemporaries");
963 let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
964 &reactive_fn, &env, Some(&hir_formatter),
965 );
966 context.log_debug(DebugLogEntry::new("PromoteUsedTemporaries", debug));
967 context.timing.stop();
968 }
969
970 context
971 .timing
972 .start("ExtractScopeDeclarationsFromDestructuring");
973 react_compiler_reactive_scopes::extract_scope_declarations_from_destructuring(
974 &mut reactive_fn,
975 &mut env,
976 )?;
977 context.timing.stop();
978
979 if context.debug_enabled {
980 context
981 .timing
982 .start("debug_print:ExtractScopeDeclarationsFromDestructuring");
983 let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
984 &reactive_fn, &env, Some(&hir_formatter),
985 );
986 context.log_debug(DebugLogEntry::new(
987 "ExtractScopeDeclarationsFromDestructuring",
988 debug,
989 ));
990 context.timing.stop();
991 }
992
993 context.timing.start("StabilizeBlockIds");
994 react_compiler_reactive_scopes::stabilize_block_ids(&mut reactive_fn, &mut env);
995 context.timing.stop();
996
997 if context.debug_enabled {
998 context.timing.start("debug_print:StabilizeBlockIds");
999 let debug_stabilize = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
1000 &reactive_fn, &env, Some(&hir_formatter),
1001 );
1002 context.log_debug(DebugLogEntry::new("StabilizeBlockIds", debug_stabilize));
1003 context.timing.stop();
1004 }
1005
1006 context.timing.start("RenameVariables");
1007 let unique_identifiers =
1008 react_compiler_reactive_scopes::rename_variables(&mut reactive_fn, &mut env);
1009 context.timing.stop();
1010
1011 for name in &unique_identifiers {
1012 context.add_new_reference(name.clone());
1013 }
1014
1015 if context.debug_enabled {
1016 context.timing.start("debug_print:RenameVariables");
1017 let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
1018 &reactive_fn, &env, Some(&hir_formatter),
1019 );
1020 context.log_debug(DebugLogEntry::new("RenameVariables", debug));
1021 context.timing.stop();
1022 }
1023
1024 context.timing.start("PruneHoistedContexts");
1025 react_compiler_reactive_scopes::prune_hoisted_contexts(&mut reactive_fn, &mut env)?;
1026 context.timing.stop();
1027
1028 if context.debug_enabled {
1029 context.timing.start("debug_print:PruneHoistedContexts");
1030 let debug = react_compiler_reactive_scopes::print_reactive_function::debug_reactive_function_with_formatter(
1031 &reactive_fn, &env, Some(&hir_formatter),
1032 );
1033 context.log_debug(DebugLogEntry::new("PruneHoistedContexts", debug));
1034 context.timing.stop();
1035 }
1036
1037 if env.config.enable_preserve_existing_memoization_guarantees
1038 || env.config.validate_preserve_existing_memoization_guarantees
1039 {
1040 context.timing.start("ValidatePreservedManualMemoization");
1041 react_compiler_validation::validate_preserved_manual_memoization(&reactive_fn, &mut env);
1042 if context.debug_enabled {
1043 context.log_debug(DebugLogEntry::new(
1044 "ValidatePreservedManualMemoization",
1045 "ok".to_string(),
1046 ));
1047 }
1048 context.timing.stop();
1049 }
1050
1051 context.timing.start("codegen");
1052 let codegen_result = react_compiler_reactive_scopes::codegen_function(
1053 &reactive_fn,
1054 &mut env,
1055 unique_identifiers,
1056 fbt_operands,
1057 )?;
1058 context.timing.stop();
1059
1060 // NOTE: we intentionally do NOT register the memo cache import here.
1061 // The import is registered in apply_compiled_functions() only for functions
1062 // that are actually applied to the output. Registering it here would cause
1063 // a spurious `import { c as _c }` when a function compiles with memo slots
1064 // but is later discarded (e.g., due to "use no memo" opt-out or errors),
1065 // while other functions in the same file compile to 0 memo slots.
1066
1067 if env.config.validate_source_locations {
1068 super::validate_source_locations::validate_source_locations(
1069 func,
1070 &codegen_result,
1071 &mut env,
1072 );
1073 }
1074
1075 // Simulate unexpected exception for testing (matches TS Pipeline.ts)
1076 if env.config.throw_unknown_exception_testonly {
1077 let mut err = CompilerError::new();
1078 err.push_error_detail(react_compiler_diagnostics::CompilerErrorDetail {
1079 category: react_compiler_diagnostics::ErrorCategory::Invariant,
1080 reason: "unexpected error".to_string(),
1081 description: None,
1082 loc: None,
1083 suggestions: None,
1084 });
1085 return Err(err);
1086 }
1087
1088 // Check for accumulated errors at the end of the pipeline
1089 // (matches TS Pipeline.ts: env.hasErrors() → Err at the end)
1090 if env.has_errors() {
1091 // Merge UIDs even on error: in TS, Babel's scope.generateUid() permanently
1092 // registers names in the scope's `uids` map regardless of whether the function
1093 // compilation succeeds or fails. Without this merge, failed compilations would
1094 // "leak" _temp names that subsequent successful compilations wouldn't see,
1095 // causing numbering mismatches vs TS.
1096 if let Some(uid_names) = env.take_uid_known_names() {
1097 context.merge_uid_known_names(&uid_names);
1098 }
1099 return Err(env.take_errors());
1100 }
1101
1102 // Re-compile outlined functions through the full pipeline.
1103 // This mirrors TS behavior where outlined functions from JSX outlining
1104 // are pushed back onto the compilation queue and compiled as components.
1105 let mut compiled_outlined: Vec<OutlinedFunction> = Vec::new();
1106 for o in codegen_result.outlined {
1107 let outlined_codegen = CodegenFunction {
1108 loc: o.func.loc,
1109 id: o.func.id,
1110 name_hint: o.func.name_hint,
1111 params: o.func.params,
1112 body: o.func.body,
1113 generator: o.func.generator,
1114 is_async: o.func.is_async,
1115 memo_slots_used: o.func.memo_slots_used,
1116 memo_blocks: o.func.memo_blocks,
1117 memo_values: o.func.memo_values,
1118 pruned_memo_blocks: o.func.pruned_memo_blocks,
1119 pruned_memo_values: o.func.pruned_memo_values,
1120 outlined: Vec::new(),
1121 };
1122 if let Some(fn_type) = o.fn_type {
1123 let fn_name = outlined_codegen.id.as_ref().map(|id| id.name.clone());
1124 match compile_outlined_fn(
1125 outlined_codegen,
1126 fn_name.as_deref(),
1127 fn_type,
1128 mode,
1129 env_config,
1130 context,
1131 ) {
1132 Ok(compiled) => {
1133 compiled_outlined.push(OutlinedFunction {
1134 func: compiled,
1135 fn_type: Some(fn_type),
1136 });
1137 }
1138 Err(_err) => {
1139 // If re-compilation fails, skip the outlined function
1140 }
1141 }
1142 } else {
1143 compiled_outlined.push(OutlinedFunction {
1144 func: outlined_codegen,
1145 fn_type: o.fn_type,
1146 });
1147 }
1148 }
1149
1150 if let Some(uid_names) = env.take_uid_known_names() {
1151 context.merge_uid_known_names(&uid_names);
1152 }
1153
1154 Ok(CodegenFunction {
1155 loc: codegen_result.loc,
1156 id: codegen_result.id,
1157 name_hint: codegen_result.name_hint,
1158 params: codegen_result.params,
1159 body: codegen_result.body,
1160 generator: codegen_result.generator,
1161 is_async: codegen_result.is_async,
1162 memo_slots_used: codegen_result.memo_slots_used,
1163 memo_blocks: codegen_result.memo_blocks,
1164 memo_values: codegen_result.memo_values,
1165 pruned_memo_blocks: codegen_result.pruned_memo_blocks,
1166 pruned_memo_values: codegen_result.pruned_memo_values,
1167 outlined: compiled_outlined,
1168 })
1169 }
1170
1171 /// Compile an outlined function's codegen AST through the full pipeline.
1172 ///
1173 /// Creates a fresh Environment, builds a synthetic ScopeInfo with unique fake
1174 /// positions for identifier resolution, lowers from AST to HIR, then runs
1175 /// the full compilation pipeline. This mirrors the TS behavior where outlined
1176 /// functions are inserted into the program AST and re-compiled from scratch.
1177 pub fn compile_outlined_fn(
1178 mut codegen_fn: CodegenFunction,
1179 fn_name: Option<&str>,
1180 fn_type: ReactFunctionType,
1181 mode: CompilerOutputMode,
1182 env_config: &EnvironmentConfig,
1183 context: &mut ProgramContext,
1184 ) -> Result<CodegenFunction, CompilerError> {
1185 let mut env = Environment::with_config(env_config.clone());
1186 env.fn_type = fn_type;
1187 env.output_mode = match mode {
1188 CompilerOutputMode::Ssr => OutputMode::Ssr,
1189 CompilerOutputMode::Client => OutputMode::Client,
1190 CompilerOutputMode::Lint => OutputMode::Lint,
1191 };
1192
1193 // Build a FunctionDeclaration from the codegen output
1194 let mut outlined_decl = react_compiler_ast::statements::FunctionDeclaration {
1195 base: react_compiler_ast::common::BaseNode::typed("FunctionDeclaration"),
1196 id: codegen_fn.id.take(),
1197 params: std::mem::take(&mut codegen_fn.params),
1198 body: std::mem::replace(
1199 &mut codegen_fn.body,
1200 react_compiler_ast::statements::BlockStatement {
1201 base: react_compiler_ast::common::BaseNode::typed("BlockStatement"),
1202 body: Vec::new(),
1203 directives: Vec::new(),
1204 },
1205 ),
1206 generator: codegen_fn.generator,
1207 is_async: codegen_fn.is_async,
1208 declare: None,
1209 return_type: None,
1210 type_parameters: None,
1211 predicate: None,
1212 component_declaration: false,
1213 hook_declaration: false,
1214 };
1215
1216 // Build scope info by assigning fake positions to all identifiers
1217 let scope_info = build_outlined_scope_info(&mut outlined_decl);
1218
1219 let func_node = react_compiler_lowering::FunctionNode::FunctionDeclaration(&outlined_decl);
1220 let mut hir = react_compiler_lowering::lower(&func_node, fn_name, &scope_info, &mut env)?;
1221
1222 if env.has_invariant_errors() {
1223 return Err(env.take_invariant_errors());
1224 }
1225
1226 run_pipeline_passes(&mut hir, &mut env, context)
1227 }
1228
1229 /// Build a ScopeInfo for an outlined function declaration by assigning unique
1230 /// fake positions to all Identifier nodes and building the binding/reference maps.
1231 fn build_outlined_scope_info(
1232 func: &mut react_compiler_ast::statements::FunctionDeclaration,
1233 ) -> react_compiler_ast::scope::ScopeInfo {
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: FxHashMap<String, BindingId> = FxHashMap::default();
1240 let mut bindings_list: Vec<BindingData> = Vec::new();
1241 let mut ref_to_binding: IndexMap<u32, BindingId, FxBuildHasher> = IndexMap::default();
1242
1243 // Helper to add a binding
1244 let _add_binding =
1245 |name: &str,
1246 kind: BindingKind,
1247 p: u32,
1248 fn_bindings: &mut FxHashMap<String, BindingId>,
1249 bindings_list: &mut Vec<BindingData>,
1250 ref_to_binding: &mut IndexMap<u32, BindingId, FxBuildHasher>| {
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: FxHashMap::default(),
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: FxHashMap<u32, ScopeId> = FxHashMap::default();
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: FxHashMap<u32, ScopeId> = FxHashMap::default();
1313 node_id_to_scope.insert(0, ScopeId(1));
1314 let ref_node_id_to_binding: IndexMap<u32, BindingId, FxBuildHasher> =
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: FxHashMap::default(),
1322 reference_to_binding: IndexMap::default(),
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.
1330 fn 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 rustc_hash::FxHashMap<String, react_compiler_ast::scope::BindingId>,
1335 bindings_list: &mut Vec<react_compiler_ast::scope::BindingData>,
1336 ref_to_binding: &mut IndexMap<u32, react_compiler_ast::scope::BindingId, FxBuildHasher>,
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.
1432 fn outlined_assign_stmt_positions(
1433 stmt: &mut react_compiler_ast::statements::Statement,
1434 pos: &mut u32,
1435 fn_bindings: &mut rustc_hash::FxHashMap<String, react_compiler_ast::scope::BindingId>,
1436 bindings_list: &mut Vec<react_compiler_ast::scope::BindingData>,
1437 ref_to_binding: &mut IndexMap<u32, react_compiler_ast::scope::BindingId, FxBuildHasher>,
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.
1477 fn outlined_assign_expr_positions(
1478 expr: &mut react_compiler_ast::expressions::Expression,
1479 pos: &mut u32,
1480 fn_bindings: &rustc_hash::FxHashMap<String, react_compiler_ast::scope::BindingId>,
1481 ref_to_binding: &mut IndexMap<u32, react_compiler_ast::scope::BindingId, FxBuildHasher>,
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
1538 fn outlined_assign_jsx_name_positions(
1539 name: &mut react_compiler_ast::jsx::JSXElementName,
1540 pos: &mut u32,
1541 fn_bindings: &rustc_hash::FxHashMap<String, react_compiler_ast::scope::BindingId>,
1542 ref_to_binding: &mut IndexMap<u32, react_compiler_ast::scope::BindingId, FxBuildHasher>,
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
1561 fn outlined_assign_jsx_member_positions(
1562 member: &mut react_compiler_ast::jsx::JSXMemberExpression,
1563 pos: &mut u32,
1564 fn_bindings: &rustc_hash::FxHashMap<String, react_compiler_ast::scope::BindingId>,
1565 ref_to_binding: &mut IndexMap<u32, react_compiler_ast::scope::BindingId, FxBuildHasher>,
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
1583 fn outlined_assign_jsx_val_positions(
1584 val: &mut react_compiler_ast::jsx::JSXAttributeValue,
1585 pos: &mut u32,
1586 fn_bindings: &rustc_hash::FxHashMap<String, react_compiler_ast::scope::BindingId>,
1587 ref_to_binding: &mut IndexMap<u32, react_compiler_ast::scope::BindingId, FxBuildHasher>,
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
1608 fn outlined_assign_jsx_child_positions(
1609 child: &mut react_compiler_ast::jsx::JSXChild,
1610 pos: &mut u32,
1611 fn_bindings: &rustc_hash::FxHashMap<String, react_compiler_ast::scope::BindingId>,
1612 ref_to_binding: &mut IndexMap<u32, react_compiler_ast::scope::BindingId, FxBuildHasher>,
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.
1644 fn 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.
1842 fn 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 }