compiler/crates/react_compiler_inference/src/infer_mutation_aliasing_effects.rs RUST 3,668 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 3,668.
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, IndexSet};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: hashset_of(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: hashset_of(ValueReason::Other),82        }83    } else {84        AbstractValue {85            kind: ValueKind::Frozen,86            reason: hashset_of(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: hashset_of(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.clone();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)]262struct AbstractValue {263    kind: ValueKind,264    reason: IndexSet<ValueReason, FxBuildHasher>,265}266267fn hashset_of(r: ValueReason) -> IndexSet<ValueReason, FxBuildHasher> {268    let mut s = IndexSet::default();269    s.insert(r);270    s271}272273// =============================================================================274// InferenceState275// =============================================================================276277/// The abstract state tracked during inference.278/// Uses interior mutability via a struct with direct fields (no Rc needed since279/// we always have exclusive access in the pass).280#[derive(Debug, Clone)]281struct InferenceState {282    is_function_expression: bool,283    /// The kind of each value, based on its allocation site284    values: FxHashMap<ValueId, AbstractValue>,285    /// The set of values pointed to by each identifier286    variables: FxHashMap<IdentifierId, FxHashSet<ValueId>>,287    /// Tracks uninitialized identifier access errors (matches TS invariant).288    /// Uses Cell so it can be set from `&self` methods like `kind()`.289    /// Stores (IdentifierId, usage_loc) where usage_loc is the source location290    /// of the Place that triggered the uninitialized access.291    uninitialized_access: std::cell::Cell<Option<(IdentifierId, Option<SourceLocation>)>>,292}293294impl InferenceState {295    fn empty(_env: &Environment, is_function_expression: bool) -> Self {296        InferenceState {297            is_function_expression,298            values: FxHashMap::default(),299            variables: FxHashMap::default(),300            uninitialized_access: std::cell::Cell::new(None),301        }302    }303304    /// Check the kind of a place, recording the usage location for error reporting.305    fn kind_with_loc(306        &self,307        place_id: IdentifierId,308        usage_loc: Option<SourceLocation>,309    ) -> AbstractValue {310        let values = match self.variables.get(&place_id) {311            Some(v) => v,312            None => {313                if self.uninitialized_access.get().is_none() {314                    self.uninitialized_access.set(Some((place_id, usage_loc)));315                }316                return AbstractValue {317                    kind: ValueKind::Mutable,318                    reason: hashset_of(ValueReason::Other),319                };320            }321        };322        let mut merged_kind: Option<AbstractValue> = None;323        for value_id in values {324            let kind = match self.values.get(value_id) {325                Some(k) => k,326                None => continue,327            };328            merged_kind = Some(match merged_kind {329                Some(prev) => merge_abstract_values(&prev, kind),330                None => kind.clone(),331            });332        }333        merged_kind.unwrap_or_else(|| AbstractValue {334            kind: ValueKind::Mutable,335            reason: hashset_of(ValueReason::Other),336        })337    }338339    fn initialize(&mut self, value_id: ValueId, kind: AbstractValue) {340        self.values.insert(value_id, kind);341    }342343    fn define(&mut self, place_id: IdentifierId, value_id: ValueId) {344        let mut set = FxHashSet::default();345        set.insert(value_id);346        self.variables.insert(place_id, set);347    }348349    fn assign(&mut self, into: IdentifierId, from: IdentifierId) {350        let values = match self.variables.get(&from) {351            Some(v) => v.clone(),352            None => {353                // Create a stable value for uninitialized identifiers354                // Use a deterministic ID based on the from identifier355                let vid = ValueId(from.0 | 0x80000000);356                let mut set = FxHashSet::default();357                set.insert(vid);358                if !self.values.contains_key(&vid) {359                    self.values.insert(360                        vid,361                        AbstractValue {362                            kind: ValueKind::Mutable,363                            reason: hashset_of(ValueReason::Other),364                        },365                    );366                }367                set368            }369        };370        self.variables.insert(into, values);371    }372373    fn append_alias(&mut self, place: IdentifierId, value: IdentifierId) {374        let new_values = match self.variables.get(&value) {375            Some(v) => v.clone(),376            None => return,377        };378        let prev_values = match self.variables.get(&place) {379            Some(v) => v.clone(),380            None => return,381        };382        let merged: FxHashSet<ValueId> = prev_values.union(&new_values).copied().collect();383        self.variables.insert(place, merged);384    }385386    fn is_defined(&self, place_id: IdentifierId) -> bool {387        self.variables.contains_key(&place_id)388    }389390    fn values_for(&self, place_id: IdentifierId) -> Vec<ValueId> {391        match self.variables.get(&place_id) {392            Some(values) => values.iter().copied().collect(),393            None => Vec::new(),394        }395    }396397    #[allow(dead_code)]398    fn kind_opt(&self, place_id: IdentifierId) -> Option<AbstractValue> {399        let values = self.variables.get(&place_id)?;400        let mut merged_kind: Option<AbstractValue> = None;401        for value_id in values {402            let kind = self.values.get(value_id)?;403            merged_kind = Some(match merged_kind {404                Some(prev) => merge_abstract_values(&prev, kind),405                None => kind.clone(),406            });407        }408        merged_kind409    }410411    fn kind(&self, place_id: IdentifierId) -> AbstractValue {412        self.kind_with_loc(place_id, None)413    }414415    fn freeze(&mut self, place_id: IdentifierId, reason: ValueReason) -> bool {416        // Check if defined first to avoid recording uninitialized access error.417        // Freeze on undefined identifiers is a no-op — this matches the TS418        // behavior where freeze() is never called on undefined identifiers419        // (the invariant in kind() catches this before freeze is reached).420        if !self.variables.contains_key(&place_id) {421            return false;422        }423        let value = self.kind(place_id);424        match value.kind {425            ValueKind::Context | ValueKind::Mutable | ValueKind::MaybeFrozen => {426                let value_ids: Vec<ValueId> = self.values_for(place_id);427                for vid in value_ids {428                    self.freeze_value(vid, reason);429                }430                true431            }432            ValueKind::Frozen | ValueKind::Global | ValueKind::Primitive => false,433        }434    }435436    fn freeze_value(&mut self, value_id: ValueId, reason: ValueReason) {437        self.values.insert(438            value_id,439            AbstractValue {440                kind: ValueKind::Frozen,441                reason: hashset_of(reason),442            },443        );444        // Note: In TS, this also transitively freezes FunctionExpression captures445        // if enableTransitivelyFreezeFunctionExpressions is set. We skip that here446        // since we don't have access to the function arena from within state.447    }448449    #[allow(dead_code)]450    fn mutate(451        &self,452        variant: MutateVariant,453        place_id: IdentifierId,454        env: &Environment,455    ) -> MutationResult {456        self.mutate_with_loc(variant, place_id, env, None)457    }458459    fn mutate_with_loc(460        &self,461        variant: MutateVariant,462        place_id: IdentifierId,463        env: &Environment,464        usage_loc: Option<SourceLocation>,465    ) -> MutationResult {466        let ty = &env.types[env.identifiers[place_id.0 as usize].type_.0 as usize];467        if react_compiler_hir::is_ref_or_ref_value(ty) {468            return MutationResult::MutateRef;469        }470        let kind = self.kind_with_loc(place_id, usage_loc).kind;471        match variant {472            MutateVariant::MutateConditionally | MutateVariant::MutateTransitiveConditionally => {473                match kind {474                    ValueKind::Mutable | ValueKind::Context => MutationResult::Mutate,475                    _ => MutationResult::None,476                }477            }478            MutateVariant::Mutate | MutateVariant::MutateTransitive => match kind {479                ValueKind::Mutable | ValueKind::Context => MutationResult::Mutate,480                ValueKind::Primitive => MutationResult::None,481                ValueKind::Frozen | ValueKind::MaybeFrozen => MutationResult::MutateFrozen,482                ValueKind::Global => MutationResult::MutateGlobal,483            },484        }485    }486487    fn merge(&self, other: &InferenceState) -> Option<InferenceState> {488        let mut next_values: Option<FxHashMap<ValueId, AbstractValue>> = None;489        let mut next_variables: Option<FxHashMap<IdentifierId, FxHashSet<ValueId>>> = None;490491        // Merge values present in both492        for (id, this_value) in &self.values {493            if let Some(other_value) = other.values.get(id) {494                let merged = merge_abstract_values(this_value, other_value);495                if merged.kind != this_value.kind496                    || !is_superset(&this_value.reason, &merged.reason)497                {498                    let nv = next_values.get_or_insert_with(|| self.values.clone());499                    nv.insert(*id, merged);500                }501            }502        }503        // Add values only in other504        for (id, other_value) in &other.values {505            if !self.values.contains_key(id) {506                let nv = next_values.get_or_insert_with(|| self.values.clone());507                nv.insert(*id, other_value.clone());508            }509        }510511        // Merge variables present in both512        for (id, this_values) in &self.variables {513            if let Some(other_values) = other.variables.get(id) {514                let mut has_new = false;515                for ov in other_values {516                    if !this_values.contains(ov) {517                        has_new = true;518                        break;519                    }520                }521                if has_new {522                    let nvars = next_variables.get_or_insert_with(|| self.variables.clone());523                    let merged: FxHashSet<ValueId> =524                        this_values.union(other_values).copied().collect();525                    nvars.insert(*id, merged);526                }527            }528        }529        // Add variables only in other530        for (id, other_values) in &other.variables {531            if !self.variables.contains_key(id) {532                let nvars = next_variables.get_or_insert_with(|| self.variables.clone());533                nvars.insert(*id, other_values.clone());534            }535        }536537        if next_variables.is_none() && next_values.is_none() {538            None539        } else {540            Some(InferenceState {541                is_function_expression: self.is_function_expression,542                values: next_values.unwrap_or_else(|| self.values.clone()),543                variables: next_variables.unwrap_or_else(|| self.variables.clone()),544                uninitialized_access: std::cell::Cell::new(None),545            })546        }547    }548549    fn infer_phi(550        &mut self,551        phi_place_id: IdentifierId,552        phi_operands: &IndexMap<BlockId, Place, FxBuildHasher>,553    ) {554        let mut values: FxHashSet<ValueId> = FxHashSet::default();555        for (_, operand) in phi_operands {556            if let Some(operand_values) = self.variables.get(&operand.identifier) {557                for v in operand_values {558                    values.insert(*v);559                }560            }561            // If not found, it's a backedge that will be handled later by merge562        }563        if !values.is_empty() {564            self.variables.insert(phi_place_id, values);565        }566    }567}568569fn is_superset(570    a: &IndexSet<ValueReason, FxBuildHasher>,571    b: &IndexSet<ValueReason, FxBuildHasher>,572) -> bool {573    b.iter().all(|x| a.contains(x))574}575576#[derive(Debug, Clone, Copy)]577enum MutateVariant {578    Mutate,579    MutateConditionally,580    MutateTransitive,581    MutateTransitiveConditionally,582}583584#[derive(Debug, Clone, Copy, PartialEq, Eq)]585enum MutationResult {586    None,587    Mutate,588    MutateFrozen,589    MutateGlobal,590    MutateRef,591}592593// =============================================================================594// Context595// =============================================================================596597struct Context {598    interned_effects: FxHashMap<String, AliasingEffect>,599    instruction_signature_cache: FxHashMap<u32, InstructionSignature>,600    catch_handlers: FxHashMap<BlockId, Place>,601    is_function_expression: bool,602    hoisted_context_declarations: FxHashMap<DeclarationId, Option<Place>>,603    non_mutating_spreads: FxHashSet<IdentifierId>,604    /// Cache of ValueIds keyed by effect hash, ensuring stable allocation-site identity605    /// across fixpoint iterations. Mirrors TS `effectInstructionValueCache`.606    effect_value_id_cache: FxHashMap<String, ValueId>,607    /// Maps ValueId to FunctionId for function expressions, so we can look up608    /// locally-declared functions when processing Apply effects.609    function_values: FxHashMap<ValueId, FunctionId>,610    /// Cache of function expression signatures, keyed by FunctionId611    function_signature_cache: FxHashMap<FunctionId, AliasingSignature>,612    /// Cache of temporary places created for aliasing signature config temporaries.613    /// Keyed by (lvalue_identifier_id, temp_name) to ensure stable allocation614    /// across fixpoint iterations.615    aliasing_config_temp_cache: FxHashMap<(IdentifierId, String), Place>,616}617618impl Context {619    fn intern_effect(&mut self, effect: AliasingEffect) -> AliasingEffect {620        let hash = hash_effect(&effect);621        self.interned_effects.entry(hash).or_insert(effect).clone()622    }623624    /// Get or create a stable ValueId for a given effect, ensuring fixpoint convergence.625    fn get_or_create_value_id(&mut self, effect: &AliasingEffect) -> ValueId {626        let hash = hash_effect(effect);627        *self628            .effect_value_id_cache629            .entry(hash)630            .or_insert_with(ValueId::new)631    }632}633634struct InstructionSignature {635    effects: Vec<AliasingEffect>,636}637638// =============================================================================639// Helper: hash_effect640// =============================================================================641642fn hash_effect(effect: &AliasingEffect) -> String {643    match effect {644        AliasingEffect::Apply {645            receiver,646            function,647            mutates_function,648            args,649            into,650            ..651        } => {652            let args_str: Vec<String> = args653                .iter()654                .map(|a| match a {655                    PlaceOrSpreadOrHole::Hole => String::new(),656                    PlaceOrSpreadOrHole::Place(p) => format!("{}", p.identifier.0),657                    PlaceOrSpreadOrHole::Spread(s) => format!("...{}", s.place.identifier.0),658                })659                .collect();660            format!(661                "Apply:{}:{}:{}:{}:{}",662                receiver.identifier.0,663                function.identifier.0,664                mutates_function,665                args_str.join(","),666                into.identifier.0667            )668        }669        AliasingEffect::CreateFrom { from, into } => {670            format!("CreateFrom:{}:{}", from.identifier.0, into.identifier.0)671        }672        AliasingEffect::ImmutableCapture { from, into } => format!(673            "ImmutableCapture:{}:{}",674            from.identifier.0, into.identifier.0675        ),676        AliasingEffect::Assign { from, into } => {677            format!("Assign:{}:{}", from.identifier.0, into.identifier.0)678        }679        AliasingEffect::Alias { from, into } => {680            format!("Alias:{}:{}", from.identifier.0, into.identifier.0)681        }682        AliasingEffect::Capture { from, into } => {683            format!("Capture:{}:{}", from.identifier.0, into.identifier.0)684        }685        AliasingEffect::MaybeAlias { from, into } => {686            format!("MaybeAlias:{}:{}", from.identifier.0, into.identifier.0)687        }688        AliasingEffect::Create {689            into,690            value,691            reason,692        } => format!("Create:{}:{:?}:{:?}", into.identifier.0, value, reason),693        AliasingEffect::Freeze { value, reason } => {694            format!("Freeze:{}:{:?}", value.identifier.0, reason)695        }696        AliasingEffect::Impure { place, .. } => format!("Impure:{}", place.identifier.0),697        AliasingEffect::Render { place } => format!("Render:{}", place.identifier.0),698        AliasingEffect::MutateFrozen { place, error } => format!(699            "MutateFrozen:{}:{}:{:?}",700            place.identifier.0, error.reason, error.description701        ),702        AliasingEffect::MutateGlobal { place, error } => format!(703            "MutateGlobal:{}:{}:{:?}",704            place.identifier.0, error.reason, error.description705        ),706        AliasingEffect::Mutate { value, .. } => format!("Mutate:{}", value.identifier.0),707        AliasingEffect::MutateConditionally { value } => {708            format!("MutateConditionally:{}", value.identifier.0)709        }710        AliasingEffect::MutateTransitive { value } => {711            format!("MutateTransitive:{}", value.identifier.0)712        }713        AliasingEffect::MutateTransitiveConditionally { value } => {714            format!("MutateTransitiveConditionally:{}", value.identifier.0)715        }716        AliasingEffect::CreateFunction {717            into,718            function_id,719            captures,720        } => {721            let cap_str: Vec<String> = captures722                .iter()723                .map(|p| format!("{}", p.identifier.0))724                .collect();725            format!(726                "CreateFunction:{}:{}:{}",727                into.identifier.0,728                function_id.0,729                cap_str.join(",")730            )731        }732    }733}734735// =============================================================================736// merge helpers737// =============================================================================738739fn merge_abstract_values(a: &AbstractValue, b: &AbstractValue) -> AbstractValue {740    let kind = merge_value_kinds(a.kind, b.kind);741    if kind == a.kind && kind == b.kind && is_superset(&a.reason, &b.reason) {742        return a.clone();743    }744    let mut reason = a.reason.clone();745    for r in &b.reason {746        reason.insert(*r);747    }748    AbstractValue { kind, reason }749}750751fn merge_value_kinds(a: ValueKind, b: ValueKind) -> ValueKind {752    if a == b {753        return a;754    }755    if a == ValueKind::MaybeFrozen || b == ValueKind::MaybeFrozen {756        return ValueKind::MaybeFrozen;757    }758    if a == ValueKind::Mutable || b == ValueKind::Mutable {759        if a == ValueKind::Frozen || b == ValueKind::Frozen {760            return ValueKind::MaybeFrozen;761        } else if a == ValueKind::Context || b == ValueKind::Context {762            return ValueKind::Context;763        } else {764            return ValueKind::Mutable;765        }766    }767    if a == ValueKind::Context || b == ValueKind::Context {768        if a == ValueKind::Frozen || b == ValueKind::Frozen {769            return ValueKind::MaybeFrozen;770        } else {771            return ValueKind::Context;772        }773    }774    if a == ValueKind::Frozen || b == ValueKind::Frozen {775        return ValueKind::Frozen;776    }777    if a == ValueKind::Global || b == ValueKind::Global {778        return ValueKind::Global;779    }780    ValueKind::Primitive781}782783// =============================================================================784// Pre-passes785// =============================================================================786787fn find_hoisted_context_declarations(788    func: &HirFunction,789    env: &Environment,790) -> FxHashMap<DeclarationId, Option<Place>> {791    let mut hoisted: FxHashMap<DeclarationId, Option<Place>> = FxHashMap::default();792793    fn visit(794        hoisted: &mut FxHashMap<DeclarationId, Option<Place>>,795        place: &Place,796        env: &Environment,797    ) {798        let decl_id = env.identifiers[place.identifier.0 as usize].declaration_id;799        if hoisted.contains_key(&decl_id) && hoisted.get(&decl_id).unwrap().is_none() {800            hoisted.insert(decl_id, Some(place.clone()));801        }802    }803804    for (_block_id, block) in &func.body.blocks {805        for instr_id in &block.instructions {806            let instr = &func.instructions[instr_id.0 as usize];807            match &instr.value {808                InstructionValue::DeclareContext { lvalue, .. } => {809                    let kind = lvalue.kind;810                    if kind == InstructionKind::HoistedConst811                        || kind == InstructionKind::HoistedFunction812                        || kind == InstructionKind::HoistedLet813                    {814                        let decl_id =815                            env.identifiers[lvalue.place.identifier.0 as usize].declaration_id;816                        hoisted.insert(decl_id, None);817                    }818                }819                _ => {820                    for operand in visitors::each_instruction_value_operand(&instr.value, env) {821                        visit(&mut hoisted, &operand, env);822                    }823                }824            }825        }826        for operand in visitors::each_terminal_operand(&block.terminal) {827            visit(&mut hoisted, &operand, env);828        }829    }830    hoisted831}832833fn find_non_mutated_destructure_spreads(834    func: &HirFunction,835    env: &Environment,836) -> FxHashSet<IdentifierId> {837    let mut known_frozen: FxHashSet<IdentifierId> = FxHashSet::default();838    if func.fn_type == ReactFunctionType::Component {839        if let Some(param) = func.params.first() {840            if let ParamPattern::Place(p) = param {841                known_frozen.insert(p.identifier);842            }843        }844    } else {845        for param in &func.params {846            if let ParamPattern::Place(p) = param {847                known_frozen.insert(p.identifier);848            }849        }850    }851852    let mut candidate_non_mutating_spreads: FxHashMap<IdentifierId, IdentifierId> =853        FxHashMap::default();854    for (_block_id, block) in &func.body.blocks {855        if !candidate_non_mutating_spreads.is_empty() {856            for phi in &block.phis {857                for (_, operand) in &phi.operands {858                    if let Some(spread) = candidate_non_mutating_spreads859                        .get(&operand.identifier)860                        .copied()861                    {862                        candidate_non_mutating_spreads.remove(&spread);863                    }864                }865            }866        }867        for instr_id in &block.instructions {868            let instr = &func.instructions[instr_id.0 as usize];869            let lvalue_id = instr.lvalue.identifier;870            match &instr.value {871                InstructionValue::Destructure { lvalue, value, .. } => {872                    if !known_frozen.contains(&value.identifier) {873                        continue;874                    }875                    if !(lvalue.kind == InstructionKind::Let876                        || lvalue.kind == InstructionKind::Const)877                    {878                        continue;879                    }880                    match &lvalue.pattern {881                        react_compiler_hir::Pattern::Object(obj_pat) => {882                            for prop in &obj_pat.properties {883                                if let react_compiler_hir::ObjectPropertyOrSpread::Spread(s) = prop884                                {885                                    candidate_non_mutating_spreads886                                        .insert(s.place.identifier, s.place.identifier);887                                }888                            }889                        }890                        _ => continue,891                    }892                }893                InstructionValue::LoadLocal { place, .. } => {894                    if let Some(spread) = candidate_non_mutating_spreads895                        .get(&place.identifier)896                        .copied()897                    {898                        candidate_non_mutating_spreads.insert(lvalue_id, spread);899                    }900                }901                InstructionValue::StoreLocal {902                    lvalue: sl,903                    value: sv,904                    ..905                } => {906                    if let Some(spread) =907                        candidate_non_mutating_spreads.get(&sv.identifier).copied()908                    {909                        candidate_non_mutating_spreads.insert(lvalue_id, spread);910                        candidate_non_mutating_spreads.insert(sl.place.identifier, spread);911                    }912                }913                InstructionValue::JsxFragment { .. } | InstructionValue::JsxExpression { .. } => {914                    // Passing objects created with spread to jsx can't mutate them915                }916                InstructionValue::PropertyLoad { .. } => {917                    // Properties must be frozen since the original value was frozen918                }919                InstructionValue::CallExpression { callee, .. }920                | InstructionValue::MethodCall {921                    property: callee, ..922                } => {923                    let callee_ty =924                        &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize];925                    if get_hook_kind_for_type(env, callee_ty)926                        .ok()927                        .flatten()928                        .is_some()929                    {930                        if !is_ref_or_ref_value_for_id(env, lvalue_id) {931                            known_frozen.insert(lvalue_id);932                        }933                    } else if !candidate_non_mutating_spreads.is_empty() {934                        for operand in visitors::each_instruction_value_operand(&instr.value, env) {935                            if let Some(spread) = candidate_non_mutating_spreads936                                .get(&operand.identifier)937                                .copied()938                            {939                                candidate_non_mutating_spreads.remove(&spread);940                            }941                        }942                    }943                }944                _ => {945                    if !candidate_non_mutating_spreads.is_empty() {946                        for operand in visitors::each_instruction_value_operand(&instr.value, env) {947                            if let Some(spread) = candidate_non_mutating_spreads948                                .get(&operand.identifier)949                                .copied()950                            {951                                candidate_non_mutating_spreads.remove(&spread);952                            }953                        }954                    }955                }956            }957        }958    }959960    let mut non_mutating: FxHashSet<IdentifierId> = FxHashSet::default();961    for (key, value) in &candidate_non_mutating_spreads {962        if key == value {963            non_mutating.insert(*key);964        }965    }966    non_mutating967}968969// =============================================================================970// inferParam971// =============================================================================972973fn infer_param(param: &ParamPattern, state: &mut InferenceState, param_kind: &AbstractValue) {974    let place = match param {975        ParamPattern::Place(p) => p,976        ParamPattern::Spread(s) => &s.place,977    };978    let value_id = ValueId::new();979    state.initialize(value_id, param_kind.clone());980    state.define(place.identifier, value_id);981}982983// =============================================================================984// inferBlock985// =============================================================================986987fn infer_block(988    context: &mut Context,989    state: &mut InferenceState,990    block_id: BlockId,991    func: &mut HirFunction,992    env: &mut Environment,993) -> Result<(), CompilerDiagnostic> {994    let block = &func.body.blocks[&block_id];995996    // Process phis997    let phis: Vec<(IdentifierId, IndexMap<BlockId, Place, FxBuildHasher>)> = block998        .phis999        .iter()1000        .map(|phi| (phi.place.identifier, phi.operands.clone()))1001        .collect();1002    for (place_id, operands) in &phis {1003        state.infer_phi(*place_id, operands);1004    }10051006    // Process instructions1007    let instr_ids: Vec<u32> = block.instructions.iter().map(|id| id.0).collect();1008    for instr_idx in &instr_ids {1009        let instr_index = *instr_idx as usize;10101011        // Compute signature if not cached1012        if !context.instruction_signature_cache.contains_key(instr_idx) {1013            let sig = compute_signature_for_instruction(1014                context,1015                env,1016                &func.instructions[instr_index],1017                func,1018            );1019            context.instruction_signature_cache.insert(*instr_idx, sig);1020        }10211022        // Apply signature1023        let effects = apply_signature(1024            context,1025            state,1026            *instr_idx,1027            &func.instructions[instr_index],1028            env,1029            func,1030        )?;1031        func.instructions[instr_index].effects = effects;1032    }10331034    // Process terminal1035    // Determine what terminal action to take without holding borrows1036    enum TerminalAction {1037        Try { handler: BlockId, binding: Place },1038        MaybeThrow { handler_id: BlockId },1039        Return,1040        None,1041    }1042    let action = {1043        let block = &func.body.blocks[&block_id];1044        match &block.terminal {1045            react_compiler_hir::Terminal::Try {1046                handler,1047                handler_binding: Some(binding),1048                ..1049            } => TerminalAction::Try {1050                handler: *handler,1051                binding: binding.clone(),1052            },1053            react_compiler_hir::Terminal::MaybeThrow {1054                handler: Some(handler_id),1055                ..1056            } => TerminalAction::MaybeThrow {1057                handler_id: *handler_id,1058            },1059            react_compiler_hir::Terminal::Return { .. } => TerminalAction::Return,1060            _ => TerminalAction::None,1061        }1062    };10631064    match action {1065        TerminalAction::Try { handler, binding } => {1066            context.catch_handlers.insert(handler, binding);1067        }1068        TerminalAction::MaybeThrow { handler_id } => {1069            if let Some(handler_param) = context.catch_handlers.get(&handler_id).cloned() {1070                if state.is_defined(handler_param.identifier) {1071                    let mut terminal_effects: Vec<AliasingEffect> = Vec::new();1072                    for instr_idx in &instr_ids {1073                        let instr = &func.instructions[*instr_idx as usize];1074                        match &instr.value {1075                            InstructionValue::CallExpression { .. }1076                            | InstructionValue::MethodCall { .. } => {1077                                state.append_alias(1078                                    handler_param.identifier,1079                                    instr.lvalue.identifier,1080                                );1081                                let kind = state.kind(instr.lvalue.identifier).kind;1082                                if kind == ValueKind::Mutable || kind == ValueKind::Context {1083                                    terminal_effects.push(context.intern_effect(1084                                        AliasingEffect::Alias {1085                                            from: instr.lvalue.clone(),1086                                            into: handler_param.clone(),1087                                        },1088                                    ));1089                                }1090                            }1091                            _ => {}1092                        }1093                    }1094                    let block_mut = func.body.blocks.get_mut(&block_id).unwrap();1095                    if let react_compiler_hir::Terminal::MaybeThrow {1096                        effects: ref mut term_effects,1097                        ..1098                    } = block_mut.terminal1099                    {1100                        *term_effects = if terminal_effects.is_empty() {1101                            None1102                        } else {1103                            Some(terminal_effects)1104                        };1105                    }1106                }1107            }1108        }1109        TerminalAction::Return => {1110            if !context.is_function_expression {1111                let block_mut = func.body.blocks.get_mut(&block_id).unwrap();1112                if let react_compiler_hir::Terminal::Return {1113                    ref value,1114                    effects: ref mut term_effects,1115                    ..1116                } = block_mut.terminal1117                {1118                    *term_effects = Some(vec![context.intern_effect(AliasingEffect::Freeze {1119                        value: value.clone(),1120                        reason: ValueReason::JsxCaptured,1121                    })]);1122                }1123            }1124        }1125        TerminalAction::None => {}1126    }1127    Ok(())1128}11291130// =============================================================================1131// applySignature1132// =============================================================================11331134fn apply_signature(1135    context: &mut Context,1136    state: &mut InferenceState,1137    instr_idx: u32,1138    instr: &react_compiler_hir::Instruction,1139    env: &mut Environment,1140    func: &HirFunction,1141) -> Result<Option<Vec<AliasingEffect>>, CompilerDiagnostic> {1142    let mut effects: Vec<AliasingEffect> = Vec::new();11431144    // For function instructions, validate frozen mutation1145    match &instr.value {1146        InstructionValue::FunctionExpression { lowered_func, .. }1147        | InstructionValue::ObjectMethod { lowered_func, .. } => {1148            let inner_func = &env.functions[lowered_func.func.0 as usize];1149            if let Some(ref aliasing_effects) = inner_func.aliasing_effects {1150                let context_ids: FxHashSet<IdentifierId> =1151                    inner_func.context.iter().map(|p| p.identifier).collect();1152                for effect in aliasing_effects {1153                    let (mutate_value, is_mutate) = match effect {1154                        AliasingEffect::Mutate { value, .. } => (value, true),1155                        AliasingEffect::MutateTransitive { value } => (value, false),1156                        _ => continue,1157                    };1158                    if !context_ids.contains(&mutate_value.identifier) {1159                        continue;1160                    }1161                    if !state.is_defined(mutate_value.identifier) {1162                        continue;1163                    }1164                    let value_abstract = state.kind(mutate_value.identifier);1165                    if value_abstract.kind == ValueKind::Frozen {1166                        let reason_str = get_write_error_reason(&value_abstract);1167                        let ident = &env.identifiers[mutate_value.identifier.0 as usize];1168                        let variable = match &ident.name {1169                            Some(react_compiler_hir::IdentifierName::Named(n)) => {1170                                format!("`{}`", n)1171                            }1172                            _ => "value".to_string(),1173                        };1174                        let mut diagnostic = CompilerDiagnostic::new(1175                            ErrorCategory::Immutability,1176                            "This value cannot be modified",1177                            Some(reason_str),1178                        );1179                        diagnostic.details.push(1180                            react_compiler_diagnostics::CompilerDiagnosticDetail::Error {1181                                loc: mutate_value.loc,1182                                message: Some(format!("{} cannot be modified", variable)),1183                                identifier_name: None,1184                            },1185                        );1186                        if is_mutate {1187                            if let AliasingEffect::Mutate {1188                                reason: Some(MutationReason::AssignCurrentProperty),1189                                ..1190                            } = effect1191                            {1192                                diagnostic.details.push(react_compiler_diagnostics::CompilerDiagnosticDetail::Hint {1193                                    message: "Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in \"Ref\".".to_string()1194                                });1195                            }1196                        }1197                        effects.push(AliasingEffect::MutateFrozen {1198                            place: mutate_value.clone(),1199                            error: diagnostic,1200                        });1201                    }1202                }1203            }1204        }1205        _ => {}1206    }12071208    // Track which values we've already initialized1209    let mut initialized: FxHashSet<IdentifierId> = FxHashSet::default();12101211    // Get the cached signature effects1212    let sig = context.instruction_signature_cache.get(&instr_idx).unwrap();1213    let sig_effects: Vec<AliasingEffect> = sig.effects.clone();12141215    for effect in &sig_effects {1216        apply_effect(1217            context,1218            state,1219            effect.clone(),1220            &mut initialized,1221            &mut effects,1222            env,1223            func,1224        )?;1225    }12261227    // If lvalue is not yet defined, initialize it with a default value.1228    // The TS version asserts this as an invariant, but the Rust port may have1229    // edge cases where effects don't cover the lvalue (e.g. missing signature entries).1230    if !state.is_defined(instr.lvalue.identifier) {1231        let vid = ValueId(instr.lvalue.identifier.0 | 0x80000000);1232        state.initialize(1233            vid,1234            AbstractValue {1235                kind: ValueKind::Mutable,1236                reason: hashset_of(ValueReason::Other),1237            },1238        );1239        state.define(instr.lvalue.identifier, vid);1240    }12411242    Ok(if effects.is_empty() {1243        None1244    } else {1245        Some(effects)1246    })1247}12481249// =============================================================================1250// Transitive freeze helper1251// =============================================================================12521253/// Recursively freeze through FunctionExpression captures. If `value_id`1254/// corresponds to a FunctionExpression, freeze each of its context captures1255/// and recurse into any that are themselves FunctionExpressions. This matches1256/// the TS `freezeValue` → `freeze` → `freezeValue` recursion chain.1257fn freeze_function_captures_transitive(1258    state: &mut InferenceState,1259    context: &Context,1260    env: &Environment,1261    value_id: ValueId,1262    reason: ValueReason,1263) {1264    if let Some(&func_id) = context.function_values.get(&value_id) {1265        let ctx_ids: Vec<IdentifierId> = env.functions[func_id.0 as usize]1266            .context1267            .iter()1268            .map(|p| p.identifier)1269            .collect();1270        for ctx_id in ctx_ids {1271            // Replicate InferenceState::freeze() logic inline —1272            // we need to recurse with context/env which freeze() doesn't have.1273            if !state.variables.contains_key(&ctx_id) {1274                continue;1275            }1276            let kind = state.kind(ctx_id).kind;1277            match kind {1278                ValueKind::Context | ValueKind::Mutable | ValueKind::MaybeFrozen => {1279                    let vids: Vec<ValueId> = state.values_for(ctx_id);1280                    for vid in vids {1281                        state.freeze_value(vid, reason);1282                        // Recurse into nested function captures1283                        freeze_function_captures_transitive(state, context, env, vid, reason);1284                    }1285                }1286                ValueKind::Frozen | ValueKind::Global | ValueKind::Primitive => {1287                    // Already frozen or immutable — no-op1288                }1289            }1290        }1291    }1292}12931294// =============================================================================1295// applyEffect1296// =============================================================================12971298fn apply_effect(1299    context: &mut Context,1300    state: &mut InferenceState,1301    effect: AliasingEffect,1302    initialized: &mut FxHashSet<IdentifierId>,1303    effects: &mut Vec<AliasingEffect>,1304    env: &mut Environment,1305    func: &HirFunction,1306) -> Result<(), CompilerDiagnostic> {1307    let effect = context.intern_effect(effect);1308    match effect {1309        AliasingEffect::Freeze { ref value, reason } => {1310            let did_freeze = state.freeze(value.identifier, reason);1311            if did_freeze {1312                effects.push(effect.clone());1313                // Transitively freeze FunctionExpression captures if enabled1314                // (matches TS freezeValue which recurses into func.context)1315                let enable_transitive = env.config.enable_preserve_existing_memoization_guarantees1316                    || env.config.enable_transitively_freeze_function_expressions;1317                if enable_transitive {1318                    // Recursively freeze through function captures. The TS1319                    // freezeValue() calls freeze() on each capture, which1320                    // calls freezeValue() again — creating a transitive1321                    // closure through arbitrarily nested function captures.1322                    let value_ids: Vec<ValueId> = state.values_for(value.identifier);1323                    for vid in &value_ids {1324                        freeze_function_captures_transitive(state, context, env, *vid, reason);1325                    }1326                }1327            }1328        }1329        AliasingEffect::Create {1330            ref into,1331            value: kind,1332            reason,1333        } => {1334            assert!(1335                !initialized.contains(&into.identifier),1336                "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"1337            );1338            initialized.insert(into.identifier);1339            let value_id = context.get_or_create_value_id(&effect);1340            state.initialize(1341                value_id,1342                AbstractValue {1343                    kind,1344                    reason: hashset_of(reason),1345                },1346            );1347            state.define(into.identifier, value_id);1348            effects.push(effect.clone());1349        }1350        AliasingEffect::ImmutableCapture { ref from, .. } => {1351            let kind = state.kind(from.identifier).kind;1352            match kind {1353                ValueKind::Global | ValueKind::Primitive => {1354                    // no-op: don't track data flow for copy types1355                }1356                _ => {1357                    effects.push(effect.clone());1358                }1359            }1360        }1361        AliasingEffect::CreateFrom { ref from, ref into } => {1362            assert!(1363                !initialized.contains(&into.identifier),1364                "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"1365            );1366            initialized.insert(into.identifier);1367            let from_value = state.kind(from.identifier);1368            let value_id = context.get_or_create_value_id(&effect);1369            state.initialize(1370                value_id,1371                AbstractValue {1372                    kind: from_value.kind,1373                    reason: from_value.reason.clone(),1374                },1375            );1376            state.define(into.identifier, value_id);1377            match from_value.kind {1378                ValueKind::Primitive | ValueKind::Global => {1379                    let first_reason = primary_reason(&from_value.reason);1380                    effects.push(AliasingEffect::Create {1381                        value: from_value.kind,1382                        into: into.clone(),1383                        reason: first_reason,1384                    });1385                }1386                ValueKind::Frozen => {1387                    let first_reason = primary_reason(&from_value.reason);1388                    effects.push(AliasingEffect::Create {1389                        value: from_value.kind,1390                        into: into.clone(),1391                        reason: first_reason,1392                    });1393                    apply_effect(1394                        context,1395                        state,1396                        AliasingEffect::ImmutableCapture {1397                            from: from.clone(),1398                            into: into.clone(),1399                        },1400                        initialized,1401                        effects,1402                        env,1403                        func,1404                    )?;1405                }1406                _ => {1407                    effects.push(effect.clone());1408                }1409            }1410        }1411        AliasingEffect::CreateFunction {1412            ref captures,1413            function_id,1414            ref into,1415        } => {1416            assert!(1417                !initialized.contains(&into.identifier),1418                "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"1419            );1420            initialized.insert(into.identifier);1421            effects.push(effect.clone());14221423            // Check if function is mutable1424            let has_captures = captures.iter().any(|capture| {1425                if !state.is_defined(capture.identifier) {1426                    return false;1427                }1428                let k = state.kind(capture.identifier).kind;1429                k == ValueKind::Context || k == ValueKind::Mutable1430            });14311432            let inner_func = &env.functions[function_id.0 as usize];1433            let has_tracked_side_effects = inner_func1434                .aliasing_effects1435                .as_ref()1436                .map(|effs| {1437                    effs.iter().any(|e| {1438                        matches!(1439                            e,1440                            AliasingEffect::MutateFrozen { .. }1441                                | AliasingEffect::MutateGlobal { .. }1442                                | AliasingEffect::Impure { .. }1443                        )1444                    })1445                })1446                .unwrap_or(false);14471448            let captures_ref = inner_func1449                .context1450                .iter()1451                .any(|operand| is_ref_or_ref_value_for_id(env, operand.identifier));14521453            let is_mutable = has_captures || has_tracked_side_effects || captures_ref;14541455            // Update context variable effects1456            let context_places: Vec<Place> = inner_func.context.clone();1457            for operand in &context_places {1458                if operand.effect != Effect::Capture {1459                    continue;1460                }1461                if !state.is_defined(operand.identifier) {1462                    continue;1463                }1464                let kind = state.kind(operand.identifier).kind;1465                if kind == ValueKind::Primitive1466                    || kind == ValueKind::Frozen1467                    || kind == ValueKind::Global1468                {1469                    // Downgrade to Read - we need to mutate the inner function1470                    let inner_func_mut = &mut env.functions[function_id.0 as usize];1471                    for ctx in &mut inner_func_mut.context {1472                        if ctx.identifier == operand.identifier && ctx.effect == Effect::Capture {1473                            ctx.effect = Effect::Read;1474                        }1475                    }1476                }1477            }14781479            let value_id = context.get_or_create_value_id(&effect);1480            // Track this value as a function expression so Apply can look it up1481            context.function_values.insert(value_id, function_id);1482            state.initialize(1483                value_id,1484                AbstractValue {1485                    kind: if is_mutable {1486                        ValueKind::Mutable1487                    } else {1488                        ValueKind::Frozen1489                    },1490                    reason: IndexSet::default(),1491                },1492            );1493            state.define(into.identifier, value_id);14941495            for capture in captures {1496                apply_effect(1497                    context,1498                    state,1499                    AliasingEffect::Capture {1500                        from: capture.clone(),1501                        into: into.clone(),1502                    },1503                    initialized,1504                    effects,1505                    env,1506                    func,1507                )?;1508            }1509        }1510        AliasingEffect::MaybeAlias { ref from, ref into }1511        | AliasingEffect::Alias { ref from, ref into }1512        | AliasingEffect::Capture { ref from, ref into } => {1513            let is_capture = matches!(effect, AliasingEffect::Capture { .. });1514            let is_maybe_alias = matches!(effect, AliasingEffect::MaybeAlias { .. });1515            // For Alias, destination must already be initialized (Capture/MaybeAlias are exempt)1516            assert!(1517                is_capture || is_maybe_alias || initialized.contains(&into.identifier),1518                "[InferMutationAliasingEffects] Expected destination to already be initialized within this instruction"1519            );15201521            // Check destination kind1522            let into_kind = state.kind_with_loc(into.identifier, into.loc).kind;1523            let destination_type = match into_kind {1524                ValueKind::Context => Some("context"),1525                ValueKind::Mutable | ValueKind::MaybeFrozen => Some("mutable"),1526                _ => None,1527            };15281529            let from_kind = state.kind_with_loc(from.identifier, from.loc).kind;1530            let source_type = match from_kind {1531                ValueKind::Context => Some("context"),1532                ValueKind::Global | ValueKind::Primitive => None,1533                ValueKind::MaybeFrozen | ValueKind::Frozen => Some("frozen"),1534                ValueKind::Mutable => Some("mutable"),1535            };15361537            if source_type == Some("frozen") {1538                apply_effect(1539                    context,1540                    state,1541                    AliasingEffect::ImmutableCapture {1542                        from: from.clone(),1543                        into: into.clone(),1544                    },1545                    initialized,1546                    effects,1547                    env,1548                    func,1549                )?;1550            } else if (source_type == Some("mutable") && destination_type == Some("mutable"))1551                || is_maybe_alias1552            {1553                effects.push(effect.clone());1554            } else if (source_type == Some("context") && destination_type.is_some())1555                || (source_type == Some("mutable") && destination_type == Some("context"))1556            {1557                apply_effect(1558                    context,1559                    state,1560                    AliasingEffect::MaybeAlias {1561                        from: from.clone(),1562                        into: into.clone(),1563                    },1564                    initialized,1565                    effects,1566                    env,1567                    func,1568                )?;1569            }1570        }1571        AliasingEffect::Assign { ref from, ref into } => {1572            assert!(1573                !initialized.contains(&into.identifier),1574                "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"1575            );1576            initialized.insert(into.identifier);1577            let from_value = state.kind_with_loc(from.identifier, from.loc);1578            match from_value.kind {1579                ValueKind::Frozen => {1580                    apply_effect(1581                        context,1582                        state,1583                        AliasingEffect::ImmutableCapture {1584                            from: from.clone(),1585                            into: into.clone(),1586                        },1587                        initialized,1588                        effects,1589                        env,1590                        func,1591                    )?;1592                    let cache_key =1593                        format!("Assign_frozen:{}:{}", from.identifier.0, into.identifier.0);1594                    let value_id = *context1595                        .effect_value_id_cache1596                        .entry(cache_key)1597                        .or_insert_with(ValueId::new);1598                    state.initialize(1599                        value_id,1600                        AbstractValue {1601                            kind: from_value.kind,1602                            reason: from_value.reason.clone(),1603                        },1604                    );1605                    state.define(into.identifier, value_id);1606                }1607                ValueKind::Global | ValueKind::Primitive => {1608                    let cache_key =1609                        format!("Assign_copy:{}:{}", from.identifier.0, into.identifier.0);1610                    let value_id = *context1611                        .effect_value_id_cache1612                        .entry(cache_key)1613                        .or_insert_with(ValueId::new);1614                    state.initialize(1615                        value_id,1616                        AbstractValue {1617                            kind: from_value.kind,1618                            reason: from_value.reason.clone(),1619                        },1620                    );1621                    state.define(into.identifier, value_id);1622                }1623                _ => {1624                    state.assign(into.identifier, from.identifier);1625                    effects.push(effect.clone());1626                }1627            }1628        }1629        AliasingEffect::Apply {1630            ref receiver,1631            ref function,1632            mutates_function,1633            ref args,1634            ref into,1635            ref signature,1636            ref loc,1637        } => {1638            // First, check if the callee is a locally-declared function expression1639            // whose aliasing effects we already know (TS lines 1016-1068)1640            if state.is_defined(function.identifier) {1641                let function_values = state.values_for(function.identifier);1642                if function_values.len() == 1 {1643                    let value_id = function_values[0];1644                    if let Some(func_id) = context.function_values.get(&value_id).copied() {1645                        let inner_func = &env.functions[func_id.0 as usize];1646                        if inner_func.aliasing_effects.is_some() {1647                            // Build or retrieve the signature from the function expression1648                            if !context.function_signature_cache.contains_key(&func_id) {1649                                let sig = build_signature_from_function_expression(env, func_id);1650                                context.function_signature_cache.insert(func_id, sig);1651                            }1652                            let sig = context1653                                .function_signature_cache1654                                .get(&func_id)1655                                .unwrap()1656                                .clone();1657                            let inner_func = &env.functions[func_id.0 as usize];1658                            let context_places: Vec<Place> = inner_func.context.clone();1659                            let sig_effects = compute_effects_for_aliasing_signature(1660                                env,1661                                &sig,1662                                into,1663                                receiver,1664                                args,1665                                &context_places,1666                                loc.as_ref(),1667                            )?;1668                            if let Some(sig_effs) = sig_effects {1669                                // Conditionally mutate the function itself first1670                                apply_effect(1671                                    context,1672                                    state,1673                                    AliasingEffect::MutateTransitiveConditionally {1674                                        value: function.clone(),1675                                    },1676                                    initialized,1677                                    effects,1678                                    env,1679                                    func,1680                                )?;1681                                for se in sig_effs {1682                                    apply_effect(1683                                        context,1684                                        state,1685                                        se,1686                                        initialized,1687                                        effects,1688                                        env,1689                                        func,1690                                    )?;1691                                }1692                                return Ok(());1693                            }1694                        }1695                    }1696                }1697            }1698            if let Some(sig) = signature {1699                // Check known_incompatible (TS line 2351-2370)1700                if let Some(ref incompatible_msg) = sig.known_incompatible {1701                    if env.enable_validations() {1702                        let mut diagnostic = CompilerDiagnostic::new(1703                            ErrorCategory::IncompatibleLibrary,1704                            "Use of incompatible library",1705                            Some(1706                                "This API returns functions which cannot be memoized without leading to stale UI. \1707                                 To prevent this, by default React Compiler will skip memoizing this component/hook. \1708                                 However, you may see issues if values from this API are passed to other components/hooks that are \1709                                 memoized".to_string(),1710                            ),1711                        );1712                        diagnostic.details.push(CompilerDiagnosticDetail::Error {1713                            loc: receiver.loc,1714                            message: Some(incompatible_msg.clone()),1715                            identifier_name: None,1716                        });1717                        // TS throws here, aborting compilation for this function1718                        return Err(diagnostic);1719                    }1720                }17211722                if let Some(ref aliasing) = sig.aliasing {1723                    let sig_effects = compute_effects_for_aliasing_signature_config(1724                        env,1725                        aliasing,1726                        into,1727                        receiver,1728                        args,1729                        &[],1730                        loc.as_ref(),1731                        &mut context.aliasing_config_temp_cache,1732                    )?;1733                    if let Some(sig_effs) = sig_effects {1734                        for se in sig_effs {1735                            apply_effect(context, state, se, initialized, effects, env, func)?;1736                        }1737                        return Ok(());1738                    }1739                }17401741                // Legacy signature1742                let mut todo_errors: Vec<react_compiler_diagnostics::CompilerErrorDetail> =1743                    Vec::new();1744                let legacy_effects = compute_effects_for_legacy_signature(1745                    state,1746                    sig,1747                    into,1748                    receiver,1749                    args,1750                    loc.as_ref(),1751                    env,1752                    &context.function_values,1753                    &mut todo_errors,1754                );1755                // Todo errors should short-circuit (TS throws throwTodo)1756                if let Some(err_detail) = todo_errors.into_iter().next() {1757                    return Err(CompilerDiagnostic::from_detail(err_detail));1758                }1759                for le in legacy_effects {1760                    apply_effect(context, state, le, initialized, effects, env, func)?;1761                }1762            } else {1763                // No signature: default behavior1764                apply_effect(1765                    context,1766                    state,1767                    AliasingEffect::Create {1768                        into: into.clone(),1769                        value: ValueKind::Mutable,1770                        reason: ValueReason::Other,1771                    },1772                    initialized,1773                    effects,1774                    env,1775                    func,1776                )?;17771778                let all_operands = build_apply_operands(receiver, function, args);1779                for (operand, _is_function_operand, is_spread) in &all_operands {1780                    // In TS, the check is `operand !== effect.function || effect.mutatesFunction`.1781                    // This compares by reference identity, so for CallExpression/NewExpression1782                    // where receiver === function, BOTH are skipped when !mutatesFunction.1783                    if operand.identifier == function.identifier && !mutates_function {1784                        // Don't mutate callee for non-mutating calls1785                    } else {1786                        apply_effect(1787                            context,1788                            state,1789                            AliasingEffect::MutateTransitiveConditionally {1790                                value: operand.clone(),1791                            },1792                            initialized,1793                            effects,1794                            env,1795                            func,1796                        )?;1797                    }17981799                    if *is_spread {1800                        let ty = &env.types1801                            [env.identifiers[operand.identifier.0 as usize].type_.0 as usize];1802                        if let Some(mutate_iter) = conditionally_mutate_iterator(operand, ty) {1803                            apply_effect(1804                                context,1805                                state,1806                                mutate_iter,1807                                initialized,1808                                effects,1809                                env,1810                                func,1811                            )?;1812                        }1813                    }18141815                    apply_effect(1816                        context,1817                        state,1818                        AliasingEffect::MaybeAlias {1819                            from: operand.clone(),1820                            into: into.clone(),1821                        },1822                        initialized,1823                        effects,1824                        env,1825                        func,1826                    )?;18271828                    // In TS, `other === arg` compares the Place extracted from1829                    // `otherArg` with the original `arg` element. For Identifier1830                    // args, the extracted Place IS the arg, so this is a reference1831                    // identity check. For Spread args, the extracted Place is1832                    // `.place` which is never `===` the Spread wrapper object,1833                    // so NO pairs are skipped when the outer arg is a Spread1834                    // (including self-pairs, producing self-captures).1835                    for (other, _other_is_func, _other_is_spread) in &all_operands {1836                        if !is_spread && other.identifier == operand.identifier {1837                            continue;1838                        }1839                        apply_effect(1840                            context,1841                            state,1842                            AliasingEffect::Capture {1843                                from: operand.clone(),1844                                into: other.clone(),1845                            },1846                            initialized,1847                            effects,1848                            env,1849                            func,1850                        )?;1851                    }1852                }1853            }1854        }1855        ref eff @ (AliasingEffect::Mutate { .. }1856        | AliasingEffect::MutateConditionally { .. }1857        | AliasingEffect::MutateTransitive { .. }1858        | AliasingEffect::MutateTransitiveConditionally { .. }) => {1859            let (mutate_place, variant) = match eff {1860                AliasingEffect::Mutate { value, .. } => (value, MutateVariant::Mutate),1861                AliasingEffect::MutateConditionally { value } => {1862                    (value, MutateVariant::MutateConditionally)1863                }1864                AliasingEffect::MutateTransitive { value } => {1865                    (value, MutateVariant::MutateTransitive)1866                }1867                AliasingEffect::MutateTransitiveConditionally { value } => {1868                    (value, MutateVariant::MutateTransitiveConditionally)1869                }1870                _ => unreachable!(),1871            };1872            let value = mutate_place;1873            let mutation_kind = state.mutate_with_loc(variant, value.identifier, env, value.loc);1874            if mutation_kind == MutationResult::Mutate {1875                effects.push(effect.clone());1876            } else if mutation_kind == MutationResult::MutateRef {1877                // no-op1878            } else if mutation_kind != MutationResult::None1879                && matches!(1880                    variant,1881                    MutateVariant::Mutate | MutateVariant::MutateTransitive1882                )1883            {1884                let abstract_value = state.kind(value.identifier);18851886                let ident = &env.identifiers[value.identifier.0 as usize];1887                let decl_id = ident.declaration_id;18881889                if mutation_kind == MutationResult::MutateFrozen1890                    && context.hoisted_context_declarations.contains_key(&decl_id)1891                {1892                    let variable = match &ident.name {1893                        Some(react_compiler_hir::IdentifierName::Named(n)) => {1894                            Some(format!("`{}`", n))1895                        }1896                        _ => None,1897                    };1898                    let hoisted_access = context1899                        .hoisted_context_declarations1900                        .get(&decl_id)1901                        .cloned()1902                        .flatten();1903                    let mut diagnostic = CompilerDiagnostic::new(1904                        ErrorCategory::Immutability,1905                        "Cannot access variable before it is declared",1906                        Some(format!(1907                            "{} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time",1908                            variable.as_deref().unwrap_or("This variable")1909                        )),1910                    );1911                    if let Some(ref access) = hoisted_access {1912                        if access.loc != value.loc {1913                            diagnostic.details.push(1914                                react_compiler_diagnostics::CompilerDiagnosticDetail::Error {1915                                    loc: access.loc,1916                                    message: Some(format!(1917                                        "{} accessed before it is declared",1918                                        variable.as_deref().unwrap_or("variable")1919                                    )),1920                                    identifier_name: None,1921                                },1922                            );1923                        }1924                    }1925                    diagnostic.details.push(1926                        react_compiler_diagnostics::CompilerDiagnosticDetail::Error {1927                            loc: value.loc,1928                            message: Some(format!(1929                                "{} is declared here",1930                                variable.as_deref().unwrap_or("variable")1931                            )),1932                            identifier_name: None,1933                        },1934                    );1935                    apply_effect(1936                        context,1937                        state,1938                        AliasingEffect::MutateFrozen {1939                            place: value.clone(),1940                            error: diagnostic,1941                        },1942                        initialized,1943                        effects,1944                        env,1945                        func,1946                    )?;1947                } else {1948                    let reason_str = get_write_error_reason(&abstract_value);1949                    let variable = match &ident.name {1950                        Some(react_compiler_hir::IdentifierName::Named(n)) => format!("`{}`", n),1951                        _ => "value".to_string(),1952                    };1953                    let mut diagnostic = CompilerDiagnostic::new(1954                        ErrorCategory::Immutability,1955                        "This value cannot be modified",1956                        Some(reason_str),1957                    );1958                    diagnostic.details.push(1959                        react_compiler_diagnostics::CompilerDiagnosticDetail::Error {1960                            loc: value.loc,1961                            message: Some(format!("{} cannot be modified", variable)),1962                            identifier_name: None,1963                        },1964                    );19651966                    if let AliasingEffect::Mutate {1967                        reason: Some(MutationReason::AssignCurrentProperty),1968                        ..1969                    } = &effect1970                    {1971                        diagnostic.details.push(react_compiler_diagnostics::CompilerDiagnosticDetail::Hint {1972                            message: "Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in \"Ref\".".to_string(),1973                        });1974                    }19751976                    let error_kind = if abstract_value.kind == ValueKind::Frozen {1977                        AliasingEffect::MutateFrozen {1978                            place: value.clone(),1979                            error: diagnostic,1980                        }1981                    } else {1982                        AliasingEffect::MutateGlobal {1983                            place: value.clone(),1984                            error: diagnostic,1985                        }1986                    };1987                    apply_effect(context, state, error_kind, initialized, effects, env, func)?;1988                }1989            }1990        }1991        AliasingEffect::Impure { .. }1992        | AliasingEffect::Render { .. }1993        | AliasingEffect::MutateFrozen { .. }1994        | AliasingEffect::MutateGlobal { .. } => {1995            effects.push(effect.clone());1996        }1997    }1998    Ok(())1999}

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.