compiler/crates/react_compiler_validation/src/validate_no_derived_computations_in_effects.rs RUST 1,461 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//! Validates that useEffect is not used for derived computations which could/should7//! be performed in render.8//!9//! See https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state10//!11//! Port of ValidateNoDerivedComputationsInEffects_exp.ts.1213use indexmap::{IndexMap, IndexSet};14use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};1516use react_compiler_diagnostics::{17    CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, CompilerErrorDetail, ErrorCategory,18};19use react_compiler_hir::environment::Environment;20use react_compiler_hir::visitors::{21    each_instruction_lvalue_ids, each_instruction_operand as canonical_each_instruction_operand,22};23use react_compiler_hir::{24    ArrayElement, BlockId, Effect, EvaluationOrder, FunctionId, HirFunction, Identifier,25    IdentifierId, IdentifierName, InstructionValue, ParamPattern, PlaceOrSpread, ReactFunctionType,26    ReturnVariant, SourceLocation, Type, is_set_state_type, is_use_effect_hook_type,27    is_use_ref_type, is_use_state_type,28};2930/// Get the user-visible name for an identifier, matching Babel's31/// loc.identifierName behavior. First checks the identifier's own name,32/// then falls back to extracting the name from the source code at the33/// given source location. This handles SSA identifiers whose names were34/// lost during compiler passes.35fn get_identifier_name_with_loc(36    id: IdentifierId,37    identifiers: &[Identifier],38    loc: &Option<SourceLocation>,39    source_code: Option<&str>,40) -> Option<String> {41    let ident = &identifiers[id.0 as usize];42    match &ident.name {43        Some(IdentifierName::Named(name)) | Some(IdentifierName::Promoted(name)) => {44            return Some(name.clone());45        }46        _ => {}47    }48    // Fall back: find another identifier with the same declaration_id that has a name.49    let decl_id = ident.declaration_id;50    for other in identifiers {51        if other.declaration_id == decl_id {52            match &other.name {53                Some(IdentifierName::Named(name)) | Some(IdentifierName::Promoted(name)) => {54                    return Some(name.clone());55                }56                _ => {}57            }58        }59    }60    // Fall back to extracting from source code using UTF-16 code unit indices.61    // Babel/JS positions use UTF-16 code unit offsets, but Rust strings are UTF-8,62    // so we need to convert between the two.63    if let (Some(loc), Some(code)) = (loc, source_code) {64        let start_utf16 = loc.start.index? as usize;65        let end_utf16 = loc.end.index? as usize;66        if start_utf16 < end_utf16 {67            // Convert UTF-16 code unit offsets to UTF-8 byte offsets68            let mut utf16_pos = 0usize;69            let mut byte_start = None;70            let mut byte_end = None;71            for (byte_idx, ch) in code.char_indices() {72                if utf16_pos == start_utf16 {73                    byte_start = Some(byte_idx);74                }75                if utf16_pos == end_utf16 {76                    byte_end = Some(byte_idx);77                    break;78                }79                utf16_pos += ch.len_utf16();80            }81            // Handle end at the very end of string82            if utf16_pos == end_utf16 && byte_end.is_none() {83                byte_end = Some(code.len());84            }85            if let (Some(start), Some(end)) = (byte_start, byte_end) {86                let slice = &code[start..end];87                if !slice.is_empty()88                    && slice89                        .chars()90                        .all(|c| c.is_alphanumeric() || c == '_' || c == '$')91                {92                    return Some(slice.to_string());93                }94            }95        }96    }97    None98}99100const MAX_FIXPOINT_ITERATIONS: usize = 100;101102#[derive(Debug, Clone, Copy, PartialEq, Eq)]103enum TypeOfValue {104    Ignored,105    FromProps,106    FromState,107    FromPropsAndState,108}109110#[derive(Debug, Clone)]111struct DerivationMetadata {112    type_of_value: TypeOfValue,113    place_identifier: IdentifierId,114    place_name: Option<IdentifierName>,115    source_ids: IndexSet<IdentifierId, FxBuildHasher>,116    is_state_source: bool,117}118119/// Metadata about a useEffect call site.120struct EffectMetadata {121    effect_func_id: FunctionId,122    dep_elements: Vec<DepElement>,123}124125#[derive(Debug, Clone)]126struct DepElement {127    identifier: IdentifierId,128    loc: Option<SourceLocation>,129}130131struct ValidationContext {132    /// Map from lvalue identifier to the FunctionId of function expressions133    functions: FxHashMap<IdentifierId, FunctionId>,134    /// Map from lvalue identifier to ArrayExpression elements (candidate deps)135    candidate_dependencies: FxHashMap<IdentifierId, Vec<DepElement>>,136    derivation_cache: DerivationCache,137    effects_cache: FxHashMap<IdentifierId, EffectMetadata>,138    set_state_loads: FxHashMap<IdentifierId, Option<IdentifierId>>,139    set_state_usages: FxHashMap<IdentifierId, FxHashSet<LocKey>>,140}141142/// A hashable key for SourceLocation to use in FxHashSet143#[derive(Debug, Clone, PartialEq, Eq, Hash)]144struct LocKey {145    start_line: u32,146    start_col: u32,147    end_line: u32,148    end_col: u32,149}150151impl LocKey {152    fn from_loc(loc: &Option<SourceLocation>) -> Self {153        match loc {154            Some(loc) => LocKey {155                start_line: loc.start.line,156                start_col: loc.start.column,157                end_line: loc.end.line,158                end_col: loc.end.column,159            },160            None => LocKey {161                start_line: 0,162                start_col: 0,163                end_line: 0,164                end_col: 0,165            },166        }167    }168}169170#[derive(Debug, Clone)]171struct DerivationCache {172    has_changes: bool,173    cache: FxHashMap<IdentifierId, DerivationMetadata>,174    previous_cache: Option<FxHashMap<IdentifierId, DerivationMetadata>>,175}176177impl DerivationCache {178    fn new() -> Self {179        DerivationCache {180            has_changes: false,181            cache: FxHashMap::default(),182            previous_cache: None,183        }184    }185186    fn take_snapshot(&mut self) {187        let mut prev = FxHashMap::default();188        for (key, value) in &self.cache {189            prev.insert(190                *key,191                DerivationMetadata {192                    place_identifier: value.place_identifier,193                    place_name: value.place_name.clone(),194                    source_ids: value.source_ids.clone(),195                    type_of_value: value.type_of_value,196                    is_state_source: value.is_state_source,197                },198            );199        }200        self.previous_cache = Some(prev);201    }202203    fn check_for_changes(&mut self) {204        let prev = match &self.previous_cache {205            Some(p) => p,206            None => {207                self.has_changes = true;208                return;209            }210        };211212        for (key, value) in &self.cache {213            match prev.get(key) {214                None => {215                    self.has_changes = true;216                    return;217                }218                Some(prev_value) => {219                    if !is_derivation_equal(prev_value, value) {220                        self.has_changes = true;221                        return;222                    }223                }224            }225        }226227        if self.cache.len() != prev.len() {228            self.has_changes = true;229            return;230        }231232        self.has_changes = false;233    }234235    fn snapshot(&mut self) -> bool {236        let has_changes = self.has_changes;237        self.has_changes = false;238        has_changes239    }240241    fn add_derivation_entry(242        &mut self,243        derived_id: IdentifierId,244        derived_name: Option<IdentifierName>,245        source_ids: IndexSet<IdentifierId, FxBuildHasher>,246        type_of_value: TypeOfValue,247        is_state_source: bool,248    ) {249        let mut final_is_source = is_state_source;250        if !final_is_source {251            for source_id in &source_ids {252                if let Some(source_metadata) = self.cache.get(source_id) {253                    if source_metadata.is_state_source254                        && !matches!(&source_metadata.place_name, Some(IdentifierName::Named(_)))255                    {256                        final_is_source = true;257                        break;258                    }259                }260            }261        }262263        self.cache.insert(264            derived_id,265            DerivationMetadata {266                place_identifier: derived_id,267                place_name: derived_name,268                source_ids,269                type_of_value,270                is_state_source: final_is_source,271            },272        );273    }274}275276fn is_derivation_equal(a: &DerivationMetadata, b: &DerivationMetadata) -> bool {277    if a.type_of_value != b.type_of_value {278        return false;279    }280    if a.source_ids.len() != b.source_ids.len() {281        return false;282    }283    for id in &a.source_ids {284        if !b.source_ids.contains(id) {285            return false;286        }287    }288    true289}290291fn join_value(lvalue_type: TypeOfValue, value_type: TypeOfValue) -> TypeOfValue {292    if lvalue_type == TypeOfValue::Ignored {293        return value_type;294    }295    if value_type == TypeOfValue::Ignored {296        return lvalue_type;297    }298    if lvalue_type == value_type {299        return lvalue_type;300    }301    TypeOfValue::FromPropsAndState302}303304fn get_root_set_state(305    key: IdentifierId,306    loads: &FxHashMap<IdentifierId, Option<IdentifierId>>,307    visited: &mut FxHashSet<IdentifierId>,308) -> Option<IdentifierId> {309    if visited.contains(&key) {310        return None;311    }312    visited.insert(key);313314    match loads.get(&key) {315        None => None,316        Some(None) => Some(key),317        Some(Some(parent_id)) => get_root_set_state(*parent_id, loads, visited),318    }319}320321fn maybe_record_set_state_for_instr(322    instr: &react_compiler_hir::Instruction,323    env: &Environment,324    set_state_loads: &mut FxHashMap<IdentifierId, Option<IdentifierId>>,325    set_state_usages: &mut FxHashMap<IdentifierId, FxHashSet<LocKey>>,326) {327    let identifiers = &env.identifiers;328    let types = &env.types;329330    let all_lvalues = each_instruction_lvalue_ids(instr);331    for &lvalue_id in &all_lvalues {332        // Check if this is a LoadLocal from a known setState333        if let InstructionValue::LoadLocal { place, .. } = &instr.value {334            if set_state_loads.contains_key(&place.identifier) {335                set_state_loads.insert(lvalue_id, Some(place.identifier));336            } else {337                // Only check root setState if not a LoadLocal from a known chain338                let lvalue_ident = &identifiers[lvalue_id.0 as usize];339                let lvalue_ty = &types[lvalue_ident.type_.0 as usize];340                if is_set_state_type(lvalue_ty) {341                    set_state_loads.insert(lvalue_id, None);342                }343            }344        } else {345            // Check if lvalue is a setState type (root setState)346            let lvalue_ident = &identifiers[lvalue_id.0 as usize];347            let lvalue_ty = &types[lvalue_ident.type_.0 as usize];348            if is_set_state_type(lvalue_ty) {349                set_state_loads.insert(lvalue_id, None);350            }351        }352353        let root = get_root_set_state(lvalue_id, set_state_loads, &mut FxHashSet::default());354        if let Some(root_id) = root {355            set_state_usages.entry(root_id).or_insert_with(|| {356                let mut set = FxHashSet::default();357                set.insert(LocKey::from_loc(&instr.lvalue.loc));358                set359            });360        }361    }362}363364fn is_mutable_at(365    env: &Environment,366    eval_order: EvaluationOrder,367    identifier_id: IdentifierId,368) -> bool {369    env.identifiers[identifier_id.0 as usize]370        .mutable_range371        .contains(eval_order)372}373374pub fn validate_no_derived_computations_in_effects_exp(375    func: &HirFunction,376    env: &Environment,377) -> Result<CompilerError, CompilerDiagnostic> {378    let identifiers = &env.identifiers;379380    let mut context = ValidationContext {381        functions: FxHashMap::default(),382        candidate_dependencies: FxHashMap::default(),383        derivation_cache: DerivationCache::new(),384        effects_cache: FxHashMap::default(),385        set_state_loads: FxHashMap::default(),386        set_state_usages: FxHashMap::default(),387    };388389    // Initialize derivation cache based on function type390    if func.fn_type == ReactFunctionType::Hook {391        for param in &func.params {392            if let ParamPattern::Place(place) = param {393                let name = identifiers[place.identifier.0 as usize].name.clone();394                context.derivation_cache.cache.insert(395                    place.identifier,396                    DerivationMetadata {397                        place_identifier: place.identifier,398                        place_name: name,399                        source_ids: IndexSet::default(),400                        type_of_value: TypeOfValue::FromProps,401                        is_state_source: true,402                    },403                );404            }405        }406    } else if func.fn_type == ReactFunctionType::Component {407        if let Some(param) = func.params.first() {408            if let ParamPattern::Place(place) = param {409                let name = identifiers[place.identifier.0 as usize].name.clone();410                context.derivation_cache.cache.insert(411                    place.identifier,412                    DerivationMetadata {413                        place_identifier: place.identifier,414                        place_name: name,415                        source_ids: IndexSet::default(),416                        type_of_value: TypeOfValue::FromProps,417                        is_state_source: true,418                    },419                );420            }421        }422    }423424    // Fixpoint iteration425    let mut is_first_pass = true;426    let mut iteration_count = 0;427    loop {428        context.derivation_cache.take_snapshot();429430        for (_block_id, block) in &func.body.blocks {431            record_phi_derivations(block, &mut context, env);432            for &instr_id in &block.instructions {433                let instr = &func.instructions[instr_id.0 as usize];434                record_instruction_derivations(instr, &mut context, is_first_pass, func, env)?;435            }436        }437438        context.derivation_cache.check_for_changes();439        is_first_pass = false;440        iteration_count += 1;441        assert!(442            iteration_count < MAX_FIXPOINT_ITERATIONS,443            "[ValidateNoDerivedComputationsInEffects] Fixpoint iteration failed to converge."444        );445446        if !context.derivation_cache.snapshot() {447            break;448        }449    }450451    // Validate all effect sites452    let mut errors = CompilerError::new();453    let effects_cache: Vec<(IdentifierId, FunctionId, Vec<DepElement>)> = context454        .effects_cache455        .iter()456        .map(|(k, v)| (*k, v.effect_func_id, v.dep_elements.clone()))457        .collect();458459    for (_key, effect_func_id, dep_elements) in &effects_cache {460        validate_effect(461            *effect_func_id,462            dep_elements,463            &mut context,464            func,465            env,466            &mut errors,467        );468    }469470    Ok(errors)471}472473fn record_phi_derivations(474    block: &react_compiler_hir::BasicBlock,475    context: &mut ValidationContext,476    env: &Environment,477) {478    let identifiers = &env.identifiers;479    for phi in &block.phis {480        let mut type_of_value = TypeOfValue::Ignored;481        let mut source_ids: IndexSet<IdentifierId, FxBuildHasher> = IndexSet::default();482483        for (_block_id, operand) in &phi.operands {484            if let Some(operand_metadata) = context.derivation_cache.cache.get(&operand.identifier)485            {486                type_of_value = join_value(type_of_value, operand_metadata.type_of_value);487                source_ids.insert(operand.identifier);488            }489        }490491        if type_of_value != TypeOfValue::Ignored {492            let name = identifiers[phi.place.identifier.0 as usize].name.clone();493            context.derivation_cache.add_derivation_entry(494                phi.place.identifier,495                name,496                source_ids,497                type_of_value,498                false,499            );500        }501    }502}503504fn record_instruction_derivations(505    instr: &react_compiler_hir::Instruction,506    context: &mut ValidationContext,507    is_first_pass: bool,508    _outer_func: &HirFunction,509    env: &Environment,510) -> Result<(), CompilerDiagnostic> {511    let identifiers = &env.identifiers;512    let types = &env.types;513    let functions = &env.functions;514    let lvalue_id = instr.lvalue.identifier;515516    // maybeRecordSetState517    maybe_record_set_state_for_instr(518        instr,519        env,520        &mut context.set_state_loads,521        &mut context.set_state_usages,522    );523524    let mut type_of_value = TypeOfValue::Ignored;525    let is_source = false;526    let mut sources: IndexSet<IdentifierId, FxBuildHasher> = IndexSet::default();527528    match &instr.value {529        InstructionValue::FunctionExpression { lowered_func, .. } => {530            context.functions.insert(lvalue_id, lowered_func.func);531            // Recurse into the inner function532            let inner_func = &functions[lowered_func.func.0 as usize];533            for (_block_id, block) in &inner_func.body.blocks {534                record_phi_derivations(block, context, env);535                for &inner_instr_id in &block.instructions {536                    let inner_instr = &inner_func.instructions[inner_instr_id.0 as usize];537                    record_instruction_derivations(538                        inner_instr,539                        context,540                        is_first_pass,541                        inner_func,542                        env,543                    )?;544                }545            }546        }547        InstructionValue::CallExpression { callee, args, .. } => {548            let callee_type = &types[identifiers[callee.identifier.0 as usize].type_.0 as usize];549            if is_use_effect_hook_type(callee_type) && args.len() == 2 {550                if let (551                    react_compiler_hir::PlaceOrSpread::Place(arg0),552                    react_compiler_hir::PlaceOrSpread::Place(arg1),553                ) = (&args[0], &args[1])554                {555                    let effect_function = context.functions.get(&arg0.identifier).copied();556                    let deps = context557                        .candidate_dependencies558                        .get(&arg1.identifier)559                        .cloned();560                    if let (Some(effect_func_id), Some(dep_elements)) = (effect_function, deps) {561                        context.effects_cache.insert(562                            arg0.identifier,563                            EffectMetadata {564                                effect_func_id,565                                dep_elements,566                            },567                        );568                    }569                }570            }571572            // Check if lvalue is useState type573            let lvalue_type = &types[identifiers[lvalue_id.0 as usize].type_.0 as usize];574            if is_use_state_type(lvalue_type) {575                let name = identifiers[lvalue_id.0 as usize].name.clone();576                context.derivation_cache.add_derivation_entry(577                    lvalue_id,578                    name,579                    IndexSet::default(),580                    TypeOfValue::FromState,581                    true,582                );583                return Ok(());584            }585        }586        InstructionValue::MethodCall { property, args, .. } => {587            let prop_type = &types[identifiers[property.identifier.0 as usize].type_.0 as usize];588            if is_use_effect_hook_type(prop_type) && args.len() == 2 {589                if let (590                    react_compiler_hir::PlaceOrSpread::Place(arg0),591                    react_compiler_hir::PlaceOrSpread::Place(arg1),592                ) = (&args[0], &args[1])593                {594                    let effect_function = context.functions.get(&arg0.identifier).copied();595                    let deps = context596                        .candidate_dependencies597                        .get(&arg1.identifier)598                        .cloned();599                    if let (Some(effect_func_id), Some(dep_elements)) = (effect_function, deps) {600                        context.effects_cache.insert(601                            arg0.identifier,602                            EffectMetadata {603                                effect_func_id,604                                dep_elements,605                            },606                        );607                    }608                }609            }610611            // Check if lvalue is useState type612            let lvalue_type = &types[identifiers[lvalue_id.0 as usize].type_.0 as usize];613            if is_use_state_type(lvalue_type) {614                let name = identifiers[lvalue_id.0 as usize].name.clone();615                context.derivation_cache.add_derivation_entry(616                    lvalue_id,617                    name,618                    IndexSet::default(),619                    TypeOfValue::FromState,620                    true,621                );622                return Ok(());623            }624        }625        InstructionValue::ArrayExpression { elements, .. } => {626            let dep_elements: Vec<DepElement> = elements627                .iter()628                .filter_map(|el| match el {629                    ArrayElement::Place(p) => Some(DepElement {630                        identifier: p.identifier,631                        loc: p.loc,632                    }),633                    _ => None,634                })635                .collect();636            context637                .candidate_dependencies638                .insert(lvalue_id, dep_elements);639        }640        _ => {}641    }642643    // Collect operand derivations644    for (operand_id, operand_loc) in each_instruction_operand(instr, env) {645        // Track setState usages646        if context.set_state_loads.contains_key(&operand_id) {647            let root = get_root_set_state(648                operand_id,649                &context.set_state_loads,650                &mut FxHashSet::default(),651            );652            if let Some(root_id) = root {653                if let Some(usages) = context.set_state_usages.get_mut(&root_id) {654                    usages.insert(LocKey::from_loc(&operand_loc));655                }656            }657        }658659        if let Some(operand_metadata) = context.derivation_cache.cache.get(&operand_id) {660            type_of_value = join_value(type_of_value, operand_metadata.type_of_value);661            sources.insert(operand_id);662        }663    }664665    if type_of_value == TypeOfValue::Ignored {666        return Ok(());667    }668669    // Record derivation for ALL lvalue places (including destructured variables)670    for &lv_id in &each_instruction_lvalue_ids(instr) {671        let name = identifiers[lv_id.0 as usize].name.clone();672        context.derivation_cache.add_derivation_entry(673            lv_id,674            name,675            sources.clone(),676            type_of_value,677            is_source,678        );679    }680681    if matches!(&instr.value, InstructionValue::FunctionExpression { .. }) {682        // Don't record mutation effects for FunctionExpressions683        return Ok(());684    }685686    // Handle mutable operands687    for operand in each_instruction_operand_with_effect(instr, env) {688        if operand.effect.is_mutable() {689            if is_mutable_at(env, instr.id, operand.id) {690                if let Some(existing) = context.derivation_cache.cache.get_mut(&operand.id) {691                    existing.type_of_value = join_value(type_of_value, existing.type_of_value);692                } else {693                    let name = identifiers[operand.id.0 as usize].name.clone();694                    context.derivation_cache.add_derivation_entry(695                        operand.id,696                        name,697                        sources.clone(),698                        type_of_value,699                        false,700                    );701                }702            }703        } else if matches!(operand.effect, Effect::Unknown) {704            return Err(CompilerDiagnostic::new(705                ErrorCategory::Invariant,706                "Unexpected unknown effect",707                None,708            ));709        }710        // Freeze | Read => no-op711    }712    Ok(())713}714715struct OperandWithEffect {716    id: IdentifierId,717    effect: Effect,718}719720/// Collects operand (IdentifierId, loc) pairs from an instruction.721/// Thin wrapper around canonical `each_instruction_operand` that maps Places to (id, loc) pairs.722fn each_instruction_operand(723    instr: &react_compiler_hir::Instruction,724    env: &Environment,725) -> Vec<(IdentifierId, Option<SourceLocation>)> {726    canonical_each_instruction_operand(instr, env)727        .into_iter()728        .map(|place| (place.identifier, place.loc))729        .collect()730}731732/// Collects operands with their effects.733/// Thin wrapper around canonical `each_instruction_operand` that maps Places to OperandWithEffect.734fn each_instruction_operand_with_effect(735    instr: &react_compiler_hir::Instruction,736    env: &Environment,737) -> Vec<OperandWithEffect> {738    canonical_each_instruction_operand(instr, env)739        .into_iter()740        .map(|place| OperandWithEffect {741            id: place.identifier,742            effect: place.effect,743        })744        .collect()745}746747// =============================================================================748// Tree building and rendering (for error messages)749// =============================================================================750751struct TreeNode {752    name: String,753    type_of_value: TypeOfValue,754    is_source: bool,755    children: Vec<TreeNode>,756}757758fn build_tree_node(759    source_id: IdentifierId,760    context: &ValidationContext,761    visited: &FxHashSet<String>,762) -> Vec<TreeNode> {763    let source_metadata = match context.derivation_cache.cache.get(&source_id) {764        Some(m) => m,765        None => return Vec::new(),766    };767768    if source_metadata.is_state_source {769        if let Some(IdentifierName::Named(name)) = &source_metadata.place_name {770            return vec![TreeNode {771                name: name.clone(),772                type_of_value: source_metadata.type_of_value,773                is_source: true,774                children: Vec::new(),775            }];776        }777    }778779    let mut children: Vec<TreeNode> = Vec::new();780    let mut named_siblings: IndexSet<String, FxBuildHasher> = IndexSet::default();781782    for child_id in &source_metadata.source_ids {783        assert_ne!(784            *child_id, source_id,785            "Unexpected self-reference: a value should not have itself as a source"786        );787788        let mut new_visited = visited.clone();789        if let Some(IdentifierName::Named(name)) = &source_metadata.place_name {790            new_visited.insert(name.clone());791        }792793        let child_nodes = build_tree_node(*child_id, context, &new_visited);794        for child_node in child_nodes {795            if !named_siblings.contains(&child_node.name) {796                named_siblings.insert(child_node.name.clone());797                children.push(child_node);798            }799        }800    }801802    if let Some(IdentifierName::Named(name)) = &source_metadata.place_name {803        if !visited.contains(name) {804            return vec![TreeNode {805                name: name.clone(),806                type_of_value: source_metadata.type_of_value,807                is_source: source_metadata.is_state_source,808                children,809            }];810        }811    }812813    children814}815816fn render_tree(817    node: &TreeNode,818    indent: &str,819    is_last: bool,820    props_set: &mut IndexSet<String, FxBuildHasher>,821    state_set: &mut IndexSet<String, FxBuildHasher>,822) -> String {823    let prefix = format!(824        "{}{}",825        indent,826        if is_last {827            "\u{2514}\u{2500}\u{2500} "828        } else {829            "\u{251c}\u{2500}\u{2500} "830        }831    );832    let child_indent = format!("{}{}", indent, if is_last { "    " } else { "\u{2502}   " });833834    let mut result = format!("{}{}", prefix, node.name);835836    if node.is_source {837        let type_label = match node.type_of_value {838            TypeOfValue::FromProps => {839                props_set.insert(node.name.clone());840                "Prop"841            }842            TypeOfValue::FromState => {843                state_set.insert(node.name.clone());844                "State"845            }846            _ => {847                props_set.insert(node.name.clone());848                state_set.insert(node.name.clone());849                "Prop and State"850            }851        };852        result += &format!(" ({})", type_label);853    }854855    if !node.children.is_empty() {856        result += "\n";857        for (index, child) in node.children.iter().enumerate() {858            let is_last_child = index == node.children.len() - 1;859            result += &render_tree(child, &child_indent, is_last_child, props_set, state_set);860            if index < node.children.len() - 1 {861                result += "\n";862            }863        }864    }865866    result867}868869fn get_fn_local_deps(870    func_id: Option<FunctionId>,871    env: &Environment,872) -> Option<FxHashSet<IdentifierId>> {873    let func_id = func_id?;874    let inner = &env.functions[func_id.0 as usize];875    let mut deps: FxHashSet<IdentifierId> = FxHashSet::default();876877    for (_block_id, block) in &inner.body.blocks {878        for &instr_id in &block.instructions {879            let instr = &inner.instructions[instr_id.0 as usize];880            if let InstructionValue::LoadLocal { place, .. } = &instr.value {881                deps.insert(place.identifier);882            }883        }884    }885886    Some(deps)887}888889fn validate_effect(890    effect_func_id: FunctionId,891    dependencies: &[DepElement],892    context: &mut ValidationContext,893    _outer_func: &HirFunction,894    env: &Environment,895    errors: &mut CompilerError,896) {897    let identifiers = &env.identifiers;898    let types = &env.types;899    let functions = &env.functions;900    let effect_function = &functions[effect_func_id.0 as usize];901    let mut seen_blocks: FxHashSet<BlockId> = FxHashSet::default();902903    struct DerivedSetStateCall {904        callee_loc: Option<SourceLocation>,905        callee_id: IdentifierId,906        callee_identifier_name: Option<String>,907        source_ids: IndexSet<IdentifierId, FxBuildHasher>,908    }909910    let mut effect_derived_set_state_calls: Vec<DerivedSetStateCall> = Vec::new();911    let mut effect_set_state_usages: FxHashMap<IdentifierId, FxHashSet<LocKey>> =912        FxHashMap::default();913914    // Consider setStates in the effect's dependency array as being part of effectSetStateUsages915    for dep in dependencies {916        let root = get_root_set_state(917            dep.identifier,918            &context.set_state_loads,919            &mut FxHashSet::default(),920        );921        if let Some(root_id) = root {922            let mut set = FxHashSet::default();923            set.insert(LocKey::from_loc(&dep.loc));924            effect_set_state_usages.insert(root_id, set);925        }926    }927928    let mut cleanup_function_deps: Option<FxHashSet<IdentifierId>> = None;929    let mut globals: FxHashSet<IdentifierId> = FxHashSet::default();930931    for (_block_id, block) in &effect_function.body.blocks {932        // Check for return -> cleanup function933        if let react_compiler_hir::Terminal::Return {934            value,935            return_variant: ReturnVariant::Explicit,936            ..937        } = &block.terminal938        {939            let func_id = context.functions.get(&value.identifier).copied();940            cleanup_function_deps = get_fn_local_deps(func_id, env);941        }942943        // Skip if block has a back edge (pred not yet seen)944        let has_back_edge = block.preds.iter().any(|pred| !seen_blocks.contains(pred));945        if has_back_edge {946            return;947        }948949        for &instr_id in &block.instructions {950            let instr = &effect_function.instructions[instr_id.0 as usize];951952            // Early return if any instruction derives from a ref953            let lvalue_type =954                &types[identifiers[instr.lvalue.identifier.0 as usize].type_.0 as usize];955            if is_use_ref_type(lvalue_type) {956                return;957            }958959            // maybeRecordSetState for effect instructions960            maybe_record_set_state_for_instr(961                instr,962                env,963                &mut context.set_state_loads,964                &mut effect_set_state_usages,965            );966967            // Track setState usages for operands968            for (operand_id, operand_loc) in each_instruction_operand(instr, env) {969                if context.set_state_loads.contains_key(&operand_id) {970                    let root = get_root_set_state(971                        operand_id,972                        &context.set_state_loads,973                        &mut FxHashSet::default(),974                    );975                    if let Some(root_id) = root {976                        if let Some(usages) = effect_set_state_usages.get_mut(&root_id) {977                            usages.insert(LocKey::from_loc(&operand_loc));978                        }979                    }980                }981            }982983            match &instr.value {984                InstructionValue::CallExpression { callee, args, .. } => {985                    let callee_type =986                        &types[identifiers[callee.identifier.0 as usize].type_.0 as usize];987                    if is_set_state_type(callee_type) && args.len() == 1 {988                        if let react_compiler_hir::PlaceOrSpread::Place(arg0) = &args[0] {989                            let callee_metadata =990                                context.derivation_cache.cache.get(&callee.identifier);991992                            // If the setState comes from a source other than local state, skip993                            if let Some(cm) = callee_metadata {994                                if cm.type_of_value != TypeOfValue::FromState {995                                    continue;996                                }997                            } else {998                                continue;999                            }10001001                            let arg_metadata = context.derivation_cache.cache.get(&arg0.identifier);1002                            if let Some(am) = arg_metadata {1003                                // Get the user-visible identifier name, matching Babel's1004                                // loc.identifierName. Falls back to extracting from source code.1005                                let callee_ident_name = get_identifier_name_with_loc(1006                                    callee.identifier,1007                                    identifiers,1008                                    &callee.loc,1009                                    env.code.as_deref(),1010                                );1011                                effect_derived_set_state_calls.push(DerivedSetStateCall {1012                                    callee_loc: callee.loc,1013                                    callee_id: callee.identifier,1014                                    callee_identifier_name: callee_ident_name,1015                                    source_ids: am.source_ids.clone(),1016                                });1017                            }1018                        }1019                    } else {1020                        // Check if callee is from props/propsAndState -> bail1021                        let callee_metadata =1022                            context.derivation_cache.cache.get(&callee.identifier);1023                        if let Some(cm) = callee_metadata {1024                            if cm.type_of_value == TypeOfValue::FromProps1025                                || cm.type_of_value == TypeOfValue::FromPropsAndState1026                            {1027                                return;1028                            }1029                        }10301031                        if globals.contains(&callee.identifier) {1032                            return;1033                        }1034                    }1035                }1036                InstructionValue::LoadGlobal { .. } => {1037                    globals.insert(instr.lvalue.identifier);1038                    for (operand_id, _) in each_instruction_operand(instr, env) {1039                        globals.insert(operand_id);1040                    }1041                }1042                _ => {}1043            }1044        }1045        seen_blocks.insert(block.id);1046    }10471048    // Emit errors for derived setState calls1049    for derived in &effect_derived_set_state_calls {1050        let root_set_state_call = get_root_set_state(1051            derived.callee_id,1052            &context.set_state_loads,1053            &mut FxHashSet::default(),1054        );1055        if let Some(root_id) = root_set_state_call {1056            let effect_usage_count = effect_set_state_usages1057                .get(&root_id)1058                .map(|s| s.len())1059                .unwrap_or(0);1060            let total_usage_count = context1061                .set_state_usages1062                .get(&root_id)1063                .map(|s| s.len())1064                .unwrap_or(0);1065            if effect_set_state_usages.contains_key(&root_id)1066                && context.set_state_usages.contains_key(&root_id)1067                && effect_usage_count == total_usage_count - 11068            {1069                let mut props_set: IndexSet<String, FxBuildHasher> = IndexSet::default();1070                let mut state_set: IndexSet<String, FxBuildHasher> = IndexSet::default();10711072                let mut root_nodes_map: IndexMap<String, TreeNode, FxBuildHasher> =1073                    IndexMap::default();1074                for id in &derived.source_ids {1075                    let nodes = build_tree_node(*id, context, &FxHashSet::default());1076                    for node in nodes {1077                        if !root_nodes_map.contains_key(&node.name) {1078                            root_nodes_map.insert(node.name.clone(), node);1079                        }1080                    }1081                }1082                let root_nodes: Vec<&TreeNode> = root_nodes_map.values().collect();10831084                let trees: Vec<String> = root_nodes1085                    .iter()1086                    .enumerate()1087                    .map(|(index, node)| {1088                        render_tree(1089                            node,1090                            "",1091                            index == root_nodes.len() - 1,1092                            &mut props_set,1093                            &mut state_set,1094                        )1095                    })1096                    .collect();10971098                // Check cleanup function dependencies1099                let should_skip = if let Some(ref cleanup_deps) = cleanup_function_deps {1100                    derived1101                        .source_ids1102                        .iter()1103                        .any(|dep| cleanup_deps.contains(dep))1104                } else {1105                    false1106                };1107                if should_skip {1108                    return;1109                }11101111                let mut root_sources = String::new();1112                if !props_set.is_empty() {1113                    let props_list: Vec<&str> = props_set.iter().map(|s| s.as_str()).collect();1114                    root_sources += &format!("Props: [{}]", props_list.join(", "));1115                }1116                if !state_set.is_empty() {1117                    if !root_sources.is_empty() {1118                        root_sources += "\n";1119                    }1120                    let state_list: Vec<&str> = state_set.iter().map(|s| s.as_str()).collect();1121                    root_sources += &format!("State: [{}]", state_list.join(", "));1122                }11231124                let description = format!(1125                    "Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\n\1126                     This setState call is setting a derived value that depends on the following reactive sources:\n\n\1127                     {}\n\n\1128                     Data Flow Tree:\n\1129                     {}\n\n\1130                     See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state",1131                    root_sources,1132                    trees.join("\n"),1133                );11341135                errors.push_diagnostic(1136                    CompilerDiagnostic::new(1137                        ErrorCategory::EffectDerivationsOfState,1138                        "You might not need an effect. Derive values in render, not effects.",1139                        Some(description),1140                    )1141                    .with_detail(CompilerDiagnosticDetail::Error {1142                        loc: derived.callee_loc,1143                        message: Some(1144                            "This should be computed during render, not in an effect".to_string(),1145                        ),1146                        identifier_name: derived.callee_identifier_name.clone(),1147                    }),1148                );1149            }1150        }1151    }1152}11531154// =============================================================================1155// Non-exp version: ValidateNoDerivedComputationsInEffects1156// Port of ValidateNoDerivedComputationsInEffects.ts1157// =============================================================================11581159/// Non-experimental version of the derived-computations-in-effects validation.1160/// Records errors directly on the Environment (matching TS `env.recordError()` behavior).1161pub fn validate_no_derived_computations_in_effects(1162    func: &HirFunction,1163    env: &mut Environment,1164) -> Result<(), CompilerError> {1165    // Phase 1: Collect effect call sites (func_id + resolved deps).1166    // Done with only immutable borrows of env fields.1167    let effects_to_validate: Vec<(FunctionId, Vec<IdentifierId>)> = {1168        let ids = &env.identifiers;1169        let tys = &env.types;1170        let mut candidate_deps: FxHashMap<IdentifierId, Vec<IdentifierId>> = FxHashMap::default();1171        let mut functions_map: FxHashMap<IdentifierId, FunctionId> = FxHashMap::default();1172        let mut locals_map: FxHashMap<IdentifierId, IdentifierId> = FxHashMap::default();1173        let mut result = Vec::new();11741175        for (_, block) in &func.body.blocks {1176            for &iid in &block.instructions {1177                let instr = &func.instructions[iid.0 as usize];1178                match &instr.value {1179                    InstructionValue::LoadLocal { place, .. } => {1180                        locals_map.insert(instr.lvalue.identifier, place.identifier);1181                    }1182                    InstructionValue::ArrayExpression { elements, .. } => {1183                        let elem_ids: Vec<IdentifierId> = elements1184                            .iter()1185                            .filter_map(|e| match e {1186                                ArrayElement::Place(p) => Some(p.identifier),1187                                _ => None,1188                            })1189                            .collect();1190                        if elem_ids.len() == elements.len() {1191                            candidate_deps.insert(instr.lvalue.identifier, elem_ids);1192                        }1193                    }1194                    InstructionValue::FunctionExpression { lowered_func, .. } => {1195                        functions_map.insert(instr.lvalue.identifier, lowered_func.func);1196                    }1197                    InstructionValue::CallExpression { callee, args, .. } => {1198                        let callee_ty = &tys[ids[callee.identifier.0 as usize].type_.0 as usize];1199                        if is_use_effect_hook_type(callee_ty) && args.len() == 2 {1200                            if let (PlaceOrSpread::Place(arg0), PlaceOrSpread::Place(arg1)) =1201                                (&args[0], &args[1])1202                            {1203                                if let (Some(&func_id), Some(dep_elements)) = (1204                                    functions_map.get(&arg0.identifier),1205                                    candidate_deps.get(&arg1.identifier),1206                                ) {1207                                    if !dep_elements.is_empty() {1208                                        let resolved: Vec<IdentifierId> = dep_elements1209                                            .iter()1210                                            .map(|d| locals_map.get(d).copied().unwrap_or(*d))1211                                            .collect();1212                                        result.push((func_id, resolved));1213                                    }1214                                }1215                            }1216                        }1217                    }1218                    InstructionValue::MethodCall { property, args, .. } => {1219                        let callee_ty = &tys[ids[property.identifier.0 as usize].type_.0 as usize];1220                        if is_use_effect_hook_type(callee_ty) && args.len() == 2 {1221                            if let (PlaceOrSpread::Place(arg0), PlaceOrSpread::Place(arg1)) =1222                                (&args[0], &args[1])1223                            {1224                                if let (Some(&func_id), Some(dep_elements)) = (1225                                    functions_map.get(&arg0.identifier),1226                                    candidate_deps.get(&arg1.identifier),1227                                ) {1228                                    if !dep_elements.is_empty() {1229                                        let resolved: Vec<IdentifierId> = dep_elements1230                                            .iter()1231                                            .map(|d| locals_map.get(d).copied().unwrap_or(*d))1232                                            .collect();1233                                        result.push((func_id, resolved));1234                                    }1235                                }1236                            }1237                        }1238                    }1239                    _ => {}1240                }1241            }1242        }1243        result1244    };12451246    // Phase 2: Validate each collected effect and record error details.1247    // Uses ErrorDetail (flat loc format) to match TS behavior where1248    // env.recordError(new CompilerErrorDetail({...})) is used.1249    for (func_id, resolved_deps) in effects_to_validate {1250        let details = validate_effect_non_exp(1251            &env.functions[func_id.0 as usize],1252            &resolved_deps,1253            &env.identifiers,1254            &env.types,1255        );1256        for detail in details {1257            env.record_error(detail)?;1258        }1259    }1260    Ok(())1261}12621263fn validate_effect_non_exp(1264    effect_func: &HirFunction,1265    effect_deps: &[IdentifierId],1266    ids: &[Identifier],1267    tys: &[Type],1268) -> Vec<CompilerErrorDetail> {1269    // Check that the effect function only captures effect deps and setState1270    for ctx in &effect_func.context {1271        let ctx_ty = &tys[ids[ctx.identifier.0 as usize].type_.0 as usize];1272        if is_set_state_type(ctx_ty) {1273            continue;1274        } else if effect_deps.iter().any(|d| *d == ctx.identifier) {1275            continue;1276        } else {1277            return Vec::new();1278        }1279    }12801281    // Check that all effect deps are actually used in the function1282    for dep in effect_deps {1283        if !effect_func.context.iter().any(|c| c.identifier == *dep) {1284            return Vec::new();1285        }1286    }12871288    let mut seen_blocks: FxHashSet<BlockId> = FxHashSet::default();1289    let mut dep_values: FxHashMap<IdentifierId, Vec<IdentifierId>> = FxHashMap::default();1290    for dep in effect_deps {1291        dep_values.insert(*dep, vec![*dep]);1292    }12931294    let mut set_state_locs: Vec<SourceLocation> = Vec::new();12951296    for (_, block) in &effect_func.body.blocks {1297        for &pred in &block.preds {1298            if !seen_blocks.contains(&pred) {1299                return Vec::new();1300            }1301        }13021303        for phi in &block.phis {1304            let mut aggregate: FxHashSet<IdentifierId> = FxHashSet::default();1305            for operand in phi.operands.values() {1306                if let Some(deps) = dep_values.get(&operand.identifier) {1307                    for d in deps {1308                        aggregate.insert(*d);1309                    }1310                }1311            }1312            if !aggregate.is_empty() {1313                dep_values.insert(phi.place.identifier, aggregate.into_iter().collect());1314            }1315        }13161317        for &iid in &block.instructions {1318            let instr = &effect_func.instructions[iid.0 as usize];1319            match &instr.value {1320                InstructionValue::Primitive { .. }1321                | InstructionValue::JSXText { .. }1322                | InstructionValue::LoadGlobal { .. } => {}1323                InstructionValue::LoadLocal { place, .. } => {1324                    if let Some(deps) = dep_values.get(&place.identifier) {1325                        dep_values.insert(instr.lvalue.identifier, deps.clone());1326                    }1327                }1328                InstructionValue::ComputedLoad { .. }1329                | InstructionValue::PropertyLoad { .. }1330                | InstructionValue::BinaryExpression { .. }1331                | InstructionValue::TemplateLiteral { .. }1332                | InstructionValue::CallExpression { .. }1333                | InstructionValue::MethodCall { .. } => {1334                    let mut aggregate: FxHashSet<IdentifierId> = FxHashSet::default();1335                    for operand in non_exp_value_operands(&instr.value) {1336                        if let Some(deps) = dep_values.get(&operand) {1337                            for d in deps {1338                                aggregate.insert(*d);1339                            }1340                        }1341                    }1342                    if !aggregate.is_empty() {1343                        dep_values.insert(instr.lvalue.identifier, aggregate.into_iter().collect());1344                    }13451346                    if let InstructionValue::CallExpression { callee, args, .. } = &instr.value {1347                        let callee_ty = &tys[ids[callee.identifier.0 as usize].type_.0 as usize];1348                        if is_set_state_type(callee_ty) && args.len() == 1 {1349                            if let PlaceOrSpread::Place(arg) = &args[0] {1350                                if let Some(deps) = dep_values.get(&arg.identifier) {1351                                    let dep_set: FxHashSet<_> = deps.iter().collect();1352                                    if dep_set.len() == effect_deps.len() {1353                                        if let Some(loc) = callee.loc {1354                                            set_state_locs.push(loc);1355                                        }1356                                    } else {1357                                        return Vec::new();1358                                    }1359                                } else {1360                                    return Vec::new();1361                                }1362                            }1363                        }1364                    }1365                }1366                _ => {1367                    return Vec::new();1368                }1369            }1370        }13711372        match &block.terminal {1373            react_compiler_hir::Terminal::Return { value, .. }1374            | react_compiler_hir::Terminal::Throw { value, .. } => {1375                if dep_values.contains_key(&value.identifier) {1376                    return Vec::new();1377                }1378            }1379            react_compiler_hir::Terminal::If { test, .. }1380            | react_compiler_hir::Terminal::Branch { test, .. } => {1381                if dep_values.contains_key(&test.identifier) {1382                    return Vec::new();1383                }1384            }1385            react_compiler_hir::Terminal::Switch { test, .. } => {1386                if dep_values.contains_key(&test.identifier) {1387                    return Vec::new();1388                }1389            }1390            _ => {}1391        }13921393        seen_blocks.insert(block.id);1394    }13951396    set_state_locs1397        .into_iter()1398        .map(|loc| {1399            CompilerErrorDetail {1400                category: ErrorCategory::EffectDerivationsOfState,1401                reason: "Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)".to_string(),1402                description: None,1403                loc: Some(loc),1404                suggestions: None,1405            }1406        })1407        .collect()1408}14091410/// Collects operand IdentifierIds for a subset of instruction variants used1411/// by `validate_effect_non_exp`.1412///1413/// NOTE: This intentionally does NOT use the canonical `each_instruction_value_operand`1414/// because: (1) `validate_effect_non_exp` only matches specific variants1415/// (ComputedLoad, PropertyLoad, BinaryExpression, TemplateLiteral, CallExpression,1416/// MethodCall), so FunctionExpression/ObjectMethod context handling is unnecessary;1417/// and (2) the caller does not have access to `env` which the canonical function requires1418/// for resolving function expression context captures.1419fn non_exp_value_operands(value: &InstructionValue) -> Vec<IdentifierId> {1420    match value {1421        InstructionValue::ComputedLoad {1422            object, property, ..1423        } => {1424            vec![object.identifier, property.identifier]1425        }1426        InstructionValue::PropertyLoad { object, .. } => vec![object.identifier],1427        InstructionValue::BinaryExpression { left, right, .. } => {1428            vec![left.identifier, right.identifier]1429        }1430        InstructionValue::TemplateLiteral { subexprs, .. } => {1431            subexprs.iter().map(|s| s.identifier).collect()1432        }1433        InstructionValue::CallExpression { callee, args, .. } => {1434            let mut op_ids = vec![callee.identifier];1435            for a in args {1436                match a {1437                    PlaceOrSpread::Place(p) => op_ids.push(p.identifier),1438                    PlaceOrSpread::Spread(s) => op_ids.push(s.place.identifier),1439                }1440            }1441            op_ids1442        }1443        InstructionValue::MethodCall {1444            receiver,1445            property,1446            args,1447            ..1448        } => {1449            let mut op_ids = vec![receiver.identifier, property.identifier];1450            for a in args {1451                match a {1452                    PlaceOrSpread::Place(p) => op_ids.push(p.identifier),1453                    PlaceOrSpread::Spread(s) => op_ids.push(s.place.identifier),1454                }1455            }1456            op_ids1457        }1458        _ => Vec::new(),1459    }1460}

