react_compiler_optimization/
name_anonymous_functions.rs1use std::collections::HashMap;
15
16use react_compiler_hir::environment::Environment;
17use react_compiler_hir::object_shape::HookKind;
18use react_compiler_hir::{
19 FunctionId, HirFunction, IdentifierId, IdentifierName, InstructionValue, JsxAttribute, JsxTag,
20 PlaceOrSpread, Instruction,
21};
22
23pub fn name_anonymous_functions(func: &mut HirFunction, env: &mut Environment) {
27 let fn_id = match &func.id {
28 Some(id) => id.clone(),
29 None => return,
30 };
31
32 let nodes = name_anonymous_functions_impl(func, env);
33
34 fn visit(
35 node: &Node,
36 prefix: &str,
37 updates: &mut Vec<(FunctionId, String)>,
38 ) {
39 if node.generated_name.is_some() && node.existing_name_hint.is_none() {
40 let name = format!("{}{}]", prefix, node.generated_name.as_ref().unwrap());
42 updates.push((node.function_id, name));
43 }
44 let fallback;
47 let label = if let Some(ref gen_name) = node.generated_name {
48 gen_name.as_str()
49 } else if let Some(ref existing) = node.fn_name {
50 existing.as_str()
51 } else {
52 fallback = "<anonymous>";
53 fallback
54 };
55 let next_prefix = format!("{}{} > ", prefix, label);
56 for inner in &node.inner {
57 visit(inner, &next_prefix, updates);
58 }
59 }
60
61 let mut updates: Vec<(FunctionId, String)> = Vec::new();
62 let prefix = format!("{}[", fn_id);
63 for node in &nodes {
64 visit(node, &prefix, &mut updates);
65 }
66
67 if updates.is_empty() {
68 return;
69 }
70 let update_map: HashMap<FunctionId, &String> =
71 updates.iter().map(|(fid, name)| (*fid, name)).collect();
72
73 for (function_id, name) in &updates {
75 env.functions[function_id.0 as usize].name_hint = Some(name.clone());
76 }
77
78 apply_name_hints_to_instructions(&mut func.instructions, &update_map);
80
81 for i in 0..env.functions.len() {
83 let mut instructions = std::mem::take(&mut env.functions[i].instructions);
85 apply_name_hints_to_instructions(&mut instructions, &update_map);
86 env.functions[i].instructions = instructions;
87 }
88}
89
90fn apply_name_hints_to_instructions(
92 instructions: &mut [Instruction],
93 update_map: &HashMap<FunctionId, &String>,
94) {
95 for instr in instructions.iter_mut() {
96 if let InstructionValue::FunctionExpression {
97 lowered_func,
98 name_hint,
99 ..
100 } = &mut instr.value
101 {
102 if let Some(new_name) = update_map.get(&lowered_func.func) {
103 *name_hint = Some((*new_name).clone());
104 }
105 }
106 }
107}
108
109struct Node {
110 function_id: FunctionId,
112 generated_name: Option<String>,
114 fn_name: Option<String>,
116 existing_name_hint: Option<String>,
118 inner: Vec<Node>,
120}
121
122fn name_anonymous_functions_impl(func: &HirFunction, env: &Environment) -> Vec<Node> {
123 let mut functions: HashMap<IdentifierId, usize> = HashMap::new();
125 let mut names: HashMap<IdentifierId, String> = HashMap::new();
127 let mut nodes: Vec<Node> = Vec::new();
129
130 for block in func.body.blocks.values() {
131 for instr_id in &block.instructions {
132 let instr = &func.instructions[instr_id.0 as usize];
133 let lvalue_id = instr.lvalue.identifier;
134 match &instr.value {
135 InstructionValue::LoadGlobal { binding, .. } => {
136 names.insert(lvalue_id, binding.name().to_string());
137 }
138 InstructionValue::LoadContext { place, .. }
139 | InstructionValue::LoadLocal { place, .. } => {
140 let ident = &env.identifiers[place.identifier.0 as usize];
141 if let Some(IdentifierName::Named(ref name)) = ident.name {
142 names.insert(lvalue_id, name.clone());
143 }
144 if let Some(&node_idx) = functions.get(&place.identifier) {
146 functions.insert(lvalue_id, node_idx);
147 }
148 }
149 InstructionValue::PropertyLoad {
150 object, property, ..
151 } => {
152 if let Some(object_name) = names.get(&object.identifier) {
153 names.insert(
154 lvalue_id,
155 format!("{}.{}", object_name, property),
156 );
157 }
158 }
159 InstructionValue::FunctionExpression {
160 name,
161 lowered_func,
162 ..
163 } => {
164 let inner_func = &env.functions[lowered_func.func.0 as usize];
165 let inner = name_anonymous_functions_impl(inner_func, env);
166 let node = Node {
167 function_id: lowered_func.func,
168 generated_name: None,
169 fn_name: name.clone(),
170 existing_name_hint: inner_func.name_hint.clone(),
171 inner,
172 };
173 let idx = nodes.len();
174 nodes.push(node);
175 if name.is_none() {
176 functions.insert(lvalue_id, idx);
178 }
179 }
180 InstructionValue::StoreContext { lvalue: store_lvalue, value, .. }
181 | InstructionValue::StoreLocal { lvalue: store_lvalue, value, .. } => {
182 if let Some(&node_idx) = functions.get(&value.identifier) {
183 let node = &mut nodes[node_idx];
184 let var_ident = &env.identifiers[store_lvalue.place.identifier.0 as usize];
185 if node.generated_name.is_none() {
186 if let Some(IdentifierName::Named(ref var_name)) = var_ident.name {
187 node.generated_name = Some(var_name.clone());
188 functions.remove(&value.identifier);
189 }
190 }
191 }
192 }
193 InstructionValue::CallExpression { callee, args, .. } => {
194 handle_call(
195 env,
196 func,
197 callee.identifier,
198 args,
199 &mut functions,
200 &names,
201 &mut nodes,
202 );
203 }
204 InstructionValue::MethodCall {
205 property, args, ..
206 } => {
207 handle_call(
208 env,
209 func,
210 property.identifier,
211 args,
212 &mut functions,
213 &names,
214 &mut nodes,
215 );
216 }
217 InstructionValue::JsxExpression { tag, props, .. } => {
218 for attr in props {
219 match attr {
220 JsxAttribute::SpreadAttribute { .. } => continue,
221 JsxAttribute::Attribute { name: attr_name, place } => {
222 if let Some(&node_idx) = functions.get(&place.identifier) {
223 let node = &mut nodes[node_idx];
224 if node.generated_name.is_none() {
225 let element_name = match tag {
226 JsxTag::Builtin(builtin) => {
227 Some(builtin.name.clone())
228 }
229 JsxTag::Place(tag_place) => {
230 names.get(&tag_place.identifier).cloned()
231 }
232 };
233 let prop_name = match element_name {
234 None => attr_name.clone(),
235 Some(ref el_name) => {
236 format!("<{}>.{}", el_name, attr_name)
237 }
238 };
239 node.generated_name = Some(prop_name);
240 functions.remove(&place.identifier);
241 }
242 }
243 }
244 }
245 }
246 }
247 _ => {}
248 }
249 }
250 }
251
252 nodes
253}
254
255fn handle_call(
257 env: &Environment,
258 _func: &HirFunction,
259 callee_id: IdentifierId,
260 args: &[PlaceOrSpread],
261 functions: &mut HashMap<IdentifierId, usize>,
262 names: &HashMap<IdentifierId, String>,
263 nodes: &mut Vec<Node>,
264) {
265 let callee_ident = &env.identifiers[callee_id.0 as usize];
266 let callee_ty = &env.types[callee_ident.type_.0 as usize];
267 let hook_kind = env.get_hook_kind_for_type(callee_ty).ok().flatten();
268
269 let callee_name: String = if let Some(hk) = hook_kind {
270 if *hk != HookKind::Custom {
271 hk.to_string()
272 } else {
273 names.get(&callee_id).cloned().unwrap_or_else(|| "(anonymous)".to_string())
274 }
275 } else {
276 names.get(&callee_id).cloned().unwrap_or_else(|| "(anonymous)".to_string())
277 };
278
279 let fn_arg_count = args
281 .iter()
282 .filter(|arg| {
283 if let PlaceOrSpread::Place(p) = arg {
284 functions.contains_key(&p.identifier)
285 } else {
286 false
287 }
288 })
289 .count();
290
291 for (i, arg) in args.iter().enumerate() {
292 let place = match arg {
293 PlaceOrSpread::Spread(_) => continue,
294 PlaceOrSpread::Place(p) => p,
295 };
296 if let Some(&node_idx) = functions.get(&place.identifier) {
297 let node = &mut nodes[node_idx];
298 if node.generated_name.is_none() {
299 let generated_name = if fn_arg_count > 1 {
300 format!("{}(arg{})", callee_name, i)
301 } else {
302 format!("{}()", callee_name)
303 };
304 node.generated_name = Some(generated_name);
305 functions.remove(&place.identifier);
306 }
307 }
308 }
309}