Skip to main content

tsz_checker/state/
state.rs

1//! # `CheckerState` - Type Checker Orchestration Layer
2//!
3//! This module serves as the orchestration layer for the TypeScript type checker.
4//! It coordinates between various specialized checking modules while maintaining
5//! shared state and caching for performance.
6//!
7//! ## Architecture - Modular Design
8//!
9//! The checker has been decomposed into focused modules, each responsible for
10//! a specific aspect of type checking:
11//!
12//! ### Core Orchestration (This Module - state.rs)
13//! - **Entry Points**: `check_source_file`, `check_statement`
14//! - **Type Resolution**: `get_type_of_node`, `get_type_of_symbol`
15//! - **Caching & Lifecycle**: `cache_symbol_type`, node type cache management
16//! - **Delegation**: Coordinates calls to specialized modules
17//!
18//! ## Extracted Modules
19//!
20//! ### Type Computation (`type_computation.rs` - 3,189 lines)
21//! - `get_type_of_binary_expression`
22//! - `get_type_of_call_expression`
23//! - `get_type_of_property_access`
24//! - `get_type_of_element_access`
25//! - `get_type_of_object_literal`
26//! - `get_type_of_array_literal`
27//! - And 30+ other type computation functions
28//!
29//! ### Type Checking (`type_checking.rs` - 9,556 lines)
30//! - **Section 1-54**: Organized by functionality
31//! - Declaration checking (classes, interfaces, enums)
32//! - Statement checking (if, while, for, return)
33//! - Property access validation
34//! - Constructor checking
35//! - Function signature validation
36//!
37//! ### Symbol Resolution (`symbol_resolver.rs` - 1,380 lines)
38//! - `resolve_type_to_symbol`
39//! - `resolve_value_symbol`
40//! - `resolve_heritage_symbol`
41//! - Private brand checking
42//! - Import/Export resolution
43//!
44//! ### Flow Analysis (`flow_analysis.rs` - 1,511 lines)
45//! - Definite assignment checking
46//! - Type narrowing (typeof, discriminant)
47//! - Control flow analysis
48//! - TDZ (temporal dead zone) detection
49//!
50//! ### Error Reporting (`error_reporter.rs` - 1,923 lines)
51//! - All `error_*` methods
52//! - Diagnostic formatting
53//! - Error reporting with detailed reasons
54//!
55//! ## Remaining in state.rs (~12,974 lines)
56//!
57//! The code remaining in this file is primarily:
58//! 1. **Orchestration** (~4,000 lines): Entry points that coordinate between modules
59//! 2. **Caching** (~2,000 lines): Node type cache, symbol type cache management
60//! 3. **Dispatchers** (~3,000 lines): `compute_type_of_node` delegates to `type_computation` functions
61//! 4. **Type Relations** (~2,000 lines): `is_assignable_to`, `is_subtype_of` (wrapper around solver)
62//! 5. **Constructor/Class Helpers** (~2,000 lines): Complex type resolution for classes and inheritance
63//!
64//! ## Performance Optimizations
65//!
66//! - **Node Type Cache**: Avoids recomputing types for the same node
67//! - **Symbol Type Cache**: Caches computed types for symbols
68//! - **Fuel Management**: Prevents infinite loops and timeouts
69//! - **Cycle Detection**: Detects circular type references
70//!
71//! ## Usage
72//!
73//! ```rust,ignore
74//! use crate::state::CheckerState;
75//!
76//! let mut checker = CheckerState::new(&arena, &binder, &types, file_name, options);
77//! checker.check_source_file(root_idx);
78//! ```
79//!
80//! # Step 12: Orchestration Layer Documentation ✅ COMPLETE
81//!
82//! **Date**: 2026-01-24
83//! **Status**: Documentation complete
84//! **Lines**: 12,974 (50.5% reduction from 26,217 original)
85//! **Extracted**: 17,559 lines across 5 specialized modules
86//!
87//! The 2,000 line target was deemed unrealistic as the remaining code is
88//! necessary orchestration that cannot be extracted without:
89//! - Breaking the clean delegation pattern to specialized modules
90//! - Creating circular dependencies between modules
91//! - Duplicating shared state management code
92
93use crate::CheckerContext;
94use crate::context::CheckerOptions;
95use tsz_binder::BinderState;
96use tsz_binder::SymbolId;
97use tsz_parser::parser::NodeIndex;
98use tsz_parser::parser::node::NodeArena;
99use tsz_parser::parser::syntax_kind_ext;
100use tsz_solver::{QueryDatabase, TypeId, substitute_this_type};
101
102thread_local! {
103    /// Shared depth counter for all cross-arena delegation points.
104    /// Prevents stack overflow from deeply nested CheckerState creation.
105    static CROSS_ARENA_DEPTH: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
106}
107
108// =============================================================================
109// CheckerState
110// =============================================================================
111
112/// Type checker state using `NodeArena` and Solver type system.
113///
114/// This is a performance-optimized checker that works directly with the
115/// cache-friendly Node architecture and uses the solver's `TypeInterner`
116/// for structural type equality.
117///
118/// The state is stored in a `CheckerContext` which can be shared with
119/// specialized checker modules (expressions, statements, declarations).
120pub struct CheckerState<'a> {
121    /// Shared checker context containing all state.
122    pub ctx: CheckerContext<'a>,
123}
124
125// Re-export from centralized limits — do NOT redefine these here.
126pub use tsz_common::limits::MAX_CALL_DEPTH;
127pub use tsz_common::limits::MAX_INSTANTIATION_DEPTH;
128pub use tsz_common::limits::MAX_TREE_WALK_ITERATIONS;
129pub use tsz_common::limits::MAX_TYPE_RESOLUTION_OPS;
130
131#[derive(Copy, Clone, Debug, PartialEq, Eq)]
132pub enum EnumKind {
133    Numeric,
134    String,
135    Mixed,
136}
137
138#[derive(Copy, Clone, Debug, PartialEq, Eq)]
139pub(crate) enum MemberAccessLevel {
140    Private,
141    Protected,
142}
143
144#[derive(Clone, Debug)]
145pub(crate) struct MemberAccessInfo {
146    pub(crate) level: MemberAccessLevel,
147    pub(crate) declaring_class_idx: NodeIndex,
148    pub(crate) declaring_class_name: String,
149}
150
151#[derive(Copy, Clone, Debug, PartialEq, Eq)]
152pub(crate) enum MemberLookup {
153    NotFound,
154    Public,
155    Restricted(MemberAccessLevel),
156}
157
158// Re-export flow analysis types for internal use
159pub(crate) use crate::flow_analysis::{ComputedKey, PropertyKey};
160
161/// Mode for resolving parameter types during extraction.
162/// Used to consolidate duplicate parameter extraction functions.
163#[derive(Copy, Clone, Debug, PartialEq, Eq)]
164pub(crate) enum ParamTypeResolutionMode {
165    /// Use `get_type_from_type_node_in_type_literal` - for type literal contexts
166    InTypeLiteral,
167    /// Use `get_type_from_type_node` - for declaration contexts
168    FromTypeNode,
169    /// Use `get_type_of_node` - for expression/general contexts
170    OfNode,
171}
172
173// =============================================================================
174// AssignabilityOverrideProvider Implementation
175// =============================================================================
176
177/// Helper struct that implements `AssignabilityOverrideProvider` by delegating
178/// to `CheckerState` methods. Captures the `TypeEnvironment` reference.
179pub(crate) struct CheckerOverrideProvider<'a, 'b> {
180    checker: &'a CheckerState<'b>,
181    env: Option<&'a tsz_solver::TypeEnvironment>,
182}
183
184impl<'a, 'b> CheckerOverrideProvider<'a, 'b> {
185    pub(crate) const fn new(
186        checker: &'a CheckerState<'b>,
187        env: Option<&'a tsz_solver::TypeEnvironment>,
188    ) -> Self {
189        Self { checker, env }
190    }
191}
192
193impl<'a, 'b> tsz_solver::AssignabilityOverrideProvider for CheckerOverrideProvider<'a, 'b> {
194    fn enum_assignability_override(&self, _source: TypeId, _target: TypeId) -> Option<bool> {
195        // Delegate to Solver's enumeration assignability override.
196        // The Solver's CompatChecker now has complete enumeration logic:
197        // - Parent identity checks (E.A -> E)
198        // - String enumeration opacity (StringEnum -> string rejected)
199        // - Member -> member nominality (E.A -> E.B rejected)
200        // - Rule #7 numeric enumeration assignability (number -> numeric enumeration TYPE allowed)
201        // Returning None allows the Solver to handle all enumeration assignability checks.
202        None
203    }
204
205    fn abstract_constructor_assignability_override(
206        &self,
207        source: TypeId,
208        target: TypeId,
209    ) -> Option<bool> {
210        self.checker
211            .abstract_constructor_assignability_override(source, target, self.env)
212    }
213
214    fn constructor_accessibility_override(&self, source: TypeId, target: TypeId) -> Option<bool> {
215        self.checker
216            .constructor_accessibility_override(source, target, self.env)
217    }
218}
219
220impl<'a> CheckerState<'a> {
221    /// Create a new `CheckerState`.
222    ///
223    /// # Arguments
224    /// * `arena` - The AST node arena
225    /// * `binder` - The binder state with symbols
226    /// * `types` - The shared type interner (for thread-safe type deduplication)
227    /// * `file_name` - The source file name
228    /// * `compiler_options` - Compiler options for type checking
229    pub fn new(
230        arena: &'a NodeArena,
231        binder: &'a BinderState,
232        types: &'a dyn QueryDatabase,
233        file_name: String,
234        compiler_options: CheckerOptions,
235    ) -> Self {
236        CheckerState {
237            ctx: CheckerContext::new(arena, binder, types, file_name, compiler_options),
238        }
239    }
240
241    /// Create a new `CheckerState` with a shared `DefinitionStore`.
242    ///
243    /// This ensures that all type definitions (interfaces, type aliases, etc.) across
244    /// different files and lib contexts share the same `DefId` namespace, preventing
245    /// `DefId` collisions.
246    ///
247    /// # Arguments
248    /// * `definition_store` - Shared `DefinitionStore` (wrapped in Arc for thread-safety)
249    /// * Other args same as `new()`
250    pub fn new_with_shared_def_store(
251        arena: &'a NodeArena,
252        binder: &'a BinderState,
253        types: &'a dyn QueryDatabase,
254        file_name: String,
255        compiler_options: CheckerOptions,
256        definition_store: std::sync::Arc<tsz_solver::def::DefinitionStore>,
257    ) -> Self {
258        CheckerState {
259            ctx: CheckerContext::new_with_shared_def_store(
260                arena,
261                binder,
262                types,
263                file_name,
264                compiler_options,
265                definition_store,
266            ),
267        }
268    }
269
270    /// Create a new `CheckerState` with a persistent cache.
271    /// This allows reusing type checking results from previous queries.
272    ///
273    /// # Arguments
274    /// * `arena` - The AST node arena
275    /// * `binder` - The binder state with symbols
276    /// * `types` - The shared type interner
277    /// * `file_name` - The source file name
278    /// * `cache` - The persistent type cache from previous queries
279    /// * `compiler_options` - Compiler options for type checking
280    pub fn with_cache(
281        arena: &'a NodeArena,
282        binder: &'a BinderState,
283        types: &'a dyn QueryDatabase,
284        file_name: String,
285        cache: crate::TypeCache,
286        compiler_options: CheckerOptions,
287    ) -> Self {
288        CheckerState {
289            ctx: CheckerContext::with_cache(
290                arena,
291                binder,
292                types,
293                file_name,
294                cache,
295                compiler_options,
296            ),
297        }
298    }
299
300    /// Create a child `CheckerState` that shares the parent's caches.
301    /// This is used for temporary checkers (e.g., cross-file symbol resolution)
302    /// to ensure cache results are not lost (fixes Cache Isolation Bug).
303    pub fn with_parent_cache(
304        arena: &'a NodeArena,
305        binder: &'a BinderState,
306        types: &'a dyn QueryDatabase,
307        file_name: String,
308        compiler_options: CheckerOptions,
309        parent: &Self,
310    ) -> Self {
311        CheckerState {
312            ctx: CheckerContext::with_parent_cache(
313                arena,
314                binder,
315                types,
316                file_name,
317                compiler_options,
318                &parent.ctx,
319            ),
320        }
321    }
322
323    /// Thread-local guard for cross-arena delegation depth.
324    /// All cross-arena delegation points (`delegate_cross_arena_symbol_resolution`,
325    /// `get_type_params_for_symbol`, `type_of_value_declaration`) MUST call this
326    /// before creating a child `CheckerState`. Returns true if delegation is allowed.
327    pub(crate) fn enter_cross_arena_delegation() -> bool {
328        let d = CROSS_ARENA_DEPTH.with(std::cell::Cell::get);
329        if d >= 5 {
330            return false;
331        }
332        CROSS_ARENA_DEPTH.with(|c| c.set(d + 1));
333        true
334    }
335
336    /// Decrement the cross-arena delegation depth counter.
337    pub(crate) fn leave_cross_arena_delegation() {
338        CROSS_ARENA_DEPTH.with(|c| c.set(c.get().saturating_sub(1)));
339    }
340
341    fn should_apply_flow_narrowing_for_identifier(&self, idx: NodeIndex) -> bool {
342        use tsz_binder::symbol_flags;
343        use tsz_scanner::SyntaxKind;
344
345        if self.ctx.skip_flow_narrowing {
346            return false;
347        }
348
349        let Some(node) = self.ctx.arena.get(idx) else {
350            return false;
351        };
352
353        if node.kind == SyntaxKind::ThisKeyword as u16 {
354            return true;
355        }
356
357        if node.kind != SyntaxKind::Identifier as u16 {
358            return false;
359        }
360
361        let Some(sym_id) = self
362            .ctx
363            .binder
364            .get_node_symbol(idx)
365            .or_else(|| self.ctx.binder.resolve_identifier(self.ctx.arena, idx))
366        else {
367            return false;
368        };
369        let Some(symbol) = self.ctx.binder.get_symbol(sym_id) else {
370            return false;
371        };
372
373        if (symbol.flags & symbol_flags::VARIABLE) == 0 {
374            return false;
375        }
376
377        let mut value_decl = symbol.value_declaration;
378        if value_decl.is_none() {
379            return true;
380        }
381
382        let Some(mut decl_node) = self.ctx.arena.get(value_decl) else {
383            return true;
384        };
385        if decl_node.kind == SyntaxKind::Identifier as u16
386            && let Some(ext) = self.ctx.arena.get_extended(value_decl)
387            && ext.parent.is_some()
388            && let Some(parent_node) = self.ctx.arena.get(ext.parent)
389            && parent_node.kind == syntax_kind_ext::VARIABLE_DECLARATION
390        {
391            value_decl = ext.parent;
392            decl_node = parent_node;
393        }
394
395        if decl_node.kind != syntax_kind_ext::VARIABLE_DECLARATION {
396            return true;
397        }
398        if !self.is_const_variable_declaration(value_decl) {
399            return true;
400        }
401
402        let Some(var_decl) = self.ctx.arena.get_variable_declaration(decl_node) else {
403            return true;
404        };
405        if var_decl.type_annotation.is_some() || var_decl.initializer.is_none() {
406            return true;
407        }
408
409        let Some(init_node) = self.ctx.arena.get(var_decl.initializer) else {
410            return true;
411        };
412        !(init_node.kind == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
413            || init_node.kind == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION)
414    }
415
416    /// Check if we are currently inside a cross-arena delegation.
417    /// Used to skip position-based checks (like TDZ) that compare node positions
418    /// from different arenas.
419    pub(crate) fn is_in_cross_arena_delegation() -> bool {
420        CROSS_ARENA_DEPTH.with(|c| c.get() > 0)
421    }
422
423    /// Check if the source file has any parse errors.
424    ///
425    /// This flag is set by the driver before type checking based on parse diagnostics.
426    /// It's used to suppress certain type-level diagnostics when the file
427    /// has syntax errors (e.g., JSON files parsed as TypeScript).
428    pub(crate) const fn has_parse_errors(&self) -> bool {
429        self.ctx.has_parse_errors
430    }
431
432    /// Check if the source file has real syntax errors (not just conflict markers).
433    /// Conflict markers (TS1185) are treated as trivia and don't affect AST structure,
434    /// so they should not suppress TS2304 errors.
435    pub(crate) const fn has_syntax_parse_errors(&self) -> bool {
436        self.ctx.has_syntax_parse_errors
437    }
438
439    /// Check if a node's span overlaps with or is very close to a parse error position.
440    /// Used to suppress cascading checker diagnostics (e.g. TS2391, TS2364) when the
441    /// node is likely a parser-recovery artifact.
442    /// Check if a parse error falls directly within the node's span (no margin).
443    /// Used for tight suppression checks where the generous margin of
444    /// `node_has_nearby_parse_error` would cause false positives.
445    pub(crate) fn node_span_contains_parse_error(&self, idx: NodeIndex) -> bool {
446        if !self.has_syntax_parse_errors() || self.ctx.syntax_parse_error_positions.is_empty() {
447            return false;
448        }
449        let Some(node) = self.ctx.arena.get(idx) else {
450            return false;
451        };
452        for &err_pos in &self.ctx.syntax_parse_error_positions {
453            if err_pos >= node.pos && err_pos < node.end {
454                return true;
455            }
456        }
457        false
458    }
459
460    pub(crate) fn node_has_nearby_parse_error(&self, idx: NodeIndex) -> bool {
461        if !self.has_syntax_parse_errors() || self.ctx.syntax_parse_error_positions.is_empty() {
462            return false;
463        }
464        let Some(node) = self.ctx.arena.get(idx) else {
465            return false;
466        };
467        // A generous window: if any parse error is within the node's span or up to
468        // 8 bytes beyond it, consider the node tainted by parser recovery.
469        const MARGIN: u32 = 8;
470        let node_start = node.pos.saturating_sub(MARGIN);
471        let node_end = node.end.saturating_add(MARGIN);
472        for &err_pos in &self.ctx.syntax_parse_error_positions {
473            if err_pos >= node_start && err_pos <= node_end {
474                return true;
475            }
476        }
477        false
478    }
479
480    /// Apply `this` type substitution to a method call's return type.
481    ///
482    /// When a method returns `this`, the return type should be the type of the receiver.
483    /// For `obj.method()` where method returns `this`, we substitute `ThisType` with typeof obj.
484    pub(crate) fn apply_this_substitution_to_call_return(
485        &mut self,
486        return_type: tsz_solver::TypeId,
487        call_expression: tsz_parser::parser::NodeIndex,
488    ) -> tsz_solver::TypeId {
489        use tsz_solver::TypeId;
490
491        // Fast path: intrinsic types can't contain ThisType
492        if return_type.is_intrinsic() {
493            return return_type;
494        }
495
496        // Try to extract the receiver from the call expression.
497        // The call_expression parameter is actually the callee expression (call.expression),
498        // which for method calls is a PropertyAccessExpression.
499        // For `obj.method()`, this is `obj.method`, whose `.expression` is `obj`.
500        let node = match self.ctx.arena.get(call_expression) {
501            Some(n) => n,
502            None => return return_type,
503        };
504
505        if let Some(access) = self.ctx.arena.get_access_expr(node) {
506            let receiver_type = self.get_type_of_node(access.expression);
507            if receiver_type != TypeId::ERROR && receiver_type != TypeId::ANY {
508                return substitute_this_type(self.ctx.types, return_type, receiver_type);
509            }
510        }
511
512        return_type
513    }
514
515    /// Create a new `CheckerState` with explicit compiler options.
516    ///
517    /// # Arguments
518    /// * `arena` - The AST node arena
519    /// * `binder` - The binder state with symbols
520    /// * `types` - The shared type interner
521    /// * `file_name` - The source file name
522    /// * `compiler_options` - Compiler options for type checking
523    pub fn with_options(
524        arena: &'a NodeArena,
525        binder: &'a BinderState,
526        types: &'a dyn QueryDatabase,
527        file_name: String,
528        compiler_options: &CheckerOptions,
529    ) -> Self {
530        CheckerState {
531            ctx: CheckerContext::with_options(arena, binder, types, file_name, compiler_options),
532        }
533    }
534
535    /// Create a new `CheckerState` with explicit compiler options and a shared `DefinitionStore`.
536    ///
537    /// This is used in parallel checking to ensure all files share the same `DefId` namespace.
538    pub fn with_options_and_shared_def_store(
539        arena: &'a NodeArena,
540        binder: &'a BinderState,
541        types: &'a dyn QueryDatabase,
542        file_name: String,
543        compiler_options: &CheckerOptions,
544        definition_store: std::sync::Arc<tsz_solver::def::DefinitionStore>,
545    ) -> Self {
546        let compiler_options = compiler_options.clone().apply_strict_defaults();
547        CheckerState {
548            ctx: CheckerContext::new_with_shared_def_store(
549                arena,
550                binder,
551                types,
552                file_name,
553                compiler_options,
554                definition_store,
555            ),
556        }
557    }
558
559    /// Create a new `CheckerState` with explicit compiler options and a persistent cache.
560    pub fn with_cache_and_options(
561        arena: &'a NodeArena,
562        binder: &'a BinderState,
563        types: &'a dyn QueryDatabase,
564        file_name: String,
565        cache: crate::TypeCache,
566        compiler_options: &CheckerOptions,
567    ) -> Self {
568        CheckerState {
569            ctx: CheckerContext::with_cache_and_options(
570                arena,
571                binder,
572                types,
573                file_name,
574                cache,
575                compiler_options,
576            ),
577        }
578    }
579
580    /// Extract the persistent cache from this checker.
581    /// This allows saving type checking results for future queries.
582    pub fn extract_cache(self) -> crate::TypeCache {
583        self.ctx.extract_cache()
584    }
585
586    // =========================================================================
587    // Symbol Type Caching
588    // =========================================================================
589
590    /// Cache a computed symbol type for fast lookup and incremental type checking.
591    ///
592    /// This function stores the computed type of a symbol in the `symbol_types` cache,
593    /// allowing subsequent lookups to avoid recomputing the type.
594    ///
595    /// ## Caching Strategy:
596    /// - Types are cached after first computation
597    /// - Cache key is the `SymbolId`
598    /// - Cache persists for the lifetime of the type check
599    ///
600    /// ## Incremental Type Checking:
601    /// - When a symbol changes, its cache entry is invalidated
602    /// - Dependent symbols are re-computed on next access
603    /// - Enables efficient re-typechecking of modified files
604    ///
605    /// ## Cache Invalidation:
606    /// - Symbol modifications trigger dependency tracking
607    /// - Dependent symbols are tracked via `record_symbol_dependency`
608    /// - Cache is cleared for invalidated symbols
609    ///
610    /// ## Performance:
611    /// - Avoids expensive type recomputation
612    /// - Critical for performance in large codebases
613    /// - Most symbol types are looked up multiple times
614    ///
615    /// ## TypeScript Examples:
616    /// ```typescript
617    /// interface User {
618    ///   name: string;
619    ///   age: number;
620    /// }
621    /// let user: User;
622    /// // First lookup: computes User type, caches it
623    /// // Second lookup: returns cached User type (fast)
624    ///
625    /// function process(u: User) {
626    ///   // User type parameter is cached
627    ///   // Multiple uses of u resolve to the same cached type
628    /// }
629    /// ```
630    pub(crate) fn cache_symbol_type(&mut self, sym_id: SymbolId, type_id: TypeId) {
631        self.ctx.symbol_types.insert(sym_id, type_id);
632    }
633
634    pub(crate) fn record_symbol_dependency(&mut self, dependency: SymbolId) {
635        let Some(&current) = self.ctx.symbol_dependency_stack.last() else {
636            return;
637        };
638        if current == dependency {
639            return;
640        }
641        self.ctx
642            .symbol_dependencies
643            .entry(current)
644            .or_default()
645            .insert(dependency);
646    }
647
648    pub(crate) fn push_symbol_dependency(&mut self, sym_id: SymbolId, clear_deps: bool) {
649        if clear_deps {
650            self.ctx.symbol_dependencies.remove(&sym_id);
651        }
652        self.ctx.symbol_dependency_stack.push(sym_id);
653    }
654
655    pub(crate) fn pop_symbol_dependency(&mut self) {
656        self.ctx.symbol_dependency_stack.pop();
657    }
658
659    /// Infer and cache parameter types using contextual typing.
660    ///
661    /// This is needed for cases like:
662    /// `export function filter<T>(arr: T[], predicate: (item: T) => boolean) { for (const item of arr) { ... } }`
663    /// where `item`'s type comes from the contextual type of `arr`.
664    pub(crate) fn infer_parameter_types_from_context(&mut self, params: &[NodeIndex]) {
665        for &param_idx in params {
666            let Some(param_node) = self.ctx.arena.get(param_idx) else {
667                continue;
668            };
669            let Some(param) = self.ctx.arena.get_parameter(param_node) else {
670                continue;
671            };
672
673            // Only infer when there's no annotation and no default value.
674            if param.type_annotation.is_some() || param.initializer.is_some() {
675                continue;
676            }
677
678            let Some(sym_id) = self
679                .ctx
680                .binder
681                .get_node_symbol(param.name)
682                .or_else(|| self.ctx.binder.get_node_symbol(param_idx))
683            else {
684                continue;
685            };
686
687            // Skip destructuring parameters here (they are handled separately by binding pattern inference).
688            if let Some(name_node) = self.ctx.arena.get(param.name)
689                && (name_node.kind == syntax_kind_ext::OBJECT_BINDING_PATTERN
690                    || name_node.kind == syntax_kind_ext::ARRAY_BINDING_PATTERN)
691            {
692                continue;
693            }
694
695            // If we already have a concrete cached type, keep it.
696            if let Some(&cached) = self.ctx.symbol_types.get(&sym_id)
697                && cached != TypeId::UNKNOWN
698                && cached != TypeId::ANY
699                && cached != TypeId::ERROR
700            {
701                continue;
702            }
703
704            // Use contextual typing by resolving the parameter's identifier in its function scope.
705            let inferred = self.get_type_of_identifier(param.name);
706            if inferred != TypeId::UNKNOWN && inferred != TypeId::ERROR {
707                self.cache_symbol_type(sym_id, inferred);
708            }
709        }
710    }
711
712    /// Push an expected return type onto the stack when entering a function.
713    ///
714    /// This function is called when entering a function to track the expected
715    /// return type. The stack is used to validate that all return statements
716    /// are compatible with the function's declared return type.
717    ///
718    /// **Return Type Stack:**
719    /// - Functions can be nested (inner functions, closures)
720    /// - Stack tracks return type for each nesting level
721    /// - Pushed when entering function, popped when exiting
722    ///
723    /// **Use Cases:**
724    /// - Function declarations: `function foo(): string {}`
725    /// - Function expressions: `const f = function(): number {}`
726    /// - Arrow functions: `const f = (): boolean => {}`
727    /// - Method declarations
728    ///
729    /// **Validation:**
730    /// - Return statements are checked against the top of stack
731    /// - Enables early error detection for mismatched return types
732    ///
733    pub fn push_return_type(&mut self, return_type: TypeId) {
734        self.ctx.push_return_type(return_type);
735    }
736
737    /// Pop an expected return type from the stack when exiting a function.
738    ///
739    /// This function is called when exiting a function to remove the expected
740    /// return type from the stack. This restores the previous return type for
741    /// nested functions.
742    ///
743    /// **Stack Management:**
744    /// - Pops the most recently pushed return type
745    /// - Restores previous return type (for nested functions)
746    /// - Must be called once per push (balanced push/pop)
747    ///
748    pub fn pop_return_type(&mut self) {
749        self.ctx.pop_return_type();
750    }
751
752    /// Get the current expected return type if in a function.
753    ///
754    /// Returns the return type at the top of the return type stack.
755    /// Returns None if not inside a function (stack is empty).
756    ///
757    /// **Use Cases:**
758    /// - Validating return statements: `return value;`
759    /// - Checking function body completeness
760    /// - Contextual typing for return expressions
761    ///
762    /// **Nesting:**
763    /// - Returns the innermost function's return type
764    /// - Handles nested functions and closures correctly
765    ///
766    pub fn current_return_type(&self) -> Option<TypeId> {
767        self.ctx.current_return_type()
768    }
769
770    // =========================================================================
771    // Diagnostics (delegated to CheckerContext)
772    // =========================================================================
773
774    /// Add an error diagnostic to the diagnostics collection.
775    ///
776    /// This is the main entry point for reporting type errors. All error reporting
777    /// flows through this function (directly or through helper functions).
778    ///
779    /// **Diagnostic Components:**
780    /// - **start**: Byte offset of error start in file
781    /// - **length**: Length of the error span in bytes
782    /// - **message**: Human-readable error message
783    /// - **code**: TypeScript error code (`TSxxxx`)
784    ///
785    /// **Error Categories:**
786    /// - **Error**: Type errors that prevent compilation
787    /// - **Warning**: Potential issues that don't prevent compilation
788    /// - **Suggestion**: Code quality suggestions
789    ///
790    /// **Error Codes:**
791    /// - TS2304: Cannot find name
792    /// - TS2322: Type is not assignable
793    /// - TS2339: Property does not exist
794    /// - And many more...
795    ///
796    /// **Use Cases:**
797    /// - Direct error emission: `self.error(start, length, message, 2304)`
798    /// - Through helper functions: `error_cannot_find_name_at`, `error_type_not_assignable_at`, etc.
799    /// - Error messages are formatted with type information
800    ///
801    pub fn error(&mut self, start: u32, length: u32, message: String, code: u32) {
802        self.ctx.error(start, length, message, code);
803    }
804
805    /// Get the (start, end) span of a node for error reporting.
806    ///
807    /// This function retrieves the position information of an AST node,
808    /// which is used for error reporting and IDE features.
809    ///
810    /// **Span Information:**
811    /// - Returns `(start, end)` tuple of byte offsets
812    /// - Start is the byte offset of the node's first character
813    /// - End is the byte offset of the node's last character
814    /// - Returns None if node doesn't exist in arena
815    ///
816    /// **Use Cases:**
817    /// - Error reporting: `self.error(start, end - start, message, code)`
818    /// - Diagnostic spans: Point to the problematic code
819    /// - Quick info: Hover information for IDE
820    /// - Code navigation: Jump to definition references
821    ///
822    pub fn get_node_span(&self, idx: NodeIndex) -> Option<(u32, u32)> {
823        self.ctx.get_node_span(idx)
824    }
825
826    /// Emit an error diagnostic at a specific source position.
827    pub fn emit_error_at(&mut self, start: u32, length: u32, message: &str, code: u32) {
828        self.ctx
829            .diagnostics
830            .push(crate::diagnostics::Diagnostic::error(
831                self.ctx.file_name.clone(),
832                start,
833                length,
834                message.to_string(),
835                code,
836            ));
837    }
838
839    // =========================================================================
840    // Symbol Resolution
841    // =========================================================================
842
843    /// Get the symbol for a node index.
844    pub fn get_symbol_at_node(&self, idx: NodeIndex) -> Option<SymbolId> {
845        self.ctx.binder.get_node_symbol(idx)
846    }
847
848    /// Get the symbol by name from file locals.
849    pub fn get_symbol_by_name(&self, name: &str) -> Option<SymbolId> {
850        self.ctx.binder.file_locals.get(name)
851    }
852
853    // =========================================================================
854    // Type Resolution - Core Methods
855    // =========================================================================
856
857    /// Get the type of a node.
858    /// Get the type of an AST node with caching and circular reference detection.
859    ///
860    /// This is the main entry point for type computation. All type checking ultimately
861    /// flows through this function to get the type of AST nodes.
862    ///
863    /// ## Caching:
864    /// - Types are cached in `ctx.node_types` by node index
865    /// - Subsequent calls for the same node return the cached type
866    /// - Cache is checked first before computation
867    ///
868    /// ## Fuel Management:
869    /// - Consumes fuel on each call to prevent infinite loops
870    /// - Returns ERROR if fuel is exhausted (prevents type checker timeout)
871    /// - Fuel is reset between file check operations
872    ///
873    /// ## Circular Reference Detection:
874    /// - Tracks currently resolving nodes in `ctx.node_resolution_set`
875    /// - Returns ERROR if a circular reference is detected
876    /// - Helps expose type resolution bugs early
877    ///
878    /// ## Examples:
879    /// ```typescript
880    /// let x = 42;           // Type: number
881    /// let y = x;            // Type: number (from cache)
882    /// let z = x + y;        // Types: x=number, y=number, result=number
883    /// ```
884    ///
885    /// ## Performance:
886    /// - Caching prevents redundant type computation
887    /// - Circular reference detection prevents infinite recursion
888    /// - Fuel management ensures termination even for malformed code
889    pub fn get_type_of_node(&mut self, idx: NodeIndex) -> TypeId {
890        // Check cache first
891        if let Some(&cached) = self.ctx.node_types.get(&idx.0) {
892            // CRITICAL FIX: For identifiers, apply flow narrowing to the cached type
893            // Identifiers can have different types in different control flow branches.
894            // Example: if (typeof x === "string") { x.toUpperCase(); }
895            // The cache stores the declared type "string | number", but inside the if block,
896            // x should have the narrowed type "string".
897            //
898            // Only apply narrowing if skip_flow_narrowing is false (respects testing/special contexts)
899            let should_narrow = self.should_apply_flow_narrowing_for_identifier(idx);
900
901            if should_narrow {
902                // Apply flow narrowing to get the context-specific type
903                let narrowed = self.apply_flow_narrowing(idx, cached);
904                // FIX: If flow analysis returns a widened version of a literal cached type
905                // (e.g., cached="foo" but flow returns string), use the cached type.
906                // This prevents zombie freshness where flow analysis undoes literal preservation.
907                // IMPORTANT: Evaluate the cached type first to expand type aliases
908                // and lazy references, so widen_type can see the actual union members.
909                if narrowed != cached && narrowed != TypeId::ERROR {
910                    let evaluated_cached = self.evaluate_type_for_assignability(cached);
911                    let widened_cached =
912                        tsz_solver::widening::widen_type(self.ctx.types, evaluated_cached);
913                    if widened_cached == narrowed {
914                        return cached;
915                    }
916                }
917                return narrowed;
918            }
919
920            // TS 5.1+ divergent accessor types: when in a write context
921            // (skip_flow_narrowing is true, used by get_type_of_assignment_target),
922            // property/element access nodes may have a different write type
923            // than the cached read type. Bypass the cache so
924            // get_type_of_property_access can return the write_type.
925            if self.ctx.skip_flow_narrowing
926                && self.ctx.arena.get(idx).is_some_and(|node| {
927                    use tsz_parser::parser::syntax_kind_ext;
928                    node.kind == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION
929                        || node.kind == syntax_kind_ext::ELEMENT_ACCESS_EXPRESSION
930                })
931            {
932                // Fall through to recompute with write-type awareness
933            } else {
934                tracing::trace!(idx = idx.0, type_id = cached.0, "(cached) get_type_of_node");
935                return cached;
936            }
937        }
938
939        // Check fuel - return ERROR if exhausted to prevent timeout
940        if !self.ctx.consume_fuel() {
941            // CRITICAL: Cache ERROR immediately to prevent repeated deep recursion
942            self.ctx.node_types.insert(idx.0, TypeId::ERROR);
943            return TypeId::ERROR;
944        }
945
946        // Check for circular reference - return ERROR to expose resolution bugs
947        if self.ctx.node_resolution_set.contains(&idx) {
948            // CRITICAL: Cache ERROR immediately to prevent repeated deep recursion
949            self.ctx.node_types.insert(idx.0, TypeId::ERROR);
950            return TypeId::ERROR;
951        }
952
953        // Push onto resolution stack
954        self.ctx.node_resolution_stack.push(idx);
955        self.ctx.node_resolution_set.insert(idx);
956
957        // CRITICAL: Pre-cache ERROR placeholder to break deep recursion chains
958        // This ensures that mid-resolution lookups get cached ERROR immediately
959        // We'll overwrite this with the real result later (line 650)
960        self.ctx.node_types.insert(idx.0, TypeId::ERROR);
961
962        let result = self.compute_type_of_node(idx);
963
964        // Pop from resolution stack
965        self.ctx.node_resolution_stack.pop();
966        self.ctx.node_resolution_set.remove(&idx);
967
968        // Cache result - identifiers cache their DECLARED type,
969        // but get_type_of_node applies flow narrowing when returning cached identifier types
970        self.ctx.node_types.insert(idx.0, result);
971
972        let should_narrow_computed = self.should_apply_flow_narrowing_for_identifier(idx);
973
974        if should_narrow_computed {
975            let mut narrowed = self.apply_flow_narrowing(idx, result);
976            // FIX: Flow narrowing may return the original fresh type from the initializer
977            // expression, undoing the freshness stripping that get_type_of_identifier
978            // already performed. Re-apply freshness stripping to prevent "Zombie Freshness"
979            // where excess property checks fire on non-literal variable references.
980            if !self.ctx.compiler_options.sound_mode {
981                use tsz_solver::relations::freshness::{is_fresh_object_type, widen_freshness};
982                if is_fresh_object_type(self.ctx.types, narrowed) {
983                    narrowed = widen_freshness(self.ctx.types, narrowed);
984                }
985            }
986            // FIX: For mutable variables with non-widened literal declared types
987            // (e.g., `declare var a: "foo"; let b = a` → b has declared type "foo"),
988            // flow analysis may return the widened primitive (string) even though
989            // there's no actual narrowing. Detect this case: if widen(result) == narrowed,
990            // the flow is just widening our literal, not genuinely narrowing.
991            // IMPORTANT: Evaluate the result type first to expand type aliases
992            // and lazy references, so widen_type can see the actual union members.
993            if narrowed != result && narrowed != TypeId::ERROR {
994                let evaluated_result = self.evaluate_type_for_assignability(result);
995                let widened_result =
996                    tsz_solver::widening::widen_type(self.ctx.types, evaluated_result);
997                if widened_result == narrowed {
998                    // Flow just widened our literal type - use the original result
999                    narrowed = result;
1000                }
1001            }
1002            tracing::trace!(
1003                idx = idx.0,
1004                type_id = result.0,
1005                narrowed_type_id = narrowed.0,
1006                "get_type_of_node (computed+narrowed)"
1007            );
1008            return narrowed;
1009        }
1010
1011        tracing::trace!(idx = idx.0, type_id = result.0, "get_type_of_node");
1012        result
1013    }
1014
1015    /// Clear type cache for a node and all its children recursively.
1016    ///
1017    /// This is used when we need to recompute types with different contextual information,
1018    /// such as when checking return statements with contextual return types.
1019    pub(crate) fn clear_type_cache_recursive(&mut self, idx: NodeIndex) {
1020        use tsz_parser::parser::syntax_kind_ext;
1021
1022        if idx.is_none() {
1023            return;
1024        }
1025
1026        // Clear this node's cache
1027        self.ctx.node_types.remove(&idx.0);
1028
1029        // Recursively clear children
1030        let Some(node) = self.ctx.arena.get(idx) else {
1031            return;
1032        };
1033
1034        match node.kind {
1035            k if k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION => {
1036                if let Some(array) = self.ctx.arena.get_literal_expr(node) {
1037                    for &elem_idx in &array.elements.nodes {
1038                        self.clear_type_cache_recursive(elem_idx);
1039                    }
1040                }
1041            }
1042            k if k == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION => {
1043                if let Some(obj) = self.ctx.arena.get_literal_expr(node) {
1044                    for &prop_idx in &obj.elements.nodes {
1045                        self.clear_type_cache_recursive(prop_idx);
1046                    }
1047                }
1048            }
1049            k if k == syntax_kind_ext::PROPERTY_ASSIGNMENT => {
1050                if let Some(prop) = self.ctx.arena.get_property_assignment(node) {
1051                    self.clear_type_cache_recursive(prop.initializer);
1052                }
1053            }
1054            k if k == syntax_kind_ext::PARENTHESIZED_EXPRESSION => {
1055                if let Some(paren) = self.ctx.arena.get_parenthesized(node) {
1056                    self.clear_type_cache_recursive(paren.expression);
1057                }
1058            }
1059            k if k == syntax_kind_ext::CALL_EXPRESSION => {
1060                if let Some(call) = self.ctx.arena.get_call_expr(node) {
1061                    self.clear_type_cache_recursive(call.expression);
1062                    if let Some(ref args) = call.arguments {
1063                        for &arg_idx in &args.nodes {
1064                            self.clear_type_cache_recursive(arg_idx);
1065                        }
1066                    }
1067                }
1068            }
1069            k if k == syntax_kind_ext::BINARY_EXPRESSION => {
1070                if let Some(bin) = self.ctx.arena.get_binary_expr(node) {
1071                    self.clear_type_cache_recursive(bin.left);
1072                    self.clear_type_cache_recursive(bin.right);
1073                }
1074            }
1075            k if k == syntax_kind_ext::CONDITIONAL_EXPRESSION => {
1076                if let Some(cond) = self.ctx.arena.get_conditional_expr(node) {
1077                    self.clear_type_cache_recursive(cond.condition);
1078                    self.clear_type_cache_recursive(cond.when_true);
1079                    self.clear_type_cache_recursive(cond.when_false);
1080                }
1081            }
1082            k if k == syntax_kind_ext::SPREAD_ELEMENT => {
1083                if let Some(spread) = self.ctx.arena.get_unary_expr_ex(node) {
1084                    self.clear_type_cache_recursive(spread.expression);
1085                }
1086            }
1087            k if k == syntax_kind_ext::AS_EXPRESSION => {
1088                if let Some(as_expr) = self.ctx.arena.get_type_assertion(node) {
1089                    self.clear_type_cache_recursive(as_expr.expression);
1090                }
1091            }
1092            _ => {}
1093        }
1094    }
1095
1096    pub(crate) fn is_keyword_type_used_as_value_position(&self, idx: NodeIndex) -> bool {
1097        use tsz_parser::parser::syntax_kind_ext;
1098
1099        let Some(ext) = self.ctx.arena.get_extended(idx) else {
1100            return false;
1101        };
1102        let parent = ext.parent;
1103        if parent.is_none() {
1104            return false;
1105        }
1106        let Some(parent_node) = self.ctx.arena.get(parent) else {
1107            return false;
1108        };
1109
1110        if matches!(
1111            parent_node.kind,
1112            k if k == syntax_kind_ext::EXPRESSION_STATEMENT
1113                || k == syntax_kind_ext::LABELED_STATEMENT
1114                || k == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION
1115                || k == syntax_kind_ext::ELEMENT_ACCESS_EXPRESSION
1116                || k == syntax_kind_ext::CALL_EXPRESSION
1117                || k == syntax_kind_ext::NEW_EXPRESSION
1118                || k == syntax_kind_ext::BINARY_EXPRESSION
1119                || k == syntax_kind_ext::RETURN_STATEMENT
1120                || k == syntax_kind_ext::VARIABLE_DECLARATION
1121                || k == syntax_kind_ext::PROPERTY_ASSIGNMENT
1122                || k == syntax_kind_ext::SHORTHAND_PROPERTY_ASSIGNMENT
1123                || k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION
1124                || k == syntax_kind_ext::PARENTHESIZED_EXPRESSION
1125                || k == syntax_kind_ext::CONDITIONAL_EXPRESSION
1126        ) {
1127            return true;
1128        }
1129
1130        // Recovery path: malformed value expressions like `number[]` are parsed
1131        // through ARRAY_TYPE wrappers, but still need TS2693 at the keyword.
1132        if parent_node.kind == syntax_kind_ext::ARRAY_TYPE {
1133            let Some(parent_ext) = self.ctx.arena.get_extended(parent) else {
1134                return false;
1135            };
1136            let grandparent = parent_ext.parent;
1137            if grandparent.is_none() {
1138                return false;
1139            }
1140            let Some(grandparent_node) = self.ctx.arena.get(grandparent) else {
1141                return false;
1142            };
1143            return matches!(
1144                grandparent_node.kind,
1145                k if k == syntax_kind_ext::EXPRESSION_STATEMENT
1146                    || k == syntax_kind_ext::LABELED_STATEMENT
1147                    || k == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION
1148                    || k == syntax_kind_ext::ELEMENT_ACCESS_EXPRESSION
1149                    || k == syntax_kind_ext::CALL_EXPRESSION
1150                    || k == syntax_kind_ext::NEW_EXPRESSION
1151                    || k == syntax_kind_ext::BINARY_EXPRESSION
1152                    || k == syntax_kind_ext::RETURN_STATEMENT
1153                    || k == syntax_kind_ext::VARIABLE_DECLARATION
1154                    || k == syntax_kind_ext::PROPERTY_ASSIGNMENT
1155                    || k == syntax_kind_ext::SHORTHAND_PROPERTY_ASSIGNMENT
1156                    || k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION
1157                    || k == syntax_kind_ext::PARENTHESIZED_EXPRESSION
1158                    || k == syntax_kind_ext::CONDITIONAL_EXPRESSION
1159            );
1160        }
1161
1162        false
1163    }
1164
1165    /// Compute the type of a node (internal, not cached).
1166    ///
1167    /// This method first delegates to `ExpressionChecker` for expression type checking.
1168    /// If `ExpressionChecker` returns `TypeId::DELEGATE`, we fall back to the full
1169    /// `CheckerState` implementation that has access to symbol resolution, contextual
1170    /// typing, and other complex type checking features.
1171    pub(crate) fn compute_type_of_node(&mut self, idx: NodeIndex) -> TypeId {
1172        use crate::ExpressionChecker;
1173
1174        // First, try ExpressionChecker for simple expression types
1175        // ExpressionChecker handles expressions that don't need full CheckerState context
1176        let expr_result = {
1177            let mut expr_checker = ExpressionChecker::new(&mut self.ctx);
1178            expr_checker.compute_type_uncached(idx)
1179        };
1180
1181        let result = if expr_result != TypeId::DELEGATE {
1182            expr_result
1183        } else {
1184            // ExpressionChecker returned DELEGATE - use full CheckerState implementation
1185            self.compute_type_of_node_complex(idx)
1186        };
1187
1188        // Validate regex literal flags against compilation target (TS1501)
1189        self.validate_regex_literal_flags(idx);
1190
1191        result
1192    }
1193
1194    /// Complex type computation that needs full `CheckerState` context.
1195    ///
1196    /// This is called when `ExpressionChecker` returns `TypeId::DELEGATE`,
1197    /// indicating the expression needs symbol resolution, contextual typing,
1198    /// or other features only available in `CheckerState`.
1199    fn compute_type_of_node_complex(&mut self, idx: NodeIndex) -> TypeId {
1200        use crate::dispatch::ExpressionDispatcher;
1201
1202        let mut dispatcher = ExpressionDispatcher::new(self);
1203        dispatcher.dispatch_type_computation(idx)
1204    }
1205
1206    // Type resolution, type analysis, type environment, and checking methods
1207    // are in state_type_resolution.rs, state_type_analysis.rs,
1208    // state_type_environment.rs, state_checking.rs, and state_checking_members.rs
1209}