compiler/crates/react_compiler_reactive_scopes/src/rename_variables.rs RUST 453 lines View on github.com → Search inside
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 {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.