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