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(¤t) = 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 ¶m_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}