compiler/crates/react_compiler_validation/src/validate_preserved_manual_memoization.rs RUST 774 lines View on github.com → Search inside
1// Copyright (c) Meta Platforms, Inc. and affiliates.2//3// This source code is licensed under the MIT license found in the4// LICENSE file in the root directory of this source tree.56//! 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) {

Get this view in your editor

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