1use rustc_hash::FxHashMap;23use react_compiler_diagnostics::{4 CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory,5};6use react_compiler_hir::environment::Environment;7use react_compiler_hir::visitors::{each_instruction_value_lvalue, each_pattern_operand};8use react_compiler_hir::{9 FunctionId, HirFunction, Identifier, IdentifierId, InstructionValue, Place,10};1112/// Variable reference kind: local, context, or destructure.13#[derive(Debug, Clone, Copy, PartialEq, Eq)]14enum VarRefKind {15 Local,16 Context,17 Destructure,18}1920impl std::fmt::Display for VarRefKind {21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {22 match self {23 VarRefKind::Local => write!(f, "local"),24 VarRefKind::Context => write!(f, "context"),25 VarRefKind::Destructure => write!(f, "destructure"),26 }27 }28}2930type IdentifierKinds = FxHashMap<IdentifierId, (Place, VarRefKind)>;3132/// Validates that context variable lvalues are used consistently.33///34/// Port of ValidateContextVariableLValues.ts35pub fn validate_context_variable_lvalues(36 func: &HirFunction,37 env: &mut Environment,38) -> Result<(), CompilerDiagnostic> {39 validate_context_variable_lvalues_with_errors(40 func,41 &env.functions,42 &env.identifiers,43 &mut env.errors,44 )45}4647/// Like [`validate_context_variable_lvalues`], but writes diagnostics into the48/// provided `errors` instead of `env.errors`. Useful when the caller wants to49/// discard the diagnostics (e.g. when lowering is incomplete).50pub fn validate_context_variable_lvalues_with_errors(51 func: &HirFunction,52 functions: &[HirFunction],53 identifiers: &[Identifier],54 errors: &mut CompilerError,55) -> Result<(), CompilerDiagnostic> {56 let mut identifier_kinds: IdentifierKinds = FxHashMap::default();57 validate_context_variable_lvalues_impl(58 func,59 &mut identifier_kinds,60 functions,61 identifiers,62 errors,63 )64}6566fn validate_context_variable_lvalues_impl(67 func: &HirFunction,68 identifier_kinds: &mut IdentifierKinds,69 functions: &[HirFunction],70 identifiers: &[Identifier],71 errors: &mut CompilerError,72) -> Result<(), CompilerDiagnostic> {73 let mut inner_function_ids: Vec<FunctionId> = Vec::new();7475 for (_block_id, block) in &func.body.blocks {76 for &instr_id in &block.instructions {77 let instr = &func.instructions[instr_id.0 as usize];78 let value = &instr.value;7980 match value {81 InstructionValue::DeclareContext { lvalue, .. }82 | InstructionValue::StoreContext { lvalue, .. } => {83 visit(84 identifier_kinds,85 &lvalue.place,86 VarRefKind::Context,87 identifiers,88 errors,89 )?;90 }91 InstructionValue::LoadContext { place, .. } => {92 visit(93 identifier_kinds,94 place,95 VarRefKind::Context,96 identifiers,97 errors,98 )?;99 }100 InstructionValue::StoreLocal { lvalue, .. }101 | InstructionValue::DeclareLocal { lvalue, .. } => {102 visit(103 identifier_kinds,104 &lvalue.place,105 VarRefKind::Local,106 identifiers,107 errors,108 )?;109 }110 InstructionValue::LoadLocal { place, .. } => {111 visit(112 identifier_kinds,113 place,114 VarRefKind::Local,115 identifiers,116 errors,117 )?;118 }119 InstructionValue::PostfixUpdate { lvalue, .. }120 | InstructionValue::PrefixUpdate { lvalue, .. } => {121 visit(122 identifier_kinds,123 lvalue,124 VarRefKind::Local,125 identifiers,126 errors,127 )?;128 }129 InstructionValue::Destructure { lvalue, .. } => {130 for place in each_pattern_operand(&lvalue.pattern) {131 visit(132 identifier_kinds,133 &place,134 VarRefKind::Destructure,135 identifiers,136 errors,137 )?;138 }139 }140 InstructionValue::FunctionExpression { lowered_func, .. }141 | InstructionValue::ObjectMethod { lowered_func, .. } => {142 inner_function_ids.push(lowered_func.func);143 }144 _ => {145 for _ in each_instruction_value_lvalue(value) {146 errors.push_diagnostic(147 CompilerDiagnostic::new(148 ErrorCategory::Todo,149 "ValidateContextVariableLValues: unhandled instruction variant",150 None,151 )152 .with_detail(153 CompilerDiagnosticDetail::Error {154 loc: value.loc().copied(),155 message: None,156 identifier_name: None,157 },158 ),159 );160 }161 }162 }163 }164 }165166 // Process inner functions after the block loop to avoid borrow conflicts167 for func_id in inner_function_ids {168 let inner_func = &functions[func_id.0 as usize];169 validate_context_variable_lvalues_impl(170 inner_func,171 identifier_kinds,172 functions,173 identifiers,174 errors,175 )?;176 }177178 Ok(())179}180181/// Format a place like TS `printPlace()`: `<effect> <name>$<id>`182fn format_place(place: &Place, identifiers: &[Identifier]) -> String {183 let id = place.identifier;184 let ident = &identifiers[id.0 as usize];185 let name = match &ident.name {186 Some(n) => n.value().to_string(),187 None => String::new(),188 };189 format!("{} {}${}", place.effect, name, id.0)190}191192fn visit(193 identifiers: &mut IdentifierKinds,194 place: &Place,195 kind: VarRefKind,196 env_identifiers: &[Identifier],197 errors: &mut CompilerError,198) -> Result<(), CompilerDiagnostic> {199 if let Some((prev_place, prev_kind)) = identifiers.get(&place.identifier) {200 let was_context = *prev_kind == VarRefKind::Context;201 let is_context = kind == VarRefKind::Context;202 if was_context != is_context {203 if *prev_kind == VarRefKind::Destructure || kind == VarRefKind::Destructure {204 let loc = if kind == VarRefKind::Destructure {205 place.loc206 } else {207 prev_place.loc208 };209 errors.push_diagnostic(210 CompilerDiagnostic::new(211 ErrorCategory::Todo,212 "Support destructuring of context variables",213 None,214 )215 .with_detail(CompilerDiagnosticDetail::Error {216 loc,217 message: None,218 identifier_name: None,219 }),220 );221 return Ok(());222 }223 let place_str = format_place(place, env_identifiers);224 return Err(CompilerDiagnostic::new(225 ErrorCategory::Invariant,226 "Expected all references to a variable to be consistently local or context references",227 Some(format!(228 "Identifier {} is referenced as a {} variable, but was previously referenced as a {} variable",229 place_str, kind, prev_kind230 )),231 )232 .with_detail(CompilerDiagnosticDetail::Error {233 loc: place.loc,234 message: Some(format!("this is {}", prev_kind)),235 identifier_name: None,236 }));237 }238 }239 identifiers.insert(place.identifier, (place.clone(), kind));240 Ok(())241}