Code quality findings 69

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 = &identifiers[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 slice = &code[start..end];
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 lvalue_ident = &identifiers[lvalue_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 lvalue_ty = &types[lvalue_ident.type_.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 lvalue_ident = &identifiers[lvalue_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 lvalue_ty = &types[lvalue_ident.type_.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
env.identifiers[identifier_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 name = identifiers[place.identifier.0 as usize].name.clone();
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 name = identifiers[place.identifier.0 as usize].name.clone();
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 = &func.instructions[instr_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 name = identifiers[phi.place.identifier.0 as usize].name.clone();
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 = &functions[lowered_func.func.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_instr = &inner_func.instructions[inner_instr_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 callee_type = &types[identifiers[callee.identifier.0 as usize].type_.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
) = (&args[0], &args[1])
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 lvalue_type = &types[identifiers[lvalue_id.0 as usize].type_.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 name = identifiers[lvalue_id.0 as usize].name.clone();
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 prop_type = &types[identifiers[property.identifier.0 as usize].type_.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
) = (&args[0], &args[1])
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 lvalue_type = &types[identifiers[lvalue_id.0 as usize].type_.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 name = identifiers[lvalue_id.0 as usize].name.clone();
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 name = identifiers[lv_id.0 as usize].name.clone();
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 name = identifiers[operand.id.0 as usize].name.clone();
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 = &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 instr = &inner.instructions[instr_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 effect_function = &functions[effect_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 instr = &effect_function.instructions[instr_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
&types[identifiers[instr.lvalue.identifier.0 as usize].type_.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
&types[identifiers[callee.identifier.0 as usize].type_.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
if let react_compiler_hir::PlaceOrSpread::Place(arg0) = &args[0] {
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 = &func.instructions[iid.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 callee_ty = &tys[ids[callee.identifier.0 as usize].type_.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
(&args[0], &args[1])
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 callee_ty = &tys[ids[property.identifier.0 as usize].type_.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
(&args[0], &args[1])
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
&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 ctx_ty = &tys[ids[ctx.identifier.0 as usize].type_.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 instr = &effect_func.instructions[iid.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 callee_ty = &tys[ids[callee.identifier.0 as usize].type_.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
if let PlaceOrSpread::Place(arg) = &args[0] {
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 &ident.name {
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
return Some(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 &other.name {
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
return Some(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
place_name: value.place_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
source_ids: value.source_ids.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 name = identifiers[place.identifier.0 as usize].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
.map(|(k, v)| (*k, v.effect_func_id, v.dep_elements.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 name = identifiers[phi.place.identifier.0 as usize].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
.filter_map(|el| match el {
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 name = identifiers[lv_id.0 as usize].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
sources.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 name = identifiers[operand.id.0 as usize].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
sources.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 mut new_visited = visited.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
new_visited.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
named_siblings.insert(child_node.name.clone());
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
children.push(child_node);
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
let type_label = match node.type_of_value {
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
props_set.insert(node.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
state_set.insert(node.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
root_nodes_map.insert(node.name.clone(), node);
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 {
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
.filter_map(|e| match e {
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
dep_values.insert(instr.lvalue.identifier, deps.clone());
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
PlaceOrSpread::Place(p) => op_ids.push(p.identifier),
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
PlaceOrSpread::Spread(s) => op_ids.push(s.place.identifier),
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
PlaceOrSpread::Place(p) => op_ids.push(p.identifier),
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
PlaceOrSpread::Spread(s) => op_ids.push(s.place.identifier),

Get this view in your editor

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