compiler/crates/react_compiler_validation/src/validate_preserved_manual_memoization.rs RUST 775 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            // `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) {

Get this view in your editor

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