react_compiler_inference/
align_object_method_scopes.rs1use std::cmp;
13use std::collections::{HashMap, HashSet};
14
15use react_compiler_hir::environment::Environment;
16use react_compiler_hir::{
17 EvaluationOrder, HirFunction, IdentifierId, InstructionValue, ObjectPropertyOrSpread, ScopeId,
18};
19use react_compiler_utils::DisjointSet;
20
21fn find_scopes_to_merge(func: &HirFunction, env: &Environment) -> DisjointSet<ScopeId> {
29 let mut object_method_decls: HashSet<IdentifierId> = HashSet::new();
30 let mut merged_scopes = DisjointSet::<ScopeId>::new();
31
32 for (_block_id, block) in &func.body.blocks {
33 for &instr_id in &block.instructions {
34 let instr = &func.instructions[instr_id.0 as usize];
35 match &instr.value {
36 InstructionValue::ObjectMethod { .. } => {
37 object_method_decls.insert(instr.lvalue.identifier);
38 }
39 InstructionValue::ObjectExpression { properties, .. } => {
40 for prop_or_spread in properties {
41 let operand_place = match prop_or_spread {
42 ObjectPropertyOrSpread::Property(prop) => &prop.place,
43 ObjectPropertyOrSpread::Spread(spread) => &spread.place,
44 };
45 if object_method_decls.contains(&operand_place.identifier) {
46 let operand_scope =
47 env.identifiers[operand_place.identifier.0 as usize].scope;
48 let lvalue_scope =
49 env.identifiers[instr.lvalue.identifier.0 as usize].scope;
50
51 let operand_sid = operand_scope.expect(
53 "Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.",
54 );
55 let lvalue_sid = lvalue_scope.expect(
56 "Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.",
57 );
58 merged_scopes.union(&[operand_sid, lvalue_sid]);
59 }
60 }
61 }
62 _ => {}
63 }
64 }
65 }
66
67 merged_scopes
68}
69
70pub fn align_object_method_scopes(func: &mut HirFunction, env: &mut Environment) {
79 for (_block_id, block) in &func.body.blocks {
81 for &instr_id in &block.instructions {
82 let instr = &func.instructions[instr_id.0 as usize];
83 match &instr.value {
84 InstructionValue::FunctionExpression { lowered_func, .. }
85 | InstructionValue::ObjectMethod { lowered_func, .. } => {
86 let func_id = lowered_func.func;
87 let mut inner_func = std::mem::replace(
88 &mut env.functions[func_id.0 as usize],
89 react_compiler_ssa::enter_ssa::placeholder_function(),
90 );
91 align_object_method_scopes(&mut inner_func, env);
92 env.functions[func_id.0 as usize] = inner_func;
93 }
94 _ => {}
95 }
96 }
97 }
98
99 let mut merged_scopes = find_scopes_to_merge(func, env);
100
101 let mut range_updates: HashMap<ScopeId, (EvaluationOrder, EvaluationOrder)> = HashMap::new();
105
106 merged_scopes.for_each(|scope_id, root_id| {
107 if scope_id == root_id {
108 return;
109 }
110 let scope_range = env.scopes[scope_id.0 as usize].range.clone();
111 let root_range = env.scopes[root_id.0 as usize].range.clone();
112
113 let entry = range_updates.entry(root_id).or_insert_with(|| {
114 (root_range.start, root_range.end)
115 });
116 entry.0 = EvaluationOrder(cmp::min(entry.0.0, scope_range.start.0));
117 entry.1 = EvaluationOrder(cmp::max(entry.1.0, scope_range.end.0));
118 });
119
120 let original_range_ids: HashMap<ScopeId, react_compiler_hir::MutableRangeId> = range_updates
122 .keys()
123 .map(|&root_id| {
124 let range_id = env.scopes[root_id.0 as usize].range.id;
125 (root_id, range_id)
126 })
127 .collect();
128
129 for (root_id, (new_start, new_end)) in &range_updates {
130 env.scopes[root_id.0 as usize].range.start = *new_start;
131 env.scopes[root_id.0 as usize].range.end = *new_end;
132 }
133
134 for ident in &mut env.identifiers {
137 if let Some(scope_id) = ident.scope {
138 if let Some(&orig_range_id) = original_range_ids.get(&scope_id) {
139 if ident.mutable_range.id == orig_range_id {
140 let new_range = &env.scopes[scope_id.0 as usize].range;
141 ident.mutable_range.start = new_range.start;
142 ident.mutable_range.end = new_range.end;
143 }
144 }
145 }
146 }
147
148 let mut scope_remap: HashMap<ScopeId, ScopeId> = HashMap::new();
151 merged_scopes.for_each(|scope_id, root_id| {
152 if scope_id != root_id {
153 scope_remap.insert(scope_id, root_id);
154 }
155 });
156
157 for (_block_id, block) in &func.body.blocks {
158 for &instr_id in &block.instructions {
159 let lvalue_id = func.instructions[instr_id.0 as usize].lvalue.identifier;
160
161 if let Some(current_scope) = env.identifiers[lvalue_id.0 as usize].scope {
162 if let Some(&root) = scope_remap.get(¤t_scope) {
163 env.identifiers[lvalue_id.0 as usize].scope = Some(root);
164 }
165 }
166 }
167 }
168}