1// Copyright (c) Meta Platforms, Inc. and affiliates.2//3// This source code is licensed under the MIT license found in the4// LICENSE file in the root directory of this source tree.56//! RenameVariables — renames variables for output, assigns unique names,7//! handles SSA renames.8//!9//! Corresponds to `src/ReactiveScopes/RenameVariables.ts`.1011use rustc_hash::{FxHashMap, FxHashSet};1213use react_compiler_hir::DeclarationId;14use react_compiler_hir::EvaluationOrder;15use react_compiler_hir::FunctionId;16use react_compiler_hir::IdentifierName;17use react_compiler_hir::InstructionValue;18use react_compiler_hir::ParamPattern;19use react_compiler_hir::Place;20use react_compiler_hir::PrunedReactiveScopeBlock;21use react_compiler_hir::ReactiveBlock;22use react_compiler_hir::ReactiveFunction;23use react_compiler_hir::ReactiveScopeBlock;24use react_compiler_hir::ReactiveValue;25use react_compiler_hir::environment::Environment;2627use crate::visitors::ReactiveFunctionVisitor;28use crate::visitors::{self};2930// =============================================================================31// Scopes32// =============================================================================3334struct Scopes {35 seen: FxHashMap<DeclarationId, IdentifierName>,36 stack: Vec<FxHashMap<String, DeclarationId>>,37 globals: FxHashSet<String>,38 names: FxHashSet<String>,39}4041impl Scopes {42 fn new(globals: FxHashSet<String>) -> Self {43 Self {44 seen: FxHashMap::default(),45 stack: vec![FxHashMap::default()],46 globals,47 names: FxHashSet::default(),48 }49 }5051 fn visit_identifier(52 &mut self,53 identifier_id: react_compiler_hir::IdentifierId,54 env: &Environment,55 ) {56 let identifier = &env.identifiers[identifier_id.0 as usize];57 let original_name = match &identifier.name {58 Some(name) => name.clone(),59 None => return,60 };61 let declaration_id = identifier.declaration_id;6263 if self.seen.contains_key(&declaration_id) {64 return;65 }6667 let original_value = original_name.value().to_string();68 let is_promoted = matches!(original_name, IdentifierName::Promoted(_));69 let is_promoted_temp = is_promoted && original_value.starts_with("#t");70 let is_promoted_jsx = is_promoted && original_value.starts_with("#T");7172 let mut name: String;73 let mut id: u32 = 0;74 if is_promoted_temp {75 name = format!("t{}", id);76 id += 1;77 } else if is_promoted_jsx {78 name = format!("T{}", id);79 id += 1;80 } else {81 name = original_value.clone();82 }8384 while self.lookup(&name).is_some() || self.globals.contains(&name) {85 if is_promoted_temp {86 name = format!("t{}", id);87 id += 1;88 } else if is_promoted_jsx {89 name = format!("T{}", id);90 id += 1;91 } else {92 name = format!("{}${}", original_value, id);93 id += 1;94 }95 }9697 let identifier_name = IdentifierName::Named(name.clone());98 self.seen.insert(declaration_id, identifier_name);99 self.stack100 .last_mut()101 .unwrap()102 .insert(name.clone(), declaration_id);103 self.names.insert(name);104 }105106 fn lookup(&self, name: &str) -> Option<DeclarationId> {107 for scope in self.stack.iter().rev() {108 if let Some(id) = scope.get(name) {109 return Some(*id);110 }111 }112 None113 }114115 fn enter(&mut self) {116 self.stack.push(FxHashMap::default());117 }118119 fn leave(&mut self) {120 self.stack.pop();121 }122}123124// =============================================================================125// Visitor — TS: `class Visitor extends ReactiveFunctionVisitor<Scopes>`126// =============================================================================127128struct Visitor<'a> {129 env: &'a Environment,130}131132impl ReactiveFunctionVisitor for Visitor<'_> {133 type State = Scopes;134135 fn env(&self) -> &Environment {136 self.env137 }138139 /// TS: `visitParam(place, state) { state.visit(place.identifier) }`140 fn visit_param(&self, place: &Place, state: &mut Scopes) {141 state.visit_identifier(place.identifier, self.env);142 }143144 /// TS: `visitLValue(_id, lvalue, state) { state.visit(lvalue.identifier) }`145 fn visit_lvalue(&self, _id: EvaluationOrder, lvalue: &Place, state: &mut Scopes) {146 state.visit_identifier(lvalue.identifier, self.env);147 }148149 /// TS: `visitPlace(_id, place, state) { state.visit(place.identifier) }`150 fn visit_place(&self, _id: EvaluationOrder, place: &Place, state: &mut Scopes) {151 state.visit_identifier(place.identifier, self.env);152 }153154 /// TS: `visitBlock(block, state) { state.enter(() => { this.traverseBlock(block, state) }) }`155 fn visit_block(&self, block: &ReactiveBlock, state: &mut Scopes) {156 state.enter();157 self.traverse_block(block, state);158 state.leave();159 }160161 /// TS: `visitPrunedScope(scopeBlock, state) { this.traverseBlock(scopeBlock.instructions, state) }`162 /// No enter/leave — names assigned inside pruned scopes remain visible in163 /// the enclosing scope, preventing name reuse.164 fn visit_pruned_scope(&self, scope: &PrunedReactiveScopeBlock, state: &mut Scopes) {165 self.traverse_block(&scope.instructions, state);166 }167168 /// TS: `visitScope(scope, state) { for (const [_, decl] of scope.scope.declarations) state.visit(decl.identifier); this.traverseScope(scope, state) }`169 fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut Scopes) {170 let scope_data = &self.env.scopes[scope.scope.0 as usize];171 let decl_ids: Vec<react_compiler_hir::IdentifierId> = scope_data172 .declarations173 .iter()174 .map(|(_, d)| d.identifier)175 .collect();176 for id in decl_ids {177 state.visit_identifier(id, self.env);178 }179 self.traverse_scope(scope, state);180 }181182 /// TS: `visitValue(id, value, state) { this.traverseValue(id, value, state); if (value.kind === 'FunctionExpression' || value.kind === 'ObjectMethod') this.visitHirFunction(value.loweredFunc.func, state) }`183 fn visit_value(&self, id: EvaluationOrder, value: &ReactiveValue, state: &mut Scopes) {184 self.traverse_value(id, value, state);185 if let ReactiveValue::Instruction(iv) = value {186 match iv {187 InstructionValue::FunctionExpression { lowered_func, .. }188 | InstructionValue::ObjectMethod { lowered_func, .. } => {189 self.visit_hir_function(lowered_func.func, state);190 }191 _ => {}192 }193 }194 }195}196197// =============================================================================198// Public entry point199// =============================================================================200201/// Renames variables for output — assigns unique names, handles SSA renames.202/// Returns a Set of all unique variable names used.203/// TS: `renameVariables`204pub fn rename_variables(func: &mut ReactiveFunction, env: &mut Environment) -> FxHashSet<String> {205 rename_variables_with_parent(func, env, None)206}207208fn rename_variables_with_parent(209 func: &mut ReactiveFunction,210 env: &mut Environment,211 parent_names: Option<&FxHashSet<String>>,212) -> FxHashSet<String> {213 let globals = collect_referenced_globals(&func.body, env);214215 // Phase 1: Use ReactiveFunctionVisitor to compute the rename mapping.216 // This collects DeclarationId -> IdentifierName without mutating env.217 let mut scopes = Scopes::new(globals.clone());218 // If parent names are provided (for outlined functions), pre-populate219 // the scope stack so that parameter names don't collide with parent220 // variables. In the TS compiler, outlined functions are placed in the221 // parent function body and processed within the parent's scope context.222 if let Some(parent) = parent_names {223 scopes.enter();224 for name in parent {225 scopes226 .stack227 .last_mut()228 .unwrap()229 .insert(name.clone(), DeclarationId(u32::MAX));230 scopes.names.insert(name.clone());231 }232 }233 rename_variables_impl(func, &Visitor { env }, &mut scopes);234235 // Phase 2: Apply the computed renames to all identifiers in env.236 for identifier in env.identifiers.iter_mut() {237 if let Some(mapped_name) = scopes.seen.get(&identifier.declaration_id) {238 if identifier.name.is_some() {239 identifier.name = Some(mapped_name.clone());240 }241 }242 }243244 let mut result: FxHashSet<String> = scopes.names;245 result.extend(globals);246 result247}248249/// TS: `renameVariablesImpl`250fn rename_variables_impl(func: &ReactiveFunction, visitor: &Visitor, scopes: &mut Scopes) {251 scopes.enter();252 for param in &func.params {253 let place = match param {254 ParamPattern::Place(p) => p,255 ParamPattern::Spread(s) => &s.place,256 };257 visitor.visit_param(place, scopes);258 }259 visitors::visit_reactive_function(func, visitor, scopes);260 scopes.leave();261}262263// =============================================================================264// CollectReferencedGlobals265// =============================================================================266267/// Collects all globally referenced names from the reactive function.268/// TS: `collectReferencedGlobals`269fn collect_referenced_globals(block: &ReactiveBlock, env: &Environment) -> FxHashSet<String> {270 let mut globals = FxHashSet::default();271 collect_globals_block(block, &mut globals, env);272 globals273}274275fn collect_globals_block(276 block: &ReactiveBlock,277 globals: &mut FxHashSet<String>,278 env: &Environment,279) {280 for stmt in block {281 match stmt {282 react_compiler_hir::ReactiveStatement::Instruction(instr) => {283 collect_globals_value(&instr.value, globals, env);284 }285 react_compiler_hir::ReactiveStatement::Scope(scope) => {286 collect_globals_block(&scope.instructions, globals, env);287 }288 react_compiler_hir::ReactiveStatement::PrunedScope(scope) => {289 collect_globals_block(&scope.instructions, globals, env);290 }291 react_compiler_hir::ReactiveStatement::Terminal(terminal) => {292 collect_globals_terminal(terminal, globals, env);293 }294 }295 }296}297298fn collect_globals_value(299 value: &ReactiveValue,300 globals: &mut FxHashSet<String>,301 env: &Environment,302) {303 match value {304 ReactiveValue::Instruction(iv) => {305 if let InstructionValue::LoadGlobal { binding, .. } = iv {306 globals.insert(binding.name().to_string());307 }308 // Visit inner functions309 match iv {310 InstructionValue::FunctionExpression { lowered_func, .. }311 | InstructionValue::ObjectMethod { lowered_func, .. } => {312 collect_globals_hir_function(lowered_func.func, globals, env);313 }314 _ => {}315 }316 }317 ReactiveValue::SequenceExpression {318 instructions,319 value: inner,320 ..321 } => {322 for instr in instructions {323 collect_globals_value(&instr.value, globals, env);324 }325 collect_globals_value(inner, globals, env);326 }327 ReactiveValue::ConditionalExpression {328 test,329 consequent,330 alternate,331 ..332 } => {333 collect_globals_value(test, globals, env);334 collect_globals_value(consequent, globals, env);335 collect_globals_value(alternate, globals, env);336 }337 ReactiveValue::LogicalExpression { left, right, .. } => {338 collect_globals_value(left, globals, env);339 collect_globals_value(right, globals, env);340 }341 ReactiveValue::OptionalExpression { value: inner, .. } => {342 collect_globals_value(inner, globals, env);343 }344 }345}346347/// Recursively collects LoadGlobal names from an inner HIR function.348fn collect_globals_hir_function(349 func_id: FunctionId,350 globals: &mut FxHashSet<String>,351 env: &Environment,352) {353 let inner_func = &env.functions[func_id.0 as usize];354 let block_ids: Vec<_> = inner_func.body.blocks.keys().copied().collect();355 for block_id in block_ids {356 let inner_func = &env.functions[func_id.0 as usize];357 let block = &inner_func.body.blocks[&block_id];358 for instr_id in &block.instructions {359 let instr = &inner_func.instructions[instr_id.0 as usize];360 if let InstructionValue::LoadGlobal { binding, .. } = &instr.value {361 globals.insert(binding.name().to_string());362 }363 // Recurse into nested function expressions364 match &instr.value {365 InstructionValue::FunctionExpression { lowered_func, .. }366 | InstructionValue::ObjectMethod { lowered_func, .. } => {367 collect_globals_hir_function(lowered_func.func, globals, env);368 }369 _ => {}370 }371 }372 }373}374375fn collect_globals_terminal(376 stmt: &react_compiler_hir::ReactiveTerminalStatement,377 globals: &mut FxHashSet<String>,378 env: &Environment,379) {380 match &stmt.terminal {381 react_compiler_hir::ReactiveTerminal::Break { .. }382 | react_compiler_hir::ReactiveTerminal::Continue { .. } => {}383 react_compiler_hir::ReactiveTerminal::Return { .. }384 | react_compiler_hir::ReactiveTerminal::Throw { .. } => {}385 react_compiler_hir::ReactiveTerminal::For {386 init,387 test,388 update,389 loop_block,390 ..391 } => {392 collect_globals_value(init, globals, env);393 collect_globals_value(test, globals, env);394 collect_globals_block(loop_block, globals, env);395 if let Some(update) = update {396 collect_globals_value(update, globals, env);397 }398 }399 react_compiler_hir::ReactiveTerminal::ForOf {400 init,401 test,402 loop_block,403 ..404 } => {405 collect_globals_value(init, globals, env);406 collect_globals_value(test, globals, env);407 collect_globals_block(loop_block, globals, env);408 }409 react_compiler_hir::ReactiveTerminal::ForIn {410 init, loop_block, ..411 } => {412 collect_globals_value(init, globals, env);413 collect_globals_block(loop_block, globals, env);414 }415 react_compiler_hir::ReactiveTerminal::DoWhile {416 loop_block, test, ..417 } => {418 collect_globals_block(loop_block, globals, env);419 collect_globals_value(test, globals, env);420 }421 react_compiler_hir::ReactiveTerminal::While {422 test, loop_block, ..423 } => {424 collect_globals_value(test, globals, env);425 collect_globals_block(loop_block, globals, env);426 }427 react_compiler_hir::ReactiveTerminal::If {428 consequent,429 alternate,430 ..431 } => {432 collect_globals_block(consequent, globals, env);433 if let Some(alt) = alternate {434 collect_globals_block(alt, globals, env);435 }436 }437 react_compiler_hir::ReactiveTerminal::Switch { cases, .. } => {438 for case in cases {439 if let Some(block) = &case.block {440 collect_globals_block(block, globals, env);441 }442 }443 }444 react_compiler_hir::ReactiveTerminal::Label { block, .. } => {445 collect_globals_block(block, globals, env);446 }447 react_compiler_hir::ReactiveTerminal::Try { block, handler, .. } => {448 collect_globals_block(block, globals, env);449 collect_globals_block(handler, globals, env);450 }451 }452}
Code quality findings 21
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let identifier = &env.identifiers[identifier_id.0 as usize];
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
.unwrap()
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
/// TS: `visitScope(scope, state) { for (const [_, decl] of scope.scope.declarations) state.visit(decl.identifier); this.traverseScope(scope, state) }`
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let scope_data = &self.env.scopes[scope.scope.0 as usize];
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
.unwrap()
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let inner_func = &env.functions[func_id.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let inner_func = &env.functions[func_id.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let block = &inner_func.body.blocks[&block_id];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let instr = &inner_func.instructions[instr_id.0 as usize];
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
name = original_value.clone();
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
let identifier_name = IdentifierName::Named(name.clone());
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
.insert(name.clone(), declaration_id);
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info
performance
push-without-reserve
self.stack.push(FxHashMap::default());
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info
correctness
match-wildcard
match iv {
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
let mut scopes = Scopes::new(globals.clone());
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
.insert(name.clone(), DeclarationId(u32::MAX));
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
scopes.names.insert(name.clone());
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
identifier.name = Some(mapped_name.clone());
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info
correctness
match-wildcard
match iv {
Performance Info: Calling .to_string() (especially on &str) allocates a new String. If done repeatedly in loops, consider alternatives like working with &str or using crates like `itoa`/`ryu` for number-to-string conversion.
info
performance
to-string-in-loop
globals.insert(binding.name().to_string());
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info
correctness
match-wildcard
match &instr.value {