compiler/crates/react_compiler_inference/src/infer_mutation_aliasing_effects.rs RUST 3,731 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 3,731.
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//! Infers the mutation/aliasing effects for instructions and terminals.7//!8//! Ported from TypeScript `src/Inference/InferMutationAliasingEffects.ts`.9//!10//! This pass uses abstract interpretation to compute effects describing11//! creation, aliasing, mutation, freezing, and error conditions for each12//! instruction and terminal in the HIR.1314use indexmap::IndexMap;15use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};1617use react_compiler_diagnostics::CompilerDiagnostic;18use react_compiler_diagnostics::CompilerDiagnosticDetail;19use react_compiler_diagnostics::ErrorCategory;20use react_compiler_hir::AliasingEffect;21use react_compiler_hir::AliasingSignature;22use react_compiler_hir::BlockId;23use react_compiler_hir::DeclarationId;24use react_compiler_hir::Effect;25use react_compiler_hir::FunctionId;26use react_compiler_hir::HirFunction;27use react_compiler_hir::IdentifierId;28use react_compiler_hir::InstructionKind;29use react_compiler_hir::InstructionValue;30use react_compiler_hir::MutationReason;31use react_compiler_hir::ParamPattern;32use react_compiler_hir::Place;33use react_compiler_hir::PlaceOrSpread;34use react_compiler_hir::PlaceOrSpreadOrHole;35use react_compiler_hir::ReactFunctionType;36use react_compiler_hir::SourceLocation;37use react_compiler_hir::Type;38use react_compiler_hir::environment::Environment;39use react_compiler_hir::object_shape::BUILT_IN_ARRAY_ID;40use react_compiler_hir::object_shape::BUILT_IN_MAP_ID;41use react_compiler_hir::object_shape::BUILT_IN_SET_ID;42use react_compiler_hir::object_shape::FunctionSignature;43use react_compiler_hir::object_shape::HookKind;44use react_compiler_hir::type_config::ValueKind;45use react_compiler_hir::type_config::ValueReason;46use react_compiler_hir::visitors;4748// =============================================================================49// Public entry point50// =============================================================================5152/// Infers mutation/aliasing effects for all instructions and terminals in `func`.53///54/// Corresponds to TS `inferMutationAliasingEffects(fn, {isFunctionExpression})`.55pub fn infer_mutation_aliasing_effects(56    func: &mut HirFunction,57    env: &mut Environment,58    is_function_expression: bool,59) -> Result<(), CompilerDiagnostic> {60    let mut initial_state = InferenceState::empty(env, is_function_expression);6162    // Map of blocks to the last (merged) incoming state that was processed63    let mut states_by_block: FxHashMap<BlockId, InferenceState> = FxHashMap::default();6465    // Initialize context variables66    for ctx_place in &func.context {67        let value_id = ValueId::new();68        initial_state.initialize(69            value_id,70            AbstractValue {71                kind: ValueKind::Context,72                reason: ValueReasonSet::single(ValueReason::Other),73            },74        );75        initial_state.define(ctx_place.identifier, value_id);76    }7778    let param_kind: AbstractValue = if is_function_expression {79        AbstractValue {80            kind: ValueKind::Mutable,81            reason: ValueReasonSet::single(ValueReason::Other),82        }83    } else {84        AbstractValue {85            kind: ValueKind::Frozen,86            reason: ValueReasonSet::single(ValueReason::ReactiveFunctionArgument),87        }88    };8990    if func.fn_type == ReactFunctionType::Component {91        // Component: at most 2 params (props, ref)92        let params_len = func.params.len();93        if params_len > 0 {94            infer_param(&func.params[0], &mut initial_state, &param_kind);95        }96        if params_len > 1 {97            let ref_place = match &func.params[1] {98                ParamPattern::Place(p) => p,99                ParamPattern::Spread(s) => &s.place,100            };101            let value_id = ValueId::new();102            initial_state.initialize(103                value_id,104                AbstractValue {105                    kind: ValueKind::Mutable,106                    reason: ValueReasonSet::single(ValueReason::Other),107                },108            );109            initial_state.define(ref_place.identifier, value_id);110        }111    } else {112        for param in &func.params {113            infer_param(param, &mut initial_state, &param_kind);114        }115    }116117    let mut queued_states: IndexMap<BlockId, InferenceState, FxBuildHasher> = IndexMap::default();118119    // Queue helper120    fn queue(121        queued_states: &mut IndexMap<BlockId, InferenceState, FxBuildHasher>,122        states_by_block: &FxHashMap<BlockId, InferenceState>,123        block_id: BlockId,124        state: InferenceState,125    ) {126        if let Some(queued_state) = queued_states.get(&block_id) {127            let merged = queued_state.merge(&state);128            let new_state = merged.unwrap_or_else(|| queued_state.clone());129            queued_states.insert(block_id, new_state);130        } else {131            let prev_state = states_by_block.get(&block_id);132            if let Some(prev) = prev_state {133                let next_state = prev.merge(&state);134                if let Some(next) = next_state {135                    queued_states.insert(block_id, next);136                }137            } else {138                queued_states.insert(block_id, state);139            }140        }141    }142143    queue(144        &mut queued_states,145        &states_by_block,146        func.body.entry,147        initial_state,148    );149150    let hoisted_context_declarations = find_hoisted_context_declarations(func, env);151    let non_mutating_spreads = find_non_mutated_destructure_spreads(func, env);152153    let mut context = Context {154        interned_effects: FxHashMap::default(),155        instruction_signature_cache: FxHashMap::default(),156        catch_handlers: FxHashMap::default(),157        is_function_expression,158        hoisted_context_declarations,159        non_mutating_spreads,160        effect_value_id_cache: FxHashMap::default(),161        function_values: FxHashMap::default(),162        function_signature_cache: FxHashMap::default(),163        aliasing_config_temp_cache: FxHashMap::default(),164    };165166    let mut iteration_count = 0;167168    while !queued_states.is_empty() {169        iteration_count += 1;170        if iteration_count > 100 {171            return Err(CompilerDiagnostic::new(172                ErrorCategory::Invariant,173                "[InferMutationAliasingEffects] Potential infinite loop: \174                 A value, temporary place, or effect was not cached properly",175                None,176            ));177        }178179        // Collect block IDs to process in order180        let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect();181        for block_id in block_ids {182            let incoming_state = match queued_states.swap_remove(&block_id) {183                Some(s) => s,184                None => continue,185            };186187            states_by_block.insert(block_id, incoming_state.clone());188            let mut state = incoming_state;189190            infer_block(&mut context, &mut state, block_id, func, env)?;191192            // Check for uninitialized identifier access (matches TS invariant:193            // "Expected value kind to be initialized")194            if let Some((uninitialized_id, usage_loc)) = state.uninitialized_access.get() {195                let ident_info = env.identifiers.get(uninitialized_id.0 as usize);196                let name = ident_info197                    .and_then(|ident| ident.name.as_ref())198                    .map(|n| n.value().to_string())199                    .unwrap_or_else(|| "".to_string());200                // Use usage_loc if available, otherwise fall back to identifier's own loc201                let error_loc = usage_loc.or_else(|| ident_info.and_then(|i| i.loc));202                // Match TS printPlace format: "<unknown> name$id:type"203                let type_str = ident_info204                    .map(|ident| {205                        let ty = &env.types[ident.type_.0 as usize];206                        format_type_for_print(ty)207                    })208                    .unwrap_or_default();209                let description = format!("<unknown> {}${}{}", name, uninitialized_id.0, type_str);210                let diag = CompilerDiagnostic::new(211                    ErrorCategory::Invariant,212                    "[InferMutationAliasingEffects] Expected value kind to be initialized",213                    Some(description),214                )215                .with_detail(CompilerDiagnosticDetail::Error {216                    loc: error_loc,217                    message: Some("this is uninitialized".to_string()),218                    identifier_name: None,219                });220                return Err(diag);221            }222223            // Queue successors224            let successors = terminal_successors(&func.body.blocks[&block_id].terminal);225            for next_block_id in successors {226                queue(227                    &mut queued_states,228                    &states_by_block,229                    next_block_id,230                    state.clone(),231                );232            }233        }234    }235236    Ok(())237}238239// =============================================================================240// ValueId: replaces InstructionValue identity as allocation-site key241// =============================================================================242243/// Unique allocation-site identifier, replacing TS's object-identity on InstructionValue.244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]245struct ValueId(u32);246247use std::sync::atomic::AtomicU32;248use std::sync::atomic::Ordering;249static NEXT_VALUE_ID: AtomicU32 = AtomicU32::new(1);250251impl ValueId {252    fn new() -> Self {253        ValueId(NEXT_VALUE_ID.fetch_add(1, Ordering::Relaxed))254    }255}256257// =============================================================================258// AbstractValue259// =============================================================================260261#[derive(Debug, Clone, Copy)]262struct AbstractValue {263    kind: ValueKind,264    reason: ValueReasonSet,265}266267/// Capacity of [`ValueReasonSet`]. A set holds at most one of each `ValueReason`268/// variant, of which there are currently 12; the extra slots are headroom so269/// that adding variants upstream cannot overflow the set.270const VALUE_REASON_CAPACITY: usize = 16;271272/// An insertion-ordered set of [`ValueReason`]s, stored inline.273///274/// This is a deliberate replacement for `IndexSet`, enabling insertion-order275/// memory while avoiding any heap allocation. At `AbstractValue`'s scale, this276/// has a dramatic impact on heap memory and wall time.277/// This takes advantage of the format of the data it's actually storing. A set278/// can hold at most one of each variant, so the members fit into a fixed inline279/// array. `ValueReason` is implemented as a single byte, so this struct is280/// ~18 bytes on the stack.281///282/// Insertion order is preserved deliberately: [`primary_reason`] returns the283/// first non-`Other` member, matching the iteration order of the `Set` used by284/// the TypeScript implementation this is ported from.285#[derive(Debug, Clone, Copy)]286struct ValueReasonSet {287    /// Members in insertion order. Only the first `len` entries are meaningful.288    members: [ValueReason; VALUE_REASON_CAPACITY],289    len: u8,290}291292impl Default for ValueReasonSet {293    fn default() -> Self {294        ValueReasonSet {295            members: [ValueReason::Other; VALUE_REASON_CAPACITY],296            len: 0,297        }298    }299}300301impl ValueReasonSet {302    fn single(reason: ValueReason) -> Self {303        let mut set = Self::default();304        set.insert(reason);305        set306    }307308    fn contains(&self, reason: ValueReason) -> bool {309        self.members[..self.len as usize].contains(&reason)310    }311312    fn iter(&self) -> impl Iterator<Item = ValueReason> + '_ {313        self.members[..self.len as usize].iter().copied()314    }315316    /// Appends `reason` if not already present, preserving insertion order.317    fn insert(&mut self, reason: ValueReason) {318        if self.contains(reason) {319            return;320        }321        debug_assert!(322            (self.len as usize) < VALUE_REASON_CAPACITY,323            "ValueReasonSet capacity must cover every ValueReason variant"324        );325        if (self.len as usize) < VALUE_REASON_CAPACITY {326            self.members[self.len as usize] = reason;327            self.len += 1;328        }329    }330331    /// True when every member of `other` is also a member of `self`.332    fn is_superset_of(&self, other: &ValueReasonSet) -> bool {333        other.iter().all(|reason| self.contains(reason))334    }335336    /// Adds every member of `other`, keeping `self`'s existing order and337    /// appending newcomers in `other`'s order — matching `IndexSet::insert`.338    fn union_with(&mut self, other: &ValueReasonSet) {339        for reason in other.iter() {340            self.insert(reason);341        }342    }343}344345// =============================================================================346// InferenceState347// =============================================================================348349/// The abstract state tracked during inference.350/// Uses interior mutability via a struct with direct fields (no Rc needed since351/// we always have exclusive access in the pass).352#[derive(Debug, Clone)]353struct InferenceState {354    is_function_expression: bool,355    /// The kind of each value, based on its allocation site356    values: FxHashMap<ValueId, AbstractValue>,357    /// The set of values pointed to by each identifier358    variables: FxHashMap<IdentifierId, FxHashSet<ValueId>>,359    /// Tracks uninitialized identifier access errors (matches TS invariant).360    /// Uses Cell so it can be set from `&self` methods like `kind()`.361    /// Stores (IdentifierId, usage_loc) where usage_loc is the source location362    /// of the Place that triggered the uninitialized access.363    uninitialized_access: std::cell::Cell<Option<(IdentifierId, Option<SourceLocation>)>>,364}365366impl InferenceState {367    fn empty(_env: &Environment, is_function_expression: bool) -> Self {368        InferenceState {369            is_function_expression,370            values: FxHashMap::default(),371            variables: FxHashMap::default(),372            uninitialized_access: std::cell::Cell::new(None),373        }374    }375376    /// Check the kind of a place, recording the usage location for error reporting.377    fn kind_with_loc(378        &self,379        place_id: IdentifierId,380        usage_loc: Option<SourceLocation>,381    ) -> AbstractValue {382        let values = match self.variables.get(&place_id) {383            Some(v) => v,384            None => {385                if self.uninitialized_access.get().is_none() {386                    self.uninitialized_access.set(Some((place_id, usage_loc)));387                }388                return AbstractValue {389                    kind: ValueKind::Mutable,390                    reason: ValueReasonSet::single(ValueReason::Other),391                };392            }393        };394        let mut merged_kind: Option<AbstractValue> = None;395        for value_id in values {396            let kind = match self.values.get(value_id) {397                Some(k) => k,398                None => continue,399            };400            merged_kind = Some(match merged_kind {401                Some(prev) => merge_abstract_values(&prev, kind),402                None => kind.clone(),403            });404        }405        merged_kind.unwrap_or_else(|| AbstractValue {406            kind: ValueKind::Mutable,407            reason: ValueReasonSet::single(ValueReason::Other),408        })409    }410411    fn initialize(&mut self, value_id: ValueId, kind: AbstractValue) {412        self.values.insert(value_id, kind);413    }414415    fn define(&mut self, place_id: IdentifierId, value_id: ValueId) {416        let mut set = FxHashSet::default();417        set.insert(value_id);418        self.variables.insert(place_id, set);419    }420421    fn assign(&mut self, into: IdentifierId, from: IdentifierId) {422        let values = match self.variables.get(&from) {423            Some(v) => v.clone(),424            None => {425                // Create a stable value for uninitialized identifiers426                // Use a deterministic ID based on the from identifier427                let vid = ValueId(from.0 | 0x80000000);428                let mut set = FxHashSet::default();429                set.insert(vid);430                if !self.values.contains_key(&vid) {431                    self.values.insert(432                        vid,433                        AbstractValue {434                            kind: ValueKind::Mutable,435                            reason: ValueReasonSet::single(ValueReason::Other),436                        },437                    );438                }439                set440            }441        };442        self.variables.insert(into, values);443    }444445    fn append_alias(&mut self, place: IdentifierId, value: IdentifierId) {446        let new_values = match self.variables.get(&value) {447            Some(v) => v.clone(),448            None => return,449        };450        let prev_values = match self.variables.get(&place) {451            Some(v) => v.clone(),452            None => return,453        };454        let merged: FxHashSet<ValueId> = prev_values.union(&new_values).copied().collect();455        self.variables.insert(place, merged);456    }457458    fn is_defined(&self, place_id: IdentifierId) -> bool {459        self.variables.contains_key(&place_id)460    }461462    fn values_for(&self, place_id: IdentifierId) -> Vec<ValueId> {463        match self.variables.get(&place_id) {464            Some(values) => values.iter().copied().collect(),465            None => Vec::new(),466        }467    }468469    #[allow(dead_code)]470    fn kind_opt(&self, place_id: IdentifierId) -> Option<AbstractValue> {471        let values = self.variables.get(&place_id)?;472        let mut merged_kind: Option<AbstractValue> = None;473        for value_id in values {474            let kind = self.values.get(value_id)?;475            merged_kind = Some(match merged_kind {476                Some(prev) => merge_abstract_values(&prev, kind),477                None => kind.clone(),478            });479        }480        merged_kind481    }482483    fn kind(&self, place_id: IdentifierId) -> AbstractValue {484        self.kind_with_loc(place_id, None)485    }486487    fn freeze(&mut self, place_id: IdentifierId, reason: ValueReason) -> bool {488        // Check if defined first to avoid recording uninitialized access error.489        // Freeze on undefined identifiers is a no-op — this matches the TS490        // behavior where freeze() is never called on undefined identifiers491        // (the invariant in kind() catches this before freeze is reached).492        if !self.variables.contains_key(&place_id) {493            return false;494        }495        let value = self.kind(place_id);496        match value.kind {497            ValueKind::Context | ValueKind::Mutable | ValueKind::MaybeFrozen => {498                let value_ids: Vec<ValueId> = self.values_for(place_id);499                for vid in value_ids {500                    self.freeze_value(vid, reason);501                }502                true503            }504            ValueKind::Frozen | ValueKind::Global | ValueKind::Primitive => false,505        }506    }507508    fn freeze_value(&mut self, value_id: ValueId, reason: ValueReason) {509        self.values.insert(510            value_id,511            AbstractValue {512                kind: ValueKind::Frozen,513                reason: ValueReasonSet::single(reason),514            },515        );516        // Note: In TS, this also transitively freezes FunctionExpression captures517        // if enableTransitivelyFreezeFunctionExpressions is set. We skip that here518        // since we don't have access to the function arena from within state.519    }520521    #[allow(dead_code)]522    fn mutate(523        &self,524        variant: MutateVariant,525        place_id: IdentifierId,526        env: &Environment,527    ) -> MutationResult {528        self.mutate_with_loc(variant, place_id, env, None)529    }530531    fn mutate_with_loc(532        &self,533        variant: MutateVariant,534        place_id: IdentifierId,535        env: &Environment,536        usage_loc: Option<SourceLocation>,537    ) -> MutationResult {538        let ty = &env.types[env.identifiers[place_id.0 as usize].type_.0 as usize];539        if react_compiler_hir::is_ref_or_ref_value(ty) {540            return MutationResult::MutateRef;541        }542        let kind = self.kind_with_loc(place_id, usage_loc).kind;543        match variant {544            MutateVariant::MutateConditionally | MutateVariant::MutateTransitiveConditionally => {545                match kind {546                    ValueKind::Mutable | ValueKind::Context => MutationResult::Mutate,547                    _ => MutationResult::None,548                }549            }550            MutateVariant::Mutate | MutateVariant::MutateTransitive => match kind {551                ValueKind::Mutable | ValueKind::Context => MutationResult::Mutate,552                ValueKind::Primitive => MutationResult::None,553                ValueKind::Frozen | ValueKind::MaybeFrozen => MutationResult::MutateFrozen,554                ValueKind::Global => MutationResult::MutateGlobal,555            },556        }557    }558559    fn merge(&self, other: &InferenceState) -> Option<InferenceState> {560        let mut next_values: Option<FxHashMap<ValueId, AbstractValue>> = None;561        let mut next_variables: Option<FxHashMap<IdentifierId, FxHashSet<ValueId>>> = None;562563        // Merge values present in both564        for (id, this_value) in &self.values {565            if let Some(other_value) = other.values.get(id) {566                let merged = merge_abstract_values(this_value, other_value);567                if merged.kind != this_value.kind568                    || !this_value.reason.is_superset_of(&merged.reason)569                {570                    let nv = next_values.get_or_insert_with(|| self.values.clone());571                    nv.insert(*id, merged);572                }573            }574        }575        // Add values only in other576        for (id, other_value) in &other.values {577            if !self.values.contains_key(id) {578                let nv = next_values.get_or_insert_with(|| self.values.clone());579                nv.insert(*id, other_value.clone());580            }581        }582583        // Merge variables present in both584        for (id, this_values) in &self.variables {585            if let Some(other_values) = other.variables.get(id) {586                let mut has_new = false;587                for ov in other_values {588                    if !this_values.contains(ov) {589                        has_new = true;590                        break;591                    }592                }593                if has_new {594                    let nvars = next_variables.get_or_insert_with(|| self.variables.clone());595                    let merged: FxHashSet<ValueId> =596                        this_values.union(other_values).copied().collect();597                    nvars.insert(*id, merged);598                }599            }600        }601        // Add variables only in other602        for (id, other_values) in &other.variables {603            if !self.variables.contains_key(id) {604                let nvars = next_variables.get_or_insert_with(|| self.variables.clone());605                nvars.insert(*id, other_values.clone());606            }607        }608609        if next_variables.is_none() && next_values.is_none() {610            None611        } else {612            Some(InferenceState {613                is_function_expression: self.is_function_expression,614                values: next_values.unwrap_or_else(|| self.values.clone()),615                variables: next_variables.unwrap_or_else(|| self.variables.clone()),616                uninitialized_access: std::cell::Cell::new(None),617            })618        }619    }620621    fn infer_phi(622        &mut self,623        phi_place_id: IdentifierId,624        phi_operands: &IndexMap<BlockId, Place, FxBuildHasher>,625    ) {626        let mut values: FxHashSet<ValueId> = FxHashSet::default();627        for (_, operand) in phi_operands {628            if let Some(operand_values) = self.variables.get(&operand.identifier) {629                for v in operand_values {630                    values.insert(*v);631                }632            }633            // If not found, it's a backedge that will be handled later by merge634        }635        if !values.is_empty() {636            self.variables.insert(phi_place_id, values);637        }638    }639}640641#[derive(Debug, Clone, Copy)]642enum MutateVariant {643    Mutate,644    MutateConditionally,645    MutateTransitive,646    MutateTransitiveConditionally,647}648649#[derive(Debug, Clone, Copy, PartialEq, Eq)]650enum MutationResult {651    None,652    Mutate,653    MutateFrozen,654    MutateGlobal,655    MutateRef,656}657658// =============================================================================659// Context660// =============================================================================661662struct Context {663    interned_effects: FxHashMap<String, AliasingEffect>,664    instruction_signature_cache: FxHashMap<u32, InstructionSignature>,665    catch_handlers: FxHashMap<BlockId, Place>,666    is_function_expression: bool,667    hoisted_context_declarations: FxHashMap<DeclarationId, Option<Place>>,668    non_mutating_spreads: FxHashSet<IdentifierId>,669    /// Cache of ValueIds keyed by effect hash, ensuring stable allocation-site identity670    /// across fixpoint iterations. Mirrors TS `effectInstructionValueCache`.671    effect_value_id_cache: FxHashMap<String, ValueId>,672    /// Maps ValueId to FunctionId for function expressions, so we can look up673    /// locally-declared functions when processing Apply effects.674    function_values: FxHashMap<ValueId, FunctionId>,675    /// Cache of function expression signatures, keyed by FunctionId676    function_signature_cache: FxHashMap<FunctionId, AliasingSignature>,677    /// Cache of temporary places created for aliasing signature config temporaries.678    /// Keyed by (lvalue_identifier_id, temp_name) to ensure stable allocation679    /// across fixpoint iterations.680    aliasing_config_temp_cache: FxHashMap<(IdentifierId, String), Place>,681}682683impl Context {684    fn intern_effect(&mut self, effect: AliasingEffect) -> AliasingEffect {685        let hash = hash_effect(&effect);686        self.interned_effects.entry(hash).or_insert(effect).clone()687    }688689    /// Get or create a stable ValueId for a given effect, ensuring fixpoint convergence.690    fn get_or_create_value_id(&mut self, effect: &AliasingEffect) -> ValueId {691        let hash = hash_effect(effect);692        *self693            .effect_value_id_cache694            .entry(hash)695            .or_insert_with(ValueId::new)696    }697}698699struct InstructionSignature {700    effects: Vec<AliasingEffect>,701}702703// =============================================================================704// Helper: hash_effect705// =============================================================================706707fn hash_effect(effect: &AliasingEffect) -> String {708    match effect {709        AliasingEffect::Apply {710            receiver,711            function,712            mutates_function,713            args,714            into,715            ..716        } => {717            let args_str: Vec<String> = args718                .iter()719                .map(|a| match a {720                    PlaceOrSpreadOrHole::Hole => String::new(),721                    PlaceOrSpreadOrHole::Place(p) => format!("{}", p.identifier.0),722                    PlaceOrSpreadOrHole::Spread(s) => format!("...{}", s.place.identifier.0),723                })724                .collect();725            format!(726                "Apply:{}:{}:{}:{}:{}",727                receiver.identifier.0,728                function.identifier.0,729                mutates_function,730                args_str.join(","),731                into.identifier.0732            )733        }734        AliasingEffect::CreateFrom { from, into } => {735            format!("CreateFrom:{}:{}", from.identifier.0, into.identifier.0)736        }737        AliasingEffect::ImmutableCapture { from, into } => format!(738            "ImmutableCapture:{}:{}",739            from.identifier.0, into.identifier.0740        ),741        AliasingEffect::Assign { from, into } => {742            format!("Assign:{}:{}", from.identifier.0, into.identifier.0)743        }744        AliasingEffect::Alias { from, into } => {745            format!("Alias:{}:{}", from.identifier.0, into.identifier.0)746        }747        AliasingEffect::Capture { from, into } => {748            format!("Capture:{}:{}", from.identifier.0, into.identifier.0)749        }750        AliasingEffect::MaybeAlias { from, into } => {751            format!("MaybeAlias:{}:{}", from.identifier.0, into.identifier.0)752        }753        AliasingEffect::Create {754            into,755            value,756            reason,757        } => format!("Create:{}:{:?}:{:?}", into.identifier.0, value, reason),758        AliasingEffect::Freeze { value, reason } => {759            format!("Freeze:{}:{:?}", value.identifier.0, reason)760        }761        AliasingEffect::Impure { place, .. } => format!("Impure:{}", place.identifier.0),762        AliasingEffect::Render { place } => format!("Render:{}", place.identifier.0),763        AliasingEffect::MutateFrozen { place, error } => format!(764            "MutateFrozen:{}:{}:{:?}",765            place.identifier.0, error.reason, error.description766        ),767        AliasingEffect::MutateGlobal { place, error } => format!(768            "MutateGlobal:{}:{}:{:?}",769            place.identifier.0, error.reason, error.description770        ),771        AliasingEffect::Mutate { value, .. } => format!("Mutate:{}", value.identifier.0),772        AliasingEffect::MutateConditionally { value } => {773            format!("MutateConditionally:{}", value.identifier.0)774        }775        AliasingEffect::MutateTransitive { value } => {776            format!("MutateTransitive:{}", value.identifier.0)777        }778        AliasingEffect::MutateTransitiveConditionally { value } => {779            format!("MutateTransitiveConditionally:{}", value.identifier.0)780        }781        AliasingEffect::CreateFunction {782            into,783            function_id,784            captures,785        } => {786            let cap_str: Vec<String> = captures787                .iter()788                .map(|p| format!("{}", p.identifier.0))789                .collect();790            format!(791                "CreateFunction:{}:{}:{}",792                into.identifier.0,793                function_id.0,794                cap_str.join(",")795            )796        }797    }798}799800// =============================================================================801// merge helpers802// =============================================================================803804fn merge_abstract_values(a: &AbstractValue, b: &AbstractValue) -> AbstractValue {805    let kind = merge_value_kinds(a.kind, b.kind);806    if kind == a.kind && kind == b.kind && a.reason.is_superset_of(&b.reason) {807        return a.clone();808    }809    let mut reason = a.reason;810    reason.union_with(&b.reason);811    AbstractValue { kind, reason }812}813814fn merge_value_kinds(a: ValueKind, b: ValueKind) -> ValueKind {815    if a == b {816        return a;817    }818    if a == ValueKind::MaybeFrozen || b == ValueKind::MaybeFrozen {819        return ValueKind::MaybeFrozen;820    }821    if a == ValueKind::Mutable || b == ValueKind::Mutable {822        if a == ValueKind::Frozen || b == ValueKind::Frozen {823            return ValueKind::MaybeFrozen;824        } else if a == ValueKind::Context || b == ValueKind::Context {825            return ValueKind::Context;826        } else {827            return ValueKind::Mutable;828        }829    }830    if a == ValueKind::Context || b == ValueKind::Context {831        if a == ValueKind::Frozen || b == ValueKind::Frozen {832            return ValueKind::MaybeFrozen;833        } else {834            return ValueKind::Context;835        }836    }837    if a == ValueKind::Frozen || b == ValueKind::Frozen {838        return ValueKind::Frozen;839    }840    if a == ValueKind::Global || b == ValueKind::Global {841        return ValueKind::Global;842    }843    ValueKind::Primitive844}845846// =============================================================================847// Pre-passes848// =============================================================================849850fn find_hoisted_context_declarations(851    func: &HirFunction,852    env: &Environment,853) -> FxHashMap<DeclarationId, Option<Place>> {854    let mut hoisted: FxHashMap<DeclarationId, Option<Place>> = FxHashMap::default();855856    fn visit(857        hoisted: &mut FxHashMap<DeclarationId, Option<Place>>,858        place: &Place,859        env: &Environment,860    ) {861        let decl_id = env.identifiers[place.identifier.0 as usize].declaration_id;862        if hoisted.contains_key(&decl_id) && hoisted.get(&decl_id).unwrap().is_none() {863            hoisted.insert(decl_id, Some(place.clone()));864        }865    }866867    for (_block_id, block) in &func.body.blocks {868        for instr_id in &block.instructions {869            let instr = &func.instructions[instr_id.0 as usize];870            match &instr.value {871                InstructionValue::DeclareContext { lvalue, .. } => {872                    let kind = lvalue.kind;873                    if kind == InstructionKind::HoistedConst874                        || kind == InstructionKind::HoistedFunction875                        || kind == InstructionKind::HoistedLet876                    {877                        let decl_id =878                            env.identifiers[lvalue.place.identifier.0 as usize].declaration_id;879                        hoisted.insert(decl_id, None);880                    }881                }882                _ => {883                    for operand in visitors::each_instruction_value_operand(&instr.value, env) {884                        visit(&mut hoisted, &operand, env);885                    }886                }887            }888        }889        for operand in visitors::each_terminal_operand(&block.terminal) {890            visit(&mut hoisted, &operand, env);891        }892    }893    hoisted894}895896fn find_non_mutated_destructure_spreads(897    func: &HirFunction,898    env: &Environment,899) -> FxHashSet<IdentifierId> {900    let mut known_frozen: FxHashSet<IdentifierId> = FxHashSet::default();901    if func.fn_type == ReactFunctionType::Component {902        if let Some(param) = func.params.first() {903            if let ParamPattern::Place(p) = param {904                known_frozen.insert(p.identifier);905            }906        }907    } else {908        for param in &func.params {909            if let ParamPattern::Place(p) = param {910                known_frozen.insert(p.identifier);911            }912        }913    }914915    let mut candidate_non_mutating_spreads: FxHashMap<IdentifierId, IdentifierId> =916        FxHashMap::default();917    for (_block_id, block) in &func.body.blocks {918        if !candidate_non_mutating_spreads.is_empty() {919            for phi in &block.phis {920                for (_, operand) in &phi.operands {921                    if let Some(spread) = candidate_non_mutating_spreads922                        .get(&operand.identifier)923                        .copied()924                    {925                        candidate_non_mutating_spreads.remove(&spread);926                    }927                }928            }929        }930        for instr_id in &block.instructions {931            let instr = &func.instructions[instr_id.0 as usize];932            let lvalue_id = instr.lvalue.identifier;933            match &instr.value {934                InstructionValue::Destructure { lvalue, value, .. } => {935                    if !known_frozen.contains(&value.identifier) {936                        continue;937                    }938                    if !(lvalue.kind == InstructionKind::Let939                        || lvalue.kind == InstructionKind::Const)940                    {941                        continue;942                    }943                    match &lvalue.pattern {944                        react_compiler_hir::Pattern::Object(obj_pat) => {945                            for prop in &obj_pat.properties {946                                if let react_compiler_hir::ObjectPropertyOrSpread::Spread(s) = prop947                                {948                                    candidate_non_mutating_spreads949                                        .insert(s.place.identifier, s.place.identifier);950                                }951                            }952                        }953                        _ => continue,954                    }955                }956                InstructionValue::LoadLocal { place, .. } => {957                    if let Some(spread) = candidate_non_mutating_spreads958                        .get(&place.identifier)959                        .copied()960                    {961                        candidate_non_mutating_spreads.insert(lvalue_id, spread);962                    }963                }964                InstructionValue::StoreLocal {965                    lvalue: sl,966                    value: sv,967                    ..968                } => {969                    if let Some(spread) =970                        candidate_non_mutating_spreads.get(&sv.identifier).copied()971                    {972                        candidate_non_mutating_spreads.insert(lvalue_id, spread);973                        candidate_non_mutating_spreads.insert(sl.place.identifier, spread);974                    }975                }976                InstructionValue::JsxFragment { .. } | InstructionValue::JsxExpression { .. } => {977                    // Passing objects created with spread to jsx can't mutate them978                }979                InstructionValue::PropertyLoad { .. } => {980                    // Properties must be frozen since the original value was frozen981                }982                InstructionValue::CallExpression { callee, .. }983                | InstructionValue::MethodCall {984                    property: callee, ..985                } => {986                    let callee_ty =987                        &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize];988                    if get_hook_kind_for_type(env, callee_ty)989                        .ok()990                        .flatten()991                        .is_some()992                    {993                        if !is_ref_or_ref_value_for_id(env, lvalue_id) {994                            known_frozen.insert(lvalue_id);995                        }996                    } else if !candidate_non_mutating_spreads.is_empty() {997                        for operand in visitors::each_instruction_value_operand(&instr.value, env) {998                            if let Some(spread) = candidate_non_mutating_spreads999                                .get(&operand.identifier)1000                                .copied()1001                            {1002                                candidate_non_mutating_spreads.remove(&spread);1003                            }1004                        }1005                    }1006                }1007                _ => {1008                    if !candidate_non_mutating_spreads.is_empty() {1009                        for operand in visitors::each_instruction_value_operand(&instr.value, env) {1010                            if let Some(spread) = candidate_non_mutating_spreads1011                                .get(&operand.identifier)1012                                .copied()1013                            {1014                                candidate_non_mutating_spreads.remove(&spread);1015                            }1016                        }1017                    }1018                }1019            }1020        }1021    }10221023    let mut non_mutating: FxHashSet<IdentifierId> = FxHashSet::default();1024    for (key, value) in &candidate_non_mutating_spreads {1025        if key == value {1026            non_mutating.insert(*key);1027        }1028    }1029    non_mutating1030}10311032// =============================================================================1033// inferParam1034// =============================================================================10351036fn infer_param(param: &ParamPattern, state: &mut InferenceState, param_kind: &AbstractValue) {1037    let place = match param {1038        ParamPattern::Place(p) => p,1039        ParamPattern::Spread(s) => &s.place,1040    };1041    let value_id = ValueId::new();1042    state.initialize(value_id, param_kind.clone());1043    state.define(place.identifier, value_id);1044}10451046// =============================================================================1047// inferBlock1048// =============================================================================10491050fn infer_block(1051    context: &mut Context,1052    state: &mut InferenceState,1053    block_id: BlockId,1054    func: &mut HirFunction,1055    env: &mut Environment,1056) -> Result<(), CompilerDiagnostic> {1057    let block = &func.body.blocks[&block_id];10581059    // Process phis1060    let phis: Vec<(IdentifierId, IndexMap<BlockId, Place, FxBuildHasher>)> = block1061        .phis1062        .iter()1063        .map(|phi| (phi.place.identifier, phi.operands.clone()))1064        .collect();1065    for (place_id, operands) in &phis {1066        state.infer_phi(*place_id, operands);1067    }10681069    // Process instructions1070    let instr_ids: Vec<u32> = block.instructions.iter().map(|id| id.0).collect();1071    for instr_idx in &instr_ids {1072        let instr_index = *instr_idx as usize;10731074        // Compute signature if not cached1075        if !context.instruction_signature_cache.contains_key(instr_idx) {1076            let sig = compute_signature_for_instruction(1077                context,1078                env,1079                &func.instructions[instr_index],1080                func,1081            );1082            context.instruction_signature_cache.insert(*instr_idx, sig);1083        }10841085        // Apply signature1086        let effects = apply_signature(1087            context,1088            state,1089            *instr_idx,1090            &func.instructions[instr_index],1091            env,1092            func,1093        )?;1094        func.instructions[instr_index].effects = effects;1095    }10961097    // Process terminal1098    // Determine what terminal action to take without holding borrows1099    enum TerminalAction {1100        Try { handler: BlockId, binding: Place },1101        MaybeThrow { handler_id: BlockId },1102        Return,1103        None,1104    }1105    let action = {1106        let block = &func.body.blocks[&block_id];1107        match &block.terminal {1108            react_compiler_hir::Terminal::Try {1109                handler,1110                handler_binding: Some(binding),1111                ..1112            } => TerminalAction::Try {1113                handler: *handler,1114                binding: binding.clone(),1115            },1116            react_compiler_hir::Terminal::MaybeThrow {1117                handler: Some(handler_id),1118                ..1119            } => TerminalAction::MaybeThrow {1120                handler_id: *handler_id,1121            },1122            react_compiler_hir::Terminal::Return { .. } => TerminalAction::Return,1123            _ => TerminalAction::None,1124        }1125    };11261127    match action {1128        TerminalAction::Try { handler, binding } => {1129            context.catch_handlers.insert(handler, binding);1130        }1131        TerminalAction::MaybeThrow { handler_id } => {1132            if let Some(handler_param) = context.catch_handlers.get(&handler_id).cloned() {1133                if state.is_defined(handler_param.identifier) {1134                    let mut terminal_effects: Vec<AliasingEffect> = Vec::new();1135                    for instr_idx in &instr_ids {1136                        let instr = &func.instructions[*instr_idx as usize];1137                        match &instr.value {1138                            InstructionValue::CallExpression { .. }1139                            | InstructionValue::MethodCall { .. } => {1140                                state.append_alias(1141                                    handler_param.identifier,1142                                    instr.lvalue.identifier,1143                                );1144                                let kind = state.kind(instr.lvalue.identifier).kind;1145                                if kind == ValueKind::Mutable || kind == ValueKind::Context {1146                                    terminal_effects.push(context.intern_effect(1147                                        AliasingEffect::Alias {1148                                            from: instr.lvalue.clone(),1149                                            into: handler_param.clone(),1150                                        },1151                                    ));1152                                }1153                            }1154                            _ => {}1155                        }1156                    }1157                    let block_mut = func.body.blocks.get_mut(&block_id).unwrap();1158                    if let react_compiler_hir::Terminal::MaybeThrow {1159                        effects: ref mut term_effects,1160                        ..1161                    } = block_mut.terminal1162                    {1163                        *term_effects = if terminal_effects.is_empty() {1164                            None1165                        } else {1166                            Some(terminal_effects)1167                        };1168                    }1169                }1170            }1171        }1172        TerminalAction::Return => {1173            if !context.is_function_expression {1174                let block_mut = func.body.blocks.get_mut(&block_id).unwrap();1175                if let react_compiler_hir::Terminal::Return {1176                    ref value,1177                    effects: ref mut term_effects,1178                    ..1179                } = block_mut.terminal1180                {1181                    *term_effects = Some(vec![context.intern_effect(AliasingEffect::Freeze {1182                        value: value.clone(),1183                        reason: ValueReason::JsxCaptured,1184                    })]);1185                }1186            }1187        }1188        TerminalAction::None => {}1189    }1190    Ok(())1191}11921193// =============================================================================1194// applySignature1195// =============================================================================11961197fn apply_signature(1198    context: &mut Context,1199    state: &mut InferenceState,1200    instr_idx: u32,1201    instr: &react_compiler_hir::Instruction,1202    env: &mut Environment,1203    func: &HirFunction,1204) -> Result<Option<Vec<AliasingEffect>>, CompilerDiagnostic> {1205    let mut effects: Vec<AliasingEffect> = Vec::new();12061207    // For function instructions, validate frozen mutation1208    match &instr.value {1209        InstructionValue::FunctionExpression { lowered_func, .. }1210        | InstructionValue::ObjectMethod { lowered_func, .. } => {1211            let inner_func = &env.functions[lowered_func.func.0 as usize];1212            if let Some(ref aliasing_effects) = inner_func.aliasing_effects {1213                let context_ids: FxHashSet<IdentifierId> =1214                    inner_func.context.iter().map(|p| p.identifier).collect();1215                for effect in aliasing_effects {1216                    let (mutate_value, is_mutate) = match effect {1217                        AliasingEffect::Mutate { value, .. } => (value, true),1218                        AliasingEffect::MutateTransitive { value } => (value, false),1219                        _ => continue,1220                    };1221                    if !context_ids.contains(&mutate_value.identifier) {1222                        continue;1223                    }1224                    if !state.is_defined(mutate_value.identifier) {1225                        continue;1226                    }1227                    let value_abstract = state.kind(mutate_value.identifier);1228                    if value_abstract.kind == ValueKind::Frozen {1229                        let reason_str = get_write_error_reason(&value_abstract);1230                        let ident = &env.identifiers[mutate_value.identifier.0 as usize];1231                        let variable = match &ident.name {1232                            Some(react_compiler_hir::IdentifierName::Named(n)) => {1233                                format!("`{}`", n)1234                            }1235                            _ => "value".to_string(),1236                        };1237                        let mut diagnostic = CompilerDiagnostic::new(1238                            ErrorCategory::Immutability,1239                            "This value cannot be modified",1240                            Some(reason_str),1241                        );1242                        diagnostic.details.push(1243                            react_compiler_diagnostics::CompilerDiagnosticDetail::Error {1244                                loc: mutate_value.loc,1245                                message: Some(format!("{} cannot be modified", variable)),1246                                identifier_name: None,1247                            },1248                        );1249                        if is_mutate {1250                            if let AliasingEffect::Mutate {1251                                reason: Some(MutationReason::AssignCurrentProperty),1252                                ..1253                            } = effect1254                            {1255                                diagnostic.details.push(react_compiler_diagnostics::CompilerDiagnosticDetail::Hint {1256                                    message: "Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in \"Ref\".".to_string()1257                                });1258                            }1259                        }1260                        effects.push(AliasingEffect::MutateFrozen {1261                            place: mutate_value.clone(),1262                            error: diagnostic,1263                        });1264                    }1265                }1266            }1267        }1268        _ => {}1269    }12701271    // Track which values we've already initialized1272    let mut initialized: FxHashSet<IdentifierId> = FxHashSet::default();12731274    // Get the cached signature effects1275    let sig = context.instruction_signature_cache.get(&instr_idx).unwrap();1276    let sig_effects: Vec<AliasingEffect> = sig.effects.clone();12771278    for effect in &sig_effects {1279        apply_effect(1280            context,1281            state,1282            effect.clone(),1283            &mut initialized,1284            &mut effects,1285            env,1286            func,1287        )?;1288    }12891290    // If lvalue is not yet defined, initialize it with a default value.1291    // The TS version asserts this as an invariant, but the Rust port may have1292    // edge cases where effects don't cover the lvalue (e.g. missing signature entries).1293    if !state.is_defined(instr.lvalue.identifier) {1294        let vid = ValueId(instr.lvalue.identifier.0 | 0x80000000);1295        state.initialize(1296            vid,1297            AbstractValue {1298                kind: ValueKind::Mutable,1299                reason: ValueReasonSet::single(ValueReason::Other),1300            },1301        );1302        state.define(instr.lvalue.identifier, vid);1303    }13041305    Ok(if effects.is_empty() {1306        None1307    } else {1308        Some(effects)1309    })1310}13111312// =============================================================================1313// Transitive freeze helper1314// =============================================================================13151316/// Recursively freeze through FunctionExpression captures. If `value_id`1317/// corresponds to a FunctionExpression, freeze each of its context captures1318/// and recurse into any that are themselves FunctionExpressions. This matches1319/// the TS `freezeValue` → `freeze` → `freezeValue` recursion chain.1320fn freeze_function_captures_transitive(1321    state: &mut InferenceState,1322    context: &Context,1323    env: &Environment,1324    value_id: ValueId,1325    reason: ValueReason,1326) {1327    if let Some(&func_id) = context.function_values.get(&value_id) {1328        let ctx_ids: Vec<IdentifierId> = env.functions[func_id.0 as usize]1329            .context1330            .iter()1331            .map(|p| p.identifier)1332            .collect();1333        for ctx_id in ctx_ids {1334            // Replicate InferenceState::freeze() logic inline —1335            // we need to recurse with context/env which freeze() doesn't have.1336            if !state.variables.contains_key(&ctx_id) {1337                continue;1338            }1339            let kind = state.kind(ctx_id).kind;1340            match kind {1341                ValueKind::Context | ValueKind::Mutable | ValueKind::MaybeFrozen => {1342                    let vids: Vec<ValueId> = state.values_for(ctx_id);1343                    for vid in vids {1344                        state.freeze_value(vid, reason);1345                        // Recurse into nested function captures1346                        freeze_function_captures_transitive(state, context, env, vid, reason);1347                    }1348                }1349                ValueKind::Frozen | ValueKind::Global | ValueKind::Primitive => {1350                    // Already frozen or immutable — no-op1351                }1352            }1353        }1354    }1355}13561357// =============================================================================1358// applyEffect1359// =============================================================================13601361fn apply_effect(1362    context: &mut Context,1363    state: &mut InferenceState,1364    effect: AliasingEffect,1365    initialized: &mut FxHashSet<IdentifierId>,1366    effects: &mut Vec<AliasingEffect>,1367    env: &mut Environment,1368    func: &HirFunction,1369) -> Result<(), CompilerDiagnostic> {1370    let effect = context.intern_effect(effect);1371    match effect {1372        AliasingEffect::Freeze { ref value, reason } => {1373            let did_freeze = state.freeze(value.identifier, reason);1374            if did_freeze {1375                effects.push(effect.clone());1376                // Transitively freeze FunctionExpression captures if enabled1377                // (matches TS freezeValue which recurses into func.context)1378                let enable_transitive = env.config.enable_preserve_existing_memoization_guarantees1379                    || env.config.enable_transitively_freeze_function_expressions;1380                if enable_transitive {1381                    // Recursively freeze through function captures. The TS1382                    // freezeValue() calls freeze() on each capture, which1383                    // calls freezeValue() again — creating a transitive1384                    // closure through arbitrarily nested function captures.1385                    let value_ids: Vec<ValueId> = state.values_for(value.identifier);1386                    for vid in &value_ids {1387                        freeze_function_captures_transitive(state, context, env, *vid, reason);1388                    }1389                }1390            }1391        }1392        AliasingEffect::Create {1393            ref into,1394            value: kind,1395            reason,1396        } => {1397            assert!(1398                !initialized.contains(&into.identifier),1399                "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"1400            );1401            initialized.insert(into.identifier);1402            let value_id = context.get_or_create_value_id(&effect);1403            state.initialize(1404                value_id,1405                AbstractValue {1406                    kind,1407                    reason: ValueReasonSet::single(reason),1408                },1409            );1410            state.define(into.identifier, value_id);1411            effects.push(effect.clone());1412        }1413        AliasingEffect::ImmutableCapture { ref from, .. } => {1414            let kind = state.kind(from.identifier).kind;1415            match kind {1416                ValueKind::Global | ValueKind::Primitive => {1417                    // no-op: don't track data flow for copy types1418                }1419                _ => {1420                    effects.push(effect.clone());1421                }1422            }1423        }1424        AliasingEffect::CreateFrom { ref from, ref into } => {1425            assert!(1426                !initialized.contains(&into.identifier),1427                "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"1428            );1429            initialized.insert(into.identifier);1430            let from_value = state.kind(from.identifier);1431            let value_id = context.get_or_create_value_id(&effect);1432            state.initialize(1433                value_id,1434                AbstractValue {1435                    kind: from_value.kind,1436                    reason: from_value.reason,1437                },1438            );1439            state.define(into.identifier, value_id);1440            match from_value.kind {1441                ValueKind::Primitive | ValueKind::Global => {1442                    let first_reason = primary_reason(&from_value.reason);1443                    effects.push(AliasingEffect::Create {1444                        value: from_value.kind,1445                        into: into.clone(),1446                        reason: first_reason,1447                    });1448                }1449                ValueKind::Frozen => {1450                    let first_reason = primary_reason(&from_value.reason);1451                    effects.push(AliasingEffect::Create {1452                        value: from_value.kind,1453                        into: into.clone(),1454                        reason: first_reason,1455                    });1456                    apply_effect(1457                        context,1458                        state,1459                        AliasingEffect::ImmutableCapture {1460                            from: from.clone(),1461                            into: into.clone(),1462                        },1463                        initialized,1464                        effects,1465                        env,1466                        func,1467                    )?;1468                }1469                _ => {1470                    effects.push(effect.clone());1471                }1472            }1473        }1474        AliasingEffect::CreateFunction {1475            ref captures,1476            function_id,1477            ref into,1478        } => {1479            assert!(1480                !initialized.contains(&into.identifier),1481                "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"1482            );1483            initialized.insert(into.identifier);1484            effects.push(effect.clone());14851486            // Check if function is mutable1487            let has_captures = captures.iter().any(|capture| {1488                if !state.is_defined(capture.identifier) {1489                    return false;1490                }1491                let k = state.kind(capture.identifier).kind;1492                k == ValueKind::Context || k == ValueKind::Mutable1493            });14941495            let inner_func = &env.functions[function_id.0 as usize];1496            let has_tracked_side_effects = inner_func1497                .aliasing_effects1498                .as_ref()1499                .map(|effs| {1500                    effs.iter().any(|e| {1501                        matches!(1502                            e,1503                            AliasingEffect::MutateFrozen { .. }1504                                | AliasingEffect::MutateGlobal { .. }1505                                | AliasingEffect::Impure { .. }1506                        )1507                    })1508                })1509                .unwrap_or(false);15101511            let captures_ref = inner_func1512                .context1513                .iter()1514                .any(|operand| is_ref_or_ref_value_for_id(env, operand.identifier));15151516            let is_mutable = has_captures || has_tracked_side_effects || captures_ref;15171518            // Update context variable effects1519            let context_places: Vec<Place> = inner_func.context.clone();1520            for operand in &context_places {1521                if operand.effect != Effect::Capture {1522                    continue;1523                }1524                if !state.is_defined(operand.identifier) {1525                    continue;1526                }1527                let kind = state.kind(operand.identifier).kind;1528                if kind == ValueKind::Primitive1529                    || kind == ValueKind::Frozen1530                    || kind == ValueKind::Global1531                {1532                    // Downgrade to Read - we need to mutate the inner function1533                    let inner_func_mut = &mut env.functions[function_id.0 as usize];1534                    for ctx in &mut inner_func_mut.context {1535                        if ctx.identifier == operand.identifier && ctx.effect == Effect::Capture {1536                            ctx.effect = Effect::Read;1537                        }1538                    }1539                }1540            }15411542            let value_id = context.get_or_create_value_id(&effect);1543            // Track this value as a function expression so Apply can look it up1544            context.function_values.insert(value_id, function_id);1545            state.initialize(1546                value_id,1547                AbstractValue {1548                    kind: if is_mutable {1549                        ValueKind::Mutable1550                    } else {1551                        ValueKind::Frozen1552                    },1553                    reason: ValueReasonSet::default(),1554                },1555            );1556            state.define(into.identifier, value_id);15571558            for capture in captures {1559                apply_effect(1560                    context,1561                    state,1562                    AliasingEffect::Capture {1563                        from: capture.clone(),1564                        into: into.clone(),1565                    },1566                    initialized,1567                    effects,1568                    env,1569                    func,1570                )?;1571            }1572        }1573        AliasingEffect::MaybeAlias { ref from, ref into }1574        | AliasingEffect::Alias { ref from, ref into }1575        | AliasingEffect::Capture { ref from, ref into } => {1576            let is_capture = matches!(effect, AliasingEffect::Capture { .. });1577            let is_maybe_alias = matches!(effect, AliasingEffect::MaybeAlias { .. });1578            // For Alias, destination must already be initialized (Capture/MaybeAlias are exempt)1579            assert!(1580                is_capture || is_maybe_alias || initialized.contains(&into.identifier),1581                "[InferMutationAliasingEffects] Expected destination to already be initialized within this instruction"1582            );15831584            // Check destination kind1585            let into_kind = state.kind_with_loc(into.identifier, into.loc).kind;1586            let destination_type = match into_kind {1587                ValueKind::Context => Some("context"),1588                ValueKind::Mutable | ValueKind::MaybeFrozen => Some("mutable"),1589                _ => None,1590            };15911592            let from_kind = state.kind_with_loc(from.identifier, from.loc).kind;1593            let source_type = match from_kind {1594                ValueKind::Context => Some("context"),1595                ValueKind::Global | ValueKind::Primitive => None,1596                ValueKind::MaybeFrozen | ValueKind::Frozen => Some("frozen"),1597                ValueKind::Mutable => Some("mutable"),1598            };15991600            if source_type == Some("frozen") {1601                apply_effect(1602                    context,1603                    state,1604                    AliasingEffect::ImmutableCapture {1605                        from: from.clone(),1606                        into: into.clone(),1607                    },1608                    initialized,1609                    effects,1610                    env,1611                    func,1612                )?;1613            } else if (source_type == Some("mutable") && destination_type == Some("mutable"))1614                || is_maybe_alias1615            {1616                effects.push(effect.clone());1617            } else if (source_type == Some("context") && destination_type.is_some())1618                || (source_type == Some("mutable") && destination_type == Some("context"))1619            {1620                apply_effect(1621                    context,1622                    state,1623                    AliasingEffect::MaybeAlias {1624                        from: from.clone(),1625                        into: into.clone(),1626                    },1627                    initialized,1628                    effects,1629                    env,1630                    func,1631                )?;1632            }1633        }1634        AliasingEffect::Assign { ref from, ref into } => {1635            assert!(1636                !initialized.contains(&into.identifier),1637                "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"1638            );1639            initialized.insert(into.identifier);1640            let from_value = state.kind_with_loc(from.identifier, from.loc);1641            match from_value.kind {1642                ValueKind::Frozen => {1643                    apply_effect(1644                        context,1645                        state,1646                        AliasingEffect::ImmutableCapture {1647                            from: from.clone(),1648                            into: into.clone(),1649                        },1650                        initialized,1651                        effects,1652                        env,1653                        func,1654                    )?;1655                    let cache_key =1656                        format!("Assign_frozen:{}:{}", from.identifier.0, into.identifier.0);1657                    let value_id = *context1658                        .effect_value_id_cache1659                        .entry(cache_key)1660                        .or_insert_with(ValueId::new);1661                    state.initialize(1662                        value_id,1663                        AbstractValue {1664                            kind: from_value.kind,1665                            reason: from_value.reason,1666                        },1667                    );1668                    state.define(into.identifier, value_id);1669                }1670                ValueKind::Global | ValueKind::Primitive => {1671                    let cache_key =1672                        format!("Assign_copy:{}:{}", from.identifier.0, into.identifier.0);1673                    let value_id = *context1674                        .effect_value_id_cache1675                        .entry(cache_key)1676                        .or_insert_with(ValueId::new);1677                    state.initialize(1678                        value_id,1679                        AbstractValue {1680                            kind: from_value.kind,1681                            reason: from_value.reason,1682                        },1683                    );1684                    state.define(into.identifier, value_id);1685                }1686                _ => {1687                    state.assign(into.identifier, from.identifier);1688                    effects.push(effect.clone());1689                }1690            }1691        }1692        AliasingEffect::Apply {1693            ref receiver,1694            ref function,1695            mutates_function,1696            ref args,1697            ref into,1698            ref signature,1699            ref loc,1700        } => {1701            // First, check if the callee is a locally-declared function expression1702            // whose aliasing effects we already know (TS lines 1016-1068)1703            if state.is_defined(function.identifier) {1704                let function_values = state.values_for(function.identifier);1705                if function_values.len() == 1 {1706                    let value_id = function_values[0];1707                    if let Some(func_id) = context.function_values.get(&value_id).copied() {1708                        let inner_func = &env.functions[func_id.0 as usize];1709                        if inner_func.aliasing_effects.is_some() {1710                            // Build or retrieve the signature from the function expression1711                            if !context.function_signature_cache.contains_key(&func_id) {1712                                let sig = build_signature_from_function_expression(env, func_id);1713                                context.function_signature_cache.insert(func_id, sig);1714                            }1715                            let sig = context1716                                .function_signature_cache1717                                .get(&func_id)1718                                .unwrap()1719                                .clone();1720                            let inner_func = &env.functions[func_id.0 as usize];1721                            let context_places: Vec<Place> = inner_func.context.clone();1722                            let sig_effects = compute_effects_for_aliasing_signature(1723                                env,1724                                &sig,1725                                into,1726                                receiver,1727                                args,1728                                &context_places,1729                                loc.as_ref(),1730                            )?;1731                            if let Some(sig_effs) = sig_effects {1732                                // Conditionally mutate the function itself first1733                                apply_effect(1734                                    context,1735                                    state,1736                                    AliasingEffect::MutateTransitiveConditionally {1737                                        value: function.clone(),1738                                    },1739                                    initialized,1740                                    effects,1741                                    env,1742                                    func,1743                                )?;1744                                for se in sig_effs {1745                                    apply_effect(1746                                        context,1747                                        state,1748                                        se,1749                                        initialized,1750                                        effects,1751                                        env,1752                                        func,1753                                    )?;1754                                }1755                                return Ok(());1756                            }1757                        }1758                    }1759                }1760            }1761            if let Some(sig) = signature {1762                // Check known_incompatible (TS line 2351-2370)1763                if let Some(ref incompatible_msg) = sig.known_incompatible {1764                    if env.enable_validations() {1765                        let mut diagnostic = CompilerDiagnostic::new(1766                            ErrorCategory::IncompatibleLibrary,1767                            "Use of incompatible library",1768                            Some(1769                                "This API returns functions which cannot be memoized without leading to stale UI. \1770                                 To prevent this, by default React Compiler will skip memoizing this component/hook. \1771                                 However, you may see issues if values from this API are passed to other components/hooks that are \1772                                 memoized".to_string(),1773                            ),1774                        );1775                        diagnostic.details.push(CompilerDiagnosticDetail::Error {1776                            loc: receiver.loc,1777                            message: Some(incompatible_msg.clone()),1778                            identifier_name: None,1779                        });1780                        // TS throws here, aborting compilation for this function1781                        return Err(diagnostic);1782                    }1783                }17841785                if let Some(ref aliasing) = sig.aliasing {1786                    let sig_effects = compute_effects_for_aliasing_signature_config(1787                        env,1788                        aliasing,1789                        into,1790                        receiver,1791                        args,1792                        &[],1793                        loc.as_ref(),1794                        &mut context.aliasing_config_temp_cache,1795                    )?;1796                    if let Some(sig_effs) = sig_effects {1797                        for se in sig_effs {1798                            apply_effect(context, state, se, initialized, effects, env, func)?;1799                        }1800                        return Ok(());1801                    }1802                }18031804                // Legacy signature1805                let mut todo_errors: Vec<react_compiler_diagnostics::CompilerErrorDetail> =1806                    Vec::new();1807                let legacy_effects = compute_effects_for_legacy_signature(1808                    state,1809                    sig,1810                    into,1811                    receiver,1812                    args,1813                    loc.as_ref(),1814                    env,1815                    &context.function_values,1816                    &mut todo_errors,1817                );1818                // Todo errors should short-circuit (TS throws throwTodo)1819                if let Some(err_detail) = todo_errors.into_iter().next() {1820                    return Err(CompilerDiagnostic::from_detail(err_detail));1821                }1822                for le in legacy_effects {1823                    apply_effect(context, state, le, initialized, effects, env, func)?;1824                }1825            } else {1826                // No signature: default behavior1827                apply_effect(1828                    context,1829                    state,1830                    AliasingEffect::Create {1831                        into: into.clone(),1832                        value: ValueKind::Mutable,1833                        reason: ValueReason::Other,1834                    },1835                    initialized,1836                    effects,1837                    env,1838                    func,1839                )?;18401841                let all_operands = build_apply_operands(receiver, function, args);1842                for (operand, _is_function_operand, is_spread) in &all_operands {1843                    // In TS, the check is `operand !== effect.function || effect.mutatesFunction`.1844                    // This compares by reference identity, so for CallExpression/NewExpression1845                    // where receiver === function, BOTH are skipped when !mutatesFunction.1846                    if operand.identifier == function.identifier && !mutates_function {1847                        // Don't mutate callee for non-mutating calls1848                    } else {1849                        apply_effect(1850                            context,1851                            state,1852                            AliasingEffect::MutateTransitiveConditionally {1853                                value: operand.clone(),1854                            },1855                            initialized,1856                            effects,1857                            env,1858                            func,1859                        )?;1860                    }18611862                    if *is_spread {1863                        let ty = &env.types1864                            [env.identifiers[operand.identifier.0 as usize].type_.0 as usize];1865                        if let Some(mutate_iter) = conditionally_mutate_iterator(operand, ty) {1866                            apply_effect(1867                                context,1868                                state,1869                                mutate_iter,1870                                initialized,1871                                effects,1872                                env,1873                                func,1874                            )?;1875                        }1876                    }18771878                    apply_effect(1879                        context,1880                        state,1881                        AliasingEffect::MaybeAlias {1882                            from: operand.clone(),1883                            into: into.clone(),1884                        },1885                        initialized,1886                        effects,1887                        env,1888                        func,1889                    )?;18901891                    // In TS, `other === arg` compares the Place extracted from1892                    // `otherArg` with the original `arg` element. For Identifier1893                    // args, the extracted Place IS the arg, so this is a reference1894                    // identity check. For Spread args, the extracted Place is1895                    // `.place` which is never `===` the Spread wrapper object,1896                    // so NO pairs are skipped when the outer arg is a Spread1897                    // (including self-pairs, producing self-captures).1898                    for (other, _other_is_func, _other_is_spread) in &all_operands {1899                        if !is_spread && other.identifier == operand.identifier {1900                            continue;1901                        }1902                        apply_effect(1903                            context,1904                            state,1905                            AliasingEffect::Capture {1906                                from: operand.clone(),1907                                into: other.clone(),1908                            },1909                            initialized,1910                            effects,1911                            env,1912                            func,1913                        )?;1914                    }1915                }1916            }1917        }1918        ref eff @ (AliasingEffect::Mutate { .. }1919        | AliasingEffect::MutateConditionally { .. }1920        | AliasingEffect::MutateTransitive { .. }1921        | AliasingEffect::MutateTransitiveConditionally { .. }) => {1922            let (mutate_place, variant) = match eff {1923                AliasingEffect::Mutate { value, .. } => (value, MutateVariant::Mutate),1924                AliasingEffect::MutateConditionally { value } => {1925                    (value, MutateVariant::MutateConditionally)1926                }1927                AliasingEffect::MutateTransitive { value } => {1928                    (value, MutateVariant::MutateTransitive)1929                }1930                AliasingEffect::MutateTransitiveConditionally { value } => {1931                    (value, MutateVariant::MutateTransitiveConditionally)1932                }1933                _ => unreachable!(),1934            };1935            let value = mutate_place;1936            let mutation_kind = state.mutate_with_loc(variant, value.identifier, env, value.loc);1937            if mutation_kind == MutationResult::Mutate {1938                effects.push(effect.clone());1939            } else if mutation_kind == MutationResult::MutateRef {1940                // no-op1941            } else if mutation_kind != MutationResult::None1942                && matches!(1943                    variant,1944                    MutateVariant::Mutate | MutateVariant::MutateTransitive1945                )1946            {1947                let abstract_value = state.kind(value.identifier);19481949                let ident = &env.identifiers[value.identifier.0 as usize];1950                let decl_id = ident.declaration_id;19511952                if mutation_kind == MutationResult::MutateFrozen1953                    && context.hoisted_context_declarations.contains_key(&decl_id)1954                {1955                    let variable = match &ident.name {1956                        Some(react_compiler_hir::IdentifierName::Named(n)) => {1957                            Some(format!("`{}`", n))1958                        }1959                        _ => None,1960                    };1961                    let hoisted_access = context1962                        .hoisted_context_declarations1963                        .get(&decl_id)1964                        .cloned()1965                        .flatten();1966                    let mut diagnostic = CompilerDiagnostic::new(1967                        ErrorCategory::Immutability,1968                        "Cannot access variable before it is declared",1969                        Some(format!(1970                            "{} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time",1971                            variable.as_deref().unwrap_or("This variable")1972                        )),1973                    );1974                    if let Some(ref access) = hoisted_access {1975                        if access.loc != value.loc {1976                            diagnostic.details.push(1977                                react_compiler_diagnostics::CompilerDiagnosticDetail::Error {1978                                    loc: access.loc,1979                                    message: Some(format!(1980                                        "{} accessed before it is declared",1981                                        variable.as_deref().unwrap_or("variable")1982                                    )),1983                                    identifier_name: None,1984                                },1985                            );1986                        }1987                    }1988                    diagnostic.details.push(1989                        react_compiler_diagnostics::CompilerDiagnosticDetail::Error {1990                            loc: value.loc,1991                            message: Some(format!(1992                                "{} is declared here",1993                                variable.as_deref().unwrap_or("variable")1994                            )),1995                            identifier_name: None,1996                        },1997                    );1998                    apply_effect(1999                        context,2000                        state,

Findings

✓ No findings reported for this file.

Get this view in your editor

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