compiler/crates/react_compiler_hir/src/environment.rs RUST 1,155 lines View on github.com → Search inside
1use rustc_hash::{FxHashMap, FxHashSet};23use react_compiler_diagnostics::CompilerDiagnostic;4use react_compiler_diagnostics::CompilerError;5use react_compiler_diagnostics::CompilerErrorDetail;6use react_compiler_diagnostics::ErrorCategory;78use crate::default_module_type_provider::default_module_type_provider;9use crate::environment_config::EnvironmentConfig;10use crate::globals::Global;11use crate::globals::GlobalRegistry;12use crate::globals::{self};13use crate::object_shape::BUILT_IN_MIXED_READONLY_ID;14use crate::object_shape::FunctionSignature;15use crate::object_shape::HookKind;16use crate::object_shape::HookSignatureBuilder;17use crate::object_shape::ShapeRegistry;18use crate::object_shape::add_hook;19use crate::object_shape::default_mutating_hook;20use crate::object_shape::default_nonmutating_hook;21use crate::*;2223/// A variable rename from lowering: the binding at `declaration_start` position24/// was renamed from `original` to `renamed`.25#[derive(Debug, Clone)]26pub struct BindingRename {27    pub original: String,28    pub renamed: String,29    pub declaration_start: u32,30}3132/// Output mode for the compiler, mirrored from the entrypoint's CompilerOutputMode.33/// Stored on Environment so pipeline passes can access it.34#[derive(Debug, Clone, Copy, PartialEq, Eq)]35pub enum OutputMode {36    Ssr,37    Client,38    Lint,39}4041pub struct Environment {42    // Counters43    pub next_block_id_counter: u32,44    pub next_scope_id_counter: u32,45    next_mutable_range_id_counter: u32,4647    // Arenas (use direct field access for sliced borrows)48    pub identifiers: Vec<Identifier>,49    pub types: Vec<Type>,50    pub scopes: Vec<ReactiveScope>,51    pub functions: Vec<HirFunction>,5253    // Error accumulation54    pub errors: CompilerError,5556    // Function type classification (Component, Hook, Other)57    pub fn_type: ReactFunctionType,5859    // Output mode (Client, Ssr, Lint)60    pub output_mode: OutputMode,6162    // Source file code (for fast refresh hash computation)63    pub code: Option<String>,6465    // Source file name (for instrumentation)66    pub filename: Option<String>,6768    // Pre-resolved import local names for instrumentation/hook guards.69    // Set by the program-level code before compilation.70    pub instrument_fn_name: Option<String>,71    pub instrument_gating_name: Option<String>,72    pub hook_guard_name: Option<String>,7374    // Renames: tracks variable renames from lowering (original_name → new_name)75    // keyed by binding declaration position, for applying back to the Babel AST.76    pub renames: Vec<BindingRename>,7778    // Node IDs of identifiers that are actual references to bindings.79    // Used by codegen to filter type annotation renames — only rename identifiers80    // whose node_id is in this set (type labels like ObjectTypeIndexer params81    // are NOT in this set and should keep their original names).82    pub reference_node_ids: FxHashSet<u32>,8384    // Hoisted identifiers: tracks which bindings have already been hoisted85    // via DeclareContext to avoid duplicate hoisting.86    // Uses u32 to avoid depending on react_compiler_ast types.87    hoisted_identifiers: FxHashSet<u32>,8889    // Config flags for validation passes (kept for backwards compat with existing pipeline code)90    pub validate_preserve_existing_memoization_guarantees: bool,91    pub validate_no_set_state_in_render: bool,92    pub enable_preserve_existing_memoization_guarantees: bool,9394    // Type system registries95    globals: GlobalRegistry,96    pub shapes: ShapeRegistry,97    module_types: FxHashMap<String, Option<Global>>,98    module_type_errors: FxHashMap<String, Vec<String>>,99100    // Environment configuration (feature flags, custom hooks, etc.)101    pub config: EnvironmentConfig,102103    // Cached default hook types (lazily initialized)104    default_nonmutating_hook: Option<Global>,105    default_mutating_hook: Option<Global>,106107    // Outlined functions: functions extracted from the component during outlining passes108    outlined_functions: Vec<OutlinedFunctionEntry>,109110    // Known names for collision-aware UID generation. Lazily populated from111    // identifiers on first use, then updated with each generated name.112    // Matches Babel's generateUid behavior of checking hasBinding/hasReference.113    uid_known_names: Option<FxHashSet<String>>,114}115116/// An outlined function entry, stored on Environment during compilation.117/// Corresponds to TS `{ fn: HIRFunction, type: ReactFunctionType | null }`.118#[derive(Debug, Clone)]119pub struct OutlinedFunctionEntry {120    pub func: HirFunction,121    pub fn_type: Option<ReactFunctionType>,122}123124impl Environment {125    pub fn new() -> Self {126        Self::with_config(EnvironmentConfig::default())127    }128129    /// Create a new Environment with the given configuration.130    ///131    /// Initializes the shape and global registries, registers custom hooks,132    /// and sets up the module type cache.133    pub fn with_config(config: EnvironmentConfig) -> Self {134        let mut shapes = ShapeRegistry::with_base(globals::base_shapes());135        let mut global_registry = GlobalRegistry::with_base(globals::base_globals());136137        // Register custom hooks from config138        for (hook_name, hook) in &config.custom_hooks {139            // Don't overwrite existing globals (matches TS invariant)140            if global_registry.contains_key(hook_name) {141                continue;142            }143            let return_type = if hook.transitive_mixed_data {144                Type::Object {145                    shape_id: Some(BUILT_IN_MIXED_READONLY_ID.to_string()),146                }147            } else {148                Type::Poly149            };150            let hook_type = add_hook(151                &mut shapes,152                HookSignatureBuilder {153                    rest_param: Some(hook.effect_kind),154                    return_type,155                    return_value_kind: hook.value_kind,156                    hook_kind: HookKind::Custom,157                    no_alias: hook.no_alias,158                    ..Default::default()159                },160                None,161            );162            global_registry.insert(hook_name.clone(), hook_type);163        }164165        // Register reanimated module type when enabled166        let mut module_types: FxHashMap<String, Option<Global>> = FxHashMap::default();167        if config.enable_custom_type_definition_for_reanimated {168            let reanimated_module_type = globals::get_reanimated_module_type(&mut shapes);169            module_types.insert(170                "react-native-reanimated".to_string(),171                Some(reanimated_module_type),172            );173        }174175        Self {176            next_block_id_counter: 0,177            next_scope_id_counter: 0,178            next_mutable_range_id_counter: 0,179            identifiers: Vec::new(),180            types: Vec::new(),181            scopes: Vec::new(),182            functions: Vec::new(),183            errors: CompilerError::new(),184            fn_type: ReactFunctionType::Other,185            output_mode: OutputMode::Client,186            code: None,187            filename: None,188            instrument_fn_name: None,189            instrument_gating_name: None,190            hook_guard_name: None,191            renames: Vec::new(),192            reference_node_ids: FxHashSet::default(),193            hoisted_identifiers: FxHashSet::default(),194            validate_preserve_existing_memoization_guarantees: config195                .validate_preserve_existing_memoization_guarantees,196            validate_no_set_state_in_render: config.validate_no_set_state_in_render,197            enable_preserve_existing_memoization_guarantees: config198                .enable_preserve_existing_memoization_guarantees,199            globals: global_registry,200            shapes,201            module_types,202            module_type_errors: FxHashMap::default(),203            default_nonmutating_hook: None,204            default_mutating_hook: None,205            outlined_functions: Vec::new(),206            uid_known_names: None,207            config,208        }209    }210211    /// Create a child Environment for compiling an outlined function.212    ///213    /// The child shares the same config, globals, and shapes, and receives copies of214    /// all arenas (identifiers, types, scopes, functions) so that references from215    /// the outlined HIR remain valid. Block/scope counters start past the cloned216    /// data to avoid ID conflicts.217    pub fn for_outlined_fn(&self, fn_type: ReactFunctionType) -> Self {218        Self {219            // Start block counter past any existing blocks in the outlined function.220            // The outlined function has BlockId(0), parent may have more. Use parent's221            // counter which is guaranteed to be > any block ID in the outlined function.222            next_block_id_counter: self.next_block_id_counter,223            // Scope counter must be consistent with scopes vec length224            next_scope_id_counter: self.scopes.len() as u32,225            next_mutable_range_id_counter: self.next_mutable_range_id_counter,226            identifiers: self.identifiers.clone(),227            types: self.types.clone(),228            scopes: self.scopes.clone(),229            functions: self.functions.clone(),230            errors: CompilerError::new(),231            fn_type,232            output_mode: self.output_mode,233            code: self.code.clone(),234            filename: self.filename.clone(),235            instrument_fn_name: self.instrument_fn_name.clone(),236            instrument_gating_name: self.instrument_gating_name.clone(),237            hook_guard_name: self.hook_guard_name.clone(),238            renames: Vec::new(),239            reference_node_ids: FxHashSet::default(),240            hoisted_identifiers: FxHashSet::default(),241            validate_preserve_existing_memoization_guarantees: self242                .validate_preserve_existing_memoization_guarantees,243            validate_no_set_state_in_render: self.validate_no_set_state_in_render,244            enable_preserve_existing_memoization_guarantees: self245                .enable_preserve_existing_memoization_guarantees,246            globals: self.globals.clone(),247            shapes: self.shapes.clone(),248            module_types: self.module_types.clone(),249            module_type_errors: self.module_type_errors.clone(),250            config: self.config.clone(),251            default_nonmutating_hook: self.default_nonmutating_hook.clone(),252            default_mutating_hook: self.default_mutating_hook.clone(),253            outlined_functions: Vec::new(),254            uid_known_names: self.uid_known_names.clone(),255        }256    }257258    pub fn next_block_id(&mut self) -> BlockId {259        let id = BlockId(self.next_block_id_counter);260        self.next_block_id_counter += 1;261        id262    }263264    /// Create a new MutableRange with a unique ID.265    /// Use this when creating a logically new range (not copying an existing one).266    /// To copy a range preserving its identity, use `.clone()` instead.267    pub fn new_mutable_range(268        &mut self,269        start: EvaluationOrder,270        end: EvaluationOrder,271    ) -> MutableRange {272        let id = MutableRangeId(self.next_mutable_range_id_counter);273        self.next_mutable_range_id_counter += 1;274        MutableRange { id, start, end }275    }276277    /// Allocate a new Identifier in the arena with default values,278    /// returns its IdentifierId.279    pub fn next_identifier_id(&mut self) -> IdentifierId {280        let id = IdentifierId(self.identifiers.len() as u32);281        let type_id = self.make_type();282        let mutable_range = self.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0));283        self.identifiers.push(Identifier {284            id,285            declaration_id: DeclarationId(id.0),286            name: None,287            mutable_range,288            scope: None,289            type_: type_id,290            loc: None,291        });292        id293    }294295    /// Allocate a new ReactiveScope in the arena, returns its ScopeId.296    pub fn next_scope_id(&mut self) -> ScopeId {297        let id = ScopeId(self.next_scope_id_counter);298        self.next_scope_id_counter += 1;299        let range = self.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0));300        self.scopes.push(ReactiveScope {301            id,302            range,303            dependencies: Vec::new(),304            declarations: Vec::new(),305            reassignments: Vec::new(),306            early_return_value: None,307            merged: Vec::new(),308            loc: None,309        });310        id311    }312313    /// Allocate a new Type in the arena, returns its TypeId.314    pub fn next_type_id(&mut self) -> TypeId {315        let id = TypeId(self.types.len() as u32);316        self.types.push(Type::TypeVar { id });317        id318    }319320    /// Allocate a new Type (TypeVar) in the arena, returns its TypeId.321    pub fn make_type(&mut self) -> TypeId {322        self.next_type_id()323    }324325    pub fn add_function(&mut self, func: HirFunction) -> FunctionId {326        let id = FunctionId(self.functions.len() as u32);327        self.functions.push(func);328        id329    }330331    pub fn record_error(&mut self, detail: CompilerErrorDetail) -> Result<(), CompilerError> {332        if detail.category == ErrorCategory::Invariant {333            let detail_clone = detail.clone();334            self.errors.push_error_detail(detail);335            let mut err = CompilerError::new();336            err.push_error_detail(detail_clone);337            return Err(err);338        }339        self.errors.push_error_detail(detail);340        Ok(())341    }342343    pub fn record_diagnostic(&mut self, diagnostic: CompilerDiagnostic) {344        self.errors.push_diagnostic(diagnostic);345    }346347    pub fn has_errors(&self) -> bool {348        self.errors.has_any_errors()349    }350351    pub fn error_count(&self) -> usize {352        self.errors.details.len()353    }354355    /// Check if any recorded errors have Invariant category.356    /// In TS, Invariant errors throw immediately from recordError(),357    /// which aborts the current operation.358    pub fn has_invariant_errors(&self) -> bool {359        self.errors.has_invariant_errors()360    }361362    pub fn errors(&self) -> &CompilerError {363        &self.errors364    }365366    pub fn take_errors(&mut self) -> CompilerError {367        let mut errors = std::mem::take(&mut self.errors);368        // Mark as not thrown — these are accumulated errors returned at the end369        // of the pipeline, not errors thrown by a pass.370        errors.is_thrown = false;371        errors372    }373374    /// Take errors added after position `since_count`, leaving earlier errors in place.375    /// Used to detect new errors added by a specific pass.376    pub fn take_errors_since(&mut self, since_count: usize) -> CompilerError {377        let mut taken = CompilerError::new();378        if self.errors.details.len() > since_count {379            taken.details = self.errors.details.split_off(since_count);380        }381        taken382    }383384    /// Take only the Invariant errors, leaving non-Invariant errors in place.385    /// In TS, Invariant errors throw as a separate CompilerError, so only386    /// the Invariant error is surfaced.387    pub fn take_invariant_errors(&mut self) -> CompilerError {388        let mut invariant = CompilerError::new();389        let mut remaining = CompilerError::new();390        let old = std::mem::take(&mut self.errors);391        for detail in old.details {392            let is_invariant = match &detail {393                react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => {394                    d.category == react_compiler_diagnostics::ErrorCategory::Invariant395                }396                react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => {397                    d.category == react_compiler_diagnostics::ErrorCategory::Invariant398                }399            };400            if is_invariant {401                invariant.details.push(detail);402            } else {403                remaining.details.push(detail);404            }405        }406        self.errors = remaining;407        invariant408    }409410    /// Check if any recorded errors have Todo category.411    /// In TS, Todo errors throw immediately via CompilerError.throwTodo().412    pub fn has_todo_errors(&self) -> bool {413        self.errors.details.iter().any(|d| match d {414            react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => {415                d.category == react_compiler_diagnostics::ErrorCategory::Todo416            }417            react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => {418                d.category == react_compiler_diagnostics::ErrorCategory::Todo419            }420        })421    }422423    /// Take errors that would have been thrown in TS (Invariant and Todo),424    /// leaving other accumulated errors in place.425    pub fn take_thrown_errors(&mut self) -> CompilerError {426        let mut thrown = CompilerError::new();427        let mut remaining = CompilerError::new();428        let old = std::mem::take(&mut self.errors);429        for detail in old.details {430            let is_thrown = match &detail {431                react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => {432                    d.category == react_compiler_diagnostics::ErrorCategory::Invariant433                        || d.category == react_compiler_diagnostics::ErrorCategory::Todo434                }435                react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => {436                    d.category == react_compiler_diagnostics::ErrorCategory::Invariant437                        || d.category == react_compiler_diagnostics::ErrorCategory::Todo438                }439            };440            if is_thrown {441                thrown.details.push(detail);442            } else {443                remaining.details.push(detail);444            }445        }446        self.errors = remaining;447        thrown448    }449450    /// Check if a binding has been hoisted (via DeclareContext) already.451    pub fn is_hoisted_identifier(&self, binding_id: u32) -> bool {452        self.hoisted_identifiers.contains(&binding_id)453    }454455    /// Mark a binding as hoisted.456    pub fn add_hoisted_identifier(&mut self, binding_id: u32) {457        self.hoisted_identifiers.insert(binding_id);458    }459460    // =========================================================================461    // Type resolution methods (ported from Environment.ts)462    // =========================================================================463464    /// Resolve a non-local binding to its type. Ported from TS `getGlobalDeclaration`.465    ///466    /// The `loc` parameter is used for error diagnostics when validating module type467    /// configurations. Pass `None` if no source location is available.468    pub fn get_global_declaration(469        &mut self,470        binding: &NonLocalBinding,471        loc: Option<SourceLocation>,472    ) -> Result<Option<Global>, CompilerError> {473        match binding {474            NonLocalBinding::ModuleLocal { name, .. } => {475                if is_hook_name(name) {476                    Ok(Some(self.get_custom_hook_type()))477                } else {478                    Ok(None)479                }480            }481            NonLocalBinding::Global { name, .. } => {482                if let Some(ty) = self.globals.get(name) {483                    return Ok(Some(ty.clone()));484                }485                if is_hook_name(name) {486                    Ok(Some(self.get_custom_hook_type()))487                } else {488                    Ok(None)489                }490            }491            NonLocalBinding::ImportSpecifier {492                name,493                module,494                imported,495            } => {496                if self.is_known_react_module(module) {497                    if let Some(ty) = self.globals.get(imported) {498                        return Ok(Some(ty.clone()));499                    }500                    if is_hook_name(imported) || is_hook_name(name) {501                        return Ok(Some(self.get_custom_hook_type()));502                    }503                    return Ok(None);504                }505506                // Try module type provider. We resolve first, then do property507                // lookup on the cloned result to avoid double-borrow of self.508                let module_type = self.resolve_module_type(module);509510                // Check for module type validation errors (hook-name vs hook-type mismatches)511                if let Some(errors) = self.module_type_errors.remove(module.as_str()) {512                    if let Some(first_error) = errors.into_iter().next() {513                        self.record_error(514                            CompilerErrorDetail::new(515                                ErrorCategory::Config,516                                "Invalid type configuration for module",517                            )518                            .with_description(format!("{}", first_error))519                            .with_loc(loc),520                        )?;521                    }522                }523524                if let Some(module_type) = module_type {525                    if let Some(imported_type) =526                        Self::get_property_type_from_shapes(&self.shapes, &module_type, imported)527                    {528                        return Ok(Some(imported_type));529                    }530                }531532                if is_hook_name(imported) || is_hook_name(name) {533                    Ok(Some(self.get_custom_hook_type()))534                } else {535                    Ok(None)536                }537            }538            NonLocalBinding::ImportDefault { name, module }539            | NonLocalBinding::ImportNamespace { name, module } => {540                let is_default = matches!(binding, NonLocalBinding::ImportDefault { .. });541542                if self.is_known_react_module(module) {543                    if let Some(ty) = self.globals.get(name) {544                        return Ok(Some(ty.clone()));545                    }546                    if is_hook_name(name) {547                        return Ok(Some(self.get_custom_hook_type()));548                    }549                    return Ok(None);550                }551552                let module_type = self.resolve_module_type(module);553554                // Check for module type validation errors (hook-name vs hook-type mismatches)555                if let Some(errors) = self.module_type_errors.remove(module.as_str()) {556                    if let Some(first_error) = errors.into_iter().next() {557                        self.record_error(558                            CompilerErrorDetail::new(559                                ErrorCategory::Config,560                                "Invalid type configuration for module",561                            )562                            .with_description(format!("{}", first_error))563                            .with_loc(loc),564                        )?;565                    }566                }567568                if let Some(module_type) = module_type {569                    let imported_type = if is_default {570                        Self::get_property_type_from_shapes(&self.shapes, &module_type, "default")571                    } else {572                        Some(module_type)573                    };574                    if let Some(imported_type) = imported_type {575                        // Validate hook-name vs hook-type consistency for module name576                        let expect_hook = is_hook_name(module);577                        let is_hook = self578                            .get_hook_kind_for_type(&imported_type)579                            .ok()580                            .flatten()581                            .is_some();582                        if expect_hook != is_hook {583                            self.record_error(584                                CompilerErrorDetail::new(585                                    ErrorCategory::Config,586                                    "Invalid type configuration for module",587                                )588                                .with_description(format!(589                                    "Expected type for `import ... from '{}'` {} based on the module name",590                                    module,591                                    if expect_hook { "to be a hook" } else { "not to be a hook" }592                                ))593                                .with_loc(loc),594                            )?;595                        }596                        return Ok(Some(imported_type));597                    }598                }599600                if is_hook_name(name) {601                    Ok(Some(self.get_custom_hook_type()))602                } else {603                    Ok(None)604                }605            }606        }607    }608609    /// Static helper: resolve a property type using only the shapes registry.610    /// Used internally to avoid double-borrow of `self`. Includes hook-name611    /// fallback matching TS `getPropertyType`.612    fn get_property_type_from_shapes(613        shapes: &ShapeRegistry,614        receiver: &Type,615        property: &str,616    ) -> Option<Type> {617        let shape_id = match receiver {618            Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),619            _ => None,620        };621        if let Some(shape_id) = shape_id {622            let shape = shapes.get(shape_id)?;623            if let Some(ty) = shape.properties.get(property) {624                return Some(ty.clone());625            }626            if let Some(ty) = shape.properties.get("*") {627                return Some(ty.clone());628            }629            // Hook-name fallback: callers that need the custom hook type630            // check is_hook_name after this returns None, which produces631            // the same result as the TS getPropertyType hook-name fallback.632        }633        None634    }635636    /// Get the type of a named property on a receiver type.637    /// Ported from TS `getPropertyType`.638    pub fn get_property_type(639        &mut self,640        receiver: &Type,641        property: &str,642    ) -> Result<Option<Type>, CompilerDiagnostic> {643        let shape_id = match receiver {644            Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),645            _ => None,646        };647        if let Some(shape_id) = shape_id {648            let shape = self.shapes.get(shape_id).ok_or_else(|| {649                CompilerDiagnostic::new(650                    ErrorCategory::Invariant,651                    format!(652                        "[HIR] Forget internal error: cannot resolve shape {}",653                        shape_id654                    ),655                    None,656                )657            })?;658            if let Some(ty) = shape.properties.get(property) {659                return Ok(Some(ty.clone()));660            }661            // Fall through to wildcard662            if let Some(ty) = shape.properties.get("*") {663                return Ok(Some(ty.clone()));664            }665            // If property name looks like a hook, return custom hook type666            if is_hook_name(property) {667                return Ok(Some(self.get_custom_hook_type()));668            }669            return Ok(None);670        }671        // No shape ID — if property looks like a hook, return custom hook type672        if is_hook_name(property) {673            return Ok(Some(self.get_custom_hook_type()));674        }675        Ok(None)676    }677678    /// Get the type of a numeric property on a receiver type.679    /// Ported from the numeric branch of TS `getPropertyType`.680    pub fn get_property_type_numeric(681        &self,682        receiver: &Type,683    ) -> Result<Option<Type>, CompilerDiagnostic> {684        let shape_id = match receiver {685            Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),686            _ => None,687        };688        if let Some(shape_id) = shape_id {689            let shape = self.shapes.get(shape_id).ok_or_else(|| {690                CompilerDiagnostic::new(691                    ErrorCategory::Invariant,692                    format!(693                        "[HIR] Forget internal error: cannot resolve shape {}",694                        shape_id695                    ),696                    None,697                )698            })?;699            return Ok(shape.properties.get("*").cloned());700        }701        Ok(None)702    }703704    /// Get the fallthrough (wildcard `*`) property type for computed property access.705    /// Ported from TS `getFallthroughPropertyType`.706    pub fn get_fallthrough_property_type(707        &self,708        receiver: &Type,709    ) -> Result<Option<Type>, CompilerDiagnostic> {710        let shape_id = match receiver {711            Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),712            _ => None,713        };714        if let Some(shape_id) = shape_id {715            let shape = self.shapes.get(shape_id).ok_or_else(|| {716                CompilerDiagnostic::new(717                    ErrorCategory::Invariant,718                    format!(719                        "[HIR] Forget internal error: cannot resolve shape {}",720                        shape_id721                    ),722                    None,723                )724            })?;725            return Ok(shape.properties.get("*").cloned());726        }727        Ok(None)728    }729730    /// Get the function signature for a function type.731    /// Ported from TS `getFunctionSignature`.732    pub fn get_function_signature(733        &self,734        ty: &Type,735    ) -> Result<Option<&FunctionSignature>, CompilerDiagnostic> {736        let shape_id = match ty {737            Type::Function { shape_id, .. } => shape_id.as_deref(),738            _ => return Ok(None),739        };740        if let Some(shape_id) = shape_id {741            let shape = self.shapes.get(shape_id).ok_or_else(|| {742                CompilerDiagnostic::new(743                    ErrorCategory::Invariant,744                    format!(745                        "[HIR] Forget internal error: cannot resolve shape {}",746                        shape_id747                    ),748                    None,749                )750            })?;751            return Ok(shape.function_type.as_ref());752        }753        Ok(None)754    }755756    /// Get the hook kind for a type, if it represents a hook.757    /// Ported from TS `getHookKindForType` in HIR.ts.758    pub fn get_hook_kind_for_type(759        &self,760        ty: &Type,761    ) -> Result<Option<&HookKind>, CompilerDiagnostic> {762        Ok(self763            .get_function_signature(ty)?764            .and_then(|sig| sig.hook_kind.as_ref()))765    }766767    /// Resolve the module type provider for a given module name.768    /// Caches results. Checks pre-resolved provider results first, then falls769    /// back to `defaultModuleTypeProvider` (hardcoded).770    fn resolve_module_type(&mut self, module_name: &str) -> Option<Global> {771        if let Some(cached) = self.module_types.get(module_name) {772            return cached.clone();773        }774775        // Check pre-resolved provider results first, then fall back to default776        let module_config = self777            .config778            .module_type_provider779            .as_ref()780            .and_then(|map| map.get(module_name).cloned())781            .or_else(|| default_module_type_provider(module_name));782783        let module_type = module_config.map(|config| {784            let mut type_errors: Vec<String> = Vec::new();785            let ty = globals::install_type_config_with_errors(786                &mut self.globals,787                &mut self.shapes,788                &config,789                module_name,790                (),791                &mut type_errors,792            );793            // Store errors for later reporting when the import is actually used794            for err in type_errors {795                self.module_type_errors796                    .entry(module_name.to_string())797                    .or_default()798                    .push(err);799            }800            ty801        });802        self.module_types803            .insert(module_name.to_string(), module_type.clone());804        module_type805    }806807    fn is_known_react_module(&self, module_name: &str) -> bool {808        let lower = module_name.to_lowercase();809        lower == "react" || lower == "react-dom"810    }811812    fn get_custom_hook_type(&mut self) -> Global {813        if self.config.enable_assume_hooks_follow_rules_of_react {814            if self.default_nonmutating_hook.is_none() {815                self.default_nonmutating_hook = Some(default_nonmutating_hook(&mut self.shapes));816            }817            self.default_nonmutating_hook.clone().unwrap()818        } else {819            if self.default_mutating_hook.is_none() {820                self.default_mutating_hook = Some(default_mutating_hook(&mut self.shapes));821            }822            self.default_mutating_hook.clone().unwrap()823        }824    }825826    /// Public accessor for the custom hook type, used by InferTypes for827    /// property resolution fallback when a property name looks like a hook.828    pub fn get_custom_hook_type_opt(&mut self) -> Option<Global> {829        Some(self.get_custom_hook_type())830    }831832    /// Get a reference to the shapes registry.833    pub fn shapes(&self) -> &ShapeRegistry {834        &self.shapes835    }836837    /// Get a reference to the globals registry.838    pub fn globals(&self) -> &GlobalRegistry {839        &self.globals840    }841842    /// Generate a globally unique identifier name, analogous to TS843    /// `generateGloballyUniqueIdentifierName` which delegates to Babel's844    /// `scope.generateUidIdentifier`. Matches Babel's naming convention:845    /// first name is `_<name>`, subsequent are `_<name>2`, `_<name>3`, etc.846    /// Also applies Babel's `toIdentifier` sanitization on the input name.847    ///848    /// Like Babel's `generateUid`, checks for collisions against existing849    /// bindings (source-level identifier names) and previously generated UIDs,850    /// rather than using a blind counter.851    pub fn generate_globally_unique_identifier_name(&mut self, name: Option<&str>) -> String {852        let base = name.unwrap_or("temp");853        // Apply Babel's toIdentifier sanitization:854        // 1. Replace non-identifier chars with '-'855        // 2. Strip leading '-' and digits856        // 3. CamelCase: replace '-' sequences + optional following char with uppercase of that char857        let mut dashed = String::new();858        for c in base.chars() {859            if c.is_ascii_alphanumeric() || c == '_' || c == '$' {860                dashed.push(c);861            } else {862                dashed.push('-');863            }864        }865        // Strip leading dashes and digits866        let trimmed = dashed.trim_start_matches(|c: char| c == '-' || c.is_ascii_digit());867        // CamelCase conversion: replace sequences of '-' followed by optional char with uppercase868        let mut camel = String::new();869        let mut chars = trimmed.chars().peekable();870        while let Some(c) = chars.next() {871            if c == '-' {872                while chars.peek() == Some(&'-') {873                    chars.next();874                }875                if let Some(next) = chars.next() {876                    for uc in next.to_uppercase() {877                        camel.push(uc);878                    }879                }880            } else {881                camel.push(c);882            }883        }884        if camel.is_empty() {885            camel = "temp".to_string();886        }887        // Strip leading '_' and trailing digits (Babel's generateUid behavior)888        let stripped = camel.trim_start_matches('_');889        let stripped = stripped.trim_end_matches(|c: char| c.is_ascii_digit());890        let uid_base = if stripped.is_empty() {891            "temp"892        } else {893            stripped894        };895896        // Lazily build the set of known names from existing identifiers.897        // This approximates Babel's hasBinding/hasGlobal/hasReference checks.898        if self.uid_known_names.is_none() {899            let mut known = FxHashSet::default();900            for id in &self.identifiers {901                if let Some(name) = &id.name {902                    known.insert(name.value().to_string());903                }904            }905            self.uid_known_names = Some(known);906        }907908        // Find a name that doesn't collide, matching Babel's generateUid loop909        let mut i = 1u32;910        let uid = loop {911            let candidate = if i == 1 {912                format!("_{}", uid_base)913            } else {914                format!("_{}{}", uid_base, i)915            };916            i += 1;917            if !self.uid_known_names.as_ref().unwrap().contains(&candidate) {918                break candidate;919            }920        };921922        // Register the generated name so subsequent calls see it923        self.uid_known_names.as_mut().unwrap().insert(uid.clone());924925        uid926    }927928    /// Seed the UID known names set with external names (e.g. from ProgramContext).929    /// This ensures UID generation avoids names generated by previous function compilations,930    /// matching Babel's behavior where the program scope accumulates all generated UIDs.931    pub fn seed_uid_known_names(&mut self, names: &FxHashSet<String>) {932        match &mut self.uid_known_names {933            Some(existing) => existing.extend(names.iter().cloned()),934            None => self.uid_known_names = Some(names.clone()),935        }936    }937938    /// Return the UID known names accumulated during this compilation.939    pub fn take_uid_known_names(&mut self) -> Option<FxHashSet<String>> {940        self.uid_known_names.take()941    }942943    /// Record an outlined function (extracted during outlineFunctions or outlineJSX).944    /// Corresponds to TS `env.outlineFunction(fn, type)`.945    pub fn outline_function(&mut self, func: HirFunction, fn_type: Option<ReactFunctionType>) {946        self.outlined_functions947            .push(OutlinedFunctionEntry { func, fn_type });948    }949950    /// Get the outlined functions accumulated during compilation.951    pub fn get_outlined_functions(&self) -> &[OutlinedFunctionEntry] {952        &self.outlined_functions953    }954955    /// Take the outlined functions, leaving the vec empty.956    pub fn take_outlined_functions(&mut self) -> Vec<OutlinedFunctionEntry> {957        std::mem::take(&mut self.outlined_functions)958    }959960    /// Whether memoization is enabled for this compilation.961    /// Ported from TS `get enableMemoization()` in Environment.ts.962    /// Returns true for client/lint modes, false for SSR.963    pub fn enable_memoization(&self) -> bool {964        match self.output_mode {965            OutputMode::Client | OutputMode::Lint => true,966            OutputMode::Ssr => false,967        }968    }969970    /// Whether validations are enabled for this compilation.971    /// Ported from TS `get enableValidations()` in Environment.ts.972    pub fn enable_validations(&self) -> bool {973        match self.output_mode {974            OutputMode::Client | OutputMode::Lint | OutputMode::Ssr => true,975        }976    }977978    // =========================================================================979    // Name resolution helpers980    // =========================================================================981982    /// Get the user-visible name for an identifier.983    ///984    /// First checks the identifier's own name. If None, looks for another985    /// identifier with the same `declaration_id` that has a name. This handles986    /// SSA identifiers that don't carry names but share a declaration_id with987    /// the original named identifier from lowering.988    ///989    /// This is analogous to `identifierName` on Babel's SourceLocation,990    /// which the parser sets on every identifier node.991    pub fn identifier_name_for_id(&self, id: IdentifierId) -> Option<String> {992        let ident = &self.identifiers[id.0 as usize];993        if let Some(name) = &ident.name {994            return Some(name.value().to_string());995        }996        // Fall back: find another identifier with the same declaration_id that has a Named name997        let decl_id = ident.declaration_id;998        for other in &self.identifiers {999            if other.declaration_id == decl_id {1000                if let Some(IdentifierName::Named(name)) = &other.name {1001                    return Some(name.clone());1002                }1003            }1004        }1005        None1006    }10071008    // =========================================================================1009    // ID-based type helper methods1010    // =========================================================================10111012    /// Check whether the function type for an identifier has a noAlias signature.1013    /// Looks up the identifier's type and checks its function signature.1014    pub fn has_no_alias_signature(&self, identifier_id: IdentifierId) -> bool {1015        let ty = &self.types[self.identifiers[identifier_id.0 as usize].type_.0 as usize];1016        self.get_function_signature(ty)1017            .ok()1018            .flatten()1019            .map_or(false, |sig| sig.no_alias)1020    }10211022    /// Get the hook kind for an identifier, if its type represents a hook.1023    /// Looks up the identifier's type and delegates to `get_hook_kind_for_type`.1024    pub fn get_hook_kind_for_id(1025        &self,1026        identifier_id: IdentifierId,1027    ) -> Result<Option<&HookKind>, CompilerDiagnostic> {1028        let ty = &self.types[self.identifiers[identifier_id.0 as usize].type_.0 as usize];1029        self.get_hook_kind_for_type(ty)1030    }1031}10321033impl Default for Environment {1034    fn default() -> Self {1035        Self::new()1036    }1037}10381039/// Check if a name matches the React hook naming convention: `use[A-Z0-9]`.1040/// Ported from TS `isHookName` in Environment.ts.1041pub fn is_hook_name(name: &str) -> bool {1042    if name.len() < 4 {1043        return false;1044    }1045    if !name.starts_with("use") {1046        return false;1047    }1048    let fourth_char = name.as_bytes()[3];1049    fourth_char.is_ascii_uppercase() || fourth_char.is_ascii_digit()1050}10511052/// Returns true if the name follows React naming conventions (component or hook).1053/// Components start with an uppercase letter; hooks match `use[A-Z0-9]`.1054pub fn is_react_like_name(name: &str) -> bool {1055    if name.is_empty() {1056        return false;1057    }1058    let first_char = name.as_bytes()[0];1059    if first_char.is_ascii_uppercase() {1060        return true;1061    }1062    is_hook_name(name)1063}10641065#[cfg(test)]1066mod tests {1067    use super::*;10681069    #[test]1070    fn test_is_hook_name() {1071        assert!(is_hook_name("useState"));1072        assert!(is_hook_name("useEffect"));1073        assert!(is_hook_name("useMyHook"));1074        assert!(is_hook_name("use3rdParty"));1075        assert!(!is_hook_name("use"));1076        assert!(!is_hook_name("used"));1077        assert!(!is_hook_name("useless"));1078        assert!(!is_hook_name("User"));1079        assert!(!is_hook_name("foo"));1080    }10811082    #[test]1083    fn test_environment_has_globals() {1084        let env = Environment::new();1085        assert!(env.globals().contains_key("useState"));1086        assert!(env.globals().contains_key("useEffect"));1087        assert!(env.globals().contains_key("useRef"));1088        assert!(env.globals().contains_key("Math"));1089        assert!(env.globals().contains_key("console"));1090        assert!(env.globals().contains_key("Array"));1091        assert!(env.globals().contains_key("Object"));1092    }10931094    #[test]1095    fn test_get_property_type_array() {1096        let mut env = Environment::new();1097        let array_type = Type::Object {1098            shape_id: Some("BuiltInArray".to_string()),1099        };1100        let map_type = env.get_property_type(&array_type, "map").unwrap();1101        assert!(map_type.is_some());1102        let push_type = env.get_property_type(&array_type, "push").unwrap();1103        assert!(push_type.is_some());1104        let nonexistent = env1105            .get_property_type(&array_type, "nonExistentMethod")1106            .unwrap();1107        assert!(nonexistent.is_none());1108    }11091110    #[test]1111    fn test_get_function_signature() {1112        let env = Environment::new();1113        let use_state_type = env.globals().get("useState").unwrap();1114        let sig = env.get_function_signature(use_state_type).unwrap();1115        assert!(sig.is_some());1116        let sig = sig.unwrap();1117        assert!(sig.hook_kind.is_some());1118        assert_eq!(sig.hook_kind.as_ref().unwrap(), &HookKind::UseState);1119    }11201121    #[test]1122    fn test_get_global_declaration() {1123        let mut env = Environment::new();1124        // Global binding1125        let binding = NonLocalBinding::Global {1126            name: "Math".to_string(),1127        };1128        let result = env.get_global_declaration(&binding, None).unwrap();1129        assert!(result.is_some());11301131        // Import from react1132        let binding = NonLocalBinding::ImportSpecifier {1133            name: "useState".to_string(),1134            module: "react".to_string(),1135            imported: "useState".to_string(),1136        };1137        let result = env.get_global_declaration(&binding, None).unwrap();1138        assert!(result.is_some());11391140        // Unknown global1141        let binding = NonLocalBinding::Global {1142            name: "unknownThing".to_string(),1143        };1144        let result = env.get_global_declaration(&binding, None).unwrap();1145        assert!(result.is_none());11461147        // Hook-like name gets default hook type1148        let binding = NonLocalBinding::Global {1149            name: "useCustom".to_string(),1150        };1151        let result = env.get_global_declaration(&binding, None).unwrap();1152        assert!(result.is_some());1153    }1154}

