1use std::collections::HashMap;
15use std::collections::HashSet;
16
17use indexmap::IndexSet;
18use react_compiler_diagnostics::CompilerDiagnostic;
19use react_compiler_diagnostics::CompilerDiagnosticDetail;
20use react_compiler_diagnostics::ErrorCategory;
21use react_compiler_hir::AliasingEffect;
22use react_compiler_hir::AliasingSignature;
23use react_compiler_hir::BlockId;
24use react_compiler_hir::DeclarationId;
25use react_compiler_hir::Effect;
26use react_compiler_hir::FunctionId;
27use react_compiler_hir::HirFunction;
28use react_compiler_hir::IdentifierId;
29use react_compiler_hir::InstructionKind;
30use react_compiler_hir::InstructionValue;
31use react_compiler_hir::MutationReason;
32use react_compiler_hir::ParamPattern;
33use react_compiler_hir::Place;
34use react_compiler_hir::PlaceOrSpread;
35use react_compiler_hir::PlaceOrSpreadOrHole;
36use react_compiler_hir::ReactFunctionType;
37use react_compiler_hir::SourceLocation;
38use react_compiler_hir::Type;
39use react_compiler_hir::environment::Environment;
40use react_compiler_hir::object_shape::BUILT_IN_ARRAY_ID;
41use react_compiler_hir::object_shape::BUILT_IN_MAP_ID;
42use react_compiler_hir::object_shape::BUILT_IN_SET_ID;
43use react_compiler_hir::object_shape::FunctionSignature;
44use react_compiler_hir::object_shape::HookKind;
45use react_compiler_hir::type_config::ValueKind;
46use react_compiler_hir::type_config::ValueReason;
47use react_compiler_hir::visitors;
48
49pub fn infer_mutation_aliasing_effects(
57 func: &mut HirFunction,
58 env: &mut Environment,
59 is_function_expression: bool,
60) -> Result<(), CompilerDiagnostic> {
61 let mut initial_state = InferenceState::empty(env, is_function_expression);
62
63 let mut states_by_block: HashMap<BlockId, InferenceState> = HashMap::new();
65
66 for ctx_place in &func.context {
68 let value_id = ValueId::new();
69 initial_state.initialize(
70 value_id,
71 AbstractValue {
72 kind: ValueKind::Context,
73 reason: hashset_of(ValueReason::Other),
74 },
75 );
76 initial_state.define(ctx_place.identifier, value_id);
77 }
78
79 let param_kind: AbstractValue = if is_function_expression {
80 AbstractValue {
81 kind: ValueKind::Mutable,
82 reason: hashset_of(ValueReason::Other),
83 }
84 } else {
85 AbstractValue {
86 kind: ValueKind::Frozen,
87 reason: hashset_of(ValueReason::ReactiveFunctionArgument),
88 }
89 };
90
91 if func.fn_type == ReactFunctionType::Component {
92 let params_len = func.params.len();
94 if params_len > 0 {
95 infer_param(&func.params[0], &mut initial_state, ¶m_kind);
96 }
97 if params_len > 1 {
98 let ref_place = match &func.params[1] {
99 ParamPattern::Place(p) => p,
100 ParamPattern::Spread(s) => &s.place,
101 };
102 let value_id = ValueId::new();
103 initial_state.initialize(
104 value_id,
105 AbstractValue {
106 kind: ValueKind::Mutable,
107 reason: hashset_of(ValueReason::Other),
108 },
109 );
110 initial_state.define(ref_place.identifier, value_id);
111 }
112 } else {
113 for param in &func.params {
114 infer_param(param, &mut initial_state, ¶m_kind);
115 }
116 }
117
118 let mut queued_states: indexmap::IndexMap<BlockId, InferenceState> = indexmap::IndexMap::new();
119
120 fn queue(
122 queued_states: &mut indexmap::IndexMap<BlockId, InferenceState>,
123 states_by_block: &HashMap<BlockId, InferenceState>,
124 block_id: BlockId,
125 state: InferenceState,
126 ) {
127 if let Some(queued_state) = queued_states.get(&block_id) {
128 let merged = queued_state.merge(&state);
129 let new_state = merged.unwrap_or_else(|| queued_state.clone());
130 queued_states.insert(block_id, new_state);
131 } else {
132 let prev_state = states_by_block.get(&block_id);
133 if let Some(prev) = prev_state {
134 let next_state = prev.merge(&state);
135 if let Some(next) = next_state {
136 queued_states.insert(block_id, next);
137 }
138 } else {
139 queued_states.insert(block_id, state);
140 }
141 }
142 }
143
144 queue(
145 &mut queued_states,
146 &states_by_block,
147 func.body.entry,
148 initial_state,
149 );
150
151 let hoisted_context_declarations = find_hoisted_context_declarations(func, env);
152 let non_mutating_spreads = find_non_mutated_destructure_spreads(func, env);
153
154 let mut context = Context {
155 interned_effects: HashMap::new(),
156 instruction_signature_cache: HashMap::new(),
157 catch_handlers: HashMap::new(),
158 is_function_expression,
159 hoisted_context_declarations,
160 non_mutating_spreads,
161 effect_value_id_cache: HashMap::new(),
162 function_values: HashMap::new(),
163 function_signature_cache: HashMap::new(),
164 aliasing_config_temp_cache: HashMap::new(),
165 };
166
167 let mut iteration_count = 0;
168
169 while !queued_states.is_empty() {
170 iteration_count += 1;
171 if iteration_count > 100 {
172 return Err(CompilerDiagnostic::new(
173 ErrorCategory::Invariant,
174 "[InferMutationAliasingEffects] Potential infinite loop: \
175 A value, temporary place, or effect was not cached properly",
176 None,
177 ));
178 }
179
180 let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect();
182 for block_id in block_ids {
183 let incoming_state = match queued_states.swap_remove(&block_id) {
184 Some(s) => s,
185 None => continue,
186 };
187
188 states_by_block.insert(block_id, incoming_state.clone());
189 let mut state = incoming_state.clone();
190
191 infer_block(&mut context, &mut state, block_id, func, env)?;
192
193 if let Some((uninitialized_id, usage_loc)) = state.uninitialized_access.get() {
196 let ident_info = env.identifiers.get(uninitialized_id.0 as usize);
197 let name = ident_info
198 .and_then(|ident| ident.name.as_ref())
199 .map(|n| n.value().to_string())
200 .unwrap_or_else(|| "".to_string());
201 let error_loc = usage_loc.or_else(|| ident_info.and_then(|i| i.loc));
203 let type_str = ident_info
205 .map(|ident| {
206 let ty = &env.types[ident.type_.0 as usize];
207 format_type_for_print(ty)
208 })
209 .unwrap_or_default();
210 let description = format!("<unknown> {}${}{}", name, uninitialized_id.0, type_str);
211 let diag = CompilerDiagnostic::new(
212 ErrorCategory::Invariant,
213 "[InferMutationAliasingEffects] Expected value kind to be initialized",
214 Some(description),
215 )
216 .with_detail(CompilerDiagnosticDetail::Error {
217 loc: error_loc,
218 message: Some("this is uninitialized".to_string()),
219 identifier_name: None,
220 });
221 return Err(diag);
222 }
223
224 let successors = terminal_successors(&func.body.blocks[&block_id].terminal);
226 for next_block_id in successors {
227 queue(
228 &mut queued_states,
229 &states_by_block,
230 next_block_id,
231 state.clone(),
232 );
233 }
234 }
235 }
236
237 Ok(())
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
246struct ValueId(u32);
247
248use std::sync::atomic::AtomicU32;
249use std::sync::atomic::Ordering;
250static NEXT_VALUE_ID: AtomicU32 = AtomicU32::new(1);
251
252impl ValueId {
253 fn new() -> Self {
254 ValueId(NEXT_VALUE_ID.fetch_add(1, Ordering::Relaxed))
255 }
256}
257
258#[derive(Debug, Clone)]
263struct AbstractValue {
264 kind: ValueKind,
265 reason: IndexSet<ValueReason>,
266}
267
268fn hashset_of(r: ValueReason) -> IndexSet<ValueReason> {
269 let mut s = IndexSet::new();
270 s.insert(r);
271 s
272}
273
274#[derive(Debug, Clone)]
282struct InferenceState {
283 is_function_expression: bool,
284 values: HashMap<ValueId, AbstractValue>,
286 variables: HashMap<IdentifierId, HashSet<ValueId>>,
288 uninitialized_access: std::cell::Cell<Option<(IdentifierId, Option<SourceLocation>)>>,
293}
294
295impl InferenceState {
296 fn empty(_env: &Environment, is_function_expression: bool) -> Self {
297 InferenceState {
298 is_function_expression,
299 values: HashMap::new(),
300 variables: HashMap::new(),
301 uninitialized_access: std::cell::Cell::new(None),
302 }
303 }
304
305 fn kind_with_loc(
307 &self,
308 place_id: IdentifierId,
309 usage_loc: Option<SourceLocation>,
310 ) -> AbstractValue {
311 let values = match self.variables.get(&place_id) {
312 Some(v) => v,
313 None => {
314 if self.uninitialized_access.get().is_none() {
315 self.uninitialized_access.set(Some((place_id, usage_loc)));
316 }
317 return AbstractValue {
318 kind: ValueKind::Mutable,
319 reason: hashset_of(ValueReason::Other),
320 };
321 }
322 };
323 let mut merged_kind: Option<AbstractValue> = None;
324 for value_id in values {
325 let kind = match self.values.get(value_id) {
326 Some(k) => k,
327 None => continue,
328 };
329 merged_kind = Some(match merged_kind {
330 Some(prev) => merge_abstract_values(&prev, kind),
331 None => kind.clone(),
332 });
333 }
334 merged_kind.unwrap_or_else(|| AbstractValue {
335 kind: ValueKind::Mutable,
336 reason: hashset_of(ValueReason::Other),
337 })
338 }
339
340 fn initialize(&mut self, value_id: ValueId, kind: AbstractValue) {
341 self.values.insert(value_id, kind);
342 }
343
344 fn define(&mut self, place_id: IdentifierId, value_id: ValueId) {
345 let mut set = HashSet::new();
346 set.insert(value_id);
347 self.variables.insert(place_id, set);
348 }
349
350 fn assign(&mut self, into: IdentifierId, from: IdentifierId) {
351 let values = match self.variables.get(&from) {
352 Some(v) => v.clone(),
353 None => {
354 let vid = ValueId(from.0 | 0x80000000);
357 let mut set = HashSet::new();
358 set.insert(vid);
359 if !self.values.contains_key(&vid) {
360 self.values.insert(
361 vid,
362 AbstractValue {
363 kind: ValueKind::Mutable,
364 reason: hashset_of(ValueReason::Other),
365 },
366 );
367 }
368 set
369 }
370 };
371 self.variables.insert(into, values);
372 }
373
374 fn append_alias(&mut self, place: IdentifierId, value: IdentifierId) {
375 let new_values = match self.variables.get(&value) {
376 Some(v) => v.clone(),
377 None => return,
378 };
379 let prev_values = match self.variables.get(&place) {
380 Some(v) => v.clone(),
381 None => return,
382 };
383 let merged: HashSet<ValueId> = prev_values.union(&new_values).copied().collect();
384 self.variables.insert(place, merged);
385 }
386
387 fn is_defined(&self, place_id: IdentifierId) -> bool {
388 self.variables.contains_key(&place_id)
389 }
390
391 fn values_for(&self, place_id: IdentifierId) -> Vec<ValueId> {
392 match self.variables.get(&place_id) {
393 Some(values) => values.iter().copied().collect(),
394 None => Vec::new(),
395 }
396 }
397
398 #[allow(dead_code)]
399 fn kind_opt(&self, place_id: IdentifierId) -> Option<AbstractValue> {
400 let values = self.variables.get(&place_id)?;
401 let mut merged_kind: Option<AbstractValue> = None;
402 for value_id in values {
403 let kind = self.values.get(value_id)?;
404 merged_kind = Some(match merged_kind {
405 Some(prev) => merge_abstract_values(&prev, kind),
406 None => kind.clone(),
407 });
408 }
409 merged_kind
410 }
411
412 fn kind(&self, place_id: IdentifierId) -> AbstractValue {
413 self.kind_with_loc(place_id, None)
414 }
415
416 fn freeze(&mut self, place_id: IdentifierId, reason: ValueReason) -> bool {
417 if !self.variables.contains_key(&place_id) {
422 return false;
423 }
424 let value = self.kind(place_id);
425 match value.kind {
426 ValueKind::Context | ValueKind::Mutable | ValueKind::MaybeFrozen => {
427 let value_ids: Vec<ValueId> = self.values_for(place_id);
428 for vid in value_ids {
429 self.freeze_value(vid, reason);
430 }
431 true
432 }
433 ValueKind::Frozen | ValueKind::Global | ValueKind::Primitive => false,
434 }
435 }
436
437 fn freeze_value(&mut self, value_id: ValueId, reason: ValueReason) {
438 self.values.insert(
439 value_id,
440 AbstractValue {
441 kind: ValueKind::Frozen,
442 reason: hashset_of(reason),
443 },
444 );
445 }
449
450 #[allow(dead_code)]
451 fn mutate(
452 &self,
453 variant: MutateVariant,
454 place_id: IdentifierId,
455 env: &Environment,
456 ) -> MutationResult {
457 self.mutate_with_loc(variant, place_id, env, None)
458 }
459
460 fn mutate_with_loc(
461 &self,
462 variant: MutateVariant,
463 place_id: IdentifierId,
464 env: &Environment,
465 usage_loc: Option<SourceLocation>,
466 ) -> MutationResult {
467 let ty = &env.types[env.identifiers[place_id.0 as usize].type_.0 as usize];
468 if react_compiler_hir::is_ref_or_ref_value(ty) {
469 return MutationResult::MutateRef;
470 }
471 let kind = self.kind_with_loc(place_id, usage_loc).kind;
472 match variant {
473 MutateVariant::MutateConditionally | MutateVariant::MutateTransitiveConditionally => {
474 match kind {
475 ValueKind::Mutable | ValueKind::Context => MutationResult::Mutate,
476 _ => MutationResult::None,
477 }
478 }
479 MutateVariant::Mutate | MutateVariant::MutateTransitive => match kind {
480 ValueKind::Mutable | ValueKind::Context => MutationResult::Mutate,
481 ValueKind::Primitive => MutationResult::None,
482 ValueKind::Frozen | ValueKind::MaybeFrozen => MutationResult::MutateFrozen,
483 ValueKind::Global => MutationResult::MutateGlobal,
484 },
485 }
486 }
487
488 fn merge(&self, other: &InferenceState) -> Option<InferenceState> {
489 let mut next_values: Option<HashMap<ValueId, AbstractValue>> = None;
490 let mut next_variables: Option<HashMap<IdentifierId, HashSet<ValueId>>> = None;
491
492 for (id, this_value) in &self.values {
494 if let Some(other_value) = other.values.get(id) {
495 let merged = merge_abstract_values(this_value, other_value);
496 if merged.kind != this_value.kind
497 || !is_superset(&this_value.reason, &merged.reason)
498 {
499 let nv = next_values.get_or_insert_with(|| self.values.clone());
500 nv.insert(*id, merged);
501 }
502 }
503 }
504 for (id, other_value) in &other.values {
506 if !self.values.contains_key(id) {
507 let nv = next_values.get_or_insert_with(|| self.values.clone());
508 nv.insert(*id, other_value.clone());
509 }
510 }
511
512 for (id, this_values) in &self.variables {
514 if let Some(other_values) = other.variables.get(id) {
515 let mut has_new = false;
516 for ov in other_values {
517 if !this_values.contains(ov) {
518 has_new = true;
519 break;
520 }
521 }
522 if has_new {
523 let nvars = next_variables.get_or_insert_with(|| self.variables.clone());
524 let merged: HashSet<ValueId> =
525 this_values.union(other_values).copied().collect();
526 nvars.insert(*id, merged);
527 }
528 }
529 }
530 for (id, other_values) in &other.variables {
532 if !self.variables.contains_key(id) {
533 let nvars = next_variables.get_or_insert_with(|| self.variables.clone());
534 nvars.insert(*id, other_values.clone());
535 }
536 }
537
538 if next_variables.is_none() && next_values.is_none() {
539 None
540 } else {
541 Some(InferenceState {
542 is_function_expression: self.is_function_expression,
543 values: next_values.unwrap_or_else(|| self.values.clone()),
544 variables: next_variables.unwrap_or_else(|| self.variables.clone()),
545 uninitialized_access: std::cell::Cell::new(None),
546 })
547 }
548 }
549
550 fn infer_phi(
551 &mut self,
552 phi_place_id: IdentifierId,
553 phi_operands: &indexmap::IndexMap<BlockId, Place>,
554 ) {
555 let mut values: HashSet<ValueId> = HashSet::new();
556 for (_, operand) in phi_operands {
557 if let Some(operand_values) = self.variables.get(&operand.identifier) {
558 for v in operand_values {
559 values.insert(*v);
560 }
561 }
562 }
564 if !values.is_empty() {
565 self.variables.insert(phi_place_id, values);
566 }
567 }
568}
569
570fn is_superset(a: &IndexSet<ValueReason>, b: &IndexSet<ValueReason>) -> bool {
571 b.iter().all(|x| a.contains(x))
572}
573
574#[derive(Debug, Clone, Copy)]
575enum MutateVariant {
576 Mutate,
577 MutateConditionally,
578 MutateTransitive,
579 MutateTransitiveConditionally,
580}
581
582#[derive(Debug, Clone, Copy, PartialEq, Eq)]
583enum MutationResult {
584 None,
585 Mutate,
586 MutateFrozen,
587 MutateGlobal,
588 MutateRef,
589}
590
591struct Context {
596 interned_effects: HashMap<String, AliasingEffect>,
597 instruction_signature_cache: HashMap<u32, InstructionSignature>,
598 catch_handlers: HashMap<BlockId, Place>,
599 is_function_expression: bool,
600 hoisted_context_declarations: HashMap<DeclarationId, Option<Place>>,
601 non_mutating_spreads: HashSet<IdentifierId>,
602 effect_value_id_cache: HashMap<String, ValueId>,
605 function_values: HashMap<ValueId, FunctionId>,
608 function_signature_cache: HashMap<FunctionId, AliasingSignature>,
610 aliasing_config_temp_cache: HashMap<(IdentifierId, String), Place>,
614}
615
616impl Context {
617 fn intern_effect(&mut self, effect: AliasingEffect) -> AliasingEffect {
618 let hash = hash_effect(&effect);
619 self.interned_effects.entry(hash).or_insert(effect).clone()
620 }
621
622 fn get_or_create_value_id(&mut self, effect: &AliasingEffect) -> ValueId {
624 let hash = hash_effect(effect);
625 *self
626 .effect_value_id_cache
627 .entry(hash)
628 .or_insert_with(ValueId::new)
629 }
630}
631
632struct InstructionSignature {
633 effects: Vec<AliasingEffect>,
634}
635
636fn hash_effect(effect: &AliasingEffect) -> String {
641 match effect {
642 AliasingEffect::Apply {
643 receiver,
644 function,
645 mutates_function,
646 args,
647 into,
648 ..
649 } => {
650 let args_str: Vec<String> = args
651 .iter()
652 .map(|a| match a {
653 PlaceOrSpreadOrHole::Hole => String::new(),
654 PlaceOrSpreadOrHole::Place(p) => format!("{}", p.identifier.0),
655 PlaceOrSpreadOrHole::Spread(s) => format!("...{}", s.place.identifier.0),
656 })
657 .collect();
658 format!(
659 "Apply:{}:{}:{}:{}:{}",
660 receiver.identifier.0,
661 function.identifier.0,
662 mutates_function,
663 args_str.join(","),
664 into.identifier.0
665 )
666 }
667 AliasingEffect::CreateFrom { from, into } => {
668 format!("CreateFrom:{}:{}", from.identifier.0, into.identifier.0)
669 }
670 AliasingEffect::ImmutableCapture { from, into } => format!(
671 "ImmutableCapture:{}:{}",
672 from.identifier.0, into.identifier.0
673 ),
674 AliasingEffect::Assign { from, into } => {
675 format!("Assign:{}:{}", from.identifier.0, into.identifier.0)
676 }
677 AliasingEffect::Alias { from, into } => {
678 format!("Alias:{}:{}", from.identifier.0, into.identifier.0)
679 }
680 AliasingEffect::Capture { from, into } => {
681 format!("Capture:{}:{}", from.identifier.0, into.identifier.0)
682 }
683 AliasingEffect::MaybeAlias { from, into } => {
684 format!("MaybeAlias:{}:{}", from.identifier.0, into.identifier.0)
685 }
686 AliasingEffect::Create {
687 into,
688 value,
689 reason,
690 } => format!("Create:{}:{:?}:{:?}", into.identifier.0, value, reason),
691 AliasingEffect::Freeze { value, reason } => {
692 format!("Freeze:{}:{:?}", value.identifier.0, reason)
693 }
694 AliasingEffect::Impure { place, .. } => format!("Impure:{}", place.identifier.0),
695 AliasingEffect::Render { place } => format!("Render:{}", place.identifier.0),
696 AliasingEffect::MutateFrozen { place, error } => format!(
697 "MutateFrozen:{}:{}:{:?}",
698 place.identifier.0, error.reason, error.description
699 ),
700 AliasingEffect::MutateGlobal { place, error } => format!(
701 "MutateGlobal:{}:{}:{:?}",
702 place.identifier.0, error.reason, error.description
703 ),
704 AliasingEffect::Mutate { value, .. } => format!("Mutate:{}", value.identifier.0),
705 AliasingEffect::MutateConditionally { value } => {
706 format!("MutateConditionally:{}", value.identifier.0)
707 }
708 AliasingEffect::MutateTransitive { value } => {
709 format!("MutateTransitive:{}", value.identifier.0)
710 }
711 AliasingEffect::MutateTransitiveConditionally { value } => {
712 format!("MutateTransitiveConditionally:{}", value.identifier.0)
713 }
714 AliasingEffect::CreateFunction {
715 into,
716 function_id,
717 captures,
718 } => {
719 let cap_str: Vec<String> = captures
720 .iter()
721 .map(|p| format!("{}", p.identifier.0))
722 .collect();
723 format!(
724 "CreateFunction:{}:{}:{}",
725 into.identifier.0,
726 function_id.0,
727 cap_str.join(",")
728 )
729 }
730 }
731}
732
733fn merge_abstract_values(a: &AbstractValue, b: &AbstractValue) -> AbstractValue {
738 let kind = merge_value_kinds(a.kind, b.kind);
739 if kind == a.kind && kind == b.kind && is_superset(&a.reason, &b.reason) {
740 return a.clone();
741 }
742 let mut reason = a.reason.clone();
743 for r in &b.reason {
744 reason.insert(*r);
745 }
746 AbstractValue { kind, reason }
747}
748
749fn merge_value_kinds(a: ValueKind, b: ValueKind) -> ValueKind {
750 if a == b {
751 return a;
752 }
753 if a == ValueKind::MaybeFrozen || b == ValueKind::MaybeFrozen {
754 return ValueKind::MaybeFrozen;
755 }
756 if a == ValueKind::Mutable || b == ValueKind::Mutable {
757 if a == ValueKind::Frozen || b == ValueKind::Frozen {
758 return ValueKind::MaybeFrozen;
759 } else if a == ValueKind::Context || b == ValueKind::Context {
760 return ValueKind::Context;
761 } else {
762 return ValueKind::Mutable;
763 }
764 }
765 if a == ValueKind::Context || b == ValueKind::Context {
766 if a == ValueKind::Frozen || b == ValueKind::Frozen {
767 return ValueKind::MaybeFrozen;
768 } else {
769 return ValueKind::Context;
770 }
771 }
772 if a == ValueKind::Frozen || b == ValueKind::Frozen {
773 return ValueKind::Frozen;
774 }
775 if a == ValueKind::Global || b == ValueKind::Global {
776 return ValueKind::Global;
777 }
778 ValueKind::Primitive
779}
780
781fn find_hoisted_context_declarations(
786 func: &HirFunction,
787 env: &Environment,
788) -> HashMap<DeclarationId, Option<Place>> {
789 let mut hoisted: HashMap<DeclarationId, Option<Place>> = HashMap::new();
790
791 fn visit(
792 hoisted: &mut HashMap<DeclarationId, Option<Place>>,
793 place: &Place,
794 env: &Environment,
795 ) {
796 let decl_id = env.identifiers[place.identifier.0 as usize].declaration_id;
797 if hoisted.contains_key(&decl_id) && hoisted.get(&decl_id).unwrap().is_none() {
798 hoisted.insert(decl_id, Some(place.clone()));
799 }
800 }
801
802 for (_block_id, block) in &func.body.blocks {
803 for instr_id in &block.instructions {
804 let instr = &func.instructions[instr_id.0 as usize];
805 match &instr.value {
806 InstructionValue::DeclareContext { lvalue, .. } => {
807 let kind = lvalue.kind;
808 if kind == InstructionKind::HoistedConst
809 || kind == InstructionKind::HoistedFunction
810 || kind == InstructionKind::HoistedLet
811 {
812 let decl_id =
813 env.identifiers[lvalue.place.identifier.0 as usize].declaration_id;
814 hoisted.insert(decl_id, None);
815 }
816 }
817 _ => {
818 for operand in visitors::each_instruction_value_operand(&instr.value, env) {
819 visit(&mut hoisted, &operand, env);
820 }
821 }
822 }
823 }
824 for operand in visitors::each_terminal_operand(&block.terminal) {
825 visit(&mut hoisted, &operand, env);
826 }
827 }
828 hoisted
829}
830
831fn find_non_mutated_destructure_spreads(
832 func: &HirFunction,
833 env: &Environment,
834) -> HashSet<IdentifierId> {
835 let mut known_frozen: HashSet<IdentifierId> = HashSet::new();
836 if func.fn_type == ReactFunctionType::Component {
837 if let Some(param) = func.params.first() {
838 if let ParamPattern::Place(p) = param {
839 known_frozen.insert(p.identifier);
840 }
841 }
842 } else {
843 for param in &func.params {
844 if let ParamPattern::Place(p) = param {
845 known_frozen.insert(p.identifier);
846 }
847 }
848 }
849
850 let mut candidate_non_mutating_spreads: HashMap<IdentifierId, IdentifierId> = HashMap::new();
851 for (_block_id, block) in &func.body.blocks {
852 if !candidate_non_mutating_spreads.is_empty() {
853 for phi in &block.phis {
854 for (_, operand) in &phi.operands {
855 if let Some(spread) = candidate_non_mutating_spreads
856 .get(&operand.identifier)
857 .copied()
858 {
859 candidate_non_mutating_spreads.remove(&spread);
860 }
861 }
862 }
863 }
864 for instr_id in &block.instructions {
865 let instr = &func.instructions[instr_id.0 as usize];
866 let lvalue_id = instr.lvalue.identifier;
867 match &instr.value {
868 InstructionValue::Destructure { lvalue, value, .. } => {
869 if !known_frozen.contains(&value.identifier) {
870 continue;
871 }
872 if !(lvalue.kind == InstructionKind::Let
873 || lvalue.kind == InstructionKind::Const)
874 {
875 continue;
876 }
877 match &lvalue.pattern {
878 react_compiler_hir::Pattern::Object(obj_pat) => {
879 for prop in &obj_pat.properties {
880 if let react_compiler_hir::ObjectPropertyOrSpread::Spread(s) = prop
881 {
882 candidate_non_mutating_spreads
883 .insert(s.place.identifier, s.place.identifier);
884 }
885 }
886 }
887 _ => continue,
888 }
889 }
890 InstructionValue::LoadLocal { place, .. } => {
891 if let Some(spread) = candidate_non_mutating_spreads
892 .get(&place.identifier)
893 .copied()
894 {
895 candidate_non_mutating_spreads.insert(lvalue_id, spread);
896 }
897 }
898 InstructionValue::StoreLocal {
899 lvalue: sl,
900 value: sv,
901 ..
902 } => {
903 if let Some(spread) =
904 candidate_non_mutating_spreads.get(&sv.identifier).copied()
905 {
906 candidate_non_mutating_spreads.insert(lvalue_id, spread);
907 candidate_non_mutating_spreads.insert(sl.place.identifier, spread);
908 }
909 }
910 InstructionValue::JsxFragment { .. } | InstructionValue::JsxExpression { .. } => {
911 }
913 InstructionValue::PropertyLoad { .. } => {
914 }
916 InstructionValue::CallExpression { callee, .. }
917 | InstructionValue::MethodCall {
918 property: callee, ..
919 } => {
920 let callee_ty =
921 &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize];
922 if get_hook_kind_for_type(env, callee_ty)
923 .ok()
924 .flatten()
925 .is_some()
926 {
927 if !is_ref_or_ref_value_for_id(env, lvalue_id) {
928 known_frozen.insert(lvalue_id);
929 }
930 } else if !candidate_non_mutating_spreads.is_empty() {
931 for operand in visitors::each_instruction_value_operand(&instr.value, env) {
932 if let Some(spread) = candidate_non_mutating_spreads
933 .get(&operand.identifier)
934 .copied()
935 {
936 candidate_non_mutating_spreads.remove(&spread);
937 }
938 }
939 }
940 }
941 _ => {
942 if !candidate_non_mutating_spreads.is_empty() {
943 for operand in visitors::each_instruction_value_operand(&instr.value, env) {
944 if let Some(spread) = candidate_non_mutating_spreads
945 .get(&operand.identifier)
946 .copied()
947 {
948 candidate_non_mutating_spreads.remove(&spread);
949 }
950 }
951 }
952 }
953 }
954 }
955 }
956
957 let mut non_mutating: HashSet<IdentifierId> = HashSet::new();
958 for (key, value) in &candidate_non_mutating_spreads {
959 if key == value {
960 non_mutating.insert(*key);
961 }
962 }
963 non_mutating
964}
965
966fn infer_param(param: &ParamPattern, state: &mut InferenceState, param_kind: &AbstractValue) {
971 let place = match param {
972 ParamPattern::Place(p) => p,
973 ParamPattern::Spread(s) => &s.place,
974 };
975 let value_id = ValueId::new();
976 state.initialize(value_id, param_kind.clone());
977 state.define(place.identifier, value_id);
978}
979
980fn infer_block(
985 context: &mut Context,
986 state: &mut InferenceState,
987 block_id: BlockId,
988 func: &mut HirFunction,
989 env: &mut Environment,
990) -> Result<(), CompilerDiagnostic> {
991 let block = &func.body.blocks[&block_id];
992
993 let phis: Vec<(IdentifierId, indexmap::IndexMap<BlockId, Place>)> = block
995 .phis
996 .iter()
997 .map(|phi| (phi.place.identifier, phi.operands.clone()))
998 .collect();
999 for (place_id, operands) in &phis {
1000 state.infer_phi(*place_id, operands);
1001 }
1002
1003 let instr_ids: Vec<u32> = block.instructions.iter().map(|id| id.0).collect();
1005 for instr_idx in &instr_ids {
1006 let instr_index = *instr_idx as usize;
1007
1008 if !context.instruction_signature_cache.contains_key(instr_idx) {
1010 let sig = compute_signature_for_instruction(
1011 context,
1012 env,
1013 &func.instructions[instr_index],
1014 func,
1015 );
1016 context.instruction_signature_cache.insert(*instr_idx, sig);
1017 }
1018
1019 let effects = apply_signature(
1021 context,
1022 state,
1023 *instr_idx,
1024 &func.instructions[instr_index],
1025 env,
1026 func,
1027 )?;
1028 func.instructions[instr_index].effects = effects;
1029 }
1030
1031 enum TerminalAction {
1034 Try { handler: BlockId, binding: Place },
1035 MaybeThrow { handler_id: BlockId },
1036 Return,
1037 None,
1038 }
1039 let action = {
1040 let block = &func.body.blocks[&block_id];
1041 match &block.terminal {
1042 react_compiler_hir::Terminal::Try {
1043 handler,
1044 handler_binding: Some(binding),
1045 ..
1046 } => TerminalAction::Try {
1047 handler: *handler,
1048 binding: binding.clone(),
1049 },
1050 react_compiler_hir::Terminal::MaybeThrow {
1051 handler: Some(handler_id),
1052 ..
1053 } => TerminalAction::MaybeThrow {
1054 handler_id: *handler_id,
1055 },
1056 react_compiler_hir::Terminal::Return { .. } => TerminalAction::Return,
1057 _ => TerminalAction::None,
1058 }
1059 };
1060
1061 match action {
1062 TerminalAction::Try { handler, binding } => {
1063 context.catch_handlers.insert(handler, binding);
1064 }
1065 TerminalAction::MaybeThrow { handler_id } => {
1066 if let Some(handler_param) = context.catch_handlers.get(&handler_id).cloned() {
1067 if state.is_defined(handler_param.identifier) {
1068 let mut terminal_effects: Vec<AliasingEffect> = Vec::new();
1069 for instr_idx in &instr_ids {
1070 let instr = &func.instructions[*instr_idx as usize];
1071 match &instr.value {
1072 InstructionValue::CallExpression { .. }
1073 | InstructionValue::MethodCall { .. } => {
1074 state.append_alias(
1075 handler_param.identifier,
1076 instr.lvalue.identifier,
1077 );
1078 let kind = state.kind(instr.lvalue.identifier).kind;
1079 if kind == ValueKind::Mutable || kind == ValueKind::Context {
1080 terminal_effects.push(context.intern_effect(
1081 AliasingEffect::Alias {
1082 from: instr.lvalue.clone(),
1083 into: handler_param.clone(),
1084 },
1085 ));
1086 }
1087 }
1088 _ => {}
1089 }
1090 }
1091 let block_mut = func.body.blocks.get_mut(&block_id).unwrap();
1092 if let react_compiler_hir::Terminal::MaybeThrow {
1093 effects: ref mut term_effects,
1094 ..
1095 } = block_mut.terminal
1096 {
1097 *term_effects = if terminal_effects.is_empty() {
1098 None
1099 } else {
1100 Some(terminal_effects)
1101 };
1102 }
1103 }
1104 }
1105 }
1106 TerminalAction::Return => {
1107 if !context.is_function_expression {
1108 let block_mut = func.body.blocks.get_mut(&block_id).unwrap();
1109 if let react_compiler_hir::Terminal::Return {
1110 ref value,
1111 effects: ref mut term_effects,
1112 ..
1113 } = block_mut.terminal
1114 {
1115 *term_effects = Some(vec![context.intern_effect(AliasingEffect::Freeze {
1116 value: value.clone(),
1117 reason: ValueReason::JsxCaptured,
1118 })]);
1119 }
1120 }
1121 }
1122 TerminalAction::None => {}
1123 }
1124 Ok(())
1125}
1126
1127fn apply_signature(
1132 context: &mut Context,
1133 state: &mut InferenceState,
1134 instr_idx: u32,
1135 instr: &react_compiler_hir::Instruction,
1136 env: &mut Environment,
1137 func: &HirFunction,
1138) -> Result<Option<Vec<AliasingEffect>>, CompilerDiagnostic> {
1139 let mut effects: Vec<AliasingEffect> = Vec::new();
1140
1141 match &instr.value {
1143 InstructionValue::FunctionExpression { lowered_func, .. }
1144 | InstructionValue::ObjectMethod { lowered_func, .. } => {
1145 let inner_func = &env.functions[lowered_func.func.0 as usize];
1146 if let Some(ref aliasing_effects) = inner_func.aliasing_effects {
1147 let context_ids: HashSet<IdentifierId> =
1148 inner_func.context.iter().map(|p| p.identifier).collect();
1149 for effect in aliasing_effects {
1150 let (mutate_value, is_mutate) = match effect {
1151 AliasingEffect::Mutate { value, .. } => (value, true),
1152 AliasingEffect::MutateTransitive { value } => (value, false),
1153 _ => continue,
1154 };
1155 if !context_ids.contains(&mutate_value.identifier) {
1156 continue;
1157 }
1158 if !state.is_defined(mutate_value.identifier) {
1159 continue;
1160 }
1161 let value_abstract = state.kind(mutate_value.identifier);
1162 if value_abstract.kind == ValueKind::Frozen {
1163 let reason_str = get_write_error_reason(&value_abstract);
1164 let ident = &env.identifiers[mutate_value.identifier.0 as usize];
1165 let variable = match &ident.name {
1166 Some(react_compiler_hir::IdentifierName::Named(n)) => {
1167 format!("`{}`", n)
1168 }
1169 _ => "value".to_string(),
1170 };
1171 let mut diagnostic = CompilerDiagnostic::new(
1172 ErrorCategory::Immutability,
1173 "This value cannot be modified",
1174 Some(reason_str),
1175 );
1176 diagnostic.details.push(
1177 react_compiler_diagnostics::CompilerDiagnosticDetail::Error {
1178 loc: mutate_value.loc,
1179 message: Some(format!("{} cannot be modified", variable)),
1180 identifier_name: None,
1181 },
1182 );
1183 if is_mutate {
1184 if let AliasingEffect::Mutate {
1185 reason: Some(MutationReason::AssignCurrentProperty),
1186 ..
1187 } = effect
1188 {
1189 diagnostic.details.push(react_compiler_diagnostics::CompilerDiagnosticDetail::Hint {
1190 message: "Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in \"Ref\".".to_string()
1191 });
1192 }
1193 }
1194 effects.push(AliasingEffect::MutateFrozen {
1195 place: mutate_value.clone(),
1196 error: diagnostic,
1197 });
1198 }
1199 }
1200 }
1201 }
1202 _ => {}
1203 }
1204
1205 let mut initialized: HashSet<IdentifierId> = HashSet::new();
1207
1208 let sig = context.instruction_signature_cache.get(&instr_idx).unwrap();
1210 let sig_effects: Vec<AliasingEffect> = sig.effects.clone();
1211
1212 for effect in &sig_effects {
1213 apply_effect(
1214 context,
1215 state,
1216 effect.clone(),
1217 &mut initialized,
1218 &mut effects,
1219 env,
1220 func,
1221 )?;
1222 }
1223
1224 if !state.is_defined(instr.lvalue.identifier) {
1228 let vid = ValueId(instr.lvalue.identifier.0 | 0x80000000);
1229 state.initialize(
1230 vid,
1231 AbstractValue {
1232 kind: ValueKind::Mutable,
1233 reason: hashset_of(ValueReason::Other),
1234 },
1235 );
1236 state.define(instr.lvalue.identifier, vid);
1237 }
1238
1239 Ok(if effects.is_empty() {
1240 None
1241 } else {
1242 Some(effects)
1243 })
1244}
1245
1246fn freeze_function_captures_transitive(
1255 state: &mut InferenceState,
1256 context: &Context,
1257 env: &Environment,
1258 value_id: ValueId,
1259 reason: ValueReason,
1260) {
1261 if let Some(&func_id) = context.function_values.get(&value_id) {
1262 let ctx_ids: Vec<IdentifierId> = env.functions[func_id.0 as usize]
1263 .context
1264 .iter()
1265 .map(|p| p.identifier)
1266 .collect();
1267 for ctx_id in ctx_ids {
1268 if !state.variables.contains_key(&ctx_id) {
1271 continue;
1272 }
1273 let kind = state.kind(ctx_id).kind;
1274 match kind {
1275 ValueKind::Context | ValueKind::Mutable | ValueKind::MaybeFrozen => {
1276 let vids: Vec<ValueId> = state.values_for(ctx_id);
1277 for vid in vids {
1278 state.freeze_value(vid, reason);
1279 freeze_function_captures_transitive(state, context, env, vid, reason);
1281 }
1282 }
1283 ValueKind::Frozen | ValueKind::Global | ValueKind::Primitive => {
1284 }
1286 }
1287 }
1288 }
1289}
1290
1291fn apply_effect(
1296 context: &mut Context,
1297 state: &mut InferenceState,
1298 effect: AliasingEffect,
1299 initialized: &mut HashSet<IdentifierId>,
1300 effects: &mut Vec<AliasingEffect>,
1301 env: &mut Environment,
1302 func: &HirFunction,
1303) -> Result<(), CompilerDiagnostic> {
1304 let effect = context.intern_effect(effect);
1305 match effect {
1306 AliasingEffect::Freeze { ref value, reason } => {
1307 let did_freeze = state.freeze(value.identifier, reason);
1308 if did_freeze {
1309 effects.push(effect.clone());
1310 let enable_transitive = env.config.enable_preserve_existing_memoization_guarantees
1313 || env.config.enable_transitively_freeze_function_expressions;
1314 if enable_transitive {
1315 let value_ids: Vec<ValueId> = state.values_for(value.identifier);
1320 for vid in &value_ids {
1321 freeze_function_captures_transitive(state, context, env, *vid, reason);
1322 }
1323 }
1324 }
1325 }
1326 AliasingEffect::Create {
1327 ref into,
1328 value: kind,
1329 reason,
1330 } => {
1331 assert!(
1332 !initialized.contains(&into.identifier),
1333 "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"
1334 );
1335 initialized.insert(into.identifier);
1336 let value_id = context.get_or_create_value_id(&effect);
1337 state.initialize(
1338 value_id,
1339 AbstractValue {
1340 kind,
1341 reason: hashset_of(reason),
1342 },
1343 );
1344 state.define(into.identifier, value_id);
1345 effects.push(effect.clone());
1346 }
1347 AliasingEffect::ImmutableCapture { ref from, .. } => {
1348 let kind = state.kind(from.identifier).kind;
1349 match kind {
1350 ValueKind::Global | ValueKind::Primitive => {
1351 }
1353 _ => {
1354 effects.push(effect.clone());
1355 }
1356 }
1357 }
1358 AliasingEffect::CreateFrom { ref from, ref into } => {
1359 assert!(
1360 !initialized.contains(&into.identifier),
1361 "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"
1362 );
1363 initialized.insert(into.identifier);
1364 let from_value = state.kind(from.identifier);
1365 let value_id = context.get_or_create_value_id(&effect);
1366 state.initialize(
1367 value_id,
1368 AbstractValue {
1369 kind: from_value.kind,
1370 reason: from_value.reason.clone(),
1371 },
1372 );
1373 state.define(into.identifier, value_id);
1374 match from_value.kind {
1375 ValueKind::Primitive | ValueKind::Global => {
1376 let first_reason = primary_reason(&from_value.reason);
1377 effects.push(AliasingEffect::Create {
1378 value: from_value.kind,
1379 into: into.clone(),
1380 reason: first_reason,
1381 });
1382 }
1383 ValueKind::Frozen => {
1384 let first_reason = primary_reason(&from_value.reason);
1385 effects.push(AliasingEffect::Create {
1386 value: from_value.kind,
1387 into: into.clone(),
1388 reason: first_reason,
1389 });
1390 apply_effect(
1391 context,
1392 state,
1393 AliasingEffect::ImmutableCapture {
1394 from: from.clone(),
1395 into: into.clone(),
1396 },
1397 initialized,
1398 effects,
1399 env,
1400 func,
1401 )?;
1402 }
1403 _ => {
1404 effects.push(effect.clone());
1405 }
1406 }
1407 }
1408 AliasingEffect::CreateFunction {
1409 ref captures,
1410 function_id,
1411 ref into,
1412 } => {
1413 assert!(
1414 !initialized.contains(&into.identifier),
1415 "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"
1416 );
1417 initialized.insert(into.identifier);
1418 effects.push(effect.clone());
1419
1420 let has_captures = captures.iter().any(|capture| {
1422 if !state.is_defined(capture.identifier) {
1423 return false;
1424 }
1425 let k = state.kind(capture.identifier).kind;
1426 k == ValueKind::Context || k == ValueKind::Mutable
1427 });
1428
1429 let inner_func = &env.functions[function_id.0 as usize];
1430 let has_tracked_side_effects = inner_func
1431 .aliasing_effects
1432 .as_ref()
1433 .map(|effs| {
1434 effs.iter().any(|e| {
1435 matches!(
1436 e,
1437 AliasingEffect::MutateFrozen { .. }
1438 | AliasingEffect::MutateGlobal { .. }
1439 | AliasingEffect::Impure { .. }
1440 )
1441 })
1442 })
1443 .unwrap_or(false);
1444
1445 let captures_ref = inner_func
1446 .context
1447 .iter()
1448 .any(|operand| is_ref_or_ref_value_for_id(env, operand.identifier));
1449
1450 let is_mutable = has_captures || has_tracked_side_effects || captures_ref;
1451
1452 let context_places: Vec<Place> = inner_func.context.clone();
1454 for operand in &context_places {
1455 if operand.effect != Effect::Capture {
1456 continue;
1457 }
1458 if !state.is_defined(operand.identifier) {
1459 continue;
1460 }
1461 let kind = state.kind(operand.identifier).kind;
1462 if kind == ValueKind::Primitive
1463 || kind == ValueKind::Frozen
1464 || kind == ValueKind::Global
1465 {
1466 let inner_func_mut = &mut env.functions[function_id.0 as usize];
1468 for ctx in &mut inner_func_mut.context {
1469 if ctx.identifier == operand.identifier && ctx.effect == Effect::Capture {
1470 ctx.effect = Effect::Read;
1471 }
1472 }
1473 }
1474 }
1475
1476 let value_id = context.get_or_create_value_id(&effect);
1477 context.function_values.insert(value_id, function_id);
1479 state.initialize(
1480 value_id,
1481 AbstractValue {
1482 kind: if is_mutable {
1483 ValueKind::Mutable
1484 } else {
1485 ValueKind::Frozen
1486 },
1487 reason: IndexSet::new(),
1488 },
1489 );
1490 state.define(into.identifier, value_id);
1491
1492 for capture in captures {
1493 apply_effect(
1494 context,
1495 state,
1496 AliasingEffect::Capture {
1497 from: capture.clone(),
1498 into: into.clone(),
1499 },
1500 initialized,
1501 effects,
1502 env,
1503 func,
1504 )?;
1505 }
1506 }
1507 AliasingEffect::MaybeAlias { ref from, ref into }
1508 | AliasingEffect::Alias { ref from, ref into }
1509 | AliasingEffect::Capture { ref from, ref into } => {
1510 let is_capture = matches!(effect, AliasingEffect::Capture { .. });
1511 let is_maybe_alias = matches!(effect, AliasingEffect::MaybeAlias { .. });
1512 assert!(
1514 is_capture || is_maybe_alias || initialized.contains(&into.identifier),
1515 "[InferMutationAliasingEffects] Expected destination to already be initialized within this instruction"
1516 );
1517
1518 let into_kind = state.kind_with_loc(into.identifier, into.loc).kind;
1520 let destination_type = match into_kind {
1521 ValueKind::Context => Some("context"),
1522 ValueKind::Mutable | ValueKind::MaybeFrozen => Some("mutable"),
1523 _ => None,
1524 };
1525
1526 let from_kind = state.kind_with_loc(from.identifier, from.loc).kind;
1527 let source_type = match from_kind {
1528 ValueKind::Context => Some("context"),
1529 ValueKind::Global | ValueKind::Primitive => None,
1530 ValueKind::MaybeFrozen | ValueKind::Frozen => Some("frozen"),
1531 ValueKind::Mutable => Some("mutable"),
1532 };
1533
1534 if source_type == Some("frozen") {
1535 apply_effect(
1536 context,
1537 state,
1538 AliasingEffect::ImmutableCapture {
1539 from: from.clone(),
1540 into: into.clone(),
1541 },
1542 initialized,
1543 effects,
1544 env,
1545 func,
1546 )?;
1547 } else if (source_type == Some("mutable") && destination_type == Some("mutable"))
1548 || is_maybe_alias
1549 {
1550 effects.push(effect.clone());
1551 } else if (source_type == Some("context") && destination_type.is_some())
1552 || (source_type == Some("mutable") && destination_type == Some("context"))
1553 {
1554 apply_effect(
1555 context,
1556 state,
1557 AliasingEffect::MaybeAlias {
1558 from: from.clone(),
1559 into: into.clone(),
1560 },
1561 initialized,
1562 effects,
1563 env,
1564 func,
1565 )?;
1566 }
1567 }
1568 AliasingEffect::Assign { ref from, ref into } => {
1569 assert!(
1570 !initialized.contains(&into.identifier),
1571 "[InferMutationAliasingEffects] Cannot re-initialize variable within an instruction"
1572 );
1573 initialized.insert(into.identifier);
1574 let from_value = state.kind_with_loc(from.identifier, from.loc);
1575 match from_value.kind {
1576 ValueKind::Frozen => {
1577 apply_effect(
1578 context,
1579 state,
1580 AliasingEffect::ImmutableCapture {
1581 from: from.clone(),
1582 into: into.clone(),
1583 },
1584 initialized,
1585 effects,
1586 env,
1587 func,
1588 )?;
1589 let cache_key =
1590 format!("Assign_frozen:{}:{}", from.identifier.0, into.identifier.0);
1591 let value_id = *context
1592 .effect_value_id_cache
1593 .entry(cache_key)
1594 .or_insert_with(ValueId::new);
1595 state.initialize(
1596 value_id,
1597 AbstractValue {
1598 kind: from_value.kind,
1599 reason: from_value.reason.clone(),
1600 },
1601 );
1602 state.define(into.identifier, value_id);
1603 }
1604 ValueKind::Global | ValueKind::Primitive => {
1605 let cache_key =
1606 format!("Assign_copy:{}:{}", from.identifier.0, into.identifier.0);
1607 let value_id = *context
1608 .effect_value_id_cache
1609 .entry(cache_key)
1610 .or_insert_with(ValueId::new);
1611 state.initialize(
1612 value_id,
1613 AbstractValue {
1614 kind: from_value.kind,
1615 reason: from_value.reason.clone(),
1616 },
1617 );
1618 state.define(into.identifier, value_id);
1619 }
1620 _ => {
1621 state.assign(into.identifier, from.identifier);
1622 effects.push(effect.clone());
1623 }
1624 }
1625 }
1626 AliasingEffect::Apply {
1627 ref receiver,
1628 ref function,
1629 mutates_function,
1630 ref args,
1631 ref into,
1632 ref signature,
1633 ref loc,
1634 } => {
1635 if state.is_defined(function.identifier) {
1638 let function_values = state.values_for(function.identifier);
1639 if function_values.len() == 1 {
1640 let value_id = function_values[0];
1641 if let Some(func_id) = context.function_values.get(&value_id).copied() {
1642 let inner_func = &env.functions[func_id.0 as usize];
1643 if inner_func.aliasing_effects.is_some() {
1644 if !context.function_signature_cache.contains_key(&func_id) {
1646 let sig = build_signature_from_function_expression(env, func_id);
1647 context.function_signature_cache.insert(func_id, sig);
1648 }
1649 let sig = context
1650 .function_signature_cache
1651 .get(&func_id)
1652 .unwrap()
1653 .clone();
1654 let inner_func = &env.functions[func_id.0 as usize];
1655 let context_places: Vec<Place> = inner_func.context.clone();
1656 let sig_effects = compute_effects_for_aliasing_signature(
1657 env,
1658 &sig,
1659 into,
1660 receiver,
1661 args,
1662 &context_places,
1663 loc.as_ref(),
1664 )?;
1665 if let Some(sig_effs) = sig_effects {
1666 apply_effect(
1668 context,
1669 state,
1670 AliasingEffect::MutateTransitiveConditionally {
1671 value: function.clone(),
1672 },
1673 initialized,
1674 effects,
1675 env,
1676 func,
1677 )?;
1678 for se in sig_effs {
1679 apply_effect(
1680 context,
1681 state,
1682 se,
1683 initialized,
1684 effects,
1685 env,
1686 func,
1687 )?;
1688 }
1689 return Ok(());
1690 }
1691 }
1692 }
1693 }
1694 }
1695 if let Some(sig) = signature {
1696 if let Some(ref incompatible_msg) = sig.known_incompatible {
1698 if env.enable_validations() {
1699 let mut diagnostic = CompilerDiagnostic::new(
1700 ErrorCategory::IncompatibleLibrary,
1701 "Use of incompatible library",
1702 Some(
1703 "This API returns functions which cannot be memoized without leading to stale UI. \
1704 To prevent this, by default React Compiler will skip memoizing this component/hook. \
1705 However, you may see issues if values from this API are passed to other components/hooks that are \
1706 memoized".to_string(),
1707 ),
1708 );
1709 diagnostic.details.push(CompilerDiagnosticDetail::Error {
1710 loc: receiver.loc,
1711 message: Some(incompatible_msg.clone()),
1712 identifier_name: None,
1713 });
1714 return Err(diagnostic);
1716 }
1717 }
1718
1719 if let Some(ref aliasing) = sig.aliasing {
1720 let sig_effects = compute_effects_for_aliasing_signature_config(
1721 env,
1722 aliasing,
1723 into,
1724 receiver,
1725 args,
1726 &[],
1727 loc.as_ref(),
1728 &mut context.aliasing_config_temp_cache,
1729 )?;
1730 if let Some(sig_effs) = sig_effects {
1731 for se in sig_effs {
1732 apply_effect(context, state, se, initialized, effects, env, func)?;
1733 }
1734 return Ok(());
1735 }
1736 }
1737
1738 let mut todo_errors: Vec<react_compiler_diagnostics::CompilerErrorDetail> =
1740 Vec::new();
1741 let legacy_effects = compute_effects_for_legacy_signature(
1742 state,
1743 sig,
1744 into,
1745 receiver,
1746 args,
1747 loc.as_ref(),
1748 env,
1749 &context.function_values,
1750 &mut todo_errors,
1751 );
1752 if let Some(err_detail) = todo_errors.into_iter().next() {
1754 return Err(CompilerDiagnostic::from_detail(err_detail));
1755 }
1756 for le in legacy_effects {
1757 apply_effect(context, state, le, initialized, effects, env, func)?;
1758 }
1759 } else {
1760 apply_effect(
1762 context,
1763 state,
1764 AliasingEffect::Create {
1765 into: into.clone(),
1766 value: ValueKind::Mutable,
1767 reason: ValueReason::Other,
1768 },
1769 initialized,
1770 effects,
1771 env,
1772 func,
1773 )?;
1774
1775 let all_operands = build_apply_operands(receiver, function, args);
1776 for (operand, _is_function_operand, is_spread) in &all_operands {
1777 if operand.identifier == function.identifier && !mutates_function {
1781 } else {
1783 apply_effect(
1784 context,
1785 state,
1786 AliasingEffect::MutateTransitiveConditionally {
1787 value: operand.clone(),
1788 },
1789 initialized,
1790 effects,
1791 env,
1792 func,
1793 )?;
1794 }
1795
1796 if *is_spread {
1797 let ty = &env.types
1798 [env.identifiers[operand.identifier.0 as usize].type_.0 as usize];
1799 if let Some(mutate_iter) = conditionally_mutate_iterator(operand, ty) {
1800 apply_effect(
1801 context,
1802 state,
1803 mutate_iter,
1804 initialized,
1805 effects,
1806 env,
1807 func,
1808 )?;
1809 }
1810 }
1811
1812 apply_effect(
1813 context,
1814 state,
1815 AliasingEffect::MaybeAlias {
1816 from: operand.clone(),
1817 into: into.clone(),
1818 },
1819 initialized,
1820 effects,
1821 env,
1822 func,
1823 )?;
1824
1825 for (other, _other_is_func, _other_is_spread) in &all_operands {
1833 if !is_spread && other.identifier == operand.identifier {
1834 continue;
1835 }
1836 apply_effect(
1837 context,
1838 state,
1839 AliasingEffect::Capture {
1840 from: operand.clone(),
1841 into: other.clone(),
1842 },
1843 initialized,
1844 effects,
1845 env,
1846 func,
1847 )?;
1848 }
1849 }
1850 }
1851 }
1852 ref eff @ (AliasingEffect::Mutate { .. }
1853 | AliasingEffect::MutateConditionally { .. }
1854 | AliasingEffect::MutateTransitive { .. }
1855 | AliasingEffect::MutateTransitiveConditionally { .. }) => {
1856 let (mutate_place, variant) = match eff {
1857 AliasingEffect::Mutate { value, .. } => (value, MutateVariant::Mutate),
1858 AliasingEffect::MutateConditionally { value } => {
1859 (value, MutateVariant::MutateConditionally)
1860 }
1861 AliasingEffect::MutateTransitive { value } => {
1862 (value, MutateVariant::MutateTransitive)
1863 }
1864 AliasingEffect::MutateTransitiveConditionally { value } => {
1865 (value, MutateVariant::MutateTransitiveConditionally)
1866 }
1867 _ => unreachable!(),
1868 };
1869 let value = mutate_place;
1870 let mutation_kind = state.mutate_with_loc(variant, value.identifier, env, value.loc);
1871 if mutation_kind == MutationResult::Mutate {
1872 effects.push(effect.clone());
1873 } else if mutation_kind == MutationResult::MutateRef {
1874 } else if mutation_kind != MutationResult::None
1876 && matches!(
1877 variant,
1878 MutateVariant::Mutate | MutateVariant::MutateTransitive
1879 )
1880 {
1881 let abstract_value = state.kind(value.identifier);
1882
1883 let ident = &env.identifiers[value.identifier.0 as usize];
1884 let decl_id = ident.declaration_id;
1885
1886 if mutation_kind == MutationResult::MutateFrozen
1887 && context.hoisted_context_declarations.contains_key(&decl_id)
1888 {
1889 let variable = match &ident.name {
1890 Some(react_compiler_hir::IdentifierName::Named(n)) => {
1891 Some(format!("`{}`", n))
1892 }
1893 _ => None,
1894 };
1895 let hoisted_access = context
1896 .hoisted_context_declarations
1897 .get(&decl_id)
1898 .cloned()
1899 .flatten();
1900 let mut diagnostic = CompilerDiagnostic::new(
1901 ErrorCategory::Immutability,
1902 "Cannot access variable before it is declared",
1903 Some(format!(
1904 "{} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time",
1905 variable.as_deref().unwrap_or("This variable")
1906 )),
1907 );
1908 if let Some(ref access) = hoisted_access {
1909 if access.loc != value.loc {
1910 diagnostic.details.push(
1911 react_compiler_diagnostics::CompilerDiagnosticDetail::Error {
1912 loc: access.loc,
1913 message: Some(format!(
1914 "{} accessed before it is declared",
1915 variable.as_deref().unwrap_or("variable")
1916 )),
1917 identifier_name: None,
1918 },
1919 );
1920 }
1921 }
1922 diagnostic.details.push(
1923 react_compiler_diagnostics::CompilerDiagnosticDetail::Error {
1924 loc: value.loc,
1925 message: Some(format!(
1926 "{} is declared here",
1927 variable.as_deref().unwrap_or("variable")
1928 )),
1929 identifier_name: None,
1930 },
1931 );
1932 apply_effect(
1933 context,
1934 state,
1935 AliasingEffect::MutateFrozen {
1936 place: value.clone(),
1937 error: diagnostic,
1938 },
1939 initialized,
1940 effects,
1941 env,
1942 func,
1943 )?;
1944 } else {
1945 let reason_str = get_write_error_reason(&abstract_value);
1946 let variable = match &ident.name {
1947 Some(react_compiler_hir::IdentifierName::Named(n)) => format!("`{}`", n),
1948 _ => "value".to_string(),
1949 };
1950 let mut diagnostic = CompilerDiagnostic::new(
1951 ErrorCategory::Immutability,
1952 "This value cannot be modified",
1953 Some(reason_str),
1954 );
1955 diagnostic.details.push(
1956 react_compiler_diagnostics::CompilerDiagnosticDetail::Error {
1957 loc: value.loc,
1958 message: Some(format!("{} cannot be modified", variable)),
1959 identifier_name: None,
1960 },
1961 );
1962
1963 if let AliasingEffect::Mutate {
1964 reason: Some(MutationReason::AssignCurrentProperty),
1965 ..
1966 } = &effect
1967 {
1968 diagnostic.details.push(react_compiler_diagnostics::CompilerDiagnosticDetail::Hint {
1969 message: "Hint: If this value is a Ref (value returned by `useRef()`), rename the variable to end in \"Ref\".".to_string(),
1970 });
1971 }
1972
1973 let error_kind = if abstract_value.kind == ValueKind::Frozen {
1974 AliasingEffect::MutateFrozen {
1975 place: value.clone(),
1976 error: diagnostic,
1977 }
1978 } else {
1979 AliasingEffect::MutateGlobal {
1980 place: value.clone(),
1981 error: diagnostic,
1982 }
1983 };
1984 apply_effect(context, state, error_kind, initialized, effects, env, func)?;
1985 }
1986 }
1987 }
1988 AliasingEffect::Impure { .. }
1989 | AliasingEffect::Render { .. }
1990 | AliasingEffect::MutateFrozen { .. }
1991 | AliasingEffect::MutateGlobal { .. } => {
1992 effects.push(effect.clone());
1993 }
1994 }
1995 Ok(())
1996}
1997
1998fn compute_signature_for_instruction(
2003 context: &mut Context,
2004 env: &Environment,
2005 instr: &react_compiler_hir::Instruction,
2006 _func: &HirFunction,
2007) -> InstructionSignature {
2008 let lvalue = &instr.lvalue;
2009 let value = &instr.value;
2010 let mut effects: Vec<AliasingEffect> = Vec::new();
2011
2012 match value {
2013 InstructionValue::ArrayExpression { elements, .. } => {
2014 effects.push(AliasingEffect::Create {
2015 into: lvalue.clone(),
2016 value: ValueKind::Mutable,
2017 reason: ValueReason::Other,
2018 });
2019 for element in elements {
2020 match element {
2021 react_compiler_hir::ArrayElement::Place(p) => {
2022 effects.push(AliasingEffect::Capture {
2023 from: p.clone(),
2024 into: lvalue.clone(),
2025 });
2026 }
2027 react_compiler_hir::ArrayElement::Spread(s) => {
2028 let ty = &env.types
2029 [env.identifiers[s.place.identifier.0 as usize].type_.0 as usize];
2030 if let Some(mutate_iter) = conditionally_mutate_iterator(&s.place, ty) {
2031 effects.push(mutate_iter);
2032 }
2033 effects.push(AliasingEffect::Capture {
2034 from: s.place.clone(),
2035 into: lvalue.clone(),
2036 });
2037 }
2038 react_compiler_hir::ArrayElement::Hole => {}
2039 }
2040 }
2041 }
2042 InstructionValue::ObjectExpression { properties, .. } => {
2043 effects.push(AliasingEffect::Create {
2044 into: lvalue.clone(),
2045 value: ValueKind::Mutable,
2046 reason: ValueReason::Other,
2047 });
2048 for property in properties {
2049 match property {
2050 react_compiler_hir::ObjectPropertyOrSpread::Property(p) => {
2051 effects.push(AliasingEffect::Capture {
2052 from: p.place.clone(),
2053 into: lvalue.clone(),
2054 });
2055 }
2056 react_compiler_hir::ObjectPropertyOrSpread::Spread(s) => {
2057 effects.push(AliasingEffect::Capture {
2058 from: s.place.clone(),
2059 into: lvalue.clone(),
2060 });
2061 }
2062 }
2063 }
2064 }
2065 InstructionValue::Await {
2066 value: await_value, ..
2067 } => {
2068 effects.push(AliasingEffect::Create {
2069 into: lvalue.clone(),
2070 value: ValueKind::Mutable,
2071 reason: ValueReason::Other,
2072 });
2073 effects.push(AliasingEffect::MutateTransitiveConditionally {
2074 value: await_value.clone(),
2075 });
2076 effects.push(AliasingEffect::Capture {
2077 from: await_value.clone(),
2078 into: lvalue.clone(),
2079 });
2080 }
2081 InstructionValue::NewExpression { callee, args, loc } => {
2082 let sig = get_function_call_signature(env, callee.identifier)
2083 .ok()
2084 .flatten();
2085 effects.push(AliasingEffect::Apply {
2086 receiver: callee.clone(),
2087 function: callee.clone(),
2088 mutates_function: false,
2089 args: args.iter().map(place_or_spread_to_hole).collect(),
2090 into: lvalue.clone(),
2091 signature: sig,
2092 loc: *loc,
2093 });
2094 }
2095 InstructionValue::CallExpression { callee, args, loc } => {
2096 let sig = get_function_call_signature(env, callee.identifier)
2097 .ok()
2098 .flatten();
2099 effects.push(AliasingEffect::Apply {
2100 receiver: callee.clone(),
2101 function: callee.clone(),
2102 mutates_function: true,
2103 args: args.iter().map(place_or_spread_to_hole).collect(),
2104 into: lvalue.clone(),
2105 signature: sig,
2106 loc: *loc,
2107 });
2108 }
2109 InstructionValue::MethodCall {
2110 receiver,
2111 property,
2112 args,
2113 loc,
2114 } => {
2115 let sig = get_function_call_signature(env, property.identifier)
2116 .ok()
2117 .flatten();
2118 effects.push(AliasingEffect::Apply {
2119 receiver: receiver.clone(),
2120 function: property.clone(),
2121 mutates_function: false,
2122 args: args.iter().map(place_or_spread_to_hole).collect(),
2123 into: lvalue.clone(),
2124 signature: sig,
2125 loc: *loc,
2126 });
2127 }
2128 InstructionValue::PropertyDelete { object, .. }
2129 | InstructionValue::ComputedDelete { object, .. } => {
2130 effects.push(AliasingEffect::Create {
2131 into: lvalue.clone(),
2132 value: ValueKind::Primitive,
2133 reason: ValueReason::Other,
2134 });
2135 effects.push(AliasingEffect::Mutate {
2136 value: object.clone(),
2137 reason: None,
2138 });
2139 }
2140 InstructionValue::PropertyLoad { object, .. }
2141 | InstructionValue::ComputedLoad { object, .. } => {
2142 let ty = &env.types[env.identifiers[lvalue.identifier.0 as usize].type_.0 as usize];
2143 if react_compiler_hir::is_primitive_type(ty) {
2144 effects.push(AliasingEffect::Create {
2145 into: lvalue.clone(),
2146 value: ValueKind::Primitive,
2147 reason: ValueReason::Other,
2148 });
2149 } else {
2150 effects.push(AliasingEffect::CreateFrom {
2151 from: object.clone(),
2152 into: lvalue.clone(),
2153 });
2154 }
2155 }
2156 InstructionValue::PropertyStore {
2157 object,
2158 property,
2159 value: store_value,
2160 ..
2161 } => {
2162 let mutation_reason: Option<MutationReason> = {
2163 let obj_ty =
2164 &env.types[env.identifiers[object.identifier.0 as usize].type_.0 as usize];
2165 if let react_compiler_hir::PropertyLiteral::String(prop_name) = property {
2166 if prop_name == "current" && matches!(obj_ty, Type::TypeVar { .. }) {
2167 Some(MutationReason::AssignCurrentProperty)
2168 } else {
2169 None
2170 }
2171 } else {
2172 None
2173 }
2174 };
2175 effects.push(AliasingEffect::Mutate {
2176 value: object.clone(),
2177 reason: mutation_reason,
2178 });
2179 effects.push(AliasingEffect::Capture {
2180 from: store_value.clone(),
2181 into: object.clone(),
2182 });
2183 effects.push(AliasingEffect::Create {
2184 into: lvalue.clone(),
2185 value: ValueKind::Primitive,
2186 reason: ValueReason::Other,
2187 });
2188 }
2189 InstructionValue::ComputedStore {
2190 object,
2191 value: store_value,
2192 ..
2193 } => {
2194 effects.push(AliasingEffect::Mutate {
2195 value: object.clone(),
2196 reason: None,
2197 });
2198 effects.push(AliasingEffect::Capture {
2199 from: store_value.clone(),
2200 into: object.clone(),
2201 });
2202 effects.push(AliasingEffect::Create {
2203 into: lvalue.clone(),
2204 value: ValueKind::Primitive,
2205 reason: ValueReason::Other,
2206 });
2207 }
2208 InstructionValue::FunctionExpression { lowered_func, .. }
2209 | InstructionValue::ObjectMethod { lowered_func, .. } => {
2210 let inner_func = &env.functions[lowered_func.func.0 as usize];
2211 let captures: Vec<Place> = inner_func
2212 .context
2213 .iter()
2214 .filter(|operand| operand.effect == Effect::Capture)
2215 .cloned()
2216 .collect();
2217 effects.push(AliasingEffect::CreateFunction {
2218 into: lvalue.clone(),
2219 function_id: lowered_func.func,
2220 captures,
2221 });
2222 }
2223 InstructionValue::GetIterator { collection, .. } => {
2224 effects.push(AliasingEffect::Create {
2225 into: lvalue.clone(),
2226 value: ValueKind::Mutable,
2227 reason: ValueReason::Other,
2228 });
2229 let ty = &env.types[env.identifiers[collection.identifier.0 as usize].type_.0 as usize];
2230 if is_builtin_collection_type(ty) {
2231 effects.push(AliasingEffect::Capture {
2232 from: collection.clone(),
2233 into: lvalue.clone(),
2234 });
2235 } else {
2236 effects.push(AliasingEffect::Alias {
2237 from: collection.clone(),
2238 into: lvalue.clone(),
2239 });
2240 effects.push(AliasingEffect::MutateTransitiveConditionally {
2241 value: collection.clone(),
2242 });
2243 }
2244 }
2245 InstructionValue::IteratorNext {
2246 iterator,
2247 collection,
2248 ..
2249 } => {
2250 effects.push(AliasingEffect::MutateConditionally {
2251 value: iterator.clone(),
2252 });
2253 effects.push(AliasingEffect::CreateFrom {
2254 from: collection.clone(),
2255 into: lvalue.clone(),
2256 });
2257 }
2258 InstructionValue::NextPropertyOf { .. } => {
2259 effects.push(AliasingEffect::Create {
2260 into: lvalue.clone(),
2261 value: ValueKind::Primitive,
2262 reason: ValueReason::Other,
2263 });
2264 }
2265 InstructionValue::JsxExpression {
2266 tag,
2267 props,
2268 children,
2269 ..
2270 } => {
2271 effects.push(AliasingEffect::Create {
2272 into: lvalue.clone(),
2273 value: ValueKind::Frozen,
2274 reason: ValueReason::JsxCaptured,
2275 });
2276 for operand in visitors::each_instruction_value_operand(value, env) {
2277 effects.push(AliasingEffect::Freeze {
2278 value: operand.clone(),
2279 reason: ValueReason::JsxCaptured,
2280 });
2281 effects.push(AliasingEffect::Capture {
2282 from: operand.clone(),
2283 into: lvalue.clone(),
2284 });
2285 }
2286 if let JsxTag::Place(tag_place) = tag {
2287 effects.push(AliasingEffect::Render {
2288 place: tag_place.clone(),
2289 });
2290 }
2291 if let Some(ch) = children {
2292 for child in ch {
2293 effects.push(AliasingEffect::Render {
2294 place: child.clone(),
2295 });
2296 }
2297 }
2298 for prop in props {
2299 if let react_compiler_hir::JsxAttribute::Attribute {
2300 place: prop_place, ..
2301 } = prop
2302 {
2303 let prop_ty = &env.types
2304 [env.identifiers[prop_place.identifier.0 as usize].type_.0 as usize];
2305 if let Type::Function { return_type, .. } = prop_ty {
2306 if react_compiler_hir::is_jsx_type(return_type)
2307 || is_phi_with_jsx(return_type)
2308 {
2309 effects.push(AliasingEffect::Render {
2310 place: prop_place.clone(),
2311 });
2312 }
2313 }
2314 }
2315 }
2316 }
2317 InstructionValue::JsxFragment { children: _, .. } => {
2318 effects.push(AliasingEffect::Create {
2319 into: lvalue.clone(),
2320 value: ValueKind::Frozen,
2321 reason: ValueReason::JsxCaptured,
2322 });
2323 for operand in visitors::each_instruction_value_operand(value, env) {
2324 effects.push(AliasingEffect::Freeze {
2325 value: operand.clone(),
2326 reason: ValueReason::JsxCaptured,
2327 });
2328 effects.push(AliasingEffect::Capture {
2329 from: operand.clone(),
2330 into: lvalue.clone(),
2331 });
2332 }
2333 }
2334 InstructionValue::DeclareLocal { lvalue: dl, .. } => {
2335 effects.push(AliasingEffect::Create {
2336 into: dl.place.clone(),
2337 value: ValueKind::Primitive,
2338 reason: ValueReason::Other,
2339 });
2340 effects.push(AliasingEffect::Create {
2341 into: lvalue.clone(),
2342 value: ValueKind::Primitive,
2343 reason: ValueReason::Other,
2344 });
2345 }
2346 InstructionValue::Destructure {
2347 lvalue: dl,
2348 value: dest_value,
2349 ..
2350 } => {
2351 for pat_item in each_pattern_items(&dl.pattern) {
2352 match pat_item {
2353 PatternItem::Place(place) => {
2354 let ty = &env.types
2355 [env.identifiers[place.identifier.0 as usize].type_.0 as usize];
2356 if react_compiler_hir::is_primitive_type(ty) {
2357 effects.push(AliasingEffect::Create {
2358 into: place.clone(),
2359 value: ValueKind::Primitive,
2360 reason: ValueReason::Other,
2361 });
2362 } else {
2363 effects.push(AliasingEffect::CreateFrom {
2364 from: dest_value.clone(),
2365 into: place.clone(),
2366 });
2367 }
2368 }
2369 PatternItem::Spread(place) => {
2370 let value_kind = if context.non_mutating_spreads.contains(&place.identifier)
2371 {
2372 ValueKind::Frozen
2373 } else {
2374 ValueKind::Mutable
2375 };
2376 effects.push(AliasingEffect::Create {
2377 into: place.clone(),
2378 reason: ValueReason::Other,
2379 value: value_kind,
2380 });
2381 effects.push(AliasingEffect::Capture {
2382 from: dest_value.clone(),
2383 into: place.clone(),
2384 });
2385 }
2386 }
2387 }
2388 effects.push(AliasingEffect::Assign {
2389 from: dest_value.clone(),
2390 into: lvalue.clone(),
2391 });
2392 }
2393 InstructionValue::LoadContext { place, .. } => {
2394 effects.push(AliasingEffect::CreateFrom {
2395 from: place.clone(),
2396 into: lvalue.clone(),
2397 });
2398 }
2399 InstructionValue::DeclareContext { lvalue: dcl, .. } => {
2400 let decl_id = env.identifiers[dcl.place.identifier.0 as usize].declaration_id;
2401 let kind = dcl.kind;
2402 if !context.hoisted_context_declarations.contains_key(&decl_id)
2403 || kind == InstructionKind::HoistedConst
2404 || kind == InstructionKind::HoistedFunction
2405 || kind == InstructionKind::HoistedLet
2406 {
2407 effects.push(AliasingEffect::Create {
2408 into: dcl.place.clone(),
2409 value: ValueKind::Mutable,
2410 reason: ValueReason::Other,
2411 });
2412 } else {
2413 effects.push(AliasingEffect::Mutate {
2414 value: dcl.place.clone(),
2415 reason: None,
2416 });
2417 }
2418 effects.push(AliasingEffect::Create {
2419 into: lvalue.clone(),
2420 value: ValueKind::Primitive,
2421 reason: ValueReason::Other,
2422 });
2423 }
2424 InstructionValue::StoreContext {
2425 lvalue: scl,
2426 value: sc_value,
2427 ..
2428 } => {
2429 let decl_id = env.identifiers[scl.place.identifier.0 as usize].declaration_id;
2430 if scl.kind == InstructionKind::Reassign
2431 || context.hoisted_context_declarations.contains_key(&decl_id)
2432 {
2433 effects.push(AliasingEffect::Mutate {
2434 value: scl.place.clone(),
2435 reason: None,
2436 });
2437 } else {
2438 effects.push(AliasingEffect::Create {
2439 into: scl.place.clone(),
2440 value: ValueKind::Mutable,
2441 reason: ValueReason::Other,
2442 });
2443 }
2444 effects.push(AliasingEffect::Capture {
2445 from: sc_value.clone(),
2446 into: scl.place.clone(),
2447 });
2448 effects.push(AliasingEffect::Assign {
2449 from: sc_value.clone(),
2450 into: lvalue.clone(),
2451 });
2452 }
2453 InstructionValue::LoadLocal { place, .. } => {
2454 effects.push(AliasingEffect::Assign {
2455 from: place.clone(),
2456 into: lvalue.clone(),
2457 });
2458 }
2459 InstructionValue::StoreLocal {
2460 lvalue: sl,
2461 value: sl_value,
2462 ..
2463 } => {
2464 effects.push(AliasingEffect::Assign {
2465 from: sl_value.clone(),
2466 into: sl.place.clone(),
2467 });
2468 effects.push(AliasingEffect::Assign {
2469 from: sl_value.clone(),
2470 into: lvalue.clone(),
2471 });
2472 }
2473 InstructionValue::PostfixUpdate {
2474 lvalue: pf_lvalue, ..
2475 }
2476 | InstructionValue::PrefixUpdate {
2477 lvalue: pf_lvalue, ..
2478 } => {
2479 effects.push(AliasingEffect::Create {
2480 into: lvalue.clone(),
2481 value: ValueKind::Primitive,
2482 reason: ValueReason::Other,
2483 });
2484 effects.push(AliasingEffect::Create {
2485 into: pf_lvalue.clone(),
2486 value: ValueKind::Primitive,
2487 reason: ValueReason::Other,
2488 });
2489 }
2490 InstructionValue::StoreGlobal {
2491 name,
2492 value: sg_value,
2493 loc: _,
2494 ..
2495 } => {
2496 let variable = format!("`{}`", name);
2497 let mut diagnostic = CompilerDiagnostic::new(
2498 ErrorCategory::Globals,
2499 "Cannot reassign variables declared outside of the component/hook",
2500 Some(format!(
2501 "Variable {} is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)",
2502 variable
2503 )),
2504 );
2505 diagnostic.details.push(
2506 react_compiler_diagnostics::CompilerDiagnosticDetail::Error {
2507 loc: instr.loc,
2508 message: Some(format!("{} cannot be reassigned", variable)),
2509 identifier_name: None,
2510 },
2511 );
2512 effects.push(AliasingEffect::MutateGlobal {
2513 place: sg_value.clone(),
2514 error: diagnostic,
2515 });
2516 effects.push(AliasingEffect::Assign {
2517 from: sg_value.clone(),
2518 into: lvalue.clone(),
2519 });
2520 }
2521 InstructionValue::TypeCastExpression {
2522 value: tc_value, ..
2523 } => {
2524 effects.push(AliasingEffect::Assign {
2525 from: tc_value.clone(),
2526 into: lvalue.clone(),
2527 });
2528 }
2529 InstructionValue::LoadGlobal { .. } => {
2530 effects.push(AliasingEffect::Create {
2531 into: lvalue.clone(),
2532 value: ValueKind::Global,
2533 reason: ValueReason::Global,
2534 });
2535 }
2536 InstructionValue::StartMemoize { .. } | InstructionValue::FinishMemoize { .. } => {
2537 if env.config.enable_preserve_existing_memoization_guarantees {
2538 for operand in visitors::each_instruction_value_operand(value, env) {
2539 effects.push(AliasingEffect::Freeze {
2540 value: operand.clone(),
2541 reason: ValueReason::HookCaptured,
2542 });
2543 }
2544 }
2545 effects.push(AliasingEffect::Create {
2546 into: lvalue.clone(),
2547 value: ValueKind::Primitive,
2548 reason: ValueReason::Other,
2549 });
2550 }
2551 InstructionValue::TaggedTemplateExpression { .. }
2553 | InstructionValue::BinaryExpression { .. }
2554 | InstructionValue::Debugger { .. }
2555 | InstructionValue::JSXText { .. }
2556 | InstructionValue::MetaProperty { .. }
2557 | InstructionValue::Primitive { .. }
2558 | InstructionValue::RegExpLiteral { .. }
2559 | InstructionValue::TemplateLiteral { .. }
2560 | InstructionValue::UnaryExpression { .. }
2561 | InstructionValue::UnsupportedNode { .. } => {
2562 effects.push(AliasingEffect::Create {
2563 into: lvalue.clone(),
2564 value: ValueKind::Primitive,
2565 reason: ValueReason::Other,
2566 });
2567 }
2568 }
2569
2570 InstructionSignature { effects }
2571}
2572
2573fn compute_effects_for_legacy_signature(
2578 state: &InferenceState,
2579 signature: &FunctionSignature,
2580 lvalue: &Place,
2581 receiver: &Place,
2582 args: &[PlaceOrSpreadOrHole],
2583 _loc: Option<&SourceLocation>,
2584 env: &Environment,
2585 function_values: &HashMap<ValueId, FunctionId>,
2586 todo_errors: &mut Vec<react_compiler_diagnostics::CompilerErrorDetail>,
2587) -> Vec<AliasingEffect> {
2588 let return_value_reason = signature.return_value_reason.unwrap_or(ValueReason::Other);
2589 let mut effects: Vec<AliasingEffect> = Vec::new();
2590
2591 effects.push(AliasingEffect::Create {
2592 into: lvalue.clone(),
2593 value: signature.return_value_kind,
2594 reason: return_value_reason,
2595 });
2596
2597 if signature.impure && env.config.validate_no_impure_functions_in_render {
2598 let mut diagnostic = CompilerDiagnostic::new(
2599 ErrorCategory::Purity,
2600 "Cannot call impure function during render",
2601 Some(format!(
2602 "{}Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)",
2603 if let Some(ref name) = signature.canonical_name {
2604 format!("`{}` is an impure function. ", name)
2605 } else {
2606 String::new()
2607 }
2608 )),
2609 );
2610 diagnostic.details.push(
2611 react_compiler_diagnostics::CompilerDiagnosticDetail::Error {
2612 loc: _loc.copied(),
2613 message: Some("Cannot call impure function".to_string()),
2614 identifier_name: None,
2615 },
2616 );
2617 effects.push(AliasingEffect::Impure {
2618 place: receiver.clone(),
2619 error: diagnostic,
2620 });
2621 }
2622
2623 if signature.mutable_only_if_operands_are_mutable
2629 && are_arguments_immutable_and_non_mutating(state, args, env, function_values)
2630 {
2631 effects.push(AliasingEffect::Alias {
2632 from: receiver.clone(),
2633 into: lvalue.clone(),
2634 });
2635 for arg in args {
2636 match arg {
2637 PlaceOrSpreadOrHole::Hole => continue,
2638 PlaceOrSpreadOrHole::Place(place)
2639 | PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place }) => {
2640 effects.push(AliasingEffect::ImmutableCapture {
2641 from: place.clone(),
2642 into: lvalue.clone(),
2643 });
2644 }
2645 }
2646 }
2647 return effects;
2648 }
2649
2650 let mut stores: Vec<Place> = Vec::new();
2651 let mut captures: Vec<Place> = Vec::new();
2652
2653 let mut visit = |place: &Place, effect: Effect, effects: &mut Vec<AliasingEffect>| match effect
2654 {
2655 Effect::Store => {
2656 effects.push(AliasingEffect::Mutate {
2657 value: place.clone(),
2658 reason: None,
2659 });
2660 stores.push(place.clone());
2661 }
2662 Effect::Capture => {
2663 captures.push(place.clone());
2664 }
2665 Effect::ConditionallyMutate => {
2666 effects.push(AliasingEffect::MutateTransitiveConditionally {
2667 value: place.clone(),
2668 });
2669 }
2670 Effect::ConditionallyMutateIterator => {
2671 let ty = &env.types[env.identifiers[place.identifier.0 as usize].type_.0 as usize];
2672 if let Some(mutate_iter) = conditionally_mutate_iterator(place, ty) {
2673 effects.push(mutate_iter);
2674 }
2675 effects.push(AliasingEffect::Capture {
2676 from: place.clone(),
2677 into: lvalue.clone(),
2678 });
2679 }
2680 Effect::Freeze => {
2681 effects.push(AliasingEffect::Freeze {
2682 value: place.clone(),
2683 reason: return_value_reason,
2684 });
2685 }
2686 Effect::Mutate => {
2687 effects.push(AliasingEffect::MutateTransitive {
2688 value: place.clone(),
2689 });
2690 }
2691 Effect::Read => {
2692 effects.push(AliasingEffect::ImmutableCapture {
2693 from: place.clone(),
2694 into: lvalue.clone(),
2695 });
2696 }
2697 _ => {}
2698 };
2699
2700 if signature.callee_effect != Effect::Capture {
2701 effects.push(AliasingEffect::Alias {
2702 from: receiver.clone(),
2703 into: lvalue.clone(),
2704 });
2705 }
2706
2707 visit(receiver, signature.callee_effect, &mut effects);
2708 for (i, arg) in args.iter().enumerate() {
2709 match arg {
2710 PlaceOrSpreadOrHole::Hole => continue,
2711 PlaceOrSpreadOrHole::Place(place)
2712 | PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place }) => {
2713 let is_spread = matches!(arg, PlaceOrSpreadOrHole::Spread(_));
2714 let sig_effect = if !is_spread && i < signature.positional_params.len() {
2715 signature.positional_params[i]
2716 } else {
2717 signature.rest_param.unwrap_or(Effect::ConditionallyMutate)
2718 };
2719 let (effect, err_detail) = get_argument_effect(sig_effect, is_spread, place.loc);
2720 if let Some(d) = err_detail {
2721 todo_errors.push(d);
2722 }
2723 visit(place, effect, &mut effects);
2724 }
2725 }
2726 }
2727
2728 if !captures.is_empty() {
2729 if stores.is_empty() {
2730 for capture in &captures {
2731 effects.push(AliasingEffect::Alias {
2732 from: capture.clone(),
2733 into: lvalue.clone(),
2734 });
2735 }
2736 } else {
2737 for capture in &captures {
2738 for store in &stores {
2739 effects.push(AliasingEffect::Capture {
2740 from: capture.clone(),
2741 into: store.clone(),
2742 });
2743 }
2744 }
2745 }
2746 }
2747
2748 effects
2749}
2750
2751fn get_argument_effect(
2752 sig_effect: Effect,
2753 is_spread: bool,
2754 spread_loc: Option<SourceLocation>,
2755) -> (
2756 Effect,
2757 Option<react_compiler_diagnostics::CompilerErrorDetail>,
2758) {
2759 if !is_spread {
2760 (sig_effect, None)
2761 } else if sig_effect == Effect::Mutate || sig_effect == Effect::ConditionallyMutate {
2762 (sig_effect, None)
2763 } else {
2764 let detail = if sig_effect == Effect::Freeze {
2767 Some(react_compiler_diagnostics::CompilerErrorDetail {
2768 reason: "Support spread syntax for hook arguments".to_string(),
2769 description: None,
2770 category: ErrorCategory::Todo,
2771 loc: spread_loc,
2772 suggestions: None,
2773 })
2774 } else {
2775 None
2776 };
2777 (Effect::ConditionallyMutateIterator, detail)
2778 }
2779}
2780
2781fn are_arguments_immutable_and_non_mutating(
2786 state: &InferenceState,
2787 args: &[PlaceOrSpreadOrHole],
2788 env: &Environment,
2789 function_values: &HashMap<ValueId, FunctionId>,
2790) -> bool {
2791 for arg in args {
2792 match arg {
2793 PlaceOrSpreadOrHole::Hole => continue,
2794 PlaceOrSpreadOrHole::Place(place)
2795 | PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place }) => {
2796 let is_place = matches!(arg, PlaceOrSpreadOrHole::Place(_));
2798 if is_place {
2799 let ty =
2800 &env.types[env.identifiers[place.identifier.0 as usize].type_.0 as usize];
2801 if let Type::Function { .. } = ty {
2802 let fn_shape = env.get_function_signature(ty).ok().flatten();
2803 if let Some(fn_sig) = fn_shape {
2804 let has_mutable_param = fn_sig
2805 .positional_params
2806 .iter()
2807 .any(|e| is_known_mutable_effect(*e));
2808 let has_mutable_rest = fn_sig
2809 .rest_param
2810 .map_or(false, |e| is_known_mutable_effect(e));
2811 return !has_mutable_param && !has_mutable_rest;
2812 }
2813 }
2814 }
2815
2816 let kind = state.kind(place.identifier);
2817 match kind.kind {
2818 ValueKind::Primitive | ValueKind::Frozen => {
2819 }
2821 _ => {
2822 return false;
2823 }
2824 }
2825
2826 let value_ids = state.values_for(place.identifier);
2829 for vid in &value_ids {
2830 if let Some(&func_id) = function_values.get(vid) {
2831 let inner_func = &env.functions[func_id.0 as usize];
2832 let mutates_params = inner_func.params.iter().any(|param| {
2833 let param_id = match param {
2834 ParamPattern::Place(p) => p.identifier,
2835 ParamPattern::Spread(s) => s.place.identifier,
2836 };
2837 let ident = &env.identifiers[param_id.0 as usize];
2838 ident.mutable_range.end.0 > ident.mutable_range.start.0 + 1
2839 });
2840 if mutates_params {
2841 return false;
2842 }
2843 }
2844 }
2845 }
2846 }
2847 }
2848 true
2849}
2850
2851fn is_known_mutable_effect(effect: Effect) -> bool {
2852 matches!(
2853 effect,
2854 Effect::Store
2855 | Effect::Mutate
2856 | Effect::ConditionallyMutate
2857 | Effect::ConditionallyMutateIterator
2858 )
2859}
2860
2861fn compute_effects_for_aliasing_signature_config(
2866 env: &mut Environment,
2867 config: &react_compiler_hir::type_config::AliasingSignatureConfig,
2868 lvalue: &Place,
2869 receiver: &Place,
2870 args: &[PlaceOrSpreadOrHole],
2871 context: &[Place],
2872 _loc: Option<&SourceLocation>,
2873 temp_cache: &mut HashMap<(IdentifierId, String), Place>,
2874) -> Result<Option<Vec<AliasingEffect>>, CompilerDiagnostic> {
2875 let mut substitutions: HashMap<String, Vec<Place>> = HashMap::new();
2877 substitutions.insert(config.receiver.clone(), vec![receiver.clone()]);
2878 substitutions.insert(config.returns.clone(), vec![lvalue.clone()]);
2879
2880 let mut mutable_spreads: HashSet<IdentifierId> = HashSet::new();
2881
2882 for (i, arg) in args.iter().enumerate() {
2883 match arg {
2884 PlaceOrSpreadOrHole::Hole => continue,
2885 PlaceOrSpreadOrHole::Place(place)
2886 | PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place }) => {
2887 if i < config.params.len() && !matches!(arg, PlaceOrSpreadOrHole::Spread(_)) {
2888 substitutions.insert(config.params[i].clone(), vec![place.clone()]);
2889 } else if let Some(ref rest) = config.rest {
2890 substitutions
2891 .entry(rest.clone())
2892 .or_default()
2893 .push(place.clone());
2894 } else {
2895 return Ok(None);
2896 }
2897
2898 if matches!(arg, PlaceOrSpreadOrHole::Spread(_)) {
2899 let ty =
2900 &env.types[env.identifiers[place.identifier.0 as usize].type_.0 as usize];
2901 let mutate_iterator = conditionally_mutate_iterator(place, ty);
2902 if mutate_iterator.is_some() {
2903 mutable_spreads.insert(place.identifier);
2904 }
2905 }
2906 }
2907 }
2908 }
2909
2910 for operand in context {
2911 let ident = &env.identifiers[operand.identifier.0 as usize];
2912 if let Some(ref name) = ident.name {
2913 substitutions.insert(format!("@{}", name.value()), vec![operand.clone()]);
2914 }
2915 }
2916
2917 for temp_name in &config.temporaries {
2919 let cache_key = (lvalue.identifier, temp_name.clone());
2920 let temp_place = temp_cache
2921 .entry(cache_key)
2922 .or_insert_with(|| create_temp_place(env, receiver.loc))
2923 .clone();
2924 substitutions.insert(temp_name.clone(), vec![temp_place]);
2925 }
2926
2927 let mut effects: Vec<AliasingEffect> = Vec::new();
2928
2929 for eff_config in &config.effects {
2930 match eff_config {
2931 react_compiler_hir::type_config::AliasingEffectConfig::Freeze { value, reason } => {
2932 let values = substitutions.get(value).cloned().unwrap_or_default();
2933 for v in values {
2934 if mutable_spreads.contains(&v.identifier) {
2935 return Err(CompilerDiagnostic::todo(
2936 "Support spread syntax for hook arguments",
2937 v.loc,
2938 ));
2939 }
2940 effects.push(AliasingEffect::Freeze { value: v, reason: *reason });
2941 }
2942 }
2943 react_compiler_hir::type_config::AliasingEffectConfig::Create { into, value, reason } => {
2944 let intos = substitutions.get(into).cloned().unwrap_or_default();
2945 for v in intos {
2946 effects.push(AliasingEffect::Create { into: v, value: *value, reason: *reason });
2947 }
2948 }
2949 react_compiler_hir::type_config::AliasingEffectConfig::CreateFrom { from, into } => {
2950 let froms = substitutions.get(from).cloned().unwrap_or_default();
2951 let intos = substitutions.get(into).cloned().unwrap_or_default();
2952 for f in &froms {
2953 for t in &intos {
2954 effects.push(AliasingEffect::CreateFrom { from: f.clone(), into: t.clone() });
2955 }
2956 }
2957 }
2958 react_compiler_hir::type_config::AliasingEffectConfig::Assign { from, into } => {
2959 let froms = substitutions.get(from).cloned().unwrap_or_default();
2960 let intos = substitutions.get(into).cloned().unwrap_or_default();
2961 for f in &froms {
2962 for t in &intos {
2963 effects.push(AliasingEffect::Assign { from: f.clone(), into: t.clone() });
2964 }
2965 }
2966 }
2967 react_compiler_hir::type_config::AliasingEffectConfig::Alias { from, into } => {
2968 let froms = substitutions.get(from).cloned().unwrap_or_default();
2969 let intos = substitutions.get(into).cloned().unwrap_or_default();
2970 for f in &froms {
2971 for t in &intos {
2972 effects.push(AliasingEffect::Alias { from: f.clone(), into: t.clone() });
2973 }
2974 }
2975 }
2976 react_compiler_hir::type_config::AliasingEffectConfig::Capture { from, into } => {
2977 let froms = substitutions.get(from).cloned().unwrap_or_default();
2978 let intos = substitutions.get(into).cloned().unwrap_or_default();
2979 for f in &froms {
2980 for t in &intos {
2981 effects.push(AliasingEffect::Capture { from: f.clone(), into: t.clone() });
2982 }
2983 }
2984 }
2985 react_compiler_hir::type_config::AliasingEffectConfig::ImmutableCapture { from, into } => {
2986 let froms = substitutions.get(from).cloned().unwrap_or_default();
2987 let intos = substitutions.get(into).cloned().unwrap_or_default();
2988 for f in &froms {
2989 for t in &intos {
2990 effects.push(AliasingEffect::ImmutableCapture { from: f.clone(), into: t.clone() });
2991 }
2992 }
2993 }
2994 react_compiler_hir::type_config::AliasingEffectConfig::Impure { place } => {
2995 let values = substitutions.get(place).cloned().unwrap_or_default();
2996 for v in values {
2997 effects.push(AliasingEffect::Impure {
2998 place: v,
2999 error: CompilerDiagnostic::new(ErrorCategory::Purity, "Impure function call", None),
3000 });
3001 }
3002 }
3003 react_compiler_hir::type_config::AliasingEffectConfig::Mutate { value } => {
3004 let values = substitutions.get(value).cloned().unwrap_or_default();
3005 for v in values {
3006 effects.push(AliasingEffect::Mutate { value: v, reason: None });
3007 }
3008 }
3009 react_compiler_hir::type_config::AliasingEffectConfig::MutateTransitiveConditionally { value } => {
3010 let values = substitutions.get(value).cloned().unwrap_or_default();
3011 for v in values {
3012 effects.push(AliasingEffect::MutateTransitiveConditionally { value: v });
3013 }
3014 }
3015 react_compiler_hir::type_config::AliasingEffectConfig::Apply { receiver: r, function: f, mutates_function, args: a, into: i } => {
3016 let recv = substitutions.get(r).and_then(|v| v.first()).cloned();
3017 let func = substitutions.get(f).and_then(|v| v.first()).cloned();
3018 let into = substitutions.get(i).and_then(|v| v.first()).cloned();
3019 if let (Some(recv), Some(func), Some(into)) = (recv, func, into) {
3020 let mut apply_args: Vec<PlaceOrSpreadOrHole> = Vec::new();
3021 for arg in a {
3022 match arg {
3023 react_compiler_hir::type_config::ApplyArgConfig::Hole { .. } => {
3024 apply_args.push(PlaceOrSpreadOrHole::Hole);
3025 }
3026 react_compiler_hir::type_config::ApplyArgConfig::Place(name) => {
3027 if let Some(places) = substitutions.get(name) {
3028 if let Some(p) = places.first() {
3029 apply_args.push(PlaceOrSpreadOrHole::Place(p.clone()));
3030 }
3031 }
3032 }
3033 react_compiler_hir::type_config::ApplyArgConfig::Spread { place: name, .. } => {
3034 if let Some(places) = substitutions.get(name) {
3035 if let Some(p) = places.first() {
3036 apply_args.push(PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place: p.clone() }));
3037 }
3038 }
3039 }
3040 }
3041 }
3042 effects.push(AliasingEffect::Apply {
3043 receiver: recv,
3044 function: func,
3045 mutates_function: *mutates_function,
3046 args: apply_args,
3047 into,
3048 signature: None,
3049 loc: _loc.copied(),
3050 });
3051 } else {
3052 return Ok(None);
3053 }
3054 }
3055 }
3056 }
3057
3058 Ok(Some(effects))
3059}
3060
3061fn build_signature_from_function_expression(
3068 env: &mut Environment,
3069 func_id: FunctionId,
3070) -> AliasingSignature {
3071 let inner_func = &env.functions[func_id.0 as usize];
3072 let mut params: Vec<IdentifierId> = Vec::new();
3073 let mut rest: Option<IdentifierId> = None;
3074 for param in &inner_func.params {
3075 match param {
3076 ParamPattern::Place(p) => params.push(p.identifier),
3077 ParamPattern::Spread(s) => rest = Some(s.place.identifier),
3078 }
3079 }
3080 let returns = inner_func.returns.identifier;
3081 let aliasing_effects = inner_func.aliasing_effects.clone().unwrap_or_default();
3082 let loc = inner_func.loc;
3083
3084 if rest.is_none() {
3085 let temp = create_temp_place(env, loc);
3086 rest = Some(temp.identifier);
3087 }
3088
3089 AliasingSignature {
3090 receiver: IdentifierId(0),
3091 params,
3092 rest,
3093 returns,
3094 effects: aliasing_effects,
3095 temporaries: Vec::new(),
3096 }
3097}
3098
3099fn compute_effects_for_aliasing_signature(
3102 env: &mut Environment,
3103 signature: &AliasingSignature,
3104 lvalue: &Place,
3105 receiver: &Place,
3106 args: &[PlaceOrSpreadOrHole],
3107 context: &[Place],
3108 _loc: Option<&SourceLocation>,
3109) -> Result<Option<Vec<AliasingEffect>>, CompilerDiagnostic> {
3110 if signature.params.len() > args.len()
3111 || (args.len() > signature.params.len() && signature.rest.is_none())
3112 {
3113 return Ok(None);
3114 }
3115
3116 let mut mutable_spreads: HashSet<IdentifierId> = HashSet::new();
3117 let mut substitutions: HashMap<IdentifierId, Vec<Place>> = HashMap::new();
3118 substitutions.insert(signature.receiver, vec![receiver.clone()]);
3119 substitutions.insert(signature.returns, vec![lvalue.clone()]);
3120
3121 for (i, arg) in args.iter().enumerate() {
3122 match arg {
3123 PlaceOrSpreadOrHole::Hole => continue,
3124 PlaceOrSpreadOrHole::Place(place)
3125 | PlaceOrSpreadOrHole::Spread(react_compiler_hir::SpreadPattern { place }) => {
3126 let is_spread = matches!(arg, PlaceOrSpreadOrHole::Spread(_));
3127 if !is_spread && i < signature.params.len() {
3128 substitutions.insert(signature.params[i], vec![place.clone()]);
3129 } else if let Some(rest_id) = signature.rest {
3130 substitutions
3131 .entry(rest_id)
3132 .or_default()
3133 .push(place.clone());
3134 } else {
3135 return Ok(None);
3136 }
3137
3138 if is_spread {
3139 let ty =
3140 &env.types[env.identifiers[place.identifier.0 as usize].type_.0 as usize];
3141 let mutate_iterator = conditionally_mutate_iterator(place, ty);
3142 if mutate_iterator.is_some() {
3143 mutable_spreads.insert(place.identifier);
3144 }
3145 }
3146 }
3147 }
3148 }
3149
3150 for operand in context {
3152 substitutions.insert(operand.identifier, vec![operand.clone()]);
3153 }
3154
3155 for temp in &signature.temporaries {
3157 let temp_place = create_temp_place(env, receiver.loc);
3158 substitutions.insert(temp.identifier, vec![temp_place]);
3159 }
3160
3161 let mut effects: Vec<AliasingEffect> = Vec::new();
3162
3163 for eff in &signature.effects {
3164 match eff {
3165 AliasingEffect::MaybeAlias { from, into }
3166 | AliasingEffect::Assign { from, into }
3167 | AliasingEffect::ImmutableCapture { from, into }
3168 | AliasingEffect::Alias { from, into }
3169 | AliasingEffect::CreateFrom { from, into }
3170 | AliasingEffect::Capture { from, into } => {
3171 let from_places = substitutions
3172 .get(&from.identifier)
3173 .cloned()
3174 .unwrap_or_default();
3175 let to_places = substitutions
3176 .get(&into.identifier)
3177 .cloned()
3178 .unwrap_or_default();
3179 for f in &from_places {
3180 for t in &to_places {
3181 effects.push(match eff {
3182 AliasingEffect::MaybeAlias { .. } => AliasingEffect::MaybeAlias {
3183 from: f.clone(),
3184 into: t.clone(),
3185 },
3186 AliasingEffect::Assign { .. } => AliasingEffect::Assign {
3187 from: f.clone(),
3188 into: t.clone(),
3189 },
3190 AliasingEffect::ImmutableCapture { .. } => {
3191 AliasingEffect::ImmutableCapture {
3192 from: f.clone(),
3193 into: t.clone(),
3194 }
3195 }
3196 AliasingEffect::Alias { .. } => AliasingEffect::Alias {
3197 from: f.clone(),
3198 into: t.clone(),
3199 },
3200 AliasingEffect::CreateFrom { .. } => AliasingEffect::CreateFrom {
3201 from: f.clone(),
3202 into: t.clone(),
3203 },
3204 AliasingEffect::Capture { .. } => AliasingEffect::Capture {
3205 from: f.clone(),
3206 into: t.clone(),
3207 },
3208 _ => unreachable!(),
3209 });
3210 }
3211 }
3212 }
3213 AliasingEffect::Impure { place, error } => {
3214 let values = substitutions
3215 .get(&place.identifier)
3216 .cloned()
3217 .unwrap_or_default();
3218 for v in values {
3219 effects.push(AliasingEffect::Impure {
3220 place: v,
3221 error: error.clone(),
3222 });
3223 }
3224 }
3225 AliasingEffect::MutateFrozen { place, error } => {
3226 let values = substitutions
3227 .get(&place.identifier)
3228 .cloned()
3229 .unwrap_or_default();
3230 for v in values {
3231 effects.push(AliasingEffect::MutateFrozen {
3232 place: v,
3233 error: error.clone(),
3234 });
3235 }
3236 }
3237 AliasingEffect::MutateGlobal { place, error } => {
3238 let values = substitutions
3239 .get(&place.identifier)
3240 .cloned()
3241 .unwrap_or_default();
3242 for v in values {
3243 effects.push(AliasingEffect::MutateGlobal {
3244 place: v,
3245 error: error.clone(),
3246 });
3247 }
3248 }
3249 AliasingEffect::Render { place } => {
3250 let values = substitutions
3251 .get(&place.identifier)
3252 .cloned()
3253 .unwrap_or_default();
3254 for v in values {
3255 effects.push(AliasingEffect::Render { place: v });
3256 }
3257 }
3258 AliasingEffect::Mutate { value, reason } => {
3259 let values = substitutions
3260 .get(&value.identifier)
3261 .cloned()
3262 .unwrap_or_default();
3263 for v in values {
3264 effects.push(AliasingEffect::Mutate {
3265 value: v,
3266 reason: reason.clone(),
3267 });
3268 }
3269 }
3270 AliasingEffect::MutateConditionally { value } => {
3271 let values = substitutions
3272 .get(&value.identifier)
3273 .cloned()
3274 .unwrap_or_default();
3275 for v in values {
3276 effects.push(AliasingEffect::MutateConditionally { value: v });
3277 }
3278 }
3279 AliasingEffect::MutateTransitive { value } => {
3280 let values = substitutions
3281 .get(&value.identifier)
3282 .cloned()
3283 .unwrap_or_default();
3284 for v in values {
3285 effects.push(AliasingEffect::MutateTransitive { value: v });
3286 }
3287 }
3288 AliasingEffect::MutateTransitiveConditionally { value } => {
3289 let values = substitutions
3290 .get(&value.identifier)
3291 .cloned()
3292 .unwrap_or_default();
3293 for v in values {
3294 effects.push(AliasingEffect::MutateTransitiveConditionally { value: v });
3295 }
3296 }
3297 AliasingEffect::Freeze { value, reason } => {
3298 let values = substitutions
3299 .get(&value.identifier)
3300 .cloned()
3301 .unwrap_or_default();
3302 for v in values {
3303 if mutable_spreads.contains(&v.identifier) {
3304 return Err(CompilerDiagnostic::todo(
3305 "Support spread syntax for hook arguments",
3306 v.loc,
3307 ));
3308 }
3309 effects.push(AliasingEffect::Freeze {
3310 value: v,
3311 reason: *reason,
3312 });
3313 }
3314 }
3315 AliasingEffect::Create {
3316 into,
3317 value,
3318 reason,
3319 } => {
3320 let intos = substitutions
3321 .get(&into.identifier)
3322 .cloned()
3323 .unwrap_or_default();
3324 for v in intos {
3325 effects.push(AliasingEffect::Create {
3326 into: v,
3327 value: *value,
3328 reason: *reason,
3329 });
3330 }
3331 }
3332 AliasingEffect::Apply {
3333 receiver: r,
3334 function: f,
3335 mutates_function: mf,
3336 args: a,
3337 into: i,
3338 signature: s,
3339 loc: _l,
3340 } => {
3341 let recv = substitutions
3342 .get(&r.identifier)
3343 .and_then(|v| v.first())
3344 .cloned();
3345 let func = substitutions
3346 .get(&f.identifier)
3347 .and_then(|v| v.first())
3348 .cloned();
3349 let apply_into = substitutions
3350 .get(&i.identifier)
3351 .and_then(|v| v.first())
3352 .cloned();
3353 if let (Some(recv), Some(func), Some(apply_into)) = (recv, func, apply_into) {
3354 let mut apply_args: Vec<PlaceOrSpreadOrHole> = Vec::new();
3355 for arg in a {
3356 match arg {
3357 PlaceOrSpreadOrHole::Hole => apply_args.push(PlaceOrSpreadOrHole::Hole),
3358 PlaceOrSpreadOrHole::Place(p) => {
3359 if let Some(places) = substitutions.get(&p.identifier) {
3360 if let Some(place) = places.first() {
3361 apply_args.push(PlaceOrSpreadOrHole::Place(place.clone()));
3362 }
3363 }
3364 }
3365 PlaceOrSpreadOrHole::Spread(sp) => {
3366 if let Some(places) = substitutions.get(&sp.place.identifier) {
3367 if let Some(place) = places.first() {
3368 apply_args.push(PlaceOrSpreadOrHole::Spread(
3369 react_compiler_hir::SpreadPattern {
3370 place: place.clone(),
3371 },
3372 ));
3373 }
3374 }
3375 }
3376 }
3377 }
3378 effects.push(AliasingEffect::Apply {
3379 receiver: recv,
3380 function: func,
3381 mutates_function: *mf,
3382 args: apply_args,
3383 into: apply_into,
3384 signature: s.clone(),
3385 loc: _loc.copied(),
3386 });
3387 } else {
3388 return Ok(None);
3389 }
3390 }
3391 AliasingEffect::CreateFunction { .. } => {
3392 return Ok(None);
3394 }
3395 }
3396 }
3397
3398 Ok(Some(effects))
3399}
3400
3401fn primary_reason(reasons: &IndexSet<ValueReason>) -> ValueReason {
3411 for &r in reasons {
3412 if r != ValueReason::Other {
3413 return r;
3414 }
3415 }
3416 ValueReason::Other
3417}
3418
3419fn get_write_error_reason(abstract_value: &AbstractValue) -> String {
3420 if abstract_value.reason.contains(&ValueReason::Global) {
3421 "Modifying a variable defined outside a component or hook is not allowed. Consider using an effect".to_string()
3422 } else if abstract_value.reason.contains(&ValueReason::JsxCaptured) {
3423 "Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX".to_string()
3424 } else if abstract_value.reason.contains(&ValueReason::Context) {
3425 "Modifying a value returned from 'useContext()' is not allowed.".to_string()
3426 } else if abstract_value
3427 .reason
3428 .contains(&ValueReason::KnownReturnSignature)
3429 {
3430 "Modifying a value returned from a function whose return value should not be mutated"
3431 .to_string()
3432 } else if abstract_value
3433 .reason
3434 .contains(&ValueReason::ReactiveFunctionArgument)
3435 {
3436 "Modifying component props or hook arguments is not allowed. Consider using a local variable instead".to_string()
3437 } else if abstract_value.reason.contains(&ValueReason::State) {
3438 "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead".to_string()
3439 } else if abstract_value.reason.contains(&ValueReason::ReducerState) {
3440 "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead".to_string()
3441 } else if abstract_value.reason.contains(&ValueReason::Effect) {
3442 "Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()".to_string()
3443 } else if abstract_value.reason.contains(&ValueReason::HookCaptured) {
3444 "Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook".to_string()
3445 } else if abstract_value.reason.contains(&ValueReason::HookReturn) {
3446 "Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed".to_string()
3447 } else {
3448 "This modifies a variable that React considers immutable".to_string()
3449 }
3450}
3451
3452fn conditionally_mutate_iterator(place: &Place, ty: &Type) -> Option<AliasingEffect> {
3453 if !is_builtin_collection_type(ty) {
3454 Some(AliasingEffect::MutateTransitiveConditionally {
3455 value: place.clone(),
3456 })
3457 } else {
3458 None
3459 }
3460}
3461
3462fn is_builtin_collection_type(ty: &Type) -> bool {
3463 matches!(ty, Type::Object { shape_id: Some(id) }
3464 if id == BUILT_IN_ARRAY_ID || id == BUILT_IN_SET_ID || id == BUILT_IN_MAP_ID
3465 )
3466}
3467
3468fn get_function_call_signature(
3469 env: &Environment,
3470 callee_id: IdentifierId,
3471) -> Result<Option<FunctionSignature>, CompilerDiagnostic> {
3472 let ty = &env.types[env.identifiers[callee_id.0 as usize].type_.0 as usize];
3473 Ok(env.get_function_signature(ty)?.cloned())
3474}
3475
3476fn is_ref_or_ref_value_for_id(env: &Environment, id: IdentifierId) -> bool {
3477 let ty = &env.types[env.identifiers[id.0 as usize].type_.0 as usize];
3478 react_compiler_hir::is_ref_or_ref_value(ty)
3479}
3480
3481fn get_hook_kind_for_type<'a>(
3482 env: &'a Environment,
3483 ty: &Type,
3484) -> Result<Option<&'a HookKind>, CompilerDiagnostic> {
3485 env.get_hook_kind_for_type(ty)
3486}
3487
3488fn format_type_for_print(ty: &Type) -> String {
3490 match ty {
3491 Type::Primitive => String::new(),
3492 Type::Function {
3493 shape_id,
3494 return_type,
3495 ..
3496 } => {
3497 if let Some(sid) = shape_id {
3498 let ret = format_type_for_print(return_type);
3499 if ret.is_empty() {
3500 format!(":TFunction<{}>()", sid)
3501 } else {
3502 format!(":TFunction<{}>(): {}", sid, ret)
3503 }
3504 } else {
3505 ":TFunction".to_string()
3506 }
3507 }
3508 Type::Object { shape_id } => {
3509 if let Some(sid) = shape_id {
3510 format!(":TObject<{}>", sid)
3511 } else {
3512 ":TObject".to_string()
3513 }
3514 }
3515 Type::Poly => ":TPoly".to_string(),
3516 Type::Phi { .. } => ":TPhi".to_string(),
3517 Type::Property { .. } => ":TProperty".to_string(),
3518 Type::TypeVar { .. } => String::new(),
3519 Type::ObjectMethod => ":TObjectMethod".to_string(),
3520 }
3521}
3522
3523fn is_phi_with_jsx(ty: &Type) -> bool {
3524 if let Type::Phi { operands } = ty {
3525 operands
3526 .iter()
3527 .any(|op| react_compiler_hir::is_jsx_type(op))
3528 } else {
3529 false
3530 }
3531}
3532
3533fn place_or_spread_to_hole(pos: &PlaceOrSpread) -> PlaceOrSpreadOrHole {
3534 match pos {
3535 PlaceOrSpread::Place(p) => PlaceOrSpreadOrHole::Place(p.clone()),
3536 PlaceOrSpread::Spread(s) => PlaceOrSpreadOrHole::Spread(s.clone()),
3537 }
3538}
3539
3540use react_compiler_hir::JsxTag;
3541
3542fn build_apply_operands(
3543 receiver: &Place,
3544 function: &Place,
3545 args: &[PlaceOrSpreadOrHole],
3546) -> Vec<(Place, bool, bool)> {
3547 let mut result = vec![
3548 (receiver.clone(), false, false),
3549 (function.clone(), true, false),
3550 ];
3551 for arg in args {
3552 match arg {
3553 PlaceOrSpreadOrHole::Hole => continue,
3554 PlaceOrSpreadOrHole::Place(p) => result.push((p.clone(), false, false)),
3555 PlaceOrSpreadOrHole::Spread(s) => result.push((s.place.clone(), false, true)),
3556 }
3557 }
3558 result
3559}
3560
3561fn create_temp_place(env: &mut Environment, loc: Option<SourceLocation>) -> Place {
3562 let id = env.next_identifier_id();
3563 env.identifiers[id.0 as usize].loc = loc;
3564 Place {
3565 identifier: id,
3566 effect: Effect::Unknown,
3567 reactive: false,
3568 loc,
3569 }
3570}
3571
3572fn terminal_successors(terminal: &react_compiler_hir::Terminal) -> Vec<BlockId> {
3583 use react_compiler_hir::Terminal;
3584 match terminal {
3585 Terminal::Goto { block, .. } => vec![*block],
3586 Terminal::If {
3587 consequent,
3588 alternate,
3589 ..
3590 } => vec![*consequent, *alternate],
3591 Terminal::Branch {
3592 consequent,
3593 alternate,
3594 ..
3595 } => vec![*consequent, *alternate],
3596 Terminal::Switch { cases, .. } => cases.iter().map(|c| c.block).collect(),
3597 Terminal::For { init, .. } => vec![*init],
3598 Terminal::ForOf { init, .. } | Terminal::ForIn { init, .. } => vec![*init],
3599 Terminal::DoWhile { loop_block, .. } => vec![*loop_block],
3600 Terminal::While { test, .. } => vec![*test],
3601 Terminal::Return { .. }
3602 | Terminal::Throw { .. }
3603 | Terminal::Unreachable { .. }
3604 | Terminal::Unsupported { .. } => vec![],
3605 Terminal::Try { block, .. } => vec![*block],
3606 Terminal::MaybeThrow {
3607 continuation,
3608 handler,
3609 ..
3610 } => {
3611 let mut v = vec![*continuation];
3612 if let Some(h) = handler {
3613 v.push(*h);
3614 }
3615 v
3616 }
3617 Terminal::Label { block, .. } | Terminal::Sequence { block, .. } => vec![*block],
3618 Terminal::Logical { test, .. } | Terminal::Ternary { test, .. } => vec![*test],
3619 Terminal::Optional { test, .. } => vec![*test],
3620 Terminal::Scope { block, .. } | Terminal::PrunedScope { block, .. } => vec![*block],
3621 }
3622}
3623
3624enum PatternItem<'a> {
3630 Place(&'a Place),
3631 Spread(&'a Place),
3632}
3633
3634fn each_pattern_items(pattern: &react_compiler_hir::Pattern) -> Vec<PatternItem<'_>> {
3635 let mut items = Vec::new();
3636 match pattern {
3637 react_compiler_hir::Pattern::Array(arr) => {
3638 for el in &arr.items {
3639 match el {
3640 react_compiler_hir::ArrayPatternElement::Place(p) => {
3641 items.push(PatternItem::Place(p))
3642 }
3643 react_compiler_hir::ArrayPatternElement::Spread(s) => {
3644 items.push(PatternItem::Spread(&s.place))
3645 }
3646 react_compiler_hir::ArrayPatternElement::Hole => {}
3647 }
3648 }
3649 }
3650 react_compiler_hir::Pattern::Object(obj) => {
3651 for prop in &obj.properties {
3652 match prop {
3653 react_compiler_hir::ObjectPropertyOrSpread::Property(p) => {
3654 items.push(PatternItem::Place(&p.place))
3655 }
3656 react_compiler_hir::ObjectPropertyOrSpread::Spread(s) => {
3657 items.push(PatternItem::Spread(&s.place))
3658 }
3659 }
3660 }
3661 }
3662 }
3663 items
3664}