compiler/crates/react_compiler_reactive_scopes/src/prune_unused_lvalues.rs RUST 238 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//! PruneUnusedLValues (PruneTemporaryLValues)7//!8//! Nulls out lvalues for temporary variables that are never accessed later.9//!10//! Corresponds to `src/ReactiveScopes/PruneTemporaryLValues.ts`.1112use rustc_hash::FxHashSet;1314use react_compiler_hir::{15    DeclarationId, EvaluationOrder, Place, ReactiveFunction, ReactiveInstruction,16    ReactiveStatement, ReactiveValue, environment::Environment,17};1819use crate::visitors::{self, ReactiveFunctionVisitor};2021/// Nulls out lvalues for unnamed temporaries that are never used.22/// TS: `pruneUnusedLValues`23///24/// Uses ReactiveFunctionVisitor to collect unnamed lvalue DeclarationIds,25/// removing them when referenced as operands. After the visitor pass,26/// a second pass nulls out the remaining unused lvalues.27///28/// This uses a two-phase approach because Rust's ReactiveFunctionVisitor29/// takes immutable references, so we cannot modify lvalues during the visit.30/// The TS version stores mutable instruction references and modifies them31/// after the visitor completes.32pub fn prune_unused_lvalues(func: &mut ReactiveFunction, env: &Environment) {33    // Phase 1: Use ReactiveFunctionVisitor to identify unused unnamed lvalues.34    // When we see an unnamed lvalue on an instruction, we add its DeclarationId.35    // When we see a place reference (operand), we remove its DeclarationId.36    let visitor = Visitor { env };37    let mut lvalues: FxHashSet<DeclarationId> = FxHashSet::default();38    visitors::visit_reactive_function(func, &visitor, &mut lvalues);3940    // Phase 2: Null out lvalues whose DeclarationId remains in the map.41    // In the TS, this is done by iterating the stored instruction references.42    // In Rust, we walk the tree to find instructions with matching DeclarationIds.43    if !lvalues.is_empty() {44        null_unused_lvalues(&mut func.body, env, &lvalues);45    }46}4748/// TS: `type LValues = Map<DeclarationId, ReactiveInstruction>`49/// In Rust, we only need the set of DeclarationIds (not the instruction refs)50/// because we apply changes in a separate pass.51type LValues = FxHashSet<DeclarationId>;5253/// TS: `class Visitor extends ReactiveFunctionVisitor<LValues>`54struct Visitor<'a> {55    env: &'a Environment,56}5758impl ReactiveFunctionVisitor for Visitor<'_> {59    type State = LValues;6061    fn env(&self) -> &Environment {62        self.env63    }6465    /// TS: `visitPlace(_id, place, state) { state.delete(place.identifier.declarationId) }`66    fn visit_place(&self, _id: EvaluationOrder, place: &Place, state: &mut LValues) {67        let ident = &self.env.identifiers[place.identifier.0 as usize];68        state.remove(&ident.declaration_id);69    }7071    /// TS: `visitInstruction(instruction, state)`72    /// Calls traverseInstruction first (visits operands via visitPlace),73    /// then checks if the lvalue is unnamed and adds to map.74    fn visit_instruction(&self, instruction: &ReactiveInstruction, state: &mut LValues) {75        self.traverse_instruction(instruction, state);76        if let Some(lv) = &instruction.lvalue {77            let ident = &self.env.identifiers[lv.identifier.0 as usize];78            if ident.name.is_none() {79                state.insert(ident.declaration_id);80            }81        }82    }83}8485/// Phase 2: Walk the tree and null out lvalues whose DeclarationId is unused.86/// This is necessary because Rust's visitor takes immutable references.87fn null_unused_lvalues(88    block: &mut Vec<ReactiveStatement>,89    env: &Environment,90    unused: &FxHashSet<DeclarationId>,91) {92    for stmt in block.iter_mut() {93        match stmt {94            ReactiveStatement::Instruction(instr) => {95                null_unused_in_instruction(instr, env, unused);96            }97            ReactiveStatement::Scope(scope) => {98                null_unused_lvalues(&mut scope.instructions, env, unused);99            }100            ReactiveStatement::PrunedScope(scope) => {101                null_unused_lvalues(&mut scope.instructions, env, unused);102            }103            ReactiveStatement::Terminal(stmt) => {104                null_unused_in_terminal(&mut stmt.terminal, env, unused);105            }106        }107    }108}109110fn null_unused_in_instruction(111    instr: &mut ReactiveInstruction,112    env: &Environment,113    unused: &FxHashSet<DeclarationId>,114) {115    if let Some(lv) = &instr.lvalue {116        let ident = &env.identifiers[lv.identifier.0 as usize];117        if unused.contains(&ident.declaration_id) {118            instr.lvalue = None;119        }120    }121    null_unused_in_value(&mut instr.value, env, unused);122}123124fn null_unused_in_value(125    value: &mut ReactiveValue,126    env: &Environment,127    unused: &FxHashSet<DeclarationId>,128) {129    match value {130        ReactiveValue::SequenceExpression {131            instructions,132            value: inner,133            ..134        } => {135            for instr in instructions.iter_mut() {136                null_unused_in_instruction(instr, env, unused);137            }138            null_unused_in_value(inner, env, unused);139        }140        ReactiveValue::LogicalExpression { left, right, .. } => {141            null_unused_in_value(left, env, unused);142            null_unused_in_value(right, env, unused);143        }144        ReactiveValue::ConditionalExpression {145            test,146            consequent,147            alternate,148            ..149        } => {150            null_unused_in_value(test, env, unused);151            null_unused_in_value(consequent, env, unused);152            null_unused_in_value(alternate, env, unused);153        }154        ReactiveValue::OptionalExpression { value: inner, .. } => {155            null_unused_in_value(inner, env, unused);156        }157        ReactiveValue::Instruction(_) => {}158    }159}160161fn null_unused_in_terminal(162    terminal: &mut react_compiler_hir::ReactiveTerminal,163    env: &Environment,164    unused: &FxHashSet<DeclarationId>,165) {166    use react_compiler_hir::ReactiveTerminal;167    match terminal {168        ReactiveTerminal::Break { .. } | ReactiveTerminal::Continue { .. } => {}169        ReactiveTerminal::Return { .. } | ReactiveTerminal::Throw { .. } => {}170        ReactiveTerminal::For {171            init,172            test,173            update,174            loop_block,175            ..176        } => {177            null_unused_in_value(init, env, unused);178            null_unused_in_value(test, env, unused);179            null_unused_lvalues(loop_block, env, unused);180            if let Some(update) = update {181                null_unused_in_value(update, env, unused);182            }183        }184        ReactiveTerminal::ForOf {185            init,186            test,187            loop_block,188            ..189        } => {190            null_unused_in_value(init, env, unused);191            null_unused_in_value(test, env, unused);192            null_unused_lvalues(loop_block, env, unused);193        }194        ReactiveTerminal::ForIn {195            init, loop_block, ..196        } => {197            null_unused_in_value(init, env, unused);198            null_unused_lvalues(loop_block, env, unused);199        }200        ReactiveTerminal::DoWhile {201            loop_block, test, ..202        } => {203            null_unused_lvalues(loop_block, env, unused);204            null_unused_in_value(test, env, unused);205        }206        ReactiveTerminal::While {207            test, loop_block, ..208        } => {209            null_unused_in_value(test, env, unused);210            null_unused_lvalues(loop_block, env, unused);211        }212        ReactiveTerminal::If {213            consequent,214            alternate,215            ..216        } => {217            null_unused_lvalues(consequent, env, unused);218            if let Some(alt) = alternate {219                null_unused_lvalues(alt, env, unused);220            }221        }222        ReactiveTerminal::Switch { cases, .. } => {223            for case in cases.iter_mut() {224                if let Some(block) = &mut case.block {225                    null_unused_lvalues(block, env, unused);226                }227            }228        }229        ReactiveTerminal::Label { block, .. } => {230            null_unused_lvalues(block, env, unused);231        }232        ReactiveTerminal::Try { block, handler, .. } => {233            null_unused_lvalues(block, env, unused);234            null_unused_lvalues(handler, env, unused);235        }236    }237}

Code quality findings 3

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 ident = &self.env.identifiers[place.identifier.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 ident = &self.env.identifiers[lv.identifier.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 ident = &env.identifiers[lv.identifier.0 as usize];

Get this view in your editor

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