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//! Port of ValidatePreservedManualMemoization.ts7//!8//! Validates that all explicit manual memoization (useMemo/useCallback) was9//! accurately preserved, and that no originally memoized values became10//! unmemoized in the output.1112use rustc_hash::{FxHashMap, FxHashSet};1314use react_compiler_diagnostics::{15 CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, SourceLocation,16};17use react_compiler_hir::environment::Environment;18use react_compiler_hir::{19 DeclarationId, DependencyPathEntry, Identifier, IdentifierId, IdentifierName, InstructionKind,20 InstructionValue, ManualMemoDependency, ManualMemoDependencyRoot, Place, ReactiveBlock,21 ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock, ReactiveStatement, ReactiveValue,22 ScopeId,23};2425/// State tracked during manual memo validation within a StartMemoize..FinishMemoize range.26struct ManualMemoBlockState {27 /// Reassigned temporaries (declaration_id -> set of identifier ids that were reassigned to it).28 reassignments: FxHashMap<DeclarationId, FxHashSet<IdentifierId>>,29 /// Source location of the StartMemoize instruction.30 loc: Option<SourceLocation>,31 /// Declarations produced within this manual memo block.32 decls: FxHashSet<DeclarationId>,33 /// Normalized deps from source (useMemo/useCallback dep array).34 deps_from_source: Option<Vec<ManualMemoDependency>>,35 /// Manual memo id from StartMemoize.36 manual_memo_id: u32,37}3839/// Top-level visitor state.40struct VisitorState<'a> {41 env: &'a mut Environment,42 manual_memo_state: Option<ManualMemoBlockState>,43 /// Completed (non-pruned) scope IDs.44 scopes: FxHashSet<ScopeId>,45 /// Completed pruned scope IDs.46 pruned_scopes: FxHashSet<ScopeId>,47 /// Map from identifier ID to its normalized manual memo dependency.48 temporaries: FxHashMap<IdentifierId, ManualMemoDependency>,49}5051/// Validate that manual memoization (useMemo/useCallback) is preserved.52///53/// Walks the reactive function looking for StartMemoize/FinishMemoize instructions54/// and checks that:55/// 1. Dependencies' scopes have completed before the memo block starts56/// 2. Memoized values are actually within scopes (not unmemoized)57/// 3. Inferred scope dependencies match the source dependencies58pub fn validate_preserved_manual_memoization(func: &ReactiveFunction, env: &mut Environment) {59 let mut state = VisitorState {60 env,61 manual_memo_state: None,62 scopes: FxHashSet::default(),63 pruned_scopes: FxHashSet::default(),64 temporaries: FxHashMap::default(),65 };66 visit_block(&func.body, &mut state);67}6869fn is_named(ident: &Identifier) -> bool {70 matches!(ident.name, Some(IdentifierName::Named(_)))71}7273fn visit_block(block: &ReactiveBlock, state: &mut VisitorState) {74 for stmt in block {75 visit_statement(stmt, state);76 }77}7879fn visit_statement(stmt: &ReactiveStatement, state: &mut VisitorState) {80 match stmt {81 ReactiveStatement::Instruction(instr) => {82 visit_instruction(instr, state);83 }84 ReactiveStatement::Terminal(terminal) => {85 visit_terminal(terminal, state);86 }87 ReactiveStatement::Scope(scope_block) => {88 visit_scope(scope_block, state);89 }90 ReactiveStatement::PrunedScope(pruned) => {91 visit_pruned_scope(pruned, state);92 }93 }94}9596fn visit_terminal(97 terminal: &react_compiler_hir::ReactiveTerminalStatement,98 state: &mut VisitorState,99) {100 use react_compiler_hir::ReactiveTerminal;101 match &terminal.terminal {102 ReactiveTerminal::If {103 consequent,104 alternate,105 ..106 } => {107 visit_block(consequent, state);108 if let Some(alt) = alternate {109 visit_block(alt, state);110 }111 }112 ReactiveTerminal::Switch { cases, .. } => {113 for case in cases {114 if let Some(ref block) = case.block {115 visit_block(block, state);116 }117 }118 }119 ReactiveTerminal::For { loop_block, .. }120 | ReactiveTerminal::ForOf { loop_block, .. }121 | ReactiveTerminal::ForIn { loop_block, .. }122 | ReactiveTerminal::While { loop_block, .. }123 | ReactiveTerminal::DoWhile { loop_block, .. } => {124 visit_block(loop_block, state);125 }126 ReactiveTerminal::Label { block, .. } => {127 visit_block(block, state);128 }129 ReactiveTerminal::Try { block, handler, .. } => {130 visit_block(block, state);131 visit_block(handler, state);132 }133 _ => {}134 }135}136137fn visit_scope(scope_block: &ReactiveScopeBlock, state: &mut VisitorState) {138 // Traverse the scope's instructions first139 visit_block(&scope_block.instructions, state);140141 // After traversing, validate scope dependencies against manual memo deps142 if let Some(ref memo_state) = state.manual_memo_state {143 if let Some(ref deps_from_source) = memo_state.deps_from_source {144 let scope = &state.env.scopes[scope_block.scope.0 as usize];145 // `dependencies` still has to be cloned because `env` is passed146 // mutably below. `temporaries`, `decls` and `deps_from_source` do147 // not: they live in fields disjoint from `env`, so they can simply148 // be borrowed.149 let deps = scope.dependencies.clone();150 let memo_loc = memo_state.loc;151 for dep in &deps {152 validate_inferred_dep(153 dep.identifier,154 &dep.path,155 &state.temporaries,156 &memo_state.decls,157 deps_from_source,158 state.env,159 memo_loc,160 );161 }162 }163 }164165 // Mark scope and merged scopes as completed166 let scope = &state.env.scopes[scope_block.scope.0 as usize];167 let merged = scope.merged.clone();168 state.scopes.insert(scope_block.scope);169 for merged_id in merged {170 state.scopes.insert(merged_id);171 }172}173174fn visit_pruned_scope(175 pruned: &react_compiler_hir::PrunedReactiveScopeBlock,176 state: &mut VisitorState,177) {178 visit_block(&pruned.instructions, state);179 state.pruned_scopes.insert(pruned.scope);180}181182fn visit_instruction(instr: &ReactiveInstruction, state: &mut VisitorState) {183 // Record temporaries and deps in the instruction's value184 record_temporaries(instr, state);185186 match &instr.value {187 ReactiveValue::Instruction(InstructionValue::StartMemoize {188 manual_memo_id,189 deps,190 has_invalid_deps,191 ..192 }) => {193 // TS: CompilerError.invariant(state.manualMemoState == null, ...)194 if state.manual_memo_state.is_some() {195 return;196 }197198 // TS: if (value.hasInvalidDeps === true) { return; }199 if *has_invalid_deps {200 return;201 }202203 let deps_from_source = deps.clone();204205 state.manual_memo_state = Some(ManualMemoBlockState {206 loc: instr.loc,207 decls: FxHashSet::default(),208 deps_from_source,209 manual_memo_id: *manual_memo_id,210 reassignments: FxHashMap::default(),211 });212213 // Check that each dependency's scope has completed before the memo214 // TS: for (const {identifier, loc} of eachInstructionValueOperand(value))215 let operand_places = start_memoize_operands(deps);216 for place in &operand_places {217 let ident = &state.env.identifiers[place.identifier.0 as usize];218 if let Some(scope_id) = ident.scope {219 if !state.scopes.contains(&scope_id) && !state.pruned_scopes.contains(&scope_id)220 {221 let diag = CompilerDiagnostic::new(222 ErrorCategory::PreserveManualMemo,223 "Existing memoization could not be preserved",224 Some(225 "React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. \226 This dependency may be mutated later, which could cause the value to change unexpectedly".to_string(),227 ),228 )229 .with_detail(CompilerDiagnosticDetail::Error {230 loc: place.loc,231 message: Some(232 "This dependency may be modified later".to_string(),233 ),234 identifier_name: None,235 });236 state.env.record_diagnostic(diag);237 }238 }239 }240 }241 ReactiveValue::Instruction(InstructionValue::FinishMemoize {242 decl,243 pruned,244 manual_memo_id,245 ..246 }) => {247 if state.manual_memo_state.is_none() {248 // StartMemoize had invalid deps, skip validation249 return;250 }251252 // TS: CompilerError.invariant(state.manualMemoState.manualMemoId === value.manualMemoId, ...)253 if state254 .manual_memo_state255 .as_ref()256 .map_or(true, |s| s.manual_memo_id != *manual_memo_id)257 {258 state.manual_memo_state = None;259 return;260 }261262 let memo_state = state.manual_memo_state.take().unwrap();263264 if !pruned {265 // Check if the declared value is unmemoized266 let decl_ident = &state.env.identifiers[decl.identifier.0 as usize];267268 if decl_ident.scope.is_none() {269 // If the manual memo was inlined (useMemo -> IIFE), check reassignments270 let decls_to_check = memo_state271 .reassignments272 .get(&decl_ident.declaration_id)273 .map(|ids| ids.iter().copied().collect::<Vec<_>>())274 .unwrap_or_else(|| vec![decl.identifier]);275276 for id in decls_to_check {277 if is_unmemoized(id, &state.scopes, &state.env.identifiers) {278 record_unmemoized_error(decl.loc, state.env);279 }280 }281 } else {282 // Single identifier with scope283 if is_unmemoized(decl.identifier, &state.scopes, &state.env.identifiers) {284 record_unmemoized_error(decl.loc, state.env);285 }286 }287 }288 }289 ReactiveValue::Instruction(InstructionValue::StoreLocal { lvalue, value, .. }) => {290 // Track reassignments from inlining of manual memo291 if state.manual_memo_state.is_some() && lvalue.kind == InstructionKind::Reassign {292 let decl_id =293 state.env.identifiers[lvalue.place.identifier.0 as usize].declaration_id;294 state295 .manual_memo_state296 .as_mut()297 .unwrap()298 .reassignments299 .entry(decl_id)300 .or_default()301 .insert(value.identifier);302 }303 }304 ReactiveValue::Instruction(InstructionValue::LoadLocal { place, .. }) => {305 if state.manual_memo_state.is_some() {306 let place_ident = &state.env.identifiers[place.identifier.0 as usize];307 if let Some(ref lvalue) = instr.lvalue {308 let lvalue_ident = &state.env.identifiers[lvalue.identifier.0 as usize];309 if place_ident.scope.is_some() && lvalue_ident.scope.is_none() {310 state311 .manual_memo_state312 .as_mut()313 .unwrap()314 .reassignments315 .entry(lvalue_ident.declaration_id)316 .or_default()317 .insert(place.identifier);318 }319 }320 }321 }322 _ => {}323 }324}325326fn record_unmemoized_error(loc: Option<SourceLocation>, env: &mut Environment) {327 let diag = CompilerDiagnostic::new(328 ErrorCategory::PreserveManualMemo,329 "Existing memoization could not be preserved",330 Some(331 "React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output".to_string(),332 ),333 )334 .with_detail(CompilerDiagnosticDetail::Error {335 loc,336 message: Some("Could not preserve existing memoization".to_string()),337 identifier_name: None,338 });339 env.record_diagnostic(diag);340}341342/// Record temporaries from an instruction.343/// TS: `recordTemporaries`344fn record_temporaries(instr: &ReactiveInstruction, state: &mut VisitorState) {345 let lvalue = &instr.lvalue;346 let lv_id = lvalue.as_ref().map(|lv| lv.identifier);347 if let Some(id) = lv_id {348 if state.temporaries.contains_key(&id) {349 return;350 }351 }352353 if let Some(ref lvalue) = instr.lvalue {354 let lv_ident = &state.env.identifiers[lvalue.identifier.0 as usize];355 if is_named(lv_ident) && state.manual_memo_state.is_some() {356 state357 .manual_memo_state358 .as_mut()359 .unwrap()360 .decls361 .insert(lv_ident.declaration_id);362 }363 }364365 // Record deps from the instruction value first (before setting lvalue temporary)366 record_deps_in_value(&instr.value, state);367368 // Then set the lvalue temporary (TS always sets this, even for unnamed lvalues)369 if let Some(ref lvalue) = instr.lvalue {370 state.temporaries.insert(371 lvalue.identifier,372 ManualMemoDependency {373 root: ManualMemoDependencyRoot::NamedLocal {374 value: lvalue.clone(),375 constant: false,376 },377 path: Vec::new(),378 loc: lvalue.loc,379 },380 );381 }382}383384/// Record dependencies from a reactive value.385/// TS: `recordDepsInValue`386fn record_deps_in_value(value: &ReactiveValue, state: &mut VisitorState) {387 match value {388 ReactiveValue::SequenceExpression {389 instructions,390 value,391 ..392 } => {393 for instr in instructions {394 visit_instruction(instr, state);395 }396 record_deps_in_value(value, state);397 }398 ReactiveValue::OptionalExpression { value: inner, .. } => {399 record_deps_in_value(inner, state);400 }401 ReactiveValue::ConditionalExpression {402 test,403 consequent,404 alternate,405 ..406 } => {407 record_deps_in_value(test, state);408 record_deps_in_value(consequent, state);409 record_deps_in_value(alternate, state);410 }411 ReactiveValue::LogicalExpression { left, right, .. } => {412 record_deps_in_value(left, state);413 record_deps_in_value(right, state);414 }415 ReactiveValue::Instruction(iv) => {416 // TS: collectMaybeMemoDependencies(value, this.temporaries, false)417 // Called for side-effect of building up the dependency chain through418 // LoadGlobal -> PropertyLoad -> ... The return value is discarded here419 // (only used in DropManualMemoization's caller), but we need to store420 // the result in temporaries for the lvalue of the enclosing instruction.421 // That storage is handled by record_temporaries after this function returns.422423 // Track store targets within manual memo blocks424 // TS: if (value.kind === 'StoreLocal' || value.kind === 'StoreContext' || value.kind === 'Destructure')425 match iv {426 InstructionValue::StoreLocal { lvalue, .. }427 | InstructionValue::StoreContext { lvalue, .. } => {428 if let Some(ref mut memo_state) = state.manual_memo_state {429 let ident = &state.env.identifiers[lvalue.place.identifier.0 as usize];430 memo_state.decls.insert(ident.declaration_id);431 if is_named(ident) {432 state.temporaries.insert(433 lvalue.place.identifier,434 ManualMemoDependency {435 root: ManualMemoDependencyRoot::NamedLocal {436 value: lvalue.place.clone(),437 constant: false,438 },439 path: Vec::new(),440 loc: lvalue.place.loc,441 },442 );443 }444 }445 }446 InstructionValue::Destructure { lvalue, .. } => {447 if let Some(ref mut memo_state) = state.manual_memo_state {448 for place in destructure_lvalue_places(&lvalue.pattern) {449 let ident = &state.env.identifiers[place.identifier.0 as usize];450 memo_state.decls.insert(ident.declaration_id);451 if is_named(ident) {452 state.temporaries.insert(453 place.identifier,454 ManualMemoDependency {455 root: ManualMemoDependencyRoot::NamedLocal {456 value: place.clone(),457 constant: false,458 },459 path: Vec::new(),460 loc: place.loc,461 },462 );463 }464 }465 }466 }467 _ => {}468 }469 }470 }471}472473/// Get operand places from a StartMemoize instruction's deps.474fn start_memoize_operands(deps: &Option<Vec<ManualMemoDependency>>) -> Vec<Place> {475 let mut result = Vec::new();476 if let Some(deps) = deps {477 for dep in deps {478 if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &dep.root {479 result.push(value.clone());480 }481 }482 }483 result484}485486/// Get lvalue places from a Destructure pattern.487fn destructure_lvalue_places(pattern: &react_compiler_hir::Pattern) -> Vec<&Place> {488 let mut result = Vec::new();489 match pattern {490 react_compiler_hir::Pattern::Array(arr) => {491 for item in &arr.items {492 match item {493 react_compiler_hir::ArrayPatternElement::Place(place) => {494 result.push(place);495 }496 react_compiler_hir::ArrayPatternElement::Spread(spread) => {497 result.push(&spread.place);498 }499 react_compiler_hir::ArrayPatternElement::Hole => {}500 }501 }502 }503 react_compiler_hir::Pattern::Object(obj) => {504 for entry in &obj.properties {505 match entry {506 react_compiler_hir::ObjectPropertyOrSpread::Property(prop) => {507 result.push(&prop.place);508 }509 react_compiler_hir::ObjectPropertyOrSpread::Spread(spread) => {510 result.push(&spread.place);511 }512 }513 }514 }515 }516 result517}518519/// Check if an identifier is unmemoized (has a scope that hasn't completed).520fn is_unmemoized(521 id: IdentifierId,522 completed_scopes: &FxHashSet<ScopeId>,523 identifiers: &[Identifier],524) -> bool {525 let ident = &identifiers[id.0 as usize];526 if let Some(scope_id) = ident.scope {527 !completed_scopes.contains(&scope_id)528 } else {529 false530 }531}532533// =============================================================================534// Dependency comparison (port of compareDeps / validateInferredDep)535// =============================================================================536537#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]538enum CompareDependencyResult {539 Ok = 0,540 RootDifference = 1,541 PathDifference = 2,542 Subpath = 3,543 RefAccessDifference = 4,544}545546fn compare_deps(547 inferred: &ManualMemoDependency,548 source: &ManualMemoDependency,549) -> CompareDependencyResult {550 let roots_equal = match (&inferred.root, &source.root) {551 (552 ManualMemoDependencyRoot::Global { identifier_name: a },553 ManualMemoDependencyRoot::Global { identifier_name: b },554 ) => a == b,555 (556 ManualMemoDependencyRoot::NamedLocal { value: a, .. },557 ManualMemoDependencyRoot::NamedLocal { value: b, .. },558 ) => a.identifier == b.identifier,559 _ => false,560 };561 if !roots_equal {562 return CompareDependencyResult::RootDifference;563 }564565 let min_len = inferred.path.len().min(source.path.len());566 let mut is_subpath = true;567 for i in 0..min_len {568 if inferred.path[i].property != source.path[i].property {569 is_subpath = false;570 break;571 } else if inferred.path[i].optional != source.path[i].optional {572 return CompareDependencyResult::PathDifference;573 }574 }575576 if is_subpath577 && (source.path.len() == inferred.path.len()578 || (inferred.path.len() >= source.path.len()579 && !inferred.path.iter().any(|t| {580 t.property == react_compiler_hir::PropertyLiteral::String("current".to_string())581 })))582 {583 CompareDependencyResult::Ok584 } else if is_subpath {585 if source.path.iter().any(|t| {586 t.property == react_compiler_hir::PropertyLiteral::String("current".to_string())587 }) || inferred.path.iter().any(|t| {588 t.property == react_compiler_hir::PropertyLiteral::String("current".to_string())589 }) {590 CompareDependencyResult::RefAccessDifference591 } else {592 CompareDependencyResult::Subpath593 }594 } else {595 CompareDependencyResult::PathDifference596 }597}598599/// Pretty-print a reactive scope dependency (e.g., `x.a.b?.c`)600fn pretty_print_scope_dependency(601 dep_id: IdentifierId,602 dep_path: &[DependencyPathEntry],603 identifiers: &[react_compiler_hir::Identifier],604) -> String {605 let ident = &identifiers[dep_id.0 as usize];606 let root_str = match &ident.name {607 Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(),608 Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(),609 None => "[unnamed]".to_string(),610 };611 let path_str: String = dep_path612 .iter()613 .map(|entry| {614 let prop = match &entry.property {615 react_compiler_hir::PropertyLiteral::String(s) => s.clone(),616 react_compiler_hir::PropertyLiteral::Number(n) => format!("{}", n),617 };618 if entry.optional {619 format!("?.{}", prop)620 } else {621 format!(".{}", prop)622 }623 })624 .collect();625 format!("{}{}", root_str, path_str)626}627628/// Pretty-print a manual memo dependency for error messages.629fn print_manual_memo_dependency(630 dep: &ManualMemoDependency,631 identifiers: &[react_compiler_hir::Identifier],632 with_optional: bool,633) -> String {634 let root_str = match &dep.root {635 ManualMemoDependencyRoot::NamedLocal { value, .. } => {636 let ident = &identifiers[value.identifier.0 as usize];637 match &ident.name {638 Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(),639 Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(),640 None => "[unnamed]".to_string(),641 }642 }643 ManualMemoDependencyRoot::Global { identifier_name } => identifier_name.clone(),644 };645 let path_str: String = dep646 .path647 .iter()648 .map(|entry| {649 let prop = match &entry.property {650 react_compiler_hir::PropertyLiteral::String(s) => s.clone(),651 react_compiler_hir::PropertyLiteral::Number(n) => format!("{}", n),652 };653 if with_optional && entry.optional {654 format!("?.{}", prop)655 } else {656 format!(".{}", prop)657 }658 })659 .collect();660 format!("{}{}", root_str, path_str)661}662663fn get_compare_dependency_result_description(result: CompareDependencyResult) -> &'static str {664 match result {665 CompareDependencyResult::Ok => "Dependencies equal",666 CompareDependencyResult::RootDifference | CompareDependencyResult::PathDifference => {667 "Inferred different dependency than source"668 }669 CompareDependencyResult::RefAccessDifference => "Differences in ref.current access",670 CompareDependencyResult::Subpath => "Inferred less specific property than source",671 }672}673674/// Validate that an inferred dependency matches a source dependency or was produced675/// within the manual memo block.676fn validate_inferred_dep(677 dep_id: IdentifierId,678 dep_path: &[DependencyPathEntry],679 temporaries: &FxHashMap<IdentifierId, ManualMemoDependency>,680 decls_within_memo_block: &FxHashSet<DeclarationId>,681 valid_deps_in_memo_block: &[ManualMemoDependency],682 env: &mut Environment,683 memo_location: Option<SourceLocation>,684) {685 // Normalize the dependency through temporaries686 let normalized_dep = if let Some(temp) = temporaries.get(&dep_id) {687 let mut path = temp.path.clone();688 path.extend_from_slice(dep_path);689 ManualMemoDependency {690 root: temp.root.clone(),691 path,692 loc: temp.loc,693 }694 } else {695 let ident = &env.identifiers[dep_id.0 as usize];696 // TS: CompilerError.invariant(dep.identifier.name?.kind === 'named', ...)697 if !is_named(ident) {698 return;699 }700 ManualMemoDependency {701 root: ManualMemoDependencyRoot::NamedLocal {702 value: Place {703 identifier: dep_id,704 effect: react_compiler_hir::Effect::Read,705 reactive: false,706 loc: ident.loc,707 },708 constant: false,709 },710 path: dep_path.to_vec(),711 loc: ident.loc,712 }713 };714715 // Check if the dep was declared within the memo block716 if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &normalized_dep.root {717 let ident = &env.identifiers[value.identifier.0 as usize];718 if decls_within_memo_block.contains(&ident.declaration_id) {719 return;720 }721 }722723 // Compare against each valid source dependency724 let mut error_diagnostic: Option<CompareDependencyResult> = None;725 for source_dep in valid_deps_in_memo_block {726 let result = compare_deps(&normalized_dep, source_dep);727 if result == CompareDependencyResult::Ok {728 return;729 }730 error_diagnostic = Some(match error_diagnostic {731 Some(prev) => prev.max(result),732 None => result,733 });734 }735736 let ident = &env.identifiers[dep_id.0 as usize];737738 let extra = if is_named(ident) {739 // Use the original dep_id/dep_path (matching TS prettyPrintScopeDependency(dep))740 let dep_str = pretty_print_scope_dependency(dep_id, dep_path, &env.identifiers);741 let source_deps_str: String = valid_deps_in_memo_block742 .iter()743 .map(|d| print_manual_memo_dependency(d, &env.identifiers, true))744 .collect::<Vec<_>>()745 .join(", ");746 let result_desc = error_diagnostic747 .map(|d| get_compare_dependency_result_description(d).to_string())748 .unwrap_or_else(|| "Inferred dependency not present in source".to_string());749 format!(750 "The inferred dependency was `{}`, but the source dependencies were [{}]. {}",751 dep_str, source_deps_str, result_desc752 )753 } else {754 String::new()755 };756757 let description = format!(758 "React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. \759 The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. {}",760 extra761 );762763 let diag = CompilerDiagnostic::new(764 ErrorCategory::PreserveManualMemo,765 "Existing memoization could not be preserved",766 Some(description.trim().to_string()),767 )768 .with_detail(CompilerDiagnosticDetail::Error {769 loc: memo_location,770 message: Some("Could not preserve existing manual memoization".to_string()),771 identifier_name: None,772 });773 env.record_diagnostic(diag);774}
Code quality findings 34
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 = &state.env.scopes[scope_block.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 scope = &state.env.scopes[scope_block.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 ident = &state.env.identifiers[place.identifier.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 memo_state = state.manual_memo_state.take().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 decl_ident = &state.env.identifiers[decl.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
state.env.identifiers[lvalue.place.identifier.0 as usize].declaration_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
.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 place_ident = &state.env.identifiers[place.identifier.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let lvalue_ident = &state.env.identifiers[lvalue.identifier.0 as usize];
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
.unwrap()
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let lv_ident = &state.env.identifiers[lvalue.identifier.0 as usize];
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
.unwrap()
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let ident = &state.env.identifiers[lvalue.place.identifier.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let ident = &state.env.identifiers[place.identifier.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let ident = &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
if inferred.path[i].property != source.path[i].property {
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
} else if inferred.path[i].optional != source.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
let ident = &identifiers[dep_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 ident = &identifiers[value.identifier.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let ident = &env.identifiers[dep_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 ident = &env.identifiers[value.identifier.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let ident = &env.identifiers[dep_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
"The inferred dependency was `{}`, but the source dependencies were [{}]. {}",
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 deps = scope.dependencies.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 merged = scope.merged.clone();
Performance Info: Calling .to_string() (especially on &str) allocates a new String. If done repeatedly in loops, consider alternatives like working with &str or using crates like `itoa`/`ryu` for number-to-string conversion.
info
performance
to-string-in-loop
This dependency may be mutated later, which could cause the value to change unexpectedly".to_string(),
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
value: place.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
result.push(value.clone());
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
result.push(value.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
result.push(place);
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
result.push(&spread.place);
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
result.push(&prop.place);
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
result.push(&spread.place);
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 roots_equal = match (&inferred.root, &source.root) {