Skip to main content

react_compiler_inference/
analyse_functions.rs

1// Copyright (c) Meta Platforms, Inc. and affiliates.
2//
3// This source code is licensed under the MIT license found in the
4// LICENSE file in the root directory of this source tree.
5
6//! Recursively analyzes nested function expressions and object methods to infer
7//! 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.
14
15use indexmap::IndexMap;
16use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory};
17use react_compiler_hir::environment::Environment;
18use std::collections::HashSet;
19
20use react_compiler_hir::{
21    AliasingEffect, BlockId, Effect, EvaluationOrder, FunctionId, HirFunction, IdentifierId,
22    InstructionValue, Place, ReactFunctionType, HIR,
23};
24
25/// Analyse all nested function expressions and object methods in `func`.
26///
27/// For each inner function found, runs `lower_with_mutation_aliasing` to infer
28/// its aliasing effects, then resets context variable mutable ranges.
29///
30/// The optional `debug_logger` callback is invoked after processing each inner
31/// function, receiving `(&HirFunction, &Environment)` so the caller can produce
32/// debug output. This mirrors the TS `fn.env.logger?.debugLogIRs` call inside
33/// `lowerWithMutationAliasing`.
34///
35/// Corresponds to TS `analyseFunctions(func: HIRFunction): void`.
36pub fn analyse_functions<F>(func: &mut HirFunction, env: &mut Environment, debug_logger: &mut F) -> Result<(), CompilerDiagnostic>
37where
38    F: FnMut(&HirFunction, &Environment),
39{
40    // Collect FunctionIds from FunctionExpression/ObjectMethod instructions.
41    // We collect first to avoid borrow conflicts with env.functions.
42    let mut inner_func_ids: Vec<FunctionId> = Vec::new();
43    for (_block_id, block) in &func.body.blocks {
44        for instr_id in &block.instructions {
45            let instr = &func.instructions[instr_id.0 as usize];
46            match &instr.value {
47                InstructionValue::FunctionExpression { lowered_func, .. }
48                | InstructionValue::ObjectMethod { lowered_func, .. } => {
49                    inner_func_ids.push(lowered_func.func);
50                }
51                _ => {}
52            }
53        }
54    }
55
56    // Process each inner function
57    for func_id in inner_func_ids {
58        // Take the inner function out of the arena to avoid borrow conflicts
59        let mut inner_func = std::mem::replace(
60            &mut env.functions[func_id.0 as usize],
61            placeholder_function(),
62        );
63
64        lower_with_mutation_aliasing(&mut inner_func, env, debug_logger)?;
65
66        // If an invariant error was recorded, put the function back and stop processing
67        if env.has_invariant_errors() {
68            env.functions[func_id.0 as usize] = inner_func;
69            return Ok(());
70        }
71
72        // Reset mutable range for outer inferMutationAliasingEffects.
73        //
74        // NOTE: inferReactiveScopeVariables makes identifiers in the scope
75        // point to the *same* mutableRange instance (in TS). In Rust, scopes
76        // are stored in an arena, so we reset both the identifier's range
77        // and clear its scope.
78        for operand in &inner_func.context {
79            let new_range = env.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0));
80            let ident = &mut env.identifiers[operand.identifier.0 as usize];
81            ident.mutable_range = new_range;
82            ident.scope = None;
83        }
84
85        // Put the function back
86        env.functions[func_id.0 as usize] = inner_func;
87    }
88
89    Ok(())
90}
91
92/// Run mutation/aliasing inference on an inner function.
93///
94/// Corresponds to TS `lowerWithMutationAliasing(fn: HIRFunction): void`.
95fn lower_with_mutation_aliasing<F>(func: &mut HirFunction, env: &mut Environment, debug_logger: &mut F) -> Result<(), CompilerDiagnostic>
96where
97    F: FnMut(&HirFunction, &Environment),
98{
99    // Phase 1: Recursively analyse nested functions first (depth-first)
100    analyse_functions(func, env, debug_logger)?;
101
102    // inferMutationAliasingEffects on the inner function
103    crate::infer_mutation_aliasing_effects::infer_mutation_aliasing_effects(
104        func, env, true,
105    )?;
106
107    // Check for invariant errors (e.g., uninitialized value kind)
108    // In TS, these throw from within inferMutationAliasingEffects, aborting
109    // the rest of the function processing.
110    if env.has_invariant_errors() {
111        return Ok(());
112    }
113
114    // deadCodeElimination for inner functions
115    react_compiler_optimization::dead_code_elimination(func, env);
116
117    // inferMutationAliasingRanges — returns the externally-visible function effects
118    let function_effects = crate::infer_mutation_aliasing_ranges::infer_mutation_aliasing_ranges(
119        func, env, true,
120    )?;
121
122    // rewriteInstructionKindsBasedOnReassignment
123    if let Err(err) = react_compiler_ssa::rewrite_instruction_kinds_based_on_reassignment(func, env) {
124        env.errors.merge(err);
125        return Ok(());
126    }
127
128    // inferReactiveScopeVariables on the inner function
129    crate::infer_reactive_scope_variables::infer_reactive_scope_variables(func, env)?;
130
131    func.aliasing_effects = Some(function_effects.clone());
132
133    // Phase 2: Populate the Effect of each context variable to use in inferring
134    // the outer function. Corresponds to TS Phase 2 in lowerWithMutationAliasing.
135    let mut captured_or_mutated: HashSet<IdentifierId> = HashSet::new();
136    for effect in &function_effects {
137        match effect {
138            AliasingEffect::Assign { from, .. }
139            | AliasingEffect::Alias { from, .. }
140            | AliasingEffect::Capture { from, .. }
141            | AliasingEffect::CreateFrom { from, .. }
142            | AliasingEffect::MaybeAlias { from, .. } => {
143                captured_or_mutated.insert(from.identifier);
144            }
145            AliasingEffect::Mutate { value, .. }
146            | AliasingEffect::MutateConditionally { value }
147            | AliasingEffect::MutateTransitive { value }
148            | AliasingEffect::MutateTransitiveConditionally { value } => {
149                captured_or_mutated.insert(value.identifier);
150            }
151            AliasingEffect::Impure { .. }
152            | AliasingEffect::Render { .. }
153            | AliasingEffect::MutateFrozen { .. }
154            | AliasingEffect::MutateGlobal { .. }
155            | AliasingEffect::CreateFunction { .. }
156            | AliasingEffect::Create { .. }
157            | AliasingEffect::Freeze { .. }
158            | AliasingEffect::ImmutableCapture { .. } => {
159                // no-op
160            }
161            AliasingEffect::Apply { .. } => {
162                return Err(CompilerDiagnostic::new(
163                    ErrorCategory::Invariant,
164                    "[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects",
165                    None,
166                ));
167            }
168        }
169    }
170
171    for operand in &mut func.context {
172        if captured_or_mutated.contains(&operand.identifier)
173            || operand.effect == Effect::Capture
174        {
175            operand.effect = Effect::Capture;
176        } else {
177            operand.effect = Effect::Read;
178        }
179    }
180
181    // Log the inner function's state (mirrors TS: fn.env.logger?.debugLogIRs)
182    debug_logger(func, env);
183
184    Ok(())
185}
186
187
188/// Create a placeholder HirFunction for temporarily swapping an inner function
189/// out of `env.functions` via `std::mem::replace`. The placeholder is never
190/// read — the real function is swapped back immediately after processing.
191fn placeholder_function() -> HirFunction {
192    HirFunction {
193        loc: None,
194        id: None,
195        name_hint: None,
196        fn_type: ReactFunctionType::Other,
197        params: Vec::new(),
198        return_type_annotation: None,
199        returns: Place {
200            identifier: IdentifierId(0),
201            effect: Effect::Unknown,
202            reactive: false,
203            loc: None,
204        },
205        context: Vec::new(),
206        body: HIR {
207            entry: BlockId(0),
208            blocks: IndexMap::new(),
209        },
210        instructions: Vec::new(),
211        generator: false,
212        is_async: false,
213        directives: Vec::new(),
214        aliasing_effects: None,
215    }
216}