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, ¶m_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, ¶m_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.