compiler/crates/react_compiler_validation/src/validate_no_ref_access_in_render.rs RUST 1,258 lines View on github.com → Search inside
1use rustc_hash::{FxHashMap, FxHashSet};23use react_compiler_diagnostics::{4    CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, SourceLocation,5};6use react_compiler_hir::environment::Environment;7use react_compiler_hir::object_shape::HookKind;8use react_compiler_hir::visitors::{9    each_instruction_value_operand as canonical_each_instruction_value_operand,10    each_pattern_operand, each_terminal_operand,11};12use react_compiler_hir::{13    AliasingEffect, BlockId, HirFunction, Identifier, IdentifierId, InstructionValue, Place,14    PrimitiveValue, PropertyLiteral, Terminal, Type, UnaryOperator,15};1617const ERROR_DESCRIPTION: &str = "React refs are values that are not needed for rendering. \18    Refs should only be accessed outside of render, such as in event handlers or effects. \19    Accessing a ref value (the `current` property) during render can cause your component \20    not to update as expected (https://react.dev/reference/react/useRef)";2122// --- RefId ---2324type RefId = u32;2526static REF_ID_COUNTER: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);2728fn next_ref_id() -> RefId {29    REF_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)30}3132// --- RefAccessType / RefAccessRefType / RefFnType ---3334/// Corresponds to TS `RefAccessType`.35///36/// PartialEq matches the TS `tyEqual` semantics: Ref ignores ref_id,37/// RefValue compares loc but ignores ref_id. This is critical for fixpoint38/// convergence — join creates fresh ref_ids, and comparing them would39/// prevent the environment from stabilizing.40#[derive(Debug, Clone)]41enum RefAccessType {42    None,43    Nullable,44    Guard {45        ref_id: RefId,46    },47    Ref {48        ref_id: RefId,49    },50    RefValue {51        loc: Option<SourceLocation>,52        ref_id: Option<RefId>,53    },54    Structure {55        value: Option<Box<RefAccessRefType>>,56        fn_type: Option<RefFnType>,57    },58}5960impl PartialEq for RefAccessType {61    fn eq(&self, other: &Self) -> bool {62        match (self, other) {63            (RefAccessType::None, RefAccessType::None) => true,64            (RefAccessType::Nullable, RefAccessType::Nullable) => true,65            (RefAccessType::Guard { ref_id: a }, RefAccessType::Guard { ref_id: b }) => a == b,66            (RefAccessType::Ref { .. }, RefAccessType::Ref { .. }) => true,67            (RefAccessType::RefValue { loc: a, .. }, RefAccessType::RefValue { loc: b, .. }) => {68                a == b69            }70            (71                RefAccessType::Structure {72                    value: a_val,73                    fn_type: a_fn,74                },75                RefAccessType::Structure {76                    value: b_val,77                    fn_type: b_fn,78                },79            ) => a_val == b_val && a_fn == b_fn,80            _ => false,81        }82    }83}8485/// Corresponds to TS `RefAccessRefType` — the subset of `RefAccessType` that can appear86/// inside `Structure.value` and be joined via `join_ref_access_ref_types`.87///88/// PartialEq mirrors RefAccessType: Ref ignores ref_id, RefValue compares89/// loc only.90#[derive(Debug, Clone)]91enum RefAccessRefType {92    Ref {93        ref_id: RefId,94    },95    RefValue {96        loc: Option<SourceLocation>,97        ref_id: Option<RefId>,98    },99    Structure {100        value: Option<Box<RefAccessRefType>>,101        fn_type: Option<RefFnType>,102    },103}104105impl PartialEq for RefAccessRefType {106    fn eq(&self, other: &Self) -> bool {107        match (self, other) {108            (RefAccessRefType::Ref { .. }, RefAccessRefType::Ref { .. }) => true,109            (110                RefAccessRefType::RefValue { loc: a, .. },111                RefAccessRefType::RefValue { loc: b, .. },112            ) => a == b,113            (114                RefAccessRefType::Structure {115                    value: a_val,116                    fn_type: a_fn,117                },118                RefAccessRefType::Structure {119                    value: b_val,120                    fn_type: b_fn,121                },122            ) => a_val == b_val && a_fn == b_fn,123            _ => false,124        }125    }126}127128#[derive(Debug, Clone, PartialEq)]129struct RefFnType {130    read_ref_effect: bool,131    return_type: Box<RefAccessType>,132}133134impl RefAccessType {135    /// Try to convert a `RefAccessType` to a `RefAccessRefType` (the Ref/RefValue/Structure subset).136    fn to_ref_type(&self) -> Option<RefAccessRefType> {137        match self {138            RefAccessType::Ref { ref_id } => Some(RefAccessRefType::Ref { ref_id: *ref_id }),139            RefAccessType::RefValue { loc, ref_id } => Some(RefAccessRefType::RefValue {140                loc: *loc,141                ref_id: *ref_id,142            }),143            RefAccessType::Structure { value, fn_type } => Some(RefAccessRefType::Structure {144                value: value.clone(),145                fn_type: fn_type.clone(),146            }),147            _ => None,148        }149    }150151    /// Convert a `RefAccessRefType` back to a `RefAccessType`.152    fn from_ref_type(ref_type: &RefAccessRefType) -> Self {153        match ref_type {154            RefAccessRefType::Ref { ref_id } => RefAccessType::Ref { ref_id: *ref_id },155            RefAccessRefType::RefValue { loc, ref_id } => RefAccessType::RefValue {156                loc: *loc,157                ref_id: *ref_id,158            },159            RefAccessRefType::Structure { value, fn_type } => RefAccessType::Structure {160                value: value.clone(),161                fn_type: fn_type.clone(),162            },163        }164    }165}166167// --- Join operations ---168169fn join_ref_access_ref_types(a: &RefAccessRefType, b: &RefAccessRefType) -> RefAccessRefType {170    match (a, b) {171        (172            RefAccessRefType::RefValue { ref_id: a_id, .. },173            RefAccessRefType::RefValue { ref_id: b_id, .. },174        ) => {175            if a_id == b_id {176                a.clone()177            } else {178                RefAccessRefType::RefValue {179                    loc: None,180                    ref_id: None,181                }182            }183        }184        (RefAccessRefType::RefValue { .. }, _) => RefAccessRefType::RefValue {185            loc: None,186            ref_id: None,187        },188        (_, RefAccessRefType::RefValue { .. }) => RefAccessRefType::RefValue {189            loc: None,190            ref_id: None,191        },192        (RefAccessRefType::Ref { ref_id: a_id }, RefAccessRefType::Ref { ref_id: b_id }) => {193            if a_id == b_id {194                a.clone()195            } else {196                RefAccessRefType::Ref {197                    ref_id: next_ref_id(),198                }199            }200        }201        (RefAccessRefType::Ref { .. }, _) | (_, RefAccessRefType::Ref { .. }) => {202            RefAccessRefType::Ref {203                ref_id: next_ref_id(),204            }205        }206        (207            RefAccessRefType::Structure {208                value: a_value,209                fn_type: a_fn,210            },211            RefAccessRefType::Structure {212                value: b_value,213                fn_type: b_fn,214            },215        ) => {216            let fn_type = match (a_fn, b_fn) {217                (None, other) | (other, None) => other.clone(),218                (Some(a_fn), Some(b_fn)) => Some(RefFnType {219                    read_ref_effect: a_fn.read_ref_effect || b_fn.read_ref_effect,220                    return_type: Box::new(join_ref_access_types(221                        &a_fn.return_type,222                        &b_fn.return_type,223                    )),224                }),225            };226            let value = match (a_value, b_value) {227                (None, other) | (other, None) => other.clone(),228                (Some(a_val), Some(b_val)) => {229                    Some(Box::new(join_ref_access_ref_types(a_val, b_val)))230                }231            };232            RefAccessRefType::Structure { value, fn_type }233        }234    }235}236237fn join_ref_access_types(a: &RefAccessType, b: &RefAccessType) -> RefAccessType {238    match (a, b) {239        (RefAccessType::None, other) | (other, RefAccessType::None) => other.clone(),240        (RefAccessType::Guard { ref_id: a_id }, RefAccessType::Guard { ref_id: b_id }) => {241            if a_id == b_id {242                a.clone()243            } else {244                RefAccessType::None245            }246        }247        (RefAccessType::Guard { .. }, RefAccessType::Nullable)248        | (RefAccessType::Nullable, RefAccessType::Guard { .. }) => RefAccessType::None,249        (RefAccessType::Guard { .. }, other) | (other, RefAccessType::Guard { .. }) => {250            other.clone()251        }252        (RefAccessType::Nullable, other) | (other, RefAccessType::Nullable) => other.clone(),253        _ => match (a.to_ref_type(), b.to_ref_type()) {254            (Some(a_ref), Some(b_ref)) => {255                RefAccessType::from_ref_type(&join_ref_access_ref_types(&a_ref, &b_ref))256            }257            (Some(r), None) | (None, Some(r)) => RefAccessType::from_ref_type(&r),258            _ => RefAccessType::None,259        },260    }261}262263fn join_ref_access_types_many(types: &[RefAccessType]) -> RefAccessType {264    types265        .iter()266        .fold(RefAccessType::None, |acc, t| join_ref_access_types(&acc, t))267}268269// --- Env ---270271struct Env {272    changed: bool,273    data: FxHashMap<IdentifierId, RefAccessType>,274    temporaries: FxHashMap<IdentifierId, Place>,275}276277impl Env {278    fn new() -> Self {279        Self {280            changed: false,281            data: FxHashMap::default(),282            temporaries: FxHashMap::default(),283        }284    }285286    fn define(&mut self, key: IdentifierId, value: Place) {287        self.temporaries.insert(key, value);288    }289290    fn reset_changed(&mut self) {291        self.changed = false;292    }293294    fn has_changed(&self) -> bool {295        self.changed296    }297298    fn get(&self, key: IdentifierId) -> Option<&RefAccessType> {299        let operand_id = self300            .temporaries301            .get(&key)302            .map(|p| p.identifier)303            .unwrap_or(key);304        self.data.get(&operand_id)305    }306307    fn set(&mut self, key: IdentifierId, value: RefAccessType) {308        let operand_id = self309            .temporaries310            .get(&key)311            .map(|p| p.identifier)312            .unwrap_or(key);313        let current = self.data.get(&operand_id);314        let widened_value = join_ref_access_types(&value, current.unwrap_or(&RefAccessType::None));315        if current.is_none() && widened_value == RefAccessType::None {316            // No change needed317        } else if current.map_or(true, |c| c != &widened_value) {318            self.changed = true;319        }320        self.data.insert(operand_id, widened_value);321    }322}323324// --- Helper functions ---325326fn ref_type_of_type(id: IdentifierId, identifiers: &[Identifier], types: &[Type]) -> RefAccessType {327    let identifier = &identifiers[id.0 as usize];328    let ty = &types[identifier.type_.0 as usize];329    if react_compiler_hir::is_ref_value_type(ty) {330        RefAccessType::RefValue {331            loc: None,332            ref_id: None,333        }334    } else if react_compiler_hir::is_use_ref_type(ty) {335        RefAccessType::Ref {336            ref_id: next_ref_id(),337        }338    } else {339        RefAccessType::None340    }341}342343fn is_ref_type(id: IdentifierId, identifiers: &[Identifier], types: &[Type]) -> bool {344    let identifier = &identifiers[id.0 as usize];345    react_compiler_hir::is_use_ref_type(&types[identifier.type_.0 as usize])346}347348fn is_ref_value_type(id: IdentifierId, identifiers: &[Identifier], types: &[Type]) -> bool {349    let identifier = &identifiers[id.0 as usize];350    react_compiler_hir::is_ref_value_type(&types[identifier.type_.0 as usize])351}352353fn destructure(ty: &RefAccessType) -> RefAccessType {354    match ty {355        RefAccessType::Structure {356            value: Some(inner), ..357        } => destructure(&RefAccessType::from_ref_type(inner)),358        other => other.clone(),359    }360}361362// --- Validation helpers ---363364fn validate_no_direct_ref_value_access(365    errors: &mut Vec<CompilerDiagnostic>,366    operand: &Place,367    env: &Env,368) {369    if let Some(ty) = env.get(operand.identifier) {370        let ty = destructure(ty);371        if let RefAccessType::RefValue { loc, .. } = &ty {372            errors.push(373                CompilerDiagnostic::new(374                    ErrorCategory::Refs,375                    "Cannot access refs during render",376                    Some(ERROR_DESCRIPTION.to_string()),377                )378                .with_detail(CompilerDiagnosticDetail::Error {379                    loc: loc.or(operand.loc),380                    message: Some("Cannot access ref value during render".to_string()),381                    identifier_name: None,382                }),383            );384        }385    }386}387388fn validate_no_ref_value_access(errors: &mut Vec<CompilerDiagnostic>, env: &Env, operand: &Place) {389    if let Some(ty) = env.get(operand.identifier) {390        let ty = destructure(ty);391        match &ty {392            RefAccessType::RefValue { loc, .. } => {393                errors.push(394                    CompilerDiagnostic::new(395                        ErrorCategory::Refs,396                        "Cannot access refs during render",397                        Some(ERROR_DESCRIPTION.to_string()),398                    )399                    .with_detail(CompilerDiagnosticDetail::Error {400                        loc: loc.or(operand.loc),401                        message: Some("Cannot access ref value during render".to_string()),402                        identifier_name: None,403                    }),404                );405            }406            RefAccessType::Structure {407                fn_type: Some(fn_type),408                ..409            } if fn_type.read_ref_effect => {410                errors.push(411                    CompilerDiagnostic::new(412                        ErrorCategory::Refs,413                        "Cannot access refs during render",414                        Some(ERROR_DESCRIPTION.to_string()),415                    )416                    .with_detail(CompilerDiagnosticDetail::Error {417                        loc: operand.loc,418                        message: Some("Cannot access ref value during render".to_string()),419                        identifier_name: None,420                    }),421                );422            }423            _ => {}424        }425    }426}427428fn validate_no_ref_passed_to_function(429    errors: &mut Vec<CompilerDiagnostic>,430    env: &Env,431    operand: &Place,432    loc: Option<SourceLocation>,433) {434    if let Some(ty) = env.get(operand.identifier) {435        let ty = destructure(ty);436        match &ty {437            RefAccessType::Ref { .. } | RefAccessType::RefValue { .. } => {438                let error_loc = if let RefAccessType::RefValue { loc: ref_loc, .. } = &ty {439                    ref_loc.or(loc)440                } else {441                    loc442                };443                errors.push(444                    CompilerDiagnostic::new(445                        ErrorCategory::Refs,446                        "Cannot access refs during render",447                        Some(ERROR_DESCRIPTION.to_string()),448                    )449                    .with_detail(CompilerDiagnosticDetail::Error {450                        loc: error_loc,451                        message: Some(452                            "Passing a ref to a function may read its value during render"453                                .to_string(),454                        ),455                        identifier_name: None,456                    }),457                );458            }459            RefAccessType::Structure {460                fn_type: Some(fn_type),461                ..462            } if fn_type.read_ref_effect => {463                errors.push(464                    CompilerDiagnostic::new(465                        ErrorCategory::Refs,466                        "Cannot access refs during render",467                        Some(ERROR_DESCRIPTION.to_string()),468                    )469                    .with_detail(CompilerDiagnosticDetail::Error {470                        loc,471                        message: Some(472                            "Passing a ref to a function may read its value during render"473                                .to_string(),474                        ),475                        identifier_name: None,476                    }),477                );478            }479            _ => {}480        }481    }482}483484fn validate_no_ref_update(485    errors: &mut Vec<CompilerDiagnostic>,486    env: &Env,487    operand: &Place,488    loc: Option<SourceLocation>,489) {490    if let Some(ty) = env.get(operand.identifier) {491        let ty = destructure(ty);492        match &ty {493            RefAccessType::Ref { .. } | RefAccessType::RefValue { .. } => {494                let error_loc = if let RefAccessType::RefValue { loc: ref_loc, .. } = &ty {495                    ref_loc.or(loc)496                } else {497                    loc498                };499                errors.push(500                    CompilerDiagnostic::new(501                        ErrorCategory::Refs,502                        "Cannot access refs during render",503                        Some(ERROR_DESCRIPTION.to_string()),504                    )505                    .with_detail(CompilerDiagnosticDetail::Error {506                        loc: error_loc,507                        message: Some("Cannot update ref during render".to_string()),508                        identifier_name: None,509                    }),510                );511            }512            _ => {}513        }514    }515}516517fn guard_check(errors: &mut Vec<CompilerDiagnostic>, operand: &Place, env: &Env) {518    if matches!(519        env.get(operand.identifier),520        Some(RefAccessType::Guard { .. })521    ) {522        errors.push(523            CompilerDiagnostic::new(524                ErrorCategory::Refs,525                "Cannot access refs during render",526                Some(ERROR_DESCRIPTION.to_string()),527            )528            .with_detail(CompilerDiagnosticDetail::Error {529                loc: operand.loc,530                message: Some("Cannot access ref value during render".to_string()),531                identifier_name: None,532            }),533        );534    }535}536537// --- Main entry point ---538539pub fn validate_no_ref_access_in_render(func: &HirFunction, env: &mut Environment) {540    let mut ref_env = Env::new();541    collect_temporaries_sidemap(func, &mut ref_env, &env.identifiers, &env.types);542    let mut errors: Vec<CompilerDiagnostic> = Vec::new();543    validate_no_ref_access_in_render_impl(544        func,545        &env.identifiers,546        &env.types,547        &env.functions,548        &*env,549        &mut ref_env,550        &mut errors,551    );552    for diagnostic in errors {553        env.record_diagnostic(diagnostic);554    }555}556557fn collect_temporaries_sidemap(558    func: &HirFunction,559    env: &mut Env,560    identifiers: &[Identifier],561    types: &[Type],562) {563    for (_, block) in &func.body.blocks {564        for &instr_id in &block.instructions {565            let instr = &func.instructions[instr_id.0 as usize];566            match &instr.value {567                InstructionValue::LoadLocal { place, .. } => {568                    let temp = env569                        .temporaries570                        .get(&place.identifier)571                        .cloned()572                        .unwrap_or_else(|| place.clone());573                    env.define(instr.lvalue.identifier, temp);574                }575                InstructionValue::StoreLocal { lvalue, value, .. } => {576                    let temp = env577                        .temporaries578                        .get(&value.identifier)579                        .cloned()580                        .unwrap_or_else(|| value.clone());581                    env.define(instr.lvalue.identifier, temp.clone());582                    env.define(lvalue.place.identifier, temp);583                }584                InstructionValue::PropertyLoad {585                    object, property, ..586                } => {587                    if is_ref_type(object.identifier, identifiers, types)588                        && *property == PropertyLiteral::String("current".to_string())589                    {590                        continue;591                    }592                    let temp = env593                        .temporaries594                        .get(&object.identifier)595                        .cloned()596                        .unwrap_or_else(|| object.clone());597                    env.define(instr.lvalue.identifier, temp);598                }599                _ => {}600            }601        }602    }603}604605fn validate_no_ref_access_in_render_impl(606    func: &HirFunction,607    identifiers: &[Identifier],608    types: &[Type],609    functions: &[HirFunction],610    env: &Environment,611    ref_env: &mut Env,612    errors: &mut Vec<CompilerDiagnostic>,613) -> RefAccessType {614    let mut return_values: Vec<RefAccessType> = Vec::new();615616    // Process params617    for param in &func.params {618        let place = match param {619            react_compiler_hir::ParamPattern::Place(p) => p,620            react_compiler_hir::ParamPattern::Spread(s) => &s.place,621        };622        ref_env.set(623            place.identifier,624            ref_type_of_type(place.identifier, identifiers, types),625        );626    }627628    // Collect identifiers that are interpolated as JSX children629    let mut interpolated_as_jsx: FxHashSet<IdentifierId> = FxHashSet::default();630    for (_, block) in &func.body.blocks {631        for &instr_id in &block.instructions {632            let instr = &func.instructions[instr_id.0 as usize];633            match &instr.value {634                InstructionValue::JsxExpression {635                    children: Some(children),636                    ..637                } => {638                    for child in children {639                        interpolated_as_jsx.insert(child.identifier);640                    }641                }642                InstructionValue::JsxFragment { children, .. } => {643                    for child in children {644                        interpolated_as_jsx.insert(child.identifier);645                    }646                }647                _ => {}648            }649        }650    }651652    // Fixed-point iteration (up to 10 iterations)653    for iteration in 0..10 {654        if iteration > 0 && !ref_env.has_changed() {655            break;656        }657        ref_env.reset_changed();658        return_values.clear();659        let mut safe_blocks: Vec<(BlockId, RefId)> = Vec::new();660661        for (_, block) in &func.body.blocks {662            safe_blocks.retain(|(block_id, _)| *block_id != block.id);663664            // Process phis665            for phi in &block.phis {666                let phi_types: Vec<RefAccessType> = phi667                    .operands668                    .values()669                    .map(|operand| {670                        ref_env671                            .get(operand.identifier)672                            .cloned()673                            .unwrap_or(RefAccessType::None)674                    })675                    .collect();676                ref_env.set(phi.place.identifier, join_ref_access_types_many(&phi_types));677            }678679            // Process instructions680            for &instr_id in &block.instructions {681                let instr = &func.instructions[instr_id.0 as usize];682                match &instr.value {683                    InstructionValue::JsxExpression { .. }684                    | InstructionValue::JsxFragment { .. } => {685                        for operand in &canonical_each_instruction_value_operand(&instr.value, env)686                        {687                            validate_no_direct_ref_value_access(errors, operand, ref_env);688                        }689                    }690                    InstructionValue::ComputedLoad {691                        object, property, ..692                    } => {693                        validate_no_direct_ref_value_access(errors, property, ref_env);694                        let obj_type = ref_env.get(object.identifier).cloned();695                        let lookup_type = match &obj_type {696                            Some(RefAccessType::Structure {697                                value: Some(value), ..698                            }) => Some(RefAccessType::from_ref_type(value)),699                            Some(RefAccessType::Ref { ref_id }) => Some(RefAccessType::RefValue {700                                loc: instr.loc,701                                ref_id: Some(*ref_id),702                            }),703                            _ => None,704                        };705                        ref_env.set(706                            instr.lvalue.identifier,707                            lookup_type.unwrap_or_else(|| {708                                ref_type_of_type(instr.lvalue.identifier, identifiers, types)709                            }),710                        );711                    }712                    InstructionValue::PropertyLoad { object, .. } => {713                        let obj_type = ref_env.get(object.identifier).cloned();714                        let lookup_type = match &obj_type {715                            Some(RefAccessType::Structure {716                                value: Some(value), ..717                            }) => Some(RefAccessType::from_ref_type(value)),718                            Some(RefAccessType::Ref { ref_id }) => Some(RefAccessType::RefValue {719                                loc: instr.loc,720                                ref_id: Some(*ref_id),721                            }),722                            _ => None,723                        };724                        ref_env.set(725                            instr.lvalue.identifier,726                            lookup_type.unwrap_or_else(|| {727                                ref_type_of_type(instr.lvalue.identifier, identifiers, types)728                            }),729                        );730                    }731                    InstructionValue::TypeCastExpression { value, .. } => {732                        ref_env.set(733                            instr.lvalue.identifier,734                            ref_env.get(value.identifier).cloned().unwrap_or_else(|| {735                                ref_type_of_type(instr.lvalue.identifier, identifiers, types)736                            }),737                        );738                    }739                    InstructionValue::LoadContext { place, .. }740                    | InstructionValue::LoadLocal { place, .. } => {741                        ref_env.set(742                            instr.lvalue.identifier,743                            ref_env.get(place.identifier).cloned().unwrap_or_else(|| {744                                ref_type_of_type(instr.lvalue.identifier, identifiers, types)745                            }),746                        );747                    }748                    InstructionValue::StoreContext { lvalue, value, .. }749                    | InstructionValue::StoreLocal { lvalue, value, .. } => {750                        ref_env.set(751                            lvalue.place.identifier,752                            ref_env.get(value.identifier).cloned().unwrap_or_else(|| {753                                ref_type_of_type(lvalue.place.identifier, identifiers, types)754                            }),755                        );756                        ref_env.set(757                            instr.lvalue.identifier,758                            ref_env.get(value.identifier).cloned().unwrap_or_else(|| {759                                ref_type_of_type(instr.lvalue.identifier, identifiers, types)760                            }),761                        );762                    }763                    InstructionValue::Destructure { value, lvalue, .. } => {764                        let obj_type = ref_env.get(value.identifier).cloned();765                        let lookup_type = match &obj_type {766                            Some(RefAccessType::Structure {767                                value: Some(value), ..768                            }) => Some(RefAccessType::from_ref_type(value)),769                            _ => None,770                        };771                        ref_env.set(772                            instr.lvalue.identifier,773                            lookup_type.clone().unwrap_or_else(|| {774                                ref_type_of_type(instr.lvalue.identifier, identifiers, types)775                            }),776                        );777                        for pattern_place in each_pattern_operand(&lvalue.pattern) {778                            ref_env.set(779                                pattern_place.identifier,780                                lookup_type.clone().unwrap_or_else(|| {781                                    ref_type_of_type(pattern_place.identifier, identifiers, types)782                                }),783                            );784                        }785                    }786                    InstructionValue::ObjectMethod { lowered_func, .. }787                    | InstructionValue::FunctionExpression { lowered_func, .. } => {788                        let inner = &functions[lowered_func.func.0 as usize];789                        let mut inner_errors: Vec<CompilerDiagnostic> = Vec::new();790                        let result = validate_no_ref_access_in_render_impl(791                            inner,792                            identifiers,793                            types,794                            functions,795                            env,796                            ref_env,797                            &mut inner_errors,798                        );799                        let (return_type, read_ref_effect) = if inner_errors.is_empty() {800                            (result, false)801                        } else {802                            (RefAccessType::None, true)803                        };804                        ref_env.set(805                            instr.lvalue.identifier,806                            RefAccessType::Structure {807                                value: None,808                                fn_type: Some(RefFnType {809                                    read_ref_effect,810                                    return_type: Box::new(return_type),811                                }),812                            },813                        );814                    }815                    InstructionValue::MethodCall { property, .. }816                    | InstructionValue::CallExpression {817                        callee: property, ..818                    } => {819                        let callee = property;820                        let mut return_type = RefAccessType::None;821                        let fn_type = ref_env.get(callee.identifier).cloned();822                        let mut did_error = false;823824                        if let Some(RefAccessType::Structure {825                            fn_type: Some(fn_ty),826                            ..827                        }) = &fn_type828                        {829                            return_type = *fn_ty.return_type.clone();830                            if fn_ty.read_ref_effect {831                                did_error = true;832                                errors.push(833                                    CompilerDiagnostic::new(834                                        ErrorCategory::Refs,835                                        "Cannot access refs during render",836                                        Some(ERROR_DESCRIPTION.to_string()),837                                    )838                                    .with_detail(839                                        CompilerDiagnosticDetail::Error {840                                            loc: callee.loc,841                                            message: Some(842                                                "This function accesses a ref value".to_string(),843                                            ),844                                            identifier_name: None,845                                        },846                                    ),847                                );848                            }849                        }850851                        /*852                         * If we already reported an error on this instruction, don't report853                         * duplicate errors854                         */855                        if !did_error {856                            let is_ref_lvalue =857                                is_ref_type(instr.lvalue.identifier, identifiers, types);858                            let callee_identifier = &identifiers[callee.identifier.0 as usize];859                            let callee_type = &types[callee_identifier.type_.0 as usize];860                            let hook_kind = env.get_hook_kind_for_type(callee_type).ok().flatten();861862                            if is_ref_lvalue863                                || (hook_kind.is_some()864                                    && !matches!(hook_kind, Some(&HookKind::UseState))865                                    && !matches!(hook_kind, Some(&HookKind::UseReducer)))866                            {867                                for operand in868                                    &canonical_each_instruction_value_operand(&instr.value, env)869                                {870                                    /*871                                     * Allow passing refs or ref-accessing functions when:872                                     * 1. lvalue is a ref (mergeRefs pattern)873                                     * 2. calling hooks (independently validated)874                                     */875                                    validate_no_direct_ref_value_access(errors, operand, ref_env);876                                }877                            } else if interpolated_as_jsx.contains(&instr.lvalue.identifier) {878                                for operand in879                                    &canonical_each_instruction_value_operand(&instr.value, env)880                                {881                                    /*882                                     * Special case: the lvalue is passed as a jsx child883                                     */884                                    validate_no_ref_value_access(errors, ref_env, operand);885                                }886                            } else if hook_kind.is_none() {887                                if let Some(ref effects) = instr.effects {888                                    /*889                                     * For non-hook functions with known aliasing effects,890                                     * use the effects to determine what validation to apply.891                                     * Track visited id:kind pairs to avoid duplicate errors.892                                     */893                                    let mut visited_effects: FxHashSet<String> =894                                        FxHashSet::default();895                                    for effect in effects {896                                        let (place, validation) = match effect {897                                            AliasingEffect::Freeze { value, .. } => {898                                                (Some(value), "direct-ref")899                                            }900                                            AliasingEffect::Mutate { value, .. }901                                            | AliasingEffect::MutateTransitive { value, .. }902                                            | AliasingEffect::MutateConditionally {903                                                value, ..904                                            }905                                            | AliasingEffect::MutateTransitiveConditionally {906                                                value,907                                                ..908                                            } => (Some(value), "ref-passed"),909                                            AliasingEffect::Render { place, .. } => {910                                                (Some(place), "ref-passed")911                                            }912                                            AliasingEffect::Capture { from, .. }913                                            | AliasingEffect::Alias { from, .. }914                                            | AliasingEffect::MaybeAlias { from, .. }915                                            | AliasingEffect::Assign { from, .. }916                                            | AliasingEffect::CreateFrom { from, .. } => {917                                                (Some(from), "ref-passed")918                                            }919                                            AliasingEffect::ImmutableCapture { from, .. } => {920                                                /*921                                                 * ImmutableCapture: check whether the same922                                                 * operand also has a Freeze effect to923                                                 * distinguish known signatures from924                                                 * downgraded defaults.925                                                 */926                                                let is_frozen = effects.iter().any(|e| {927                                                    matches!(928                                                        e,929                                                        AliasingEffect::Freeze { value, .. }930                                                            if value.identifier == from.identifier931                                                    )932                                                });933                                                (934                                                    Some(from),935                                                    if is_frozen {936                                                        "direct-ref"937                                                    } else {938                                                        "ref-passed"939                                                    },940                                                )941                                            }942                                            _ => (None, "none"),943                                        };944                                        if let Some(place) = place {945                                            if validation != "none" {946                                                let key = format!(947                                                    "{}:{}",948                                                    place.identifier.0, validation949                                                );950                                                if visited_effects.insert(key) {951                                                    if validation == "direct-ref" {952                                                        validate_no_direct_ref_value_access(953                                                            errors, place, ref_env,954                                                        );955                                                    } else {956                                                        validate_no_ref_passed_to_function(957                                                            errors, ref_env, place, place.loc,958                                                        );959                                                    }960                                                }961                                            }962                                        }963                                    }964                                } else {965                                    for operand in966                                        &canonical_each_instruction_value_operand(&instr.value, env)967                                    {968                                        validate_no_ref_passed_to_function(969                                            errors,970                                            ref_env,971                                            operand,972                                            operand.loc,973                                        );974                                    }975                                }976                            } else {977                                for operand in978                                    &canonical_each_instruction_value_operand(&instr.value, env)979                                {980                                    validate_no_ref_passed_to_function(981                                        errors,982                                        ref_env,983                                        operand,984                                        operand.loc,985                                    );986                                }987                            }988                        }989                        ref_env.set(instr.lvalue.identifier, return_type);990                    }991                    InstructionValue::ObjectExpression { .. }992                    | InstructionValue::ArrayExpression { .. } => {993                        let operands = canonical_each_instruction_value_operand(&instr.value, env);994                        let mut types_vec: Vec<RefAccessType> = Vec::new();995                        for operand in &operands {996                            validate_no_direct_ref_value_access(errors, operand, ref_env);997                            types_vec.push(998                                ref_env999                                    .get(operand.identifier)1000                                    .cloned()1001                                    .unwrap_or(RefAccessType::None),1002                            );1003                        }1004                        let value = join_ref_access_types_many(&types_vec);1005                        match &value {1006                            RefAccessType::None1007                            | RefAccessType::Guard { .. }1008                            | RefAccessType::Nullable => {1009                                ref_env.set(instr.lvalue.identifier, RefAccessType::None);1010                            }1011                            _ => {1012                                ref_env.set(1013                                    instr.lvalue.identifier,1014                                    RefAccessType::Structure {1015                                        value: value.to_ref_type().map(Box::new),1016                                        fn_type: None,1017                                    },1018                                );1019                            }1020                        }1021                    }1022                    InstructionValue::PropertyDelete { object, .. }1023                    | InstructionValue::PropertyStore { object, .. }1024                    | InstructionValue::ComputedDelete { object, .. }1025                    | InstructionValue::ComputedStore { object, .. } => {1026                        let target = ref_env.get(object.identifier).cloned();1027                        let mut found_safe = false;1028                        if matches!(&instr.value, InstructionValue::PropertyStore { .. }) {1029                            if let Some(RefAccessType::Ref { ref_id }) = &target {1030                                if let Some(pos) = safe_blocks.iter().position(|(_, r)| r == ref_id)1031                                {1032                                    safe_blocks.remove(pos);1033                                    found_safe = true;1034                                }1035                            }1036                        }1037                        if !found_safe {1038                            validate_no_ref_update(errors, ref_env, object, instr.loc);1039                        }1040                        match &instr.value {1041                            InstructionValue::ComputedDelete { property, .. }1042                            | InstructionValue::ComputedStore { property, .. } => {1043                                validate_no_ref_value_access(errors, ref_env, property);1044                            }1045                            _ => {}1046                        }1047                        match &instr.value {1048                            InstructionValue::ComputedStore { value, .. }1049                            | InstructionValue::PropertyStore { value, .. } => {1050                                validate_no_direct_ref_value_access(errors, value, ref_env);1051                                let value_type = ref_env.get(value.identifier).cloned();1052                                if let Some(RefAccessType::Structure { .. }) = &value_type {1053                                    let mut object_type = value_type.unwrap();1054                                    if let Some(t) = &target {1055                                        object_type = join_ref_access_types(&object_type, t);1056                                    }1057                                    ref_env.set(object.identifier, object_type);1058                                }1059                            }1060                            _ => {}1061                        }1062                    }1063                    InstructionValue::StartMemoize { .. }1064                    | InstructionValue::FinishMemoize { .. } => {}1065                    InstructionValue::LoadGlobal { binding, .. } => {1066                        if binding.name() == "undefined" {1067                            ref_env.set(instr.lvalue.identifier, RefAccessType::Nullable);1068                        }1069                    }1070                    InstructionValue::Primitive { value, .. } => {1071                        if matches!(value, PrimitiveValue::Null | PrimitiveValue::Undefined) {1072                            ref_env.set(instr.lvalue.identifier, RefAccessType::Nullable);1073                        }1074                    }1075                    InstructionValue::UnaryExpression {1076                        operator, value, ..1077                    } => {1078                        if *operator == UnaryOperator::Not {1079                            if let Some(RefAccessType::RefValue {1080                                ref_id: Some(ref_id),1081                                ..1082                            }) = ref_env.get(value.identifier).cloned().as_ref()1083                            {1084                                /*1085                                 * Record an error suggesting the `if (ref.current == null)` pattern,1086                                 * but also record the lvalue as a guard so that we don't emit a1087                                 * second error for the write to the ref1088                                 */1089                                ref_env.set(1090                                    instr.lvalue.identifier,1091                                    RefAccessType::Guard { ref_id: *ref_id },1092                                );1093                                errors.push(1094                                    CompilerDiagnostic::new(1095                                        ErrorCategory::Refs,1096                                        "Cannot access refs during render",1097                                        Some(ERROR_DESCRIPTION.to_string()),1098                                    )1099                                    .with_detail(CompilerDiagnosticDetail::Error {1100                                        loc: value.loc,1101                                        message: Some(1102                                            "Cannot access ref value during render"1103                                                .to_string(),1104                                        ),1105                                        identifier_name: None,1106                                    })1107                                    .with_detail(CompilerDiagnosticDetail::Hint {1108                                        message: "To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`".to_string(),1109                                    }),1110                                );1111                            } else {1112                                validate_no_ref_value_access(errors, ref_env, value);1113                            }1114                        } else {1115                            validate_no_ref_value_access(errors, ref_env, value);1116                        }1117                    }1118                    InstructionValue::BinaryExpression { left, right, .. } => {1119                        let left_type = ref_env.get(left.identifier).cloned();1120                        let right_type = ref_env.get(right.identifier).cloned();1121                        let mut nullish = false;1122                        let mut found_ref_id: Option<RefId> = None;11231124                        if let Some(RefAccessType::RefValue {1125                            ref_id: Some(id), ..1126                        }) = &left_type1127                        {1128                            found_ref_id = Some(*id);1129                        } else if let Some(RefAccessType::RefValue {1130                            ref_id: Some(id), ..1131                        }) = &right_type1132                        {1133                            found_ref_id = Some(*id);1134                        }11351136                        if matches!(&left_type, Some(RefAccessType::Nullable)) {1137                            nullish = true;1138                        } else if matches!(&right_type, Some(RefAccessType::Nullable)) {1139                            nullish = true;1140                        }11411142                        if let Some(ref_id) = found_ref_id {1143                            if nullish {1144                                ref_env1145                                    .set(instr.lvalue.identifier, RefAccessType::Guard { ref_id });1146                            } else {1147                                validate_no_ref_value_access(errors, ref_env, left);1148                                validate_no_ref_value_access(errors, ref_env, right);1149                            }1150                        } else {1151                            validate_no_ref_value_access(errors, ref_env, left);1152                            validate_no_ref_value_access(errors, ref_env, right);1153                        }1154                    }1155                    _ => {1156                        for operand in &canonical_each_instruction_value_operand(&instr.value, env)1157                        {1158                            validate_no_ref_value_access(errors, ref_env, operand);1159                        }1160                    }1161                }11621163                // Guard values are derived from ref.current, so they can only be used1164                // in if statement targets1165                for operand in &canonical_each_instruction_value_operand(&instr.value, env) {1166                    guard_check(errors, operand, ref_env);1167                }11681169                if is_ref_type(instr.lvalue.identifier, identifiers, types)1170                    && !matches!(1171                        ref_env.get(instr.lvalue.identifier),1172                        Some(RefAccessType::Ref { .. })1173                    )1174                {1175                    let existing = ref_env1176                        .get(instr.lvalue.identifier)1177                        .cloned()1178                        .unwrap_or(RefAccessType::None);1179                    ref_env.set(1180                        instr.lvalue.identifier,1181                        join_ref_access_types(1182                            &existing,1183                            &RefAccessType::Ref {1184                                ref_id: next_ref_id(),1185                            },1186                        ),1187                    );1188                }1189                if is_ref_value_type(instr.lvalue.identifier, identifiers, types)1190                    && !matches!(1191                        ref_env.get(instr.lvalue.identifier),1192                        Some(RefAccessType::RefValue { .. })1193                    )1194                {1195                    let existing = ref_env1196                        .get(instr.lvalue.identifier)1197                        .cloned()1198                        .unwrap_or(RefAccessType::None);1199                    ref_env.set(1200                        instr.lvalue.identifier,1201                        join_ref_access_types(1202                            &existing,1203                            &RefAccessType::RefValue {1204                                loc: instr.loc,1205                                ref_id: None,1206                            },1207                        ),1208                    );1209                }1210            }12111212            // Check if terminal is an `if` — push safe block for guard1213            if let Terminal::If {1214                test, fallthrough, ..1215            } = &block.terminal1216            {1217                if let Some(RefAccessType::Guard { ref_id }) = ref_env.get(test.identifier) {1218                    if !safe_blocks.iter().any(|(_, r)| r == ref_id) {1219                        safe_blocks.push((*fallthrough, *ref_id));1220                    }1221                }1222            }12231224            // Process terminal operands1225            for operand in &each_terminal_operand(&block.terminal) {1226                if !matches!(&block.terminal, Terminal::Return { .. }) {1227                    validate_no_ref_value_access(errors, ref_env, operand);1228                    if !matches!(&block.terminal, Terminal::If { .. }) {1229                        guard_check(errors, operand, ref_env);1230                    }1231                } else {1232                    // Allow functions containing refs to be returned, but not direct ref values1233                    validate_no_direct_ref_value_access(errors, operand, ref_env);1234                    guard_check(errors, operand, ref_env);1235                    if let Some(ty) = ref_env.get(operand.identifier) {1236                        return_values.push(ty.clone());1237                    }1238                }1239            }1240        }12411242        if !errors.is_empty() {1243            return RefAccessType::None;1244        }1245    }12461247    if ref_env.has_changed() {1248        errors.push(CompilerDiagnostic::new(1249            react_compiler_diagnostics::ErrorCategory::Invariant,1250            "Ref type environment did not converge",1251            None,1252        ));1253        return RefAccessType::None;1254    }12551256    join_ref_access_types_many(&return_values)1257}

Code quality findings 26

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 identifier = &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
let ty = &types[identifier.type_.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let identifier = &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
react_compiler_hir::is_use_ref_type(&types[identifier.type_.0 as usize])
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let identifier = &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
react_compiler_hir::is_ref_value_type(&types[identifier.type_.0 as usize])
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let instr = &func.instructions[instr_id.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let instr = &func.instructions[instr_id.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let instr = &func.instructions[instr_id.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let inner = &functions[lowered_func.func.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let callee_identifier = &identifiers[callee.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 callee_type = &types[callee_identifier.type_.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 mut object_type = value_type.unwrap();
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match self {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
_ => match (a.to_ref_type(), b.to_ref_type()) {
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
.unwrap_or_else(|| place.clone());
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 lookup_type = match &obj_type {
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 lookup_type = match &obj_type {
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 lookup_type = match &obj_type {
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
lookup_type.clone().unwrap_or_else(|| {
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
lookup_type.clone().unwrap_or_else(|| {
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
types_vec.push(
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match &value {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match &instr.value {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match &instr.value {
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
safe_blocks.push((*fallthrough, *ref_id));

Get this view in your editor

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