compiler/crates/react_compiler_inference/src/analyse_functions.rs RUST 220 lines View on github.com → Search inside
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());

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.