| 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 | } |