Code quality findings 35

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
self.default_nonmutating_hook.clone().unwrap()
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
self.default_mutating_hook.clone().unwrap()
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
if !self.uid_known_names.as_ref().unwrap().contains(&candidate) {
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
self.uid_known_names.as_mut().unwrap().insert(uid.clone());
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 = &self.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 = &self.types[self.identifiers[identifier_id.0 as usize].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 ty = &self.types[self.identifiers[identifier_id.0 as usize].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
/// Check if a name matches the React hook naming convention: `use[A-Z0-9]`.
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 fourth_char = name.as_bytes()[3];
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
/// Components start with an uppercase letter; hooks match `use[A-Z0-9]`.
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 first_char = name.as_bytes()[0];
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 result = env.get_global_declaration(&binding, None).unwrap();
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 result = env.get_global_declaration(&binding, None).unwrap();
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 result = env.get_global_declaration(&binding, None).unwrap();
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use crate::*;
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
shape_id: Some(BUILT_IN_MIXED_READONLY_ID.to_string()),
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
invariant.details.push(detail);
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 shape_id = match receiver {
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 shape_id = match receiver {
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 shape_id = match receiver {
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 shape_id = match receiver {
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 shape_id = match ty {
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
.entry(module_name.to_string())
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
.push(err);
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
.insert(module_name.to_string(), module_type.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
.insert(module_name.to_string(), module_type.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
dashed.push(c);
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
dashed.push('-');
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
camel.push(uc);
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
camel.push(c);
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
camel = "temp".to_string();
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
known.insert(name.value().to_string());
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
return Some(name.value().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
return Some(name.clone());
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use super::*;

Get this view in your editor

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