1// Copyright (c) Meta Platforms, Inc. and affiliates.2//3// This source code is licensed under the MIT license found in the4// LICENSE file in the root directory of this source tree.56//! Recursively analyzes nested function expressions and object methods to infer7//! their aliasing effect signatures.8//!9//! Ported from TypeScript `src/Inference/AnalyseFunctions.ts`.10//!11//! Runs inferMutationAliasingEffects, deadCodeElimination,12//! inferMutationAliasingRanges, rewriteInstructionKindsBasedOnReassignment,13//! and inferReactiveScopeVariables on each inner function.1415use indexmap::IndexMap;16use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory};17use react_compiler_hir::environment::Environment;18use rustc_hash::FxHashSet;1920use react_compiler_hir::{21 AliasingEffect, BlockId, Effect, EvaluationOrder, FunctionId, HIR, HirFunction, IdentifierId,22 InstructionValue, Place, ReactFunctionType,23};2425/// Analyse all nested function expressions and object methods in `func`.26///27/// For each inner function found, runs `lower_with_mutation_aliasing` to infer28/// its aliasing effects, then resets context variable mutable ranges.29///30/// The optional `debug_logger` callback is invoked after processing each inner31/// function, receiving `(&HirFunction, &Environment)` so the caller can produce32/// debug output. This mirrors the TS `fn.env.logger?.debugLogIRs` call inside33/// `lowerWithMutationAliasing`.34///35/// Corresponds to TS `analyseFunctions(func: HIRFunction): void`.36pub fn analyse_functions<F>(37 func: &mut HirFunction,38 env: &mut Environment,39 debug_logger: &mut F,40) -> Result<(), CompilerDiagnostic>41where42 F: FnMut(&HirFunction, &Environment),43{44 // Collect FunctionIds from FunctionExpression/ObjectMethod instructions.45 // We collect first to avoid borrow conflicts with env.functions.46 let mut inner_func_ids: Vec<FunctionId> = Vec::new();47 for (_block_id, block) in &func.body.blocks {48 for instr_id in &block.instructions {49 let instr = &func.instructions[instr_id.0 as usize];50 match &instr.value {51 InstructionValue::FunctionExpression { lowered_func, .. }52 | InstructionValue::ObjectMethod { lowered_func, .. } => {53 inner_func_ids.push(lowered_func.func);54 }55 _ => {}56 }57 }58 }5960 // Process each inner function61 for func_id in inner_func_ids {62 // Take the inner function out of the arena to avoid borrow conflicts63 let mut inner_func = std::mem::replace(64 &mut env.functions[func_id.0 as usize],65 placeholder_function(),66 );6768 lower_with_mutation_aliasing(&mut inner_func, env, debug_logger)?;6970 // If an invariant error was recorded, put the function back and stop processing71 if env.has_invariant_errors() {72 env.functions[func_id.0 as usize] = inner_func;73 return Ok(());74 }7576 // Reset mutable range for outer inferMutationAliasingEffects.77 //78 // NOTE: inferReactiveScopeVariables makes identifiers in the scope79 // point to the *same* mutableRange instance (in TS). In Rust, scopes80 // are stored in an arena, so we reset both the identifier's range81 // and clear its scope.82 for operand in &inner_func.context {83 let new_range = env.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0));84 let ident = &mut env.identifiers[operand.identifier.0 as usize];85 ident.mutable_range = new_range;86 ident.scope = None;87 }8889 // Put the function back90 env.functions[func_id.0 as usize] = inner_func;91 }9293 Ok(())94}9596/// Run mutation/aliasing inference on an inner function.97///98/// Corresponds to TS `lowerWithMutationAliasing(fn: HIRFunction): void`.99fn lower_with_mutation_aliasing<F>(100 func: &mut HirFunction,101 env: &mut Environment,102 debug_logger: &mut F,103) -> Result<(), CompilerDiagnostic>104where105 F: FnMut(&HirFunction, &Environment),106{107 // Phase 1: Recursively analyse nested functions first (depth-first)108 analyse_functions(func, env, debug_logger)?;109110 // inferMutationAliasingEffects on the inner function111 crate::infer_mutation_aliasing_effects::infer_mutation_aliasing_effects(func, env, true)?;112113 // Check for invariant errors (e.g., uninitialized value kind)114 // In TS, these throw from within inferMutationAliasingEffects, aborting115 // the rest of the function processing.116 if env.has_invariant_errors() {117 return Ok(());118 }119120 // deadCodeElimination for inner functions121 react_compiler_optimization::dead_code_elimination(func, env);122123 // inferMutationAliasingRanges — returns the externally-visible function effects124 let function_effects =125 crate::infer_mutation_aliasing_ranges::infer_mutation_aliasing_ranges(func, env, true)?;126127 // rewriteInstructionKindsBasedOnReassignment128 if let Err(err) = react_compiler_ssa::rewrite_instruction_kinds_based_on_reassignment(func, env)129 {130 env.errors.merge(err);131 return Ok(());132 }133134 // inferReactiveScopeVariables on the inner function135 crate::infer_reactive_scope_variables::infer_reactive_scope_variables(func, env)?;136137 func.aliasing_effects = Some(function_effects.clone());138139 // Phase 2: Populate the Effect of each context variable to use in inferring140 // the outer function. Corresponds to TS Phase 2 in lowerWithMutationAliasing.141 let mut captured_or_mutated: FxHashSet<IdentifierId> = FxHashSet::default();142 for effect in &function_effects {143 match effect {144 AliasingEffect::Assign { from, .. }145 | AliasingEffect::Alias { from, .. }146 | AliasingEffect::Capture { from, .. }147 | AliasingEffect::CreateFrom { from, .. }148 | AliasingEffect::MaybeAlias { from, .. } => {149 captured_or_mutated.insert(from.identifier);150 }151 AliasingEffect::Mutate { value, .. }152 | AliasingEffect::MutateConditionally { value }153 | AliasingEffect::MutateTransitive { value }154 | AliasingEffect::MutateTransitiveConditionally { value } => {155 captured_or_mutated.insert(value.identifier);156 }157 AliasingEffect::Impure { .. }158 | AliasingEffect::Render { .. }159 | AliasingEffect::MutateFrozen { .. }160 | AliasingEffect::MutateGlobal { .. }161 | AliasingEffect::CreateFunction { .. }162 | AliasingEffect::Create { .. }163 | AliasingEffect::Freeze { .. }164 | AliasingEffect::ImmutableCapture { .. } => {165 // no-op166 }167 AliasingEffect::Apply { .. } => {168 return Err(CompilerDiagnostic::new(169 ErrorCategory::Invariant,170 "[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects",171 None,172 ));173 }174 }175 }176177 for operand in &mut func.context {178 if captured_or_mutated.contains(&operand.identifier) || operand.effect == Effect::Capture {179 operand.effect = Effect::Capture;180 } else {181 operand.effect = Effect::Read;182 }183 }184185 // Log the inner function's state (mirrors TS: fn.env.logger?.debugLogIRs)186 debug_logger(func, env);187188 Ok(())189}190191/// Create a placeholder HirFunction for temporarily swapping an inner function192/// out of `env.functions` via `std::mem::replace`. The placeholder is never193/// read — the real function is swapped back immediately after processing.194fn placeholder_function() -> HirFunction {195 HirFunction {196 loc: None,197 id: None,198 name_hint: None,199 fn_type: ReactFunctionType::Other,200 params: Vec::new(),201 return_type_annotation: None,202 returns: Place {203 identifier: IdentifierId(0),204 effect: Effect::Unknown,205 reactive: false,206 loc: None,207 },208 context: Vec::new(),209 body: HIR {210 entry: BlockId(0),211 blocks: IndexMap::default(),212 },213 instructions: Vec::new(),214 generator: false,215 is_async: false,216 directives: Vec::new(),217 aliasing_effects: None,218 }219}
Code quality findings 8
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let instr = &func.instructions[instr_id.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
&mut env.functions[func_id.0 as usize],
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
env.functions[func_id.0 as usize] = inner_func;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let ident = &mut env.identifiers[operand.identifier.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
env.functions[func_id.0 as usize] = inner_func;
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info
correctness
match-wildcard
match &instr.value {
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info
performance
push-without-reserve
inner_func_ids.push(lowered_func.func);
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
func.aliasing_effects = Some(function_effects.clone());