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 let deps = scope.dependencies.clone();146 let memo_loc = memo_state.loc;147 let decls = memo_state.decls.clone();148 let deps_from_source = deps_from_source.clone();149 let temporaries = state.temporaries.clone();150 for dep in &deps {151 validate_inferred_dep(152 dep.identifier,153 &dep.path,154 &temporaries,155 &decls,156 &deps_from_source,157 state.env,158 memo_loc,159 );160 }161 }162 }163164 // Mark scope and merged scopes as completed165 let scope = &state.env.scopes[scope_block.scope.0 as usize];166 let merged = scope.merged.clone();167 state.scopes.insert(scope_block.scope);168 for merged_id in merged {169 state.scopes.insert(merged_id);170 }171}172173fn visit_pruned_scope(174 pruned: &react_compiler_hir::PrunedReactiveScopeBlock,175 state: &mut VisitorState,176) {177 visit_block(&pruned.instructions, state);178 state.pruned_scopes.insert(pruned.scope);179}180181fn visit_instruction(instr: &ReactiveInstruction, state: &mut VisitorState) {182 // Record temporaries and deps in the instruction's value183 record_temporaries(instr, state);184185 match &instr.value {186 ReactiveValue::Instruction(InstructionValue::StartMemoize {187 manual_memo_id,188 deps,189 has_invalid_deps,190 ..191 }) => {192 // TS: CompilerError.invariant(state.manualMemoState == null, ...)193 if state.manual_memo_state.is_some() {194 return;195 }196197 // TS: if (value.hasInvalidDeps === true) { return; }198 if *has_invalid_deps {199 return;200 }201202 let deps_from_source = deps.clone();203204 state.manual_memo_state = Some(ManualMemoBlockState {205 loc: instr.loc,206 decls: FxHashSet::default(),207 deps_from_source,208 manual_memo_id: *manual_memo_id,209 reassignments: FxHashMap::default(),210 });211212 // Check that each dependency's scope has completed before the memo213 // TS: for (const {identifier, loc} of eachInstructionValueOperand(value))214 let operand_places = start_memoize_operands(deps);215 for place in &operand_places {216 let ident = &state.env.identifiers[place.identifier.0 as usize];217 if let Some(scope_id) = ident.scope {218 if !state.scopes.contains(&scope_id) && !state.pruned_scopes.contains(&scope_id)219 {220 let diag = CompilerDiagnostic::new(221 ErrorCategory::PreserveManualMemo,222 "Existing memoization could not be preserved",223 Some(224 "React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. \225 This dependency may be mutated later, which could cause the value to change unexpectedly".to_string(),226 ),227 )228 .with_detail(CompilerDiagnosticDetail::Error {229 loc: place.loc,230 message: Some(231 "This dependency may be modified later".to_string(),232 ),233 identifier_name: None,234 });235 state.env.record_diagnostic(diag);236 }237 }238 }239 }240 ReactiveValue::Instruction(InstructionValue::FinishMemoize {241 decl,242 pruned,243 manual_memo_id,244 ..245 }) => {246 if state.manual_memo_state.is_none() {247 // StartMemoize had invalid deps, skip validation248 return;249 }250251 // TS: CompilerError.invariant(state.manualMemoState.manualMemoId === value.manualMemoId, ...)252 if state253 .manual_memo_state254 .as_ref()255 .map_or(true, |s| s.manual_memo_id != *manual_memo_id)256 {257 state.manual_memo_state = None;258 return;259 }260261 let memo_state = state.manual_memo_state.take().unwrap();262263 if !pruned {264 // Check if the declared value is unmemoized265 let decl_ident = &state.env.identifiers[decl.identifier.0 as usize];266267 if decl_ident.scope.is_none() {268 // If the manual memo was inlined (useMemo -> IIFE), check reassignments269 let decls_to_check = memo_state270 .reassignments271 .get(&decl_ident.declaration_id)272 .map(|ids| ids.iter().copied().collect::<Vec<_>>())273 .unwrap_or_else(|| vec![decl.identifier]);274275 for id in decls_to_check {276 if is_unmemoized(id, &state.scopes, &state.env.identifiers) {277 record_unmemoized_error(decl.loc, state.env);278 }279 }280 } else {281 // Single identifier with scope282 if is_unmemoized(decl.identifier, &state.scopes, &state.env.identifiers) {283 record_unmemoized_error(decl.loc, state.env);284 }285 }286 }287 }288 ReactiveValue::Instruction(InstructionValue::StoreLocal { lvalue, value, .. }) => {289 // Track reassignments from inlining of manual memo290 if state.manual_memo_state.is_some() && lvalue.kind == InstructionKind::Reassign {291 let decl_id =292 state.env.identifiers[lvalue.place.identifier.0 as usize].declaration_id;293 state294 .manual_memo_state295 .as_mut()296 .unwrap()297 .reassignments298 .entry(decl_id)299 .or_default()300 .insert(value.identifier);301 }302 }303 ReactiveValue::Instruction(InstructionValue::LoadLocal { place, .. }) => {304 if state.manual_memo_state.is_some() {305 let place_ident = &state.env.identifiers[place.identifier.0 as usize];306 if let Some(ref lvalue) = instr.lvalue {307 let lvalue_ident = &state.env.identifiers[lvalue.identifier.0 as usize];308 if place_ident.scope.is_some() && lvalue_ident.scope.is_none() {309 state310 .manual_memo_state311 .as_mut()312 .unwrap()313 .reassignments314 .entry(lvalue_ident.declaration_id)315 .or_default()316 .insert(place.identifier);317 }318 }319 }320 }321 _ => {}322 }323}324325fn record_unmemoized_error(loc: Option<SourceLocation>, env: &mut Environment) {326 let diag = CompilerDiagnostic::new(327 ErrorCategory::PreserveManualMemo,328 "Existing memoization could not be preserved",329 Some(330 "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(),331 ),332 )333 .with_detail(CompilerDiagnosticDetail::Error {334 loc,335 message: Some("Could not preserve existing memoization".to_string()),336 identifier_name: None,337 });338 env.record_diagnostic(diag);339}340341/// Record temporaries from an instruction.342/// TS: `recordTemporaries`343fn record_temporaries(instr: &ReactiveInstruction, state: &mut VisitorState) {344 let lvalue = &instr.lvalue;345 let lv_id = lvalue.as_ref().map(|lv| lv.identifier);346 if let Some(id) = lv_id {347 if state.temporaries.contains_key(&id) {348 return;349 }350 }351352 if let Some(ref lvalue) = instr.lvalue {353 let lv_ident = &state.env.identifiers[lvalue.identifier.0 as usize];354 if is_named(lv_ident) && state.manual_memo_state.is_some() {355 state356 .manual_memo_state357 .as_mut()358 .unwrap()359 .decls360 .insert(lv_ident.declaration_id);361 }362 }363364 // Record deps from the instruction value first (before setting lvalue temporary)365 record_deps_in_value(&instr.value, state);366367 // Then set the lvalue temporary (TS always sets this, even for unnamed lvalues)368 if let Some(ref lvalue) = instr.lvalue {369 state.temporaries.insert(370 lvalue.identifier,371 ManualMemoDependency {372 root: ManualMemoDependencyRoot::NamedLocal {373 value: lvalue.clone(),374 constant: false,375 },376 path: Vec::new(),377 loc: lvalue.loc,378 },379 );380 }381}382383/// Record dependencies from a reactive value.384/// TS: `recordDepsInValue`385fn record_deps_in_value(value: &ReactiveValue, state: &mut VisitorState) {386 match value {387 ReactiveValue::SequenceExpression {388 instructions,389 value,390 ..391 } => {392 for instr in instructions {393 visit_instruction(instr, state);394 }395 record_deps_in_value(value, state);396 }397 ReactiveValue::OptionalExpression { value: inner, .. } => {398 record_deps_in_value(inner, state);399 }400 ReactiveValue::ConditionalExpression {401 test,402 consequent,403 alternate,404 ..405 } => {406 record_deps_in_value(test, state);407 record_deps_in_value(consequent, state);408 record_deps_in_value(alternate, state);409 }410 ReactiveValue::LogicalExpression { left, right, .. } => {411 record_deps_in_value(left, state);412 record_deps_in_value(right, state);413 }414 ReactiveValue::Instruction(iv) => {415 // TS: collectMaybeMemoDependencies(value, this.temporaries, false)416 // Called for side-effect of building up the dependency chain through417 // LoadGlobal -> PropertyLoad -> ... The return value is discarded here418 // (only used in DropManualMemoization's caller), but we need to store419 // the result in temporaries for the lvalue of the enclosing instruction.420 // That storage is handled by record_temporaries after this function returns.421422 // Track store targets within manual memo blocks423 // TS: if (value.kind === 'StoreLocal' || value.kind === 'StoreContext' || value.kind === 'Destructure')424 match iv {425 InstructionValue::StoreLocal { lvalue, .. }426 | InstructionValue::StoreContext { lvalue, .. } => {427 if let Some(ref mut memo_state) = state.manual_memo_state {428 let ident = &state.env.identifiers[lvalue.place.identifier.0 as usize];429 memo_state.decls.insert(ident.declaration_id);430 if is_named(ident) {431 state.temporaries.insert(432 lvalue.place.identifier,433 ManualMemoDependency {434 root: ManualMemoDependencyRoot::NamedLocal {435 value: lvalue.place.clone(),436 constant: false,437 },438 path: Vec::new(),439 loc: lvalue.place.loc,440 },441 );442 }443 }444 }445 InstructionValue::Destructure { lvalue, .. } => {446 if let Some(ref mut memo_state) = state.manual_memo_state {447 for place in destructure_lvalue_places(&lvalue.pattern) {448 let ident = &state.env.identifiers[place.identifier.0 as usize];449 memo_state.decls.insert(ident.declaration_id);450 if is_named(ident) {451 state.temporaries.insert(452 place.identifier,453 ManualMemoDependency {454 root: ManualMemoDependencyRoot::NamedLocal {455 value: place.clone(),456 constant: false,457 },458 path: Vec::new(),459 loc: place.loc,460 },461 );462 }463 }464 }465 }466 _ => {}467 }468 }469 }470}471472/// Get operand places from a StartMemoize instruction's deps.473fn start_memoize_operands(deps: &Option<Vec<ManualMemoDependency>>) -> Vec<Place> {474 let mut result = Vec::new();475 if let Some(deps) = deps {476 for dep in deps {477 if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &dep.root {478 result.push(value.clone());479 }480 }481 }482 result483}484485/// Get lvalue places from a Destructure pattern.486fn destructure_lvalue_places(pattern: &react_compiler_hir::Pattern) -> Vec<&Place> {487 let mut result = Vec::new();488 match pattern {489 react_compiler_hir::Pattern::Array(arr) => {490 for item in &arr.items {491 match item {492 react_compiler_hir::ArrayPatternElement::Place(place) => {493 result.push(place);494 }495 react_compiler_hir::ArrayPatternElement::Spread(spread) => {496 result.push(&spread.place);497 }498 react_compiler_hir::ArrayPatternElement::Hole => {}499 }500 }501 }502 react_compiler_hir::Pattern::Object(obj) => {503 for entry in &obj.properties {504 match entry {505 react_compiler_hir::ObjectPropertyOrSpread::Property(prop) => {506 result.push(&prop.place);507 }508 react_compiler_hir::ObjectPropertyOrSpread::Spread(spread) => {509 result.push(&spread.place);510 }511 }512 }513 }514 }515 result516}517518/// Check if an identifier is unmemoized (has a scope that hasn't completed).519fn is_unmemoized(520 id: IdentifierId,521 completed_scopes: &FxHashSet<ScopeId>,522 identifiers: &[Identifier],523) -> bool {524 let ident = &identifiers[id.0 as usize];525 if let Some(scope_id) = ident.scope {526 !completed_scopes.contains(&scope_id)527 } else {528 false529 }530}531532// =============================================================================533// Dependency comparison (port of compareDeps / validateInferredDep)534// =============================================================================535536#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]537enum CompareDependencyResult {538 Ok = 0,539 RootDifference = 1,540 PathDifference = 2,541 Subpath = 3,542 RefAccessDifference = 4,543}544545fn compare_deps(546 inferred: &ManualMemoDependency,547 source: &ManualMemoDependency,548) -> CompareDependencyResult {549 let roots_equal = match (&inferred.root, &source.root) {550 (551 ManualMemoDependencyRoot::Global { identifier_name: a },552 ManualMemoDependencyRoot::Global { identifier_name: b },553 ) => a == b,554 (555 ManualMemoDependencyRoot::NamedLocal { value: a, .. },556 ManualMemoDependencyRoot::NamedLocal { value: b, .. },557 ) => a.identifier == b.identifier,558 _ => false,559 };560 if !roots_equal {561 return CompareDependencyResult::RootDifference;562 }563564 let min_len = inferred.path.len().min(source.path.len());565 let mut is_subpath = true;566 for i in 0..min_len {567 if inferred.path[i].property != source.path[i].property {568 is_subpath = false;569 break;570 } else if inferred.path[i].optional != source.path[i].optional {571 return CompareDependencyResult::PathDifference;572 }573 }574575 if is_subpath576 && (source.path.len() == inferred.path.len()577 || (inferred.path.len() >= source.path.len()578 && !inferred.path.iter().any(|t| {579 t.property == react_compiler_hir::PropertyLiteral::String("current".to_string())580 })))581 {582 CompareDependencyResult::Ok583 } else if is_subpath {584 if source.path.iter().any(|t| {585 t.property == react_compiler_hir::PropertyLiteral::String("current".to_string())586 }) || inferred.path.iter().any(|t| {587 t.property == react_compiler_hir::PropertyLiteral::String("current".to_string())588 }) {589 CompareDependencyResult::RefAccessDifference590 } else {591 CompareDependencyResult::Subpath592 }593 } else {594 CompareDependencyResult::PathDifference595 }596}597598/// Pretty-print a reactive scope dependency (e.g., `x.a.b?.c`)599fn pretty_print_scope_dependency(600 dep_id: IdentifierId,601 dep_path: &[DependencyPathEntry],602 identifiers: &[react_compiler_hir::Identifier],603) -> String {604 let ident = &identifiers[dep_id.0 as usize];605 let root_str = match &ident.name {606 Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(),607 Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(),608 None => "[unnamed]".to_string(),609 };610 let path_str: String = dep_path611 .iter()612 .map(|entry| {613 let prop = match &entry.property {614 react_compiler_hir::PropertyLiteral::String(s) => s.clone(),615 react_compiler_hir::PropertyLiteral::Number(n) => format!("{}", n),616 };617 if entry.optional {618 format!("?.{}", prop)619 } else {620 format!(".{}", prop)621 }622 })623 .collect();624 format!("{}{}", root_str, path_str)625}626627/// Pretty-print a manual memo dependency for error messages.628fn print_manual_memo_dependency(629 dep: &ManualMemoDependency,630 identifiers: &[react_compiler_hir::Identifier],631 with_optional: bool,632) -> String {633 let root_str = match &dep.root {634 ManualMemoDependencyRoot::NamedLocal { value, .. } => {635 let ident = &identifiers[value.identifier.0 as usize];636 match &ident.name {637 Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(),638 Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(),639 None => "[unnamed]".to_string(),640 }641 }642 ManualMemoDependencyRoot::Global { identifier_name } => identifier_name.clone(),643 };644 let path_str: String = dep645 .path646 .iter()647 .map(|entry| {648 let prop = match &entry.property {649 react_compiler_hir::PropertyLiteral::String(s) => s.clone(),650 react_compiler_hir::PropertyLiteral::Number(n) => format!("{}", n),651 };652 if with_optional && entry.optional {653 format!("?.{}", prop)654 } else {655 format!(".{}", prop)656 }657 })658 .collect();659 format!("{}{}", root_str, path_str)660}661662fn get_compare_dependency_result_description(result: CompareDependencyResult) -> &'static str {663 match result {664 CompareDependencyResult::Ok => "Dependencies equal",665 CompareDependencyResult::RootDifference | CompareDependencyResult::PathDifference => {666 "Inferred different dependency than source"667 }668 CompareDependencyResult::RefAccessDifference => "Differences in ref.current access",669 CompareDependencyResult::Subpath => "Inferred less specific property than source",670 }671}672673/// Validate that an inferred dependency matches a source dependency or was produced674/// within the manual memo block.675fn validate_inferred_dep(676 dep_id: IdentifierId,677 dep_path: &[DependencyPathEntry],678 temporaries: &FxHashMap<IdentifierId, ManualMemoDependency>,679 decls_within_memo_block: &FxHashSet<DeclarationId>,680 valid_deps_in_memo_block: &[ManualMemoDependency],681 env: &mut Environment,682 memo_location: Option<SourceLocation>,683) {684 // Normalize the dependency through temporaries685 let normalized_dep = if let Some(temp) = temporaries.get(&dep_id) {686 let mut path = temp.path.clone();687 path.extend_from_slice(dep_path);688 ManualMemoDependency {689 root: temp.root.clone(),690 path,691 loc: temp.loc,692 }693 } else {694 let ident = &env.identifiers[dep_id.0 as usize];695 // TS: CompilerError.invariant(dep.identifier.name?.kind === 'named', ...)696 if !is_named(ident) {697 return;698 }699 ManualMemoDependency {700 root: ManualMemoDependencyRoot::NamedLocal {701 value: Place {702 identifier: dep_id,703 effect: react_compiler_hir::Effect::Read,704 reactive: false,705 loc: ident.loc,706 },707 constant: false,708 },709 path: dep_path.to_vec(),710 loc: ident.loc,711 }712 };713714 // Check if the dep was declared within the memo block715 if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &normalized_dep.root {716 let ident = &env.identifiers[value.identifier.0 as usize];717 if decls_within_memo_block.contains(&ident.declaration_id) {718 return;719 }720 }721722 // Compare against each valid source dependency723 let mut error_diagnostic: Option<CompareDependencyResult> = None;724 for source_dep in valid_deps_in_memo_block {725 let result = compare_deps(&normalized_dep, source_dep);726 if result == CompareDependencyResult::Ok {727 return;728 }729 error_diagnostic = Some(match error_diagnostic {730 Some(prev) => prev.max(result),731 None => result,732 });733 }734735 let ident = &env.identifiers[dep_id.0 as usize];736737 let extra = if is_named(ident) {738 // Use the original dep_id/dep_path (matching TS prettyPrintScopeDependency(dep))739 let dep_str = pretty_print_scope_dependency(dep_id, dep_path, &env.identifiers);740 let source_deps_str: String = valid_deps_in_memo_block741 .iter()742 .map(|d| print_manual_memo_dependency(d, &env.identifiers, true))743 .collect::<Vec<_>>()744 .join(", ");745 let result_desc = error_diagnostic746 .map(|d| get_compare_dependency_result_description(d).to_string())747 .unwrap_or_else(|| "Inferred dependency not present in source".to_string());748 format!(749 "The inferred dependency was `{}`, but the source dependencies were [{}]. {}",750 dep_str, source_deps_str, result_desc751 )752 } else {753 String::new()754 };755756 let description = format!(757 "React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. \758 The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. {}",759 extra760 );761762 let diag = CompilerDiagnostic::new(763 ErrorCategory::PreserveManualMemo,764 "Existing memoization could not be preserved",765 Some(description.trim().to_string()),766 )767 .with_detail(CompilerDiagnosticDetail::Error {768 loc: memo_location,769 message: Some("Could not preserve existing manual memoization".to_string()),770 identifier_name: None,771 });772 env.record_diagnostic(diag);773}
Code quality findings 37
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 decls = memo_state.decls.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 deps_from_source = deps_from_source.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 temporaries = state.temporaries.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) {