File is large — showing lines 1–2,000 of 2,348.
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//! Propagates scope dependencies through the HIR, computing which values each7//! reactive scope depends on.8//!9//! Ported from TypeScript:10//! - `src/HIR/PropagateScopeDependenciesHIR.ts`11//! - `src/HIR/CollectOptionalChainDependencies.ts`12//! - `src/HIR/CollectHoistablePropertyLoads.ts`13//! - `src/HIR/DeriveMinimalDependenciesHIR.ts`1415use indexmap::IndexMap;16use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};17use std::collections::BTreeSet;1819use react_compiler_hir::environment::Environment;20use react_compiler_hir::visitors::{ScopeBlockInfo, ScopeBlockTraversal};21use react_compiler_hir::{22 BasicBlock, BlockId, DeclarationId, DependencyPathEntry, EvaluationOrder, FunctionId,23 GotoVariant, HirFunction, IdentifierId, Instruction, InstructionId, InstructionKind,24 InstructionValue, MutableRange, ParamPattern, Place, PlaceOrSpread, PropertyLiteral,25 ReactFunctionType, ReactiveScopeDependency, ScopeId, Terminal, Type, visitors,26};2728// =============================================================================29// Public entry point30// =============================================================================3132/// Main entry point: propagate scope dependencies through the HIR.33/// Corresponds to TS `propagateScopeDependenciesHIR(fn)`.34pub fn propagate_scope_dependencies_hir(func: &mut HirFunction, env: &mut Environment) {35 let used_outside_declaring_scope = find_temporaries_used_outside_declaring_scope(func, env);36 let temporaries = collect_temporaries_sidemap(func, env, &used_outside_declaring_scope);3738 let OptionalChainSidemap {39 temporaries_read_in_optional,40 processed_instrs_in_optional,41 hoistable_objects,42 } = collect_optional_chain_sidemap(func, env);4344 let hoistable_property_loads = {45 let (working, registry) =46 collect_hoistable_and_propagate(func, env, &temporaries, &hoistable_objects);47 // Convert to scope-keyed map with full dependency paths48 let mut keyed: FxHashMap<ScopeId, Vec<ReactiveScopeDependency>> = FxHashMap::default();49 for (_block_id, block) in &func.body.blocks {50 if let Terminal::Scope {51 scope,52 block: inner_block,53 ..54 } = &block.terminal55 {56 if let Some(node_indices) = working.get(inner_block) {57 let deps: Vec<ReactiveScopeDependency> = node_indices58 .iter()59 .map(|&idx| registry.nodes[idx].full_path.clone())60 .collect();61 keyed.insert(*scope, deps);62 }63 }64 }65 keyed66 };6768 // Merge temporaries + temporariesReadInOptional69 let mut merged_temporaries = temporaries;70 for (k, v) in temporaries_read_in_optional {71 merged_temporaries.insert(k, v);72 }7374 let scope_deps = collect_dependencies(75 func,76 env,77 &used_outside_declaring_scope,78 &merged_temporaries,79 &processed_instrs_in_optional,80 );8182 // Derive the minimal set of hoistable dependencies for each scope.83 for (scope_id, deps) in &scope_deps {84 if deps.is_empty() {85 continue;86 }8788 let hoistables = hoistable_property_loads.get(scope_id);89 let hoistables =90 hoistables.expect("[PropagateScopeDependencies] Scope not found in tracked blocks");9192 // Step 2: Calculate hoistable dependencies using the tree.93 let mut tree = ReactiveScopeDependencyTreeHIR::new(hoistables.iter(), env);94 for dep in deps {95 tree.add_dependency(dep.clone(), env);96 }9798 // Step 3: Reduce dependencies to a minimal set.99 let candidates = tree.derive_minimal_dependencies(env);100 let scope = &mut env.scopes[scope_id.0 as usize];101 for candidate_dep in candidates {102 let already_exists = scope.dependencies.iter().any(|existing_dep| {103 let existing_decl_id =104 env.identifiers[existing_dep.identifier.0 as usize].declaration_id;105 let candidate_decl_id =106 env.identifiers[candidate_dep.identifier.0 as usize].declaration_id;107 existing_decl_id == candidate_decl_id108 && are_equal_paths(&existing_dep.path, &candidate_dep.path)109 });110 if !already_exists {111 scope.dependencies.push(candidate_dep);112 }113 }114 }115}116117fn are_equal_paths(a: &[DependencyPathEntry], b: &[DependencyPathEntry]) -> bool {118 a.len() == b.len()119 && a.iter()120 .zip(b.iter())121 .all(|(ai, bi)| ai.property == bi.property && ai.optional == bi.optional)122}123124// =============================================================================125// findTemporariesUsedOutsideDeclaringScope126// =============================================================================127128/// Corresponds to TS `findTemporariesUsedOutsideDeclaringScope`.129fn find_temporaries_used_outside_declaring_scope(130 func: &HirFunction,131 env: &Environment,132) -> FxHashSet<DeclarationId> {133 let mut declarations: FxHashMap<DeclarationId, ScopeId> = FxHashMap::default();134 let mut pruned_scopes: FxHashSet<ScopeId> = FxHashSet::default();135 let mut traversal = ScopeBlockTraversal::new();136 let mut used_outside_declaring_scope: FxHashSet<DeclarationId> = FxHashSet::default();137138 let handle_place = |place_id: IdentifierId,139 declarations: &FxHashMap<DeclarationId, ScopeId>,140 traversal: &ScopeBlockTraversal,141 pruned_scopes: &FxHashSet<ScopeId>,142 used_outside: &mut FxHashSet<DeclarationId>,143 env: &Environment| {144 let decl_id = env.identifiers[place_id.0 as usize].declaration_id;145 if let Some(&declaring_scope) = declarations.get(&decl_id) {146 if !traversal.is_scope_active(declaring_scope)147 && !pruned_scopes.contains(&declaring_scope)148 {149 used_outside.insert(decl_id);150 }151 }152 };153154 for (block_id, block) in &func.body.blocks {155 // recordScopes156 traversal.record_scopes(block);157158 let scope_start_info = traversal.block_infos.get(block_id);159 if let Some(ScopeBlockInfo::Begin {160 scope,161 pruned: true,162 ..163 }) = scope_start_info164 {165 pruned_scopes.insert(*scope);166 }167168 for &instr_id in &block.instructions {169 let instr = &func.instructions[instr_id.0 as usize];170 // Handle operands171 for op_id in visitors::each_instruction_operand(instr, env)172 .into_iter()173 .map(|p| p.identifier)174 .collect::<Vec<_>>()175 {176 handle_place(177 op_id,178 &declarations,179 &traversal,180 &pruned_scopes,181 &mut used_outside_declaring_scope,182 env,183 );184 }185 // Handle instruction (track declarations)186 let current_scope = traversal.current_scope();187 if let Some(scope) = current_scope {188 if !pruned_scopes.contains(&scope) {189 match &instr.value {190 InstructionValue::LoadLocal { .. }191 | InstructionValue::LoadContext { .. }192 | InstructionValue::PropertyLoad { .. } => {193 let decl_id =194 env.identifiers[instr.lvalue.identifier.0 as usize].declaration_id;195 declarations.insert(decl_id, scope);196 }197 _ => {}198 }199 }200 }201 }202203 // Terminal operands204 for op_id in visitors::each_terminal_operand(&block.terminal)205 .into_iter()206 .map(|p| p.identifier)207 .collect::<Vec<_>>()208 {209 handle_place(210 op_id,211 &declarations,212 &traversal,213 &pruned_scopes,214 &mut used_outside_declaring_scope,215 env,216 );217 }218 }219220 used_outside_declaring_scope221}222223// =============================================================================224// collectTemporariesSidemap225// =============================================================================226227/// Corresponds to TS `collectTemporariesSidemap`.228fn collect_temporaries_sidemap(229 func: &HirFunction,230 env: &Environment,231 used_outside_declaring_scope: &FxHashSet<DeclarationId>,232) -> FxHashMap<IdentifierId, ReactiveScopeDependency> {233 let mut temporaries = FxHashMap::default();234 collect_temporaries_sidemap_impl(235 func,236 env,237 used_outside_declaring_scope,238 &mut temporaries,239 None,240 );241 temporaries242}243244/// Corresponds to TS `isLoadContextMutable`.245fn is_load_context_mutable(246 value: &InstructionValue,247 id: EvaluationOrder,248 env: &Environment,249) -> bool {250 if let InstructionValue::LoadContext { place, .. } = value {251 if let Some(scope_id) = env.identifiers[place.identifier.0 as usize].scope {252 let scope_range = &env.scopes[scope_id.0 as usize].range;253 return id >= scope_range.end;254 }255 }256 false257}258259/// Corresponds to TS `convertHoistedLValueKind` — returns None for non-hoisted kinds.260fn convert_hoisted_lvalue_kind(kind: InstructionKind) -> Option<InstructionKind> {261 match kind {262 InstructionKind::HoistedLet => Some(InstructionKind::Let),263 InstructionKind::HoistedConst => Some(InstructionKind::Const),264 InstructionKind::HoistedFunction => Some(InstructionKind::Function),265 _ => None,266 }267}268269/// Recursive implementation. Corresponds to TS `collectTemporariesSidemapImpl`.270fn collect_temporaries_sidemap_impl(271 func: &HirFunction,272 env: &Environment,273 used_outside_declaring_scope: &FxHashSet<DeclarationId>,274 temporaries: &mut FxHashMap<IdentifierId, ReactiveScopeDependency>,275 inner_fn_context: Option<EvaluationOrder>,276) {277 for (_block_id, block) in &func.body.blocks {278 for &instr_id in &block.instructions {279 let instr = &func.instructions[instr_id.0 as usize];280 let instr_eval_order = if let Some(outer_id) = inner_fn_context {281 outer_id282 } else {283 instr.id284 };285 let lvalue_decl_id = env.identifiers[instr.lvalue.identifier.0 as usize].declaration_id;286 let used_outside = used_outside_declaring_scope.contains(&lvalue_decl_id);287288 match &instr.value {289 InstructionValue::PropertyLoad {290 object,291 property,292 loc,293 ..294 } if !used_outside => {295 if inner_fn_context.is_none() || temporaries.contains_key(&object.identifier) {296 let prop = get_property(object, property, false, *loc, temporaries, env);297 temporaries.insert(instr.lvalue.identifier, prop);298 }299 }300 InstructionValue::LoadLocal { place, loc, .. }301 if env.identifiers[instr.lvalue.identifier.0 as usize]302 .name303 .is_none()304 && env.identifiers[place.identifier.0 as usize].name.is_some()305 && !used_outside =>306 {307 if inner_fn_context.is_none()308 || func309 .context310 .iter()311 .any(|ctx| ctx.identifier == place.identifier)312 {313 temporaries.insert(314 instr.lvalue.identifier,315 ReactiveScopeDependency {316 identifier: place.identifier,317 reactive: place.reactive,318 path: vec![],319 loc: *loc,320 },321 );322 }323 }324 value @ InstructionValue::LoadContext { place, loc, .. }325 if is_load_context_mutable(value, instr_eval_order, env)326 && env.identifiers[instr.lvalue.identifier.0 as usize]327 .name328 .is_none()329 && env.identifiers[place.identifier.0 as usize].name.is_some()330 && !used_outside =>331 {332 if inner_fn_context.is_none()333 || func334 .context335 .iter()336 .any(|ctx| ctx.identifier == place.identifier)337 {338 temporaries.insert(339 instr.lvalue.identifier,340 ReactiveScopeDependency {341 identifier: place.identifier,342 reactive: place.reactive,343 path: vec![],344 loc: *loc,345 },346 );347 }348 }349 InstructionValue::FunctionExpression { lowered_func, .. }350 | InstructionValue::ObjectMethod { lowered_func, .. } => {351 let inner_func = &env.functions[lowered_func.func.0 as usize];352 let ctx = inner_fn_context.unwrap_or(instr.id);353 collect_temporaries_sidemap_impl(354 inner_func,355 env,356 used_outside_declaring_scope,357 temporaries,358 Some(ctx),359 );360 }361 _ => {}362 }363 }364 }365}366367/// Corresponds to TS `getProperty`.368fn get_property(369 object: &Place,370 property_name: &PropertyLiteral,371 optional: bool,372 loc: Option<react_compiler_hir::SourceLocation>,373 temporaries: &FxHashMap<IdentifierId, ReactiveScopeDependency>,374 _env: &Environment,375) -> ReactiveScopeDependency {376 let resolved = temporaries.get(&object.identifier);377 if let Some(resolved) = resolved {378 let mut path = resolved.path.clone();379 path.push(DependencyPathEntry {380 property: property_name.clone(),381 optional,382 loc,383 });384 ReactiveScopeDependency {385 identifier: resolved.identifier,386 reactive: resolved.reactive,387 path,388 loc,389 }390 } else {391 ReactiveScopeDependency {392 identifier: object.identifier,393 reactive: object.reactive,394 path: vec![DependencyPathEntry {395 property: property_name.clone(),396 optional,397 loc,398 }],399 loc,400 }401 }402}403404// =============================================================================405// CollectOptionalChainDependencies406// =============================================================================407408struct OptionalChainSidemap {409 temporaries_read_in_optional: FxHashMap<IdentifierId, ReactiveScopeDependency>,410 processed_instrs_in_optional: FxHashSet<ProcessedInstr>,411 hoistable_objects: FxHashMap<BlockId, ReactiveScopeDependency>,412}413414/// We track processed instructions/terminals by their lvalue IdentifierId + block id.415/// In TS this uses reference identity (Set<Instruction | Terminal>).416/// We use IdentifierId for instructions (globally unique across functions) and417/// BlockId for terminals. Note: EvaluationOrder (instruction id) is NOT unique418/// across functions, so we cannot use it here.419#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]420enum ProcessedInstr {421 Instruction(IdentifierId),422 Terminal(BlockId),423}424425fn collect_optional_chain_sidemap(func: &HirFunction, env: &Environment) -> OptionalChainSidemap {426 let mut ctx = OptionalTraversalContext {427 seen_optionals: FxHashSet::default(),428 processed_instrs_in_optional: FxHashSet::default(),429 temporaries_read_in_optional: FxHashMap::default(),430 hoistable_objects: FxHashMap::default(),431 };432433 traverse_function_optional(func, env, &mut ctx);434435 OptionalChainSidemap {436 temporaries_read_in_optional: ctx.temporaries_read_in_optional,437 processed_instrs_in_optional: ctx.processed_instrs_in_optional,438 hoistable_objects: ctx.hoistable_objects,439 }440}441442struct OptionalTraversalContext {443 seen_optionals: FxHashSet<BlockId>,444 processed_instrs_in_optional: FxHashSet<ProcessedInstr>,445 temporaries_read_in_optional: FxHashMap<IdentifierId, ReactiveScopeDependency>,446 hoistable_objects: FxHashMap<BlockId, ReactiveScopeDependency>,447}448449fn traverse_function_optional(450 func: &HirFunction,451 env: &Environment,452 ctx: &mut OptionalTraversalContext,453) {454 for (_block_id, block) in &func.body.blocks {455 for &instr_id in &block.instructions {456 let instr = &func.instructions[instr_id.0 as usize];457 match &instr.value {458 InstructionValue::FunctionExpression { lowered_func, .. }459 | InstructionValue::ObjectMethod { lowered_func, .. } => {460 let inner_func = &env.functions[lowered_func.func.0 as usize];461 traverse_function_optional(inner_func, env, ctx);462 }463 _ => {}464 }465 }466 if let Terminal::Optional { .. } = &block.terminal {467 if !ctx.seen_optionals.contains(&block.id) {468 traverse_optional_block(block, func, env, ctx, None);469 }470 }471 }472}473474struct MatchConsequentResult {475 consequent_id: IdentifierId,476 property: PropertyLiteral,477 property_id: IdentifierId,478 store_local_lvalue_id: IdentifierId,479 consequent_goto: BlockId,480 property_load_loc: Option<react_compiler_hir::SourceLocation>,481}482483fn match_optional_test_block(484 test: &Terminal,485 func: &HirFunction,486 _env: &Environment,487) -> Option<MatchConsequentResult> {488 let (test_place, consequent_block_id, alternate_block_id) = match test {489 Terminal::Branch {490 test,491 consequent,492 alternate,493 ..494 } => (test, *consequent, *alternate),495 _ => return None,496 };497498 let consequent_block = func.body.blocks.get(&consequent_block_id)?;499 if consequent_block.instructions.len() != 2 {500 return None;501 }502503 let instr0 = &func.instructions[consequent_block.instructions[0].0 as usize];504 let instr1 = &func.instructions[consequent_block.instructions[1].0 as usize];505506 let (property_load_object, property, property_load_loc) = match &instr0.value {507 InstructionValue::PropertyLoad {508 object,509 property,510 loc,511 } => (object, property, loc),512 _ => return None,513 };514515 let store_local_value = match &instr1.value {516 InstructionValue::StoreLocal { value, lvalue, .. } => {517 // Verify the store local's value matches the property load's lvalue518 if value.identifier != instr0.lvalue.identifier {519 return None;520 }521 &lvalue.place522 }523 _ => return None,524 };525526 // Verify property load's object matches the test527 if property_load_object.identifier != test_place.identifier {528 return None;529 }530531 // Check consequent block terminal is goto break532 match &consequent_block.terminal {533 Terminal::Goto {534 variant: GotoVariant::Break,535 block: goto_block,536 ..537 } => {538 // Verify alternate block structure539 let alternate_block = func.body.blocks.get(&alternate_block_id)?;540 if alternate_block.instructions.len() != 2 {541 return None;542 }543 let alt_instr0 = &func.instructions[alternate_block.instructions[0].0 as usize];544 let alt_instr1 = &func.instructions[alternate_block.instructions[1].0 as usize];545 match (&alt_instr0.value, &alt_instr1.value) {546 (InstructionValue::Primitive { .. }, InstructionValue::StoreLocal { .. }) => {}547 _ => return None,548 }549550 Some(MatchConsequentResult {551 consequent_id: store_local_value.identifier,552 property: property.clone(),553 property_id: instr0.lvalue.identifier,554 store_local_lvalue_id: instr1.lvalue.identifier,555 consequent_goto: *goto_block,556 property_load_loc: *property_load_loc,557 })558 }559 _ => None,560 }561}562563fn traverse_optional_block(564 optional_block: &BasicBlock,565 func: &HirFunction,566 env: &Environment,567 ctx: &mut OptionalTraversalContext,568 outer_alternate: Option<BlockId>,569) -> Option<IdentifierId> {570 ctx.seen_optionals.insert(optional_block.id);571572 let (test_block_id, is_optional, fallthrough_block_id) = match &optional_block.terminal {573 Terminal::Optional {574 test,575 optional,576 fallthrough,577 ..578 } => (*test, *optional, *fallthrough),579 _ => return None,580 };581582 let maybe_test_block = func.body.blocks.get(&test_block_id)?;583584 let (test_terminal, base_object) = match &maybe_test_block.terminal {585 Terminal::Branch { .. } => {586 // Base case: optional must be true587 if !is_optional {588 return None;589 }590 // Match base expression that is straightforward PropertyLoad chain591 if maybe_test_block.instructions.is_empty() {592 return None;593 }594 let first_instr = &func.instructions[maybe_test_block.instructions[0].0 as usize];595 if !matches!(&first_instr.value, InstructionValue::LoadLocal { .. }) {596 return None;597 }598599 let mut path: Vec<DependencyPathEntry> = Vec::new();600 for i in 1..maybe_test_block.instructions.len() {601 let curr_instr = &func.instructions[maybe_test_block.instructions[i].0 as usize];602 let prev_instr =603 &func.instructions[maybe_test_block.instructions[i - 1].0 as usize];604 match &curr_instr.value {605 InstructionValue::PropertyLoad {606 object,607 property,608 loc,609 ..610 } if object.identifier == prev_instr.lvalue.identifier => {611 path.push(DependencyPathEntry {612 property: property.clone(),613 optional: false,614 loc: *loc,615 });616 }617 _ => return None,618 }619 }620621 // Verify test expression matches last instruction's lvalue622 let last_instr_id = *maybe_test_block.instructions.last().unwrap();623 let last_instr = &func.instructions[last_instr_id.0 as usize];624 let test_ident = match &maybe_test_block.terminal {625 Terminal::Branch { test, .. } => test.identifier,626 _ => return None,627 };628 if test_ident != last_instr.lvalue.identifier {629 return None;630 }631632 let first_place = match &first_instr.value {633 InstructionValue::LoadLocal { place, .. } => place,634 _ => return None,635 };636637 let base = ReactiveScopeDependency {638 identifier: first_place.identifier,639 reactive: first_place.reactive,640 path,641 loc: first_place.loc,642 };643 (&maybe_test_block.terminal, base)644 }645 Terminal::Optional {646 fallthrough: inner_fallthrough,647 optional: _inner_optional,648 ..649 } => {650 let test_block = func.body.blocks.get(inner_fallthrough)?;651 if !matches!(&test_block.terminal, Terminal::Branch { .. }) {652 return None;653 }654655 // Recurse into inner optional656 let inner_alternate = match &test_block.terminal {657 Terminal::Branch { alternate, .. } => Some(*alternate),658 _ => None,659 };660 let inner_optional_result =661 traverse_optional_block(maybe_test_block, func, env, ctx, inner_alternate);662 let inner_optional_id = inner_optional_result?;663664 // Check that inner optional is part of the same chain665 let test_ident = match &test_block.terminal {666 Terminal::Branch { test, .. } => test.identifier,667 _ => return None,668 };669 if test_ident != inner_optional_id {670 return None;671 }672673 if !is_optional {674 // Non-optional load: record that PropertyLoads from inner optional are hoistable675 if let Some(inner_dep) = ctx.temporaries_read_in_optional.get(&inner_optional_id) {676 ctx.hoistable_objects677 .insert(optional_block.id, inner_dep.clone());678 }679 }680681 let base = ctx682 .temporaries_read_in_optional683 .get(&inner_optional_id)?684 .clone();685 (&test_block.terminal, base)686 }687 _ => return None,688 };689690 // Verify alternate matches outer_alternate if present691 if let Some(outer_alt) = outer_alternate {692 let test_alternate = match test_terminal {693 Terminal::Branch { alternate, .. } => *alternate,694 _ => return None,695 };696 if test_alternate == outer_alt {697 // Verify optional block has no instructions698 if !optional_block.instructions.is_empty() {699 return None;700 }701 }702 }703704 let match_result = match_optional_test_block(test_terminal, func, env)?;705706 // Verify consequent goto matches optional fallthrough707 if match_result.consequent_goto != fallthrough_block_id {708 return None;709 }710711 let load = ReactiveScopeDependency {712 identifier: base_object.identifier,713 reactive: base_object.reactive,714 path: {715 let mut p = base_object.path.clone();716 p.push(DependencyPathEntry {717 property: match_result.property.clone(),718 optional: is_optional,719 loc: match_result.property_load_loc,720 });721 p722 },723 loc: match_result.property_load_loc,724 };725726 ctx.processed_instrs_in_optional727 .insert(ProcessedInstr::Instruction(728 match_result.store_local_lvalue_id,729 ));730 ctx.processed_instrs_in_optional731 .insert(ProcessedInstr::Terminal(match &test_terminal {732 Terminal::Branch { .. } => {733 // Find the block ID for this terminal734 // The terminal belongs to either maybe_test_block or the fallthrough block of inner optional735 // We need to identify which block this terminal belongs to.736 // For the base case, it's test_block_id.737 // For nested optional, it's the fallthrough block.738 // We'll use the block_id approach based on what we know.739 // Actually, we tracked the terminal by its block, so we need to find which block740 // contains this terminal. Let's use a pragmatic approach:741 // The test terminal we matched was from maybe_test_block or from the inner fallthrough block.742 // We'll search for it.743744 // For the base case (Branch terminal at maybe_test_block), block_id = test_block_id745 // For the nested case, the test terminal is at the fallthrough block of inner optional746 // In either case, we stored the terminal as test_terminal which comes from a known block.747 // We need to find the block that owns this terminal.748749 // Let's take a simpler approach: find the block whose terminal matches750 // This is the block we got test_terminal from.751 // In the first branch of the match, test_terminal = &maybe_test_block.terminal752 // and maybe_test_block.id = test_block_id753 // In the second branch, test_terminal = &test_block.terminal754 // and test_block = func.body.blocks.get(inner_fallthrough)755 // We can't easily tell which case we're in here since we're past the match.756757 // Actually, since test_terminal is a reference to a terminal in a block,758 // we can just look up which block it belongs to by finding blocks whose terminal759 // pointer matches. But that's expensive. Instead, let's use the block approach760 // and find the block from the terminal's properties.761762 // For simplicity, use a sentinel approach: just check all blocks.763 // This is O(n) but only happens for optional chains.764 let mut found_block = BlockId(0);765 for (bid, blk) in &func.body.blocks {766 if std::ptr::eq(&blk.terminal, test_terminal) {767 found_block = *bid;768 break;769 }770 }771 found_block772 }773 _ => BlockId(0),774 }));775 ctx.temporaries_read_in_optional776 .insert(match_result.consequent_id, load.clone());777 ctx.temporaries_read_in_optional778 .insert(match_result.property_id, load);779780 Some(match_result.consequent_id)781}782783// =============================================================================784// CollectHoistablePropertyLoads785// =============================================================================786787#[derive(Debug, Clone)]788struct PropertyPathNode {789 properties: FxHashMap<PropertyLiteral, usize>, // index into registry790 optional_properties: FxHashMap<PropertyLiteral, usize>, // index into registry791 #[allow(dead_code)]792 parent: Option<usize>,793 full_path: ReactiveScopeDependency,794 has_optional: bool,795 #[allow(dead_code)]796 root: Option<IdentifierId>,797}798799struct PropertyPathRegistry {800 nodes: Vec<PropertyPathNode>,801 roots: FxHashMap<IdentifierId, usize>,802}803804impl PropertyPathRegistry {805 fn new() -> Self {806 Self {807 nodes: Vec::new(),808 roots: FxHashMap::default(),809 }810 }811812 fn get_or_create_identifier(813 &mut self,814 identifier_id: IdentifierId,815 reactive: bool,816 loc: Option<react_compiler_hir::SourceLocation>,817 ) -> usize {818 if let Some(&idx) = self.roots.get(&identifier_id) {819 return idx;820 }821 let idx = self.nodes.len();822 self.nodes.push(PropertyPathNode {823 properties: FxHashMap::default(),824 optional_properties: FxHashMap::default(),825 parent: None,826 full_path: ReactiveScopeDependency {827 identifier: identifier_id,828 reactive,829 path: vec![],830 loc,831 },832 has_optional: false,833 root: Some(identifier_id),834 });835 self.roots.insert(identifier_id, idx);836 idx837 }838839 fn get_or_create_property_entry(840 &mut self,841 parent_idx: usize,842 entry: &DependencyPathEntry,843 ) -> usize {844 let map_key = entry.property.clone();845 let existing = if entry.optional {846 self.nodes[parent_idx]847 .optional_properties848 .get(&map_key)849 .copied()850 } else {851 self.nodes[parent_idx].properties.get(&map_key).copied()852 };853 if let Some(idx) = existing {854 return idx;855 }856 let parent_full_path = self.nodes[parent_idx].full_path.clone();857 let parent_has_optional = self.nodes[parent_idx].has_optional;858 let idx = self.nodes.len();859 let mut new_path = parent_full_path.path.clone();860 new_path.push(entry.clone());861 self.nodes.push(PropertyPathNode {862 properties: FxHashMap::default(),863 optional_properties: FxHashMap::default(),864 parent: Some(parent_idx),865 full_path: ReactiveScopeDependency {866 identifier: parent_full_path.identifier,867 reactive: parent_full_path.reactive,868 path: new_path,869 loc: entry.loc,870 },871 has_optional: parent_has_optional || entry.optional,872 root: None,873 });874 if entry.optional {875 self.nodes[parent_idx]876 .optional_properties877 .insert(map_key, idx);878 } else {879 self.nodes[parent_idx].properties.insert(map_key, idx);880 }881 idx882 }883884 fn get_or_create_property(&mut self, dep: &ReactiveScopeDependency) -> usize {885 let mut curr = self.get_or_create_identifier(dep.identifier, dep.reactive, dep.loc);886 for entry in &dep.path {887 curr = self.get_or_create_property_entry(curr, entry);888 }889 curr890 }891}892893/// Reduces optional chains in a set of property path nodes.894///895/// Any two optional chains with different operations (`.` vs `?.`) but the same set896/// of property string paths de-duplicates. If unconditional reads from `<base>` are897/// hoistable (i.e., `<base>` is in the set), we replace `<base>?.PROPERTY` with898/// `<base>.PROPERTY`.899///900/// Port of `reduceMaybeOptionalChains` from CollectHoistablePropertyLoads.ts.901fn reduce_maybe_optional_chains(nodes: &mut BTreeSet<usize>, registry: &mut PropertyPathRegistry) {902 // Collect indices of nodes that have optional in their path903 let mut optional_chain_nodes: BTreeSet<usize> = nodes904 .iter()905 .copied()906 .filter(|&idx| registry.nodes[idx].has_optional)907 .collect();908909 if optional_chain_nodes.is_empty() {910 return;911 }912913 loop {914 let mut changed = false;915916 // Collect the indices to process (snapshot to avoid borrow issues)917 let to_process: Vec<usize> = optional_chain_nodes.iter().copied().collect();918919 for original_idx in to_process {920 let full_path = registry.nodes[original_idx].full_path.clone();921922 let mut curr_node = registry.get_or_create_identifier(923 full_path.identifier,924 full_path.reactive,925 full_path.loc,926 );927928 for entry in &full_path.path {929 // If the base is known to be non-null (in the set), replace optional with non-optional930 let next_entry = if entry.optional && nodes.contains(&curr_node) {931 DependencyPathEntry {932 property: entry.property.clone(),933 optional: false,934 loc: entry.loc,935 }936 } else {937 entry.clone()938 };939 curr_node = registry.get_or_create_property_entry(curr_node, &next_entry);940 }941942 if curr_node != original_idx {943 changed = true;944 optional_chain_nodes.remove(&original_idx);945 optional_chain_nodes.insert(curr_node);946 nodes.remove(&original_idx);947 nodes.insert(curr_node);948 }949 }950951 if !changed {952 break;953 }954 }955}956957#[derive(Debug, Clone)]958struct BlockInfo {959 assumed_non_null_objects: BTreeSet<usize>, // indices into PropertyPathRegistry960}961962#[allow(dead_code)]963fn collect_hoistable_property_loads(964 func: &HirFunction,965 env: &Environment,966 temporaries: &FxHashMap<IdentifierId, ReactiveScopeDependency>,967 hoistable_from_optionals: &FxHashMap<BlockId, ReactiveScopeDependency>,968) -> FxHashMap<BlockId, BlockInfo> {969 let mut registry = PropertyPathRegistry::new();970 let known_immutable_identifiers: FxHashSet<IdentifierId> = if func.fn_type971 == ReactFunctionType::Component972 || func.fn_type == ReactFunctionType::Hook973 {974 func.params975 .iter()976 .filter_map(|p| match p {977 ParamPattern::Place(place) => Some(place.identifier),978 _ => None,979 })980 .collect()981 } else {982 FxHashSet::default()983 };984985 let assumed_invoked_fns = get_assumed_invoked_functions(func, env);986 let ctx = CollectHoistableContext {987 temporaries,988 known_immutable_identifiers: &known_immutable_identifiers,989 hoistable_from_optionals,990 nested_fn_immutable_context: None,991 assumed_invoked_fns: &assumed_invoked_fns,992 };993994 collect_hoistable_property_loads_impl(func, env, &ctx, &mut registry)995}996997struct CollectHoistableContext<'a> {998 temporaries: &'a FxHashMap<IdentifierId, ReactiveScopeDependency>,999 known_immutable_identifiers: &'a FxHashSet<IdentifierId>,1000 hoistable_from_optionals: &'a FxHashMap<BlockId, ReactiveScopeDependency>,1001 nested_fn_immutable_context: Option<&'a FxHashSet<IdentifierId>>,1002 assumed_invoked_fns: &'a FxHashSet<FunctionId>,1003}10041005fn is_immutable_at_instr(1006 identifier_id: IdentifierId,1007 instr_id: EvaluationOrder,1008 env: &Environment,1009 ctx: &CollectHoistableContext,1010) -> bool {1011 if let Some(nested_ctx) = ctx.nested_fn_immutable_context {1012 return nested_ctx.contains(&identifier_id);1013 }1014 let ident = &env.identifiers[identifier_id.0 as usize];1015 let mutable_at_instr = ident.mutable_range.end1016 > EvaluationOrder(ident.mutable_range.start.0 + 1)1017 && ident.scope.is_some()1018 && {1019 let scope = &env.scopes[ident.scope.unwrap().0 as usize];1020 in_range(instr_id, &scope.range)1021 };1022 !mutable_at_instr || ctx.known_immutable_identifiers.contains(&identifier_id)1023}10241025fn in_range(id: EvaluationOrder, range: &MutableRange) -> bool {1026 id >= range.start && id < range.end1027}10281029fn get_maybe_non_null_in_instruction(1030 value: &InstructionValue,1031 temporaries: &FxHashMap<IdentifierId, ReactiveScopeDependency>,1032) -> Option<ReactiveScopeDependency> {1033 match value {1034 InstructionValue::PropertyLoad { object, .. } => Some(1035 temporaries1036 .get(&object.identifier)1037 .cloned()1038 .unwrap_or_else(|| ReactiveScopeDependency {1039 identifier: object.identifier,1040 reactive: object.reactive,1041 path: vec![],1042 loc: object.loc,1043 }),1044 ),1045 InstructionValue::Destructure { value: val, .. } => {1046 temporaries.get(&val.identifier).cloned()1047 }1048 InstructionValue::ComputedLoad { object, .. } => {1049 temporaries.get(&object.identifier).cloned()1050 }1051 _ => None,1052 }1053}10541055#[allow(dead_code)]1056fn collect_hoistable_property_loads_impl(1057 func: &HirFunction,1058 env: &Environment,1059 ctx: &CollectHoistableContext,1060 registry: &mut PropertyPathRegistry,1061) -> FxHashMap<BlockId, BlockInfo> {1062 let nodes = collect_non_nulls_in_blocks(func, env, ctx, registry);1063 let working = propagate_non_null(func, &nodes, registry);1064 // Return the propagated results, converting FxHashSet<usize> back to BlockInfo1065 working1066 .into_iter()1067 .map(|(k, v)| {1068 (1069 k,1070 BlockInfo {1071 assumed_non_null_objects: v,1072 },1073 )1074 })1075 .collect()1076}10771078/// Corresponds to TS `getAssumedInvokedFunctions`.1079/// Returns the set of LoweredFunction FunctionIds that are assumed to be invoked.1080/// The `temporaries` map is shared across recursive calls (matching TS behavior where1081/// the same Map is passed to recursive invocations for inner functions).1082fn get_assumed_invoked_functions(func: &HirFunction, env: &Environment) -> FxHashSet<FunctionId> {1083 let mut temporaries: FxHashMap<IdentifierId, (FunctionId, FxHashSet<FunctionId>)> =1084 FxHashMap::default();1085 get_assumed_invoked_functions_impl(func, env, &mut temporaries)1086}10871088fn get_assumed_invoked_functions_impl(1089 func: &HirFunction,1090 env: &Environment,1091 temporaries: &mut FxHashMap<IdentifierId, (FunctionId, FxHashSet<FunctionId>)>,1092) -> FxHashSet<FunctionId> {1093 let mut hoistable: FxHashSet<FunctionId> = FxHashSet::default();10941095 // Step 1: Collect identifier to function expression mappings1096 for (_block_id, block) in &func.body.blocks {1097 for &instr_id in &block.instructions {1098 let instr = &func.instructions[instr_id.0 as usize];1099 match &instr.value {1100 InstructionValue::FunctionExpression { lowered_func, .. } => {1101 temporaries.insert(1102 instr.lvalue.identifier,1103 (lowered_func.func, FxHashSet::default()),1104 );1105 }1106 InstructionValue::StoreLocal {1107 value: val, lvalue, ..1108 } => {1109 if let Some(entry) = temporaries.get(&val.identifier).cloned() {1110 temporaries.insert(lvalue.place.identifier, entry);1111 }1112 }1113 InstructionValue::LoadLocal { place, .. } => {1114 if let Some(entry) = temporaries.get(&place.identifier).cloned() {1115 temporaries.insert(instr.lvalue.identifier, entry);1116 }1117 }1118 _ => {}1119 }1120 }1121 }11221123 // Step 2: Forward pass to analyze assumed function calls1124 for (_block_id, block) in &func.body.blocks {1125 for &instr_id in &block.instructions {1126 let instr = &func.instructions[instr_id.0 as usize];1127 match &instr.value {1128 InstructionValue::CallExpression { callee, args, .. } => {1129 let callee_ty =1130 &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize];1131 let maybe_hook = env.get_hook_kind_for_type(callee_ty).ok().flatten();1132 if let Some(entry) = temporaries.get(&callee.identifier) {1133 // Direct calls1134 hoistable.insert(entry.0);1135 } else if maybe_hook.is_some() {1136 // Assume arguments to all hooks are safe to invoke1137 for arg in args {1138 if let PlaceOrSpread::Place(p) = arg {1139 if let Some(entry) = temporaries.get(&p.identifier) {1140 hoistable.insert(entry.0);1141 }1142 }1143 }1144 }1145 }1146 InstructionValue::JsxExpression {1147 props, children, ..1148 } => {1149 // Assume JSX attributes and children are safe to invoke1150 for prop in props {1151 if let react_compiler_hir::JsxAttribute::Attribute { place, .. } = prop {1152 if let Some(entry) = temporaries.get(&place.identifier) {1153 hoistable.insert(entry.0);1154 }1155 }1156 }1157 if let Some(children) = children {1158 for child in children {1159 if let Some(entry) = temporaries.get(&child.identifier) {1160 hoistable.insert(entry.0);1161 }1162 }1163 }1164 }1165 InstructionValue::JsxFragment { children, .. } => {1166 for child in children {1167 if let Some(entry) = temporaries.get(&child.identifier) {1168 hoistable.insert(entry.0);1169 }1170 }1171 }1172 InstructionValue::FunctionExpression { lowered_func, .. } => {1173 // Recursively traverse into other function expressions1174 // TS passes the shared temporaries map to the recursive call1175 let inner_func = &env.functions[lowered_func.func.0 as usize];1176 let lambdas_called =1177 get_assumed_invoked_functions_impl(inner_func, env, temporaries);1178 if let Some(entry) = temporaries.get_mut(&instr.lvalue.identifier) {1179 for called in lambdas_called {1180 entry.1.insert(called);1181 }1182 }1183 }1184 _ => {}1185 }1186 }11871188 // Assume directly returned functions are safe to call1189 if let Terminal::Return { value, .. } = &block.terminal {1190 if let Some(entry) = temporaries.get(&value.identifier) {1191 hoistable.insert(entry.0);1192 }1193 }1194 }11951196 // Step 3: Propagate assumed-invoked status through mayInvoke chains1197 let mut changed = true;1198 while changed {1199 changed = false;1200 // Two-phase: collect then insert1201 let mut to_add = Vec::new();1202 for (_, (func_id, may_invoke)) in temporaries.iter() {1203 if hoistable.contains(func_id) {1204 for &called in may_invoke {1205 if !hoistable.contains(&called) {1206 to_add.push(called);1207 }1208 }1209 }1210 }1211 for id in to_add {1212 changed = true;1213 hoistable.insert(id);1214 }1215 if !changed {1216 break;1217 }1218 }12191220 hoistable1221}12221223fn collect_non_nulls_in_blocks(1224 func: &HirFunction,1225 env: &Environment,1226 ctx: &CollectHoistableContext,1227 registry: &mut PropertyPathRegistry,1228) -> FxHashMap<BlockId, BlockInfo> {1229 // Known non-null identifiers (e.g. component props)1230 let mut known_non_null: BTreeSet<usize> = BTreeSet::new();1231 if func.fn_type == ReactFunctionType::Component && !func.params.is_empty() {1232 if let ParamPattern::Place(place) = &func.params[0] {1233 let node_idx = registry.get_or_create_identifier(place.identifier, true, place.loc);1234 known_non_null.insert(node_idx);1235 }1236 }12371238 let mut nodes: FxHashMap<BlockId, BlockInfo> = FxHashMap::default();12391240 for (block_id, block) in &func.body.blocks {1241 let mut assumed = known_non_null.clone();12421243 // Check hoistable from optionals1244 if let Some(optional_chain) = ctx.hoistable_from_optionals.get(block_id) {1245 let node_idx = registry.get_or_create_property(optional_chain);1246 assumed.insert(node_idx);1247 }12481249 for &instr_id in &block.instructions {1250 let instr = &func.instructions[instr_id.0 as usize];1251 if let Some(path) = get_maybe_non_null_in_instruction(&instr.value, ctx.temporaries) {1252 let path_ident = path.identifier;1253 if is_immutable_at_instr(path_ident, instr.id, env, ctx) {1254 let node_idx = registry.get_or_create_property(&path);1255 assumed.insert(node_idx);1256 }1257 }12581259 // Handle StartMemoize deps for enablePreserveExistingMemoizationGuarantees1260 if env.enable_preserve_existing_memoization_guarantees {1261 if let InstructionValue::StartMemoize {1262 deps: Some(deps), ..1263 } = &instr.value1264 {1265 for dep in deps {1266 if let react_compiler_hir::ManualMemoDependencyRoot::NamedLocal {1267 value: val,1268 ..1269 } = &dep.root1270 {1271 if !is_immutable_at_instr(val.identifier, instr.id, env, ctx) {1272 continue;1273 }1274 for i in 0..dep.path.len() {1275 if dep.path[i].optional {1276 break;1277 }1278 let sub_dep = ReactiveScopeDependency {1279 identifier: val.identifier,1280 reactive: val.reactive,1281 path: dep.path[..i].to_vec(),1282 loc: dep.loc,1283 };1284 let node_idx = registry.get_or_create_property(&sub_dep);1285 assumed.insert(node_idx);1286 }1287 }1288 }1289 }1290 }12911292 // Handle assumed-invoked inner functions1293 if let InstructionValue::FunctionExpression { lowered_func, .. } = &instr.value {1294 if ctx.assumed_invoked_fns.contains(&lowered_func.func) {1295 let inner_func = &env.functions[lowered_func.func.0 as usize];1296 // Build nested fn immutable context1297 let nested_fn_immutable_context: FxHashSet<IdentifierId> =1298 if ctx.nested_fn_immutable_context.is_some() {1299 // Already in a nested fn context, use existing1300 ctx.nested_fn_immutable_context.unwrap().clone()1301 } else {1302 inner_func1303 .context1304 .iter()1305 .filter(|place| {1306 is_immutable_at_instr(place.identifier, instr.id, env, ctx)1307 })1308 .map(|place| place.identifier)1309 .collect()1310 };1311 let inner_assumed = get_assumed_invoked_functions(inner_func, env);1312 let inner_ctx = CollectHoistableContext {1313 temporaries: ctx.temporaries,1314 known_immutable_identifiers: &FxHashSet::default(),1315 hoistable_from_optionals: ctx.hoistable_from_optionals,1316 nested_fn_immutable_context: Some(&nested_fn_immutable_context),1317 assumed_invoked_fns: &inner_assumed,1318 };1319 let inner_nodes =1320 collect_non_nulls_in_blocks(inner_func, env, &inner_ctx, registry);1321 // Propagate non-null from inner function1322 let inner_working = propagate_non_null(inner_func, &inner_nodes, registry);1323 // Get hoistables from inner function's entry block (after propagation)1324 let inner_entry = inner_func.body.entry;1325 if let Some(inner_set) = inner_working.get(&inner_entry) {1326 for &node_idx in inner_set {1327 assumed.insert(node_idx);1328 }1329 }1330 }1331 }1332 }13331334 nodes.insert(1335 *block_id,1336 BlockInfo {1337 assumed_non_null_objects: assumed,1338 },1339 );1340 }13411342 nodes1343}13441345/// Recursive DFS propagation of non-null information through the CFG.1346/// Uses 'active'/'done' state tracking to correctly handle cycles (backedges in loops).1347///1348/// Port of TS `propagateNonNull` which uses `recursivelyPropagateNonNull`.1349/// Key insight: when computing the intersection of neighbor sets, only include1350/// neighbors that are 'done' (not 'active'). Active neighbors are part of a cycle1351/// and should be filtered out, allowing non-null info to propagate through non-cyclic paths.1352fn propagate_non_null(1353 func: &HirFunction,1354 nodes: &FxHashMap<BlockId, BlockInfo>,1355 registry: &mut PropertyPathRegistry,1356) -> FxHashMap<BlockId, BTreeSet<usize>> {1357 // Build successor map. Use BTreeSet to iterate successors in sorted BlockId1358 // order, matching the TS Set<BlockId> insertion order (blocks are created in1359 // ascending BlockId order).1360 let mut block_successors: FxHashMap<BlockId, BTreeSet<BlockId>> = FxHashMap::default();1361 for (block_id, block) in &func.body.blocks {1362 for pred in &block.preds {1363 block_successors.entry(*pred).or_default().insert(*block_id);1364 }1365 }13661367 // Clone nodes into mutable working set1368 let mut working: FxHashMap<BlockId, BTreeSet<usize>> = nodes1369 .iter()1370 .map(|(k, v)| (*k, v.assumed_non_null_objects.clone()))1371 .collect();13721373 let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect();1374 let mut reversed_block_ids = block_ids.clone();1375 reversed_block_ids.reverse();13761377 for _ in 0..100 {1378 let mut changed = false;13791380 // Forward pass (using predecessors)1381 let mut traversal_state: FxHashMap<BlockId, TraversalState> = FxHashMap::default();1382 for &block_id in &block_ids {1383 let block_changed = recursively_propagate_non_null(1384 block_id,1385 PropagationDirection::Forward,1386 &mut traversal_state,1387 &mut working,1388 func,1389 &block_successors,1390 registry,1391 );1392 changed |= block_changed;1393 }13941395 // Backward pass (using successors)1396 traversal_state.clear();1397 for &block_id in &reversed_block_ids {1398 let block_changed = recursively_propagate_non_null(1399 block_id,1400 PropagationDirection::Backward,1401 &mut traversal_state,1402 &mut working,1403 func,1404 &block_successors,1405 registry,1406 );1407 changed |= block_changed;1408 }14091410 if !changed {1411 break;1412 }1413 }14141415 working1416}14171418#[derive(Debug, Clone, Copy, PartialEq, Eq)]1419enum TraversalState {1420 Active,1421 Done,1422}14231424#[derive(Debug, Clone, Copy, PartialEq, Eq)]1425enum PropagationDirection {1426 Forward,1427 Backward,1428}14291430fn recursively_propagate_non_null(1431 node_id: BlockId,1432 direction: PropagationDirection,1433 traversal_state: &mut FxHashMap<BlockId, TraversalState>,1434 working: &mut FxHashMap<BlockId, BTreeSet<usize>>,1435 func: &HirFunction,1436 block_successors: &FxHashMap<BlockId, BTreeSet<BlockId>>,1437 registry: &mut PropertyPathRegistry,1438) -> bool {1439 // Avoid re-visiting computed or currently active nodes1440 if traversal_state.contains_key(&node_id) {1441 return false;1442 }1443 traversal_state.insert(node_id, TraversalState::Active);14441445 let neighbors: Vec<BlockId> = match direction {1446 PropagationDirection::Backward => block_successors1447 .get(&node_id)1448 .map(|s| s.iter().copied().collect())1449 .unwrap_or_default(),1450 PropagationDirection::Forward => func1451 .body1452 .blocks1453 .get(&node_id)1454 .map(|b| b.preds.iter().copied().collect())1455 .unwrap_or_default(),1456 };14571458 let mut changed = false;1459 for &neighbor in &neighbors {1460 if !traversal_state.contains_key(&neighbor) {1461 let neighbor_changed = recursively_propagate_non_null(1462 neighbor,1463 direction,1464 traversal_state,1465 working,1466 func,1467 block_successors,1468 registry,1469 );1470 changed |= neighbor_changed;1471 }1472 }14731474 // Compute intersection of 'done' neighbors only (filter out 'active' = cycle nodes)1475 let neighbor_intersection = {1476 let done_neighbor_sets: Vec<&BTreeSet<usize>> = neighbors1477 .iter()1478 .filter(|n| traversal_state.get(n) == Some(&TraversalState::Done))1479 .filter_map(|n| working.get(n))1480 .collect();14811482 match done_neighbor_sets.split_first() {1483 None => BTreeSet::new(),1484 Some((first, rest)) => rest.iter().fold((*first).clone(), |acc, s| {1485 acc.intersection(s).copied().collect()1486 }),1487 }1488 };14891490 // Temporarily remove the previous set out of the map so it can be safely1491 // borrowed and compared without a heavy deep clone.1492 let prev_objects = working.remove(&node_id).unwrap_or_default();1493 let mut merged: BTreeSet<usize> = prev_objects1494 .union(&neighbor_intersection)1495 .copied()1496 .collect();1497 reduce_maybe_optional_chains(&mut merged, registry);14981499 // Compare with previous value — can't just check size due to reduce_maybe_optional_chains1500 changed |= prev_objects != merged;15011502 working.insert(node_id, merged);1503 traversal_state.insert(node_id, TraversalState::Done);15041505 changed1506}15071508fn collect_hoistable_and_propagate(1509 func: &HirFunction,1510 env: &Environment,1511 temporaries: &FxHashMap<IdentifierId, ReactiveScopeDependency>,1512 hoistable_from_optionals: &FxHashMap<BlockId, ReactiveScopeDependency>,1513) -> (FxHashMap<BlockId, BTreeSet<usize>>, PropertyPathRegistry) {1514 let mut registry = PropertyPathRegistry::new();1515 let assumed_invoked_fns = get_assumed_invoked_functions(func, env);1516 let known_immutable_identifiers: FxHashSet<IdentifierId> = if func.fn_type1517 == ReactFunctionType::Component1518 || func.fn_type == ReactFunctionType::Hook1519 {1520 func.params1521 .iter()1522 .filter_map(|p| match p {1523 ParamPattern::Place(place) => Some(place.identifier),1524 _ => None,1525 })1526 .collect()1527 } else {1528 FxHashSet::default()1529 };15301531 let ctx = CollectHoistableContext {1532 temporaries,1533 known_immutable_identifiers: &known_immutable_identifiers,1534 hoistable_from_optionals,1535 nested_fn_immutable_context: None,1536 assumed_invoked_fns: &assumed_invoked_fns,1537 };15381539 let nodes = collect_non_nulls_in_blocks(func, env, &ctx, &mut registry);1540 let working = propagate_non_null(func, &nodes, &mut registry);15411542 (working, registry)1543}15441545// Restructured version used by the main entry point1546#[allow(dead_code)]1547fn key_by_scope_id(1548 func: &HirFunction,1549 block_keyed: &FxHashMap<BlockId, BlockInfo>,1550) -> FxHashMap<ScopeId, BlockInfo> {1551 let mut keyed: FxHashMap<ScopeId, BlockInfo> = FxHashMap::default();1552 for (_block_id, block) in &func.body.blocks {1553 if let Terminal::Scope {1554 scope,1555 block: inner_block,1556 ..1557 } = &block.terminal1558 {1559 if let Some(info) = block_keyed.get(inner_block) {1560 keyed.insert(*scope, info.clone());1561 }1562 }1563 }1564 keyed1565}15661567// =============================================================================1568// DeriveMinimalDependenciesHIR1569// =============================================================================15701571#[derive(Debug, Clone, Copy, PartialEq, Eq)]1572enum PropertyAccessType {1573 OptionalAccess,1574 UnconditionalAccess,1575 OptionalDependency,1576 UnconditionalDependency,1577}15781579fn is_optional_access(access: PropertyAccessType) -> bool {1580 matches!(1581 access,1582 PropertyAccessType::OptionalAccess | PropertyAccessType::OptionalDependency1583 )1584}15851586fn is_dependency_access(access: PropertyAccessType) -> bool {1587 matches!(1588 access,1589 PropertyAccessType::OptionalDependency | PropertyAccessType::UnconditionalDependency1590 )1591}15921593fn merge_access(a: PropertyAccessType, b: PropertyAccessType) -> PropertyAccessType {1594 let is_unconditional = !(is_optional_access(a) && is_optional_access(b));1595 let is_dep = is_dependency_access(a) || is_dependency_access(b);1596 match (is_unconditional, is_dep) {1597 (true, true) => PropertyAccessType::UnconditionalDependency,1598 (true, false) => PropertyAccessType::UnconditionalAccess,1599 (false, true) => PropertyAccessType::OptionalDependency,1600 (false, false) => PropertyAccessType::OptionalAccess,1601 }1602}16031604#[derive(Debug, Clone, Copy, PartialEq, Eq)]1605enum HoistableAccessType {1606 Optional,1607 NonNull,1608}16091610struct HoistableNode {1611 properties: FxHashMap<PropertyLiteral, Box<HoistableNodeEntry>>,1612 access_type: HoistableAccessType,1613}16141615struct HoistableNodeEntry {1616 node: HoistableNode,1617}16181619struct DependencyNode {1620 properties: IndexMap<PropertyLiteral, Box<DependencyNodeEntry>, FxBuildHasher>,1621 access_type: PropertyAccessType,1622 loc: Option<react_compiler_hir::SourceLocation>,1623}16241625struct DependencyNodeEntry {1626 node: DependencyNode,1627}16281629struct ReactiveScopeDependencyTreeHIR {1630 hoistable_roots: FxHashMap<IdentifierId, (HoistableNode, bool)>, // node + reactive1631 dep_roots: IndexMap<IdentifierId, (DependencyNode, bool), FxBuildHasher>, // node + reactive (preserves insertion order like JS Map)1632}16331634impl ReactiveScopeDependencyTreeHIR {1635 fn new<'a>(1636 hoistable_objects: impl Iterator<Item = &'a ReactiveScopeDependency>,1637 _env: &Environment,1638 ) -> Self {1639 let mut hoistable_roots: FxHashMap<IdentifierId, (HoistableNode, bool)> =1640 FxHashMap::default();16411642 // Sort hoistable objects so that entries with optional first path come1643 // before non-optional ones. This matches the TS behavior where1644 // hoistableFromOptionals entries are inserted into the JS Set before1645 // instruction-based entries, and the first insertion determines the1646 // root access type.1647 let mut sorted_deps: Vec<&ReactiveScopeDependency> = hoistable_objects.collect();1648 sorted_deps.sort_by(|a, b| {1649 let a_optional = !a.path.is_empty() && a.path[0].optional;1650 let b_optional = !b.path.is_empty() && b.path[0].optional;1651 b_optional.cmp(&a_optional)1652 });16531654 for dep in sorted_deps {1655 let root = hoistable_roots.entry(dep.identifier).or_insert_with(|| {1656 let access_type = if !dep.path.is_empty() && dep.path[0].optional {1657 HoistableAccessType::Optional1658 } else {1659 HoistableAccessType::NonNull1660 };1661 (1662 HoistableNode {1663 properties: FxHashMap::default(),1664 access_type,1665 },1666 dep.reactive,1667 )1668 });16691670 let mut curr = &mut root.0;1671 for i in 0..dep.path.len() {1672 let access_type = if i + 1 < dep.path.len() && dep.path[i + 1].optional {1673 HoistableAccessType::Optional1674 } else {1675 HoistableAccessType::NonNull1676 };1677 let entry = curr1678 .properties1679 .entry(dep.path[i].property.clone())1680 .or_insert_with(|| {1681 Box::new(HoistableNodeEntry {1682 node: HoistableNode {1683 properties: FxHashMap::default(),1684 access_type,1685 },1686 })1687 });1688 curr = &mut entry.node;1689 }1690 }16911692 Self {1693 hoistable_roots,1694 dep_roots: IndexMap::default(),1695 }1696 }16971698 fn add_dependency(&mut self, dep: ReactiveScopeDependency, _env: &Environment) {1699 let root = self.dep_roots.entry(dep.identifier).or_insert_with(|| {1700 (1701 DependencyNode {1702 properties: IndexMap::default(),1703 access_type: PropertyAccessType::UnconditionalAccess,1704 loc: dep.loc,1705 },1706 dep.reactive,1707 )1708 });17091710 let mut dep_cursor = &mut root.0;1711 let hoistable_cursor_root = self.hoistable_roots.get(&dep.identifier);1712 let mut hoistable_ptr: Option<&HoistableNode> = hoistable_cursor_root.map(|(n, _)| n);17131714 for entry in &dep.path {1715 let next_hoistable: Option<&HoistableNode>;1716 let access_type: PropertyAccessType;17171718 if entry.optional {1719 next_hoistable =1720 hoistable_ptr.and_then(|h| h.properties.get(&entry.property).map(|e| &e.node));17211722 if hoistable_ptr.is_some()1723 && hoistable_ptr.unwrap().access_type == HoistableAccessType::NonNull1724 {1725 access_type = PropertyAccessType::UnconditionalAccess;1726 } else {1727 access_type = PropertyAccessType::OptionalAccess;1728 }1729 } else if hoistable_ptr.is_some()1730 && hoistable_ptr.unwrap().access_type == HoistableAccessType::NonNull1731 {1732 next_hoistable =1733 hoistable_ptr.and_then(|h| h.properties.get(&entry.property).map(|e| &e.node));1734 access_type = PropertyAccessType::UnconditionalAccess;1735 } else {1736 // Break: truncate dependency1737 break;1738 }17391740 // make_or_merge_property1741 let child = dep_cursor1742 .properties1743 .entry(entry.property.clone())1744 .or_insert_with(|| {1745 Box::new(DependencyNodeEntry {1746 node: DependencyNode {1747 properties: IndexMap::default(),1748 access_type,1749 loc: entry.loc,1750 },1751 })1752 });1753 child.node.access_type = merge_access(child.node.access_type, access_type);17541755 dep_cursor = &mut child.node;1756 hoistable_ptr = next_hoistable;1757 }17581759 // Mark final node as dependency1760 dep_cursor.access_type = merge_access(1761 dep_cursor.access_type,1762 PropertyAccessType::OptionalDependency,1763 );1764 }17651766 fn derive_minimal_dependencies(&self, _env: &Environment) -> Vec<ReactiveScopeDependency> {1767 let mut results = Vec::new();1768 for (&root_id, (root_node, reactive)) in &self.dep_roots {1769 collect_minimal_deps_in_subtree(root_node, *reactive, root_id, &[], &mut results);1770 }1771 results1772 }1773}17741775fn collect_minimal_deps_in_subtree(1776 node: &DependencyNode,1777 reactive: bool,1778 root_id: IdentifierId,1779 path: &[DependencyPathEntry],1780 results: &mut Vec<ReactiveScopeDependency>,1781) {1782 if is_dependency_access(node.access_type) {1783 results.push(ReactiveScopeDependency {1784 identifier: root_id,1785 reactive,1786 path: path.to_vec(),1787 loc: node.loc,1788 });1789 } else {1790 for (child_name, child_entry) in &node.properties {1791 let mut new_path = path.to_vec();1792 new_path.push(DependencyPathEntry {1793 property: child_name.clone(),1794 optional: is_optional_access(child_entry.node.access_type),1795 loc: child_entry.node.loc,1796 });1797 collect_minimal_deps_in_subtree(1798 &child_entry.node,1799 reactive,1800 root_id,1801 &new_path,1802 results,1803 );1804 }1805 }1806}18071808// =============================================================================1809// collectDependencies1810// =============================================================================18111812/// A declaration record: instruction id + scope stack at declaration time.1813#[derive(Clone)]1814struct Decl {1815 id: EvaluationOrder,1816 scope_stack: Vec<ScopeId>, // copy of the scope stack at time of declaration1817}18181819/// Context for dependency collection.1820struct DependencyCollectionContext<'a> {1821 declarations: FxHashMap<DeclarationId, Decl>,1822 reassignments: FxHashMap<IdentifierId, Decl>,1823 scope_stack: Vec<ScopeId>,1824 dep_stack: Vec<Vec<ReactiveScopeDependency>>,1825 deps: IndexMap<ScopeId, Vec<ReactiveScopeDependency>, FxBuildHasher>,1826 temporaries: &'a FxHashMap<IdentifierId, ReactiveScopeDependency>,1827 #[allow(dead_code)]1828 temporaries_used_outside_scope: &'a FxHashSet<DeclarationId>,1829 processed_instrs_in_optional: &'a FxHashSet<ProcessedInstr>,1830 inner_fn_context: Option<EvaluationOrder>,1831}18321833impl<'a> DependencyCollectionContext<'a> {1834 fn new(1835 temporaries_used_outside_scope: &'a FxHashSet<DeclarationId>,1836 temporaries: &'a FxHashMap<IdentifierId, ReactiveScopeDependency>,1837 processed_instrs_in_optional: &'a FxHashSet<ProcessedInstr>,1838 ) -> Self {1839 Self {1840 declarations: FxHashMap::default(),1841 reassignments: FxHashMap::default(),1842 scope_stack: Vec::new(),1843 dep_stack: Vec::new(),1844 deps: IndexMap::default(),1845 temporaries,1846 temporaries_used_outside_scope,1847 processed_instrs_in_optional,1848 inner_fn_context: None,1849 }1850 }18511852 fn enter_scope(&mut self, scope_id: ScopeId) {1853 self.dep_stack.push(Vec::new());1854 self.scope_stack.push(scope_id);1855 }18561857 fn exit_scope(&mut self, scope_id: ScopeId, pruned: bool, env: &mut Environment) {1858 let scoped_deps = self1859 .dep_stack1860 .pop()1861 .expect("[PropagateScopeDeps]: Unexpected scope mismatch");1862 self.scope_stack.pop();18631864 // Propagate dependencies upward1865 for dep in &scoped_deps {1866 if self.check_valid_dependency(dep, env) {1867 if let Some(top) = self.dep_stack.last_mut() {1868 top.push(dep.clone());1869 }1870 }1871 }18721873 if !pruned {1874 self.deps.insert(scope_id, scoped_deps);1875 }1876 }18771878 fn current_scope(&self) -> Option<ScopeId> {1879 self.scope_stack.last().copied()1880 }18811882 fn declare(&mut self, identifier_id: IdentifierId, decl: Decl, env: &Environment) {1883 if self.inner_fn_context.is_some() {1884 return;1885 }1886 let decl_id = env.identifiers[identifier_id.0 as usize].declaration_id;1887 if !self.declarations.contains_key(&decl_id) {1888 self.declarations.insert(decl_id, decl.clone());1889 }1890 self.reassignments.insert(identifier_id, decl);1891 }18921893 fn has_declared(&self, identifier_id: IdentifierId, env: &Environment) -> bool {1894 let decl_id = env.identifiers[identifier_id.0 as usize].declaration_id;1895 self.declarations.contains_key(&decl_id)1896 }18971898 fn check_valid_dependency(&self, dep: &ReactiveScopeDependency, env: &Environment) -> bool {1899 // Ref value is not a valid dep1900 let ty = &env.types[env.identifiers[dep.identifier.0 as usize].type_.0 as usize];1901 if react_compiler_hir::is_ref_value_type(ty) {1902 return false;1903 }1904 // Object methods are not deps1905 if matches!(ty, Type::ObjectMethod) {1906 return false;1907 }19081909 let ident = &env.identifiers[dep.identifier.0 as usize];1910 let current_declaration = self1911 .reassignments1912 .get(&dep.identifier)1913 .or_else(|| self.declarations.get(&ident.declaration_id));19141915 if let Some(current_scope) = self.current_scope() {1916 if let Some(decl) = current_declaration {1917 let scope_range_start = env.scopes[current_scope.0 as usize].range.start;1918 return decl.id < scope_range_start;1919 }1920 }1921 false1922 }19231924 fn visit_operand(&mut self, place: &Place, env: &mut Environment) {1925 let dep = self1926 .temporaries1927 .get(&place.identifier)1928 .cloned()1929 .unwrap_or_else(|| ReactiveScopeDependency {1930 identifier: place.identifier,1931 reactive: place.reactive,1932 path: vec![],1933 loc: place.loc,1934 });1935 self.visit_dependency(dep, env);1936 }19371938 fn visit_property(1939 &mut self,1940 object: &Place,1941 property: &PropertyLiteral,1942 optional: bool,1943 loc: Option<react_compiler_hir::SourceLocation>,1944 env: &mut Environment,1945 ) {1946 let dep = get_property(object, property, optional, loc, self.temporaries, env);1947 self.visit_dependency(dep, env);1948 }19491950 fn visit_dependency(&mut self, dep: ReactiveScopeDependency, env: &mut Environment) {1951 let ident = &env.identifiers[dep.identifier.0 as usize];1952 let decl_id = ident.declaration_id;19531954 // Record scope declarations for values used outside their declaring scope1955 if let Some(original_decl) = self.declarations.get(&decl_id) {1956 if !original_decl.scope_stack.is_empty() {1957 let orig_scope_stack = original_decl.scope_stack.clone();1958 for &scope_id in &orig_scope_stack {1959 if !self.scope_stack.contains(&scope_id) {1960 // Check if already declared in this scope1961 let scope = &env.scopes[scope_id.0 as usize];1962 let already_declared = scope.declarations.iter().any(|(_, d)| {1963 env.identifiers[d.identifier.0 as usize].declaration_id == decl_id1964 });1965 if !already_declared {1966 let orig_scope_id = *orig_scope_stack.last().unwrap();1967 let new_decl = react_compiler_hir::ReactiveScopeDeclaration {1968 identifier: dep.identifier,1969 scope: orig_scope_id,1970 };1971 env.scopes[scope_id.0 as usize]1972 .declarations1973 .push((dep.identifier, new_decl));1974 }1975 }1976 }1977 }1978 }19791980 // Handle ref.current access1981 let dep = if react_compiler_hir::is_use_ref_type(1982 &env.types[env.identifiers[dep.identifier.0 as usize].type_.0 as usize],1983 ) && dep1984 .path1985 .first()1986 .map(|p| p.property == PropertyLiteral::String("current".to_string()))1987 .unwrap_or(false)1988 {1989 ReactiveScopeDependency {1990 identifier: dep.identifier,1991 reactive: dep.reactive,1992 path: vec![],1993 loc: dep.loc,1994 }1995 } else {1996 dep1997 };19981999 if self.check_valid_dependency(&dep, env) {2000 if let Some(top) = self.dep_stack.last_mut() {
Code quality findings 100
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
.map(|&idx| registry.nodes[idx].full_path.clone())
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning
correctness
expect-usage
hoistables.expect("[PropagateScopeDependencies] Scope not found in tracked blocks");
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 = &mut env.scopes[scope_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
env.identifiers[existing_dep.identifier.0 as usize].declaration_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
env.identifiers[candidate_dep.identifier.0 as usize].declaration_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 decl_id = env.identifiers[place_id.0 as usize].declaration_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 = &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
env.identifiers[instr.lvalue.identifier.0 as usize].declaration_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
if let Some(scope_id) = env.identifiers[place.identifier.0 as usize].scope {
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_range = &env.scopes[scope_id.0 as usize].range;
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 lvalue_decl_id = env.identifiers[instr.lvalue.identifier.0 as usize].declaration_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
if env.identifiers[instr.lvalue.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
&& env.identifiers[place.identifier.0 as usize].name.is_some()
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[instr.lvalue.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
&& env.identifiers[place.identifier.0 as usize].name.is_some()
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[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 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 inner_func = &env.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 instr0 = &func.instructions[consequent_block.instructions[0].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 instr1 = &func.instructions[consequent_block.instructions[1].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 alt_instr0 = &func.instructions[alternate_block.instructions[0].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 alt_instr1 = &func.instructions[alternate_block.instructions[1].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 first_instr = &func.instructions[maybe_test_block.instructions[0].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 curr_instr = &func.instructions[maybe_test_block.instructions[i].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
&func.instructions[maybe_test_block.instructions[i - 1].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
let last_instr_id = *maybe_test_block.instructions.last().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 last_instr = &func.instructions[last_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
self.nodes[parent_idx]
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
self.nodes[parent_idx].properties.get(&map_key).copied()
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 parent_full_path = self.nodes[parent_idx].full_path.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 parent_has_optional = self.nodes[parent_idx].has_optional;
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
self.nodes[parent_idx]
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
self.nodes[parent_idx].properties.insert(map_key, idx);
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
.filter(|&idx| registry.nodes[idx].has_optional)
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 full_path = registry.nodes[original_idx].full_path.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 ident = &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
let scope = &env.scopes[ident.scope.unwrap().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 scope = &env.scopes[ident.scope.unwrap().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 = &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 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
&env.types[env.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
let inner_func = &env.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
if let ParamPattern::Place(place) = &func.params[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[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
if dep.path[i].optional {
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
path: dep.path[..i].to_vec(),
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[lowered_func.func.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
ctx.nested_fn_immutable_context.unwrap().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 a_optional = !a.path.is_empty() && a.path[0].optional;
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 b_optional = !b.path.is_empty() && b.path[0].optional;
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 access_type = if !dep.path.is_empty() && dep.path[0].optional {
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
&& hoistable_ptr.unwrap().access_type == HoistableAccessType::NonNull
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
&& hoistable_ptr.unwrap().access_type == HoistableAccessType::NonNull
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning
correctness
expect-usage
.expect("[PropagateScopeDeps]: Unexpected scope mismatch");
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 decl_id = env.identifiers[identifier_id.0 as usize].declaration_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 decl_id = env.identifiers[identifier_id.0 as usize].declaration_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 ty = &env.types[env.identifiers[dep.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 ident = &env.identifiers[dep.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 scope_range_start = env.scopes[current_scope.0 as usize].range.start;
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[dep.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 scope = &env.scopes[scope_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
env.identifiers[d.identifier.0 as usize].declaration_id == decl_id
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
let orig_scope_id = *orig_scope_stack.last().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
env.scopes[scope_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
&env.types[env.identifiers[dep.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 scope = &env.scopes[current_scope.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[id.0 as usize].declaration_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
== env.identifiers[place.identifier.0 as usize].declaration_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
env.scopes[current_scope.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_instrs: Vec<Instruction> = env.functions[func_id.0 as usize].instructions.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
)> = 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_instr = &inner_instrs[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 instr = &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
.map(|&idx| registry.nodes[idx].full_path.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
tree.add_dependency(dep.clone(), env);
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
scope.dependencies.push(candidate_dep);
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
match kind {
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
let (test_place, consequent_block_id, alternate_block_id) = match test {
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 (property_load_object, property, property_load_loc) = match &instr0.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
let store_local_value = match &instr1.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
match (&alt_instr0.value, &alt_instr1.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
let (test_block_id, is_optional, fallthrough_block_id) = match &optional_block.terminal {
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 test_ident = match &maybe_test_block.terminal {
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 first_place = match &first_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
let inner_alternate = match &test_block.terminal {
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 test_ident = match &test_block.terminal {
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 test_alternate = match test_terminal {
Info: Usage of `#[allow(...)]` suppresses compiler lints. Ensure the allowance is justified, well-scoped, and ideally temporary. Overuse can hide potential issues.
info
maintainability
allow-lint
#[allow(dead_code)]
Info: Usage of `#[allow(...)]` suppresses compiler lints. Ensure the allowance is justified, well-scoped, and ideally temporary. Overuse can hide potential issues.
info
maintainability
allow-lint
#[allow(dead_code)]
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 full_path = registry.nodes[original_idx].full_path.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
property: entry.property.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
entry.clone()
Info: Usage of `#[allow(...)]` suppresses compiler lints. Ensure the allowance is justified, well-scoped, and ideally temporary. Overuse can hide potential issues.
info
maintainability
allow-lint
#[allow(dead_code)]
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(|p| match p {
Info: Usage of `#[allow(...)]` suppresses compiler lints. Ensure the allowance is justified, well-scoped, and ideally temporary. Overuse can hide potential issues.
info
maintainability
allow-lint
#[allow(dead_code)]
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
to_add.push(called);
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 assumed = known_non_null.clone();