1use rustc_hash::FxHashMap;
12use rustc_hash::FxHashSet;
13
14use react_compiler_hir::DeclarationId;
15use react_compiler_hir::EvaluationOrder;
16use react_compiler_hir::FunctionId;
17use react_compiler_hir::IdentifierName;
18use react_compiler_hir::InstructionValue;
19use react_compiler_hir::ParamPattern;
20use react_compiler_hir::Place;
21use react_compiler_hir::PrunedReactiveScopeBlock;
22use react_compiler_hir::ReactiveBlock;
23use react_compiler_hir::ReactiveFunction;
24use react_compiler_hir::ReactiveScopeBlock;
25use react_compiler_hir::ReactiveValue;
26use react_compiler_hir::environment::Environment;
27
28use crate::visitors::ReactiveFunctionVisitor;
29use crate::visitors::{self};
30
31struct Scopes {
36 seen: FxHashMap<DeclarationId, IdentifierName>,
37 stack: Vec<FxHashMap<String, DeclarationId>>,
38 globals: FxHashSet<String>,
39 names: FxHashSet<String>,
40}
41
42impl Scopes {
43 fn new(globals: FxHashSet<String>) -> Self {
44 Self {
45 seen: FxHashMap::default(),
46 stack: vec![FxHashMap::default()],
47 globals,
48 names: FxHashSet::default(),
49 }
50 }
51
52 fn visit_identifier(
53 &mut self,
54 identifier_id: react_compiler_hir::IdentifierId,
55 env: &Environment,
56 ) {
57 let identifier = &env.identifiers[identifier_id.0 as usize];
58 let original_name = match &identifier.name {
59 Some(name) => name.clone(),
60 None => return,
61 };
62 let declaration_id = identifier.declaration_id;
63
64 if self.seen.contains_key(&declaration_id) {
65 return;
66 }
67
68 let original_value = original_name.value().to_string();
69 let is_promoted = matches!(original_name, IdentifierName::Promoted(_));
70 let is_promoted_temp = is_promoted && original_value.starts_with("#t");
71 let is_promoted_jsx = is_promoted && original_value.starts_with("#T");
72
73 let mut name: String;
74 let mut id: u32 = 0;
75 if is_promoted_temp {
76 name = format!("t{}", id);
77 id += 1;
78 } else if is_promoted_jsx {
79 name = format!("T{}", id);
80 id += 1;
81 } else {
82 name = original_value.clone();
83 }
84
85 while self.lookup(&name).is_some() || self.globals.contains(&name) {
86 if is_promoted_temp {
87 name = format!("t{}", id);
88 id += 1;
89 } else if is_promoted_jsx {
90 name = format!("T{}", id);
91 id += 1;
92 } else {
93 name = format!("{}${}", original_value, id);
94 id += 1;
95 }
96 }
97
98 let identifier_name = IdentifierName::Named(name.clone());
99 self.seen.insert(declaration_id, identifier_name);
100 self.stack
101 .last_mut()
102 .unwrap()
103 .insert(name.clone(), declaration_id);
104 self.names.insert(name);
105 }
106
107 fn lookup(&self, name: &str) -> Option<DeclarationId> {
108 for scope in self.stack.iter().rev() {
109 if let Some(id) = scope.get(name) {
110 return Some(*id);
111 }
112 }
113 None
114 }
115
116 fn enter(&mut self) {
117 self.stack.push(FxHashMap::default());
118 }
119
120 fn leave(&mut self) {
121 self.stack.pop();
122 }
123}
124
125struct Visitor<'a> {
130 env: &'a Environment,
131}
132
133impl ReactiveFunctionVisitor for Visitor<'_> {
134 type State = Scopes;
135
136 fn env(&self) -> &Environment {
137 self.env
138 }
139
140 fn visit_param(&self, place: &Place, state: &mut Scopes) {
142 state.visit_identifier(place.identifier, self.env);
143 }
144
145 fn visit_lvalue(&self, _id: EvaluationOrder, lvalue: &Place, state: &mut Scopes) {
147 state.visit_identifier(lvalue.identifier, self.env);
148 }
149
150 fn visit_place(&self, _id: EvaluationOrder, place: &Place, state: &mut Scopes) {
152 state.visit_identifier(place.identifier, self.env);
153 }
154
155 fn visit_block(&self, block: &ReactiveBlock, state: &mut Scopes) {
157 state.enter();
158 self.traverse_block(block, state);
159 state.leave();
160 }
161
162 fn visit_pruned_scope(&self, scope: &PrunedReactiveScopeBlock, state: &mut Scopes) {
166 self.traverse_block(&scope.instructions, state);
167 }
168
169 fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut Scopes) {
171 let scope_data = &self.env.scopes[scope.scope.0 as usize];
172 let decl_ids: Vec<react_compiler_hir::IdentifierId> = scope_data
173 .declarations
174 .iter()
175 .map(|(_, d)| d.identifier)
176 .collect();
177 for id in decl_ids {
178 state.visit_identifier(id, self.env);
179 }
180 self.traverse_scope(scope, state);
181 }
182
183 fn visit_value(&self, id: EvaluationOrder, value: &ReactiveValue, state: &mut Scopes) {
185 self.traverse_value(id, value, state);
186 if let ReactiveValue::Instruction(iv) = value {
187 match iv {
188 InstructionValue::FunctionExpression { lowered_func, .. }
189 | InstructionValue::ObjectMethod { lowered_func, .. } => {
190 self.visit_hir_function(lowered_func.func, state);
191 }
192 _ => {}
193 }
194 }
195 }
196}
197
198pub fn rename_variables(func: &mut ReactiveFunction, env: &mut Environment) -> FxHashSet<String> {
206 rename_variables_with_parent(func, env, None)
207}
208
209fn rename_variables_with_parent(
210 func: &mut ReactiveFunction,
211 env: &mut Environment,
212 parent_names: Option<&FxHashSet<String>>,
213) -> FxHashSet<String> {
214 let globals = collect_referenced_globals(&func.body, env);
215
216 let mut scopes = Scopes::new(globals.clone());
219 if let Some(parent) = parent_names {
224 scopes.enter();
225 for name in parent {
226 scopes
227 .stack
228 .last_mut()
229 .unwrap()
230 .insert(name.clone(), DeclarationId(u32::MAX));
231 scopes.names.insert(name.clone());
232 }
233 }
234 rename_variables_impl(func, &Visitor { env }, &mut scopes);
235
236 for identifier in env.identifiers.iter_mut() {
238 if let Some(mapped_name) = scopes.seen.get(&identifier.declaration_id) {
239 if identifier.name.is_some() {
240 identifier.name = Some(mapped_name.clone());
241 }
242 }
243 }
244
245 let mut result: FxHashSet<String> = scopes.names;
246 result.extend(globals);
247 result
248}
249
250fn rename_variables_impl(func: &ReactiveFunction, visitor: &Visitor, scopes: &mut Scopes) {
252 scopes.enter();
253 for param in &func.params {
254 let place = match param {
255 ParamPattern::Place(p) => p,
256 ParamPattern::Spread(s) => &s.place,
257 };
258 visitor.visit_param(place, scopes);
259 }
260 visitors::visit_reactive_function(func, visitor, scopes);
261 scopes.leave();
262}
263
264fn collect_referenced_globals(block: &ReactiveBlock, env: &Environment) -> FxHashSet<String> {
271 let mut globals = FxHashSet::default();
272 collect_globals_block(block, &mut globals, env);
273 globals
274}
275
276fn collect_globals_block(block: &ReactiveBlock, globals: &mut FxHashSet<String>, env: &Environment) {
277 for stmt in block {
278 match stmt {
279 react_compiler_hir::ReactiveStatement::Instruction(instr) => {
280 collect_globals_value(&instr.value, globals, env);
281 }
282 react_compiler_hir::ReactiveStatement::Scope(scope) => {
283 collect_globals_block(&scope.instructions, globals, env);
284 }
285 react_compiler_hir::ReactiveStatement::PrunedScope(scope) => {
286 collect_globals_block(&scope.instructions, globals, env);
287 }
288 react_compiler_hir::ReactiveStatement::Terminal(terminal) => {
289 collect_globals_terminal(terminal, globals, env);
290 }
291 }
292 }
293}
294
295fn collect_globals_value(value: &ReactiveValue, globals: &mut FxHashSet<String>, env: &Environment) {
296 match value {
297 ReactiveValue::Instruction(iv) => {
298 if let InstructionValue::LoadGlobal { binding, .. } = iv {
299 globals.insert(binding.name().to_string());
300 }
301 match iv {
303 InstructionValue::FunctionExpression { lowered_func, .. }
304 | InstructionValue::ObjectMethod { lowered_func, .. } => {
305 collect_globals_hir_function(lowered_func.func, globals, env);
306 }
307 _ => {}
308 }
309 }
310 ReactiveValue::SequenceExpression {
311 instructions,
312 value: inner,
313 ..
314 } => {
315 for instr in instructions {
316 collect_globals_value(&instr.value, globals, env);
317 }
318 collect_globals_value(inner, globals, env);
319 }
320 ReactiveValue::ConditionalExpression {
321 test,
322 consequent,
323 alternate,
324 ..
325 } => {
326 collect_globals_value(test, globals, env);
327 collect_globals_value(consequent, globals, env);
328 collect_globals_value(alternate, globals, env);
329 }
330 ReactiveValue::LogicalExpression { left, right, .. } => {
331 collect_globals_value(left, globals, env);
332 collect_globals_value(right, globals, env);
333 }
334 ReactiveValue::OptionalExpression { value: inner, .. } => {
335 collect_globals_value(inner, globals, env);
336 }
337 }
338}
339
340fn collect_globals_hir_function(
342 func_id: FunctionId,
343 globals: &mut FxHashSet<String>,
344 env: &Environment,
345) {
346 let inner_func = &env.functions[func_id.0 as usize];
347 let block_ids: Vec<_> = inner_func.body.blocks.keys().copied().collect();
348 for block_id in block_ids {
349 let inner_func = &env.functions[func_id.0 as usize];
350 let block = &inner_func.body.blocks[&block_id];
351 for instr_id in &block.instructions {
352 let instr = &inner_func.instructions[instr_id.0 as usize];
353 if let InstructionValue::LoadGlobal { binding, .. } = &instr.value {
354 globals.insert(binding.name().to_string());
355 }
356 match &instr.value {
358 InstructionValue::FunctionExpression { lowered_func, .. }
359 | InstructionValue::ObjectMethod { lowered_func, .. } => {
360 collect_globals_hir_function(lowered_func.func, globals, env);
361 }
362 _ => {}
363 }
364 }
365 }
366}
367
368fn collect_globals_terminal(
369 stmt: &react_compiler_hir::ReactiveTerminalStatement,
370 globals: &mut FxHashSet<String>,
371 env: &Environment,
372) {
373 match &stmt.terminal {
374 react_compiler_hir::ReactiveTerminal::Break { .. }
375 | react_compiler_hir::ReactiveTerminal::Continue { .. } => {}
376 react_compiler_hir::ReactiveTerminal::Return { .. }
377 | react_compiler_hir::ReactiveTerminal::Throw { .. } => {}
378 react_compiler_hir::ReactiveTerminal::For {
379 init,
380 test,
381 update,
382 loop_block,
383 ..
384 } => {
385 collect_globals_value(init, globals, env);
386 collect_globals_value(test, globals, env);
387 collect_globals_block(loop_block, globals, env);
388 if let Some(update) = update {
389 collect_globals_value(update, globals, env);
390 }
391 }
392 react_compiler_hir::ReactiveTerminal::ForOf {
393 init,
394 test,
395 loop_block,
396 ..
397 } => {
398 collect_globals_value(init, globals, env);
399 collect_globals_value(test, globals, env);
400 collect_globals_block(loop_block, globals, env);
401 }
402 react_compiler_hir::ReactiveTerminal::ForIn {
403 init, loop_block, ..
404 } => {
405 collect_globals_value(init, globals, env);
406 collect_globals_block(loop_block, globals, env);
407 }
408 react_compiler_hir::ReactiveTerminal::DoWhile {
409 loop_block, test, ..
410 } => {
411 collect_globals_block(loop_block, globals, env);
412 collect_globals_value(test, globals, env);
413 }
414 react_compiler_hir::ReactiveTerminal::While {
415 test, loop_block, ..
416 } => {
417 collect_globals_value(test, globals, env);
418 collect_globals_block(loop_block, globals, env);
419 }
420 react_compiler_hir::ReactiveTerminal::If {
421 consequent,
422 alternate,
423 ..
424 } => {
425 collect_globals_block(consequent, globals, env);
426 if let Some(alt) = alternate {
427 collect_globals_block(alt, globals, env);
428 }
429 }
430 react_compiler_hir::ReactiveTerminal::Switch { cases, .. } => {
431 for case in cases {
432 if let Some(block) = &case.block {
433 collect_globals_block(block, globals, env);
434 }
435 }
436 }
437 react_compiler_hir::ReactiveTerminal::Label { block, .. } => {
438 collect_globals_block(block, globals, env);
439 }
440 react_compiler_hir::ReactiveTerminal::Try { block, handler, .. } => {
441 collect_globals_block(block, globals, env);
442 collect_globals_block(handler, globals, env);
443 }
444 }
445}