Skip to main content

tsz_checker/context/
mod.rs

1//! Checker Context
2//!
3//! Holds the shared state used throughout the type checking process.
4//! This separates state from logic, allowing specialized checkers (expressions, statements)
5//! to borrow the context mutably.
6//!
7//! Sub-modules:
8//! - `constructors` - `CheckerContext` constructor methods
9//! - `resolver` - `TypeResolver` trait implementation
10//! - `def_mapping` - DefId migration helpers
11//! - `compiler_options` - Compiler option accessors and solver config derivation
12//! - `lib_queries` - Library/global type availability queries
13
14mod compiler_options;
15mod constructors;
16mod def_mapping;
17mod lib_queries;
18mod resolver;
19mod strict_mode;
20
21use rustc_hash::{FxHashMap, FxHashSet};
22use std::cell::{Cell, RefCell};
23use std::collections::VecDeque;
24use std::rc::Rc;
25use std::sync::Arc;
26use tsz_common::interner::Atom;
27
28use crate::control_flow::FlowGraph;
29use crate::diagnostics::Diagnostic;
30use crate::module_resolution::module_specifier_candidates;
31use tsz_binder::SymbolId;
32use tsz_parser::parser::NodeIndex;
33use tsz_solver::def::{DefId, DefinitionStore};
34use tsz_solver::{QueryDatabase, TypeEnvironment, TypeId};
35
36// Re-export CheckerOptions and ScriptTarget from tsz-common
37use tsz_binder::BinderState;
38pub use tsz_common::checker_options::CheckerOptions;
39pub use tsz_common::common::ScriptTarget;
40use tsz_parser::parser::node::NodeArena;
41use tsz_parser::parser::syntax_kind_ext;
42
43type ResolvedModulePathMap = FxHashMap<(usize, String), usize>;
44type ResolvedModuleErrorMap = FxHashMap<(usize, String), ResolutionError>;
45
46/// Represents a failed module resolution with specific error details.
47#[derive(Clone, Debug)]
48pub struct ResolutionError {
49    pub code: u32,
50    pub message: String,
51}
52
53/// Info about the enclosing class for static member suggestions and abstract property checks.
54#[derive(Clone, Debug)]
55pub struct EnclosingClassInfo {
56    /// Name of the class.
57    pub name: String,
58    /// Node index for the class declaration.
59    pub class_idx: NodeIndex,
60    /// Member node indices for symbol lookup.
61    pub member_nodes: Vec<NodeIndex>,
62    /// Whether we're in a constructor (for error 2715 checking).
63    pub in_constructor: bool,
64    /// Whether this is a `declare class` (ambient context for error 1183).
65    pub is_declared: bool,
66    /// Whether we're in a static property initializer (for TS17011 checking).
67    pub in_static_property_initializer: bool,
68    /// Whether we're in a static method or property context.
69    pub in_static_member: bool,
70    /// Whether any `super()` call appeared while checking the current constructor body.
71    pub has_super_call_in_current_constructor: bool,
72    /// Cached instance `this` type for members of this class.
73    pub cached_instance_this_type: Option<TypeId>,
74    /// Names of the class's own type parameters (for TS2302 checking in static members).
75    pub type_param_names: Vec<String>,
76    /// The type parameter infos of the class's own type parameters.
77    pub class_type_parameters: Vec<tsz_solver::TypeParamInfo>,
78}
79
80/// Info about a label in scope for break/continue validation.
81#[derive(Clone, Debug)]
82pub struct LabelInfo {
83    /// The label name (e.g., "outer").
84    pub name: String,
85    /// Whether the label is on an iteration statement (for continue validation).
86    /// Only iteration labels can be targets of continue statements.
87    pub is_iteration: bool,
88    /// The function depth when this label was defined.
89    /// Used to detect if a jump crosses a function boundary.
90    pub function_depth: u32,
91}
92
93/// Persistent cache for type checking results across LSP queries.
94/// This cache survives between LSP requests but is invalidated when the file changes.
95#[derive(Clone, Debug)]
96pub struct TypeCache {
97    /// Cached types for symbols.
98    pub symbol_types: FxHashMap<SymbolId, TypeId>,
99
100    /// Cached instance types for class symbols (for TYPE position).
101    /// Distinguishes from `symbol_types` which holds constructor types for VALUE position.
102    pub symbol_instance_types: FxHashMap<SymbolId, TypeId>,
103
104    /// Cached types for nodes.
105    pub node_types: FxHashMap<u32, TypeId>,
106
107    /// Symbol dependency graph (symbol -> referenced symbols).
108    pub symbol_dependencies: FxHashMap<SymbolId, FxHashSet<SymbolId>>,
109
110    /// Maps `DefIds` to `SymbolIds` for declaration emit usage analysis.
111    /// Populated by `CheckerContext` during type checking, consumed by `UsageAnalyzer`.
112    pub def_to_symbol: FxHashMap<tsz_solver::DefId, SymbolId>,
113
114    /// Cache for control flow analysis results.
115    /// Key: (`FlowNodeId`, `SymbolId`, `InitialTypeId`) -> `NarrowedTypeId`
116    pub flow_analysis_cache:
117        FxHashMap<(tsz_binder::FlowNodeId, tsz_binder::SymbolId, TypeId), TypeId>,
118
119    /// Maps class instance `TypeIds` to their class declaration `NodeIndex`.
120    /// Used by `get_class_decl_from_type` to correctly identify the class
121    /// for derived classes that have no private/protected members.
122    pub class_instance_type_to_decl: FxHashMap<TypeId, NodeIndex>,
123
124    /// Forward cache: class declaration `NodeIndex` -> computed instance `TypeId`.
125    /// Avoids recomputing the full class instance type on every member check.
126    pub class_instance_type_cache: FxHashMap<NodeIndex, TypeId>,
127
128    /// Forward cache: class declaration `NodeIndex` -> computed constructor `TypeId`.
129    /// Avoids recomputing constructor shape/inheritance on repeated class queries.
130    pub class_constructor_type_cache: FxHashMap<NodeIndex, TypeId>,
131
132    /// Set of import specifier nodes that should be elided from JavaScript output.
133    /// These are imports that reference type-only declarations (interfaces, type aliases).
134    pub type_only_nodes: FxHashSet<NodeIndex>,
135}
136
137impl TypeCache {
138    /// Invalidate cached symbol types that depend on the provided roots.
139    /// Returns the number of affected symbols.
140    pub fn invalidate_symbols(&mut self, roots: &[SymbolId]) -> usize {
141        if roots.is_empty() {
142            return 0;
143        }
144
145        let mut reverse: FxHashMap<SymbolId, Vec<SymbolId>> = FxHashMap::default();
146        for (symbol, deps) in &self.symbol_dependencies {
147            for dep in deps {
148                reverse.entry(*dep).or_default().push(*symbol);
149            }
150        }
151
152        let mut affected: FxHashSet<SymbolId> = FxHashSet::default();
153        let mut pending = VecDeque::new();
154        for &root in roots {
155            if affected.insert(root) {
156                pending.push_back(root);
157            }
158        }
159
160        while let Some(sym_id) = pending.pop_front() {
161            if let Some(dependents) = reverse.get(&sym_id) {
162                for &dependent in dependents {
163                    if affected.insert(dependent) {
164                        pending.push_back(dependent);
165                    }
166                }
167            }
168        }
169
170        for sym_id in &affected {
171            self.symbol_types.remove(sym_id);
172            self.symbol_instance_types.remove(sym_id);
173            self.symbol_dependencies.remove(sym_id);
174        }
175        self.node_types.clear();
176        self.class_instance_type_cache.clear();
177        self.class_constructor_type_cache.clear();
178        self.class_instance_type_to_decl.clear();
179        affected.len()
180    }
181
182    /// Merge another `TypeCache` into this one.
183    /// Used to accumulate type information from multiple file checks for declaration emit.
184    pub fn merge(&mut self, other: Self) {
185        self.symbol_types.extend(other.symbol_types);
186        self.symbol_instance_types
187            .extend(other.symbol_instance_types);
188        self.node_types.extend(other.node_types);
189        self.class_instance_type_to_decl
190            .extend(other.class_instance_type_to_decl);
191        self.class_instance_type_cache
192            .extend(other.class_instance_type_cache);
193        self.class_constructor_type_cache
194            .extend(other.class_constructor_type_cache);
195        self.type_only_nodes.extend(other.type_only_nodes);
196
197        // Merge symbol dependencies sets
198        for (sym, deps) in other.symbol_dependencies {
199            self.symbol_dependencies
200                .entry(sym)
201                .or_default()
202                .extend(deps);
203        }
204
205        // Merge def_to_symbol mapping
206        self.def_to_symbol.extend(other.def_to_symbol);
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::TypeCache;
213    use rustc_hash::{FxHashMap, FxHashSet};
214    use tsz_binder::SymbolId;
215    use tsz_parser::parser::NodeIndex;
216    use tsz_solver::TypeId;
217
218    fn empty_cache() -> TypeCache {
219        TypeCache {
220            symbol_types: FxHashMap::default(),
221            symbol_instance_types: FxHashMap::default(),
222            node_types: FxHashMap::default(),
223            symbol_dependencies: FxHashMap::default(),
224            def_to_symbol: FxHashMap::default(),
225            flow_analysis_cache: FxHashMap::default(),
226            class_instance_type_to_decl: FxHashMap::default(),
227            class_instance_type_cache: FxHashMap::default(),
228            class_constructor_type_cache: FxHashMap::default(),
229            type_only_nodes: FxHashSet::default(),
230        }
231    }
232
233    #[test]
234    fn type_cache_merge_keeps_constructor_type_cache() {
235        let mut lhs = empty_cache();
236        let mut rhs = empty_cache();
237
238        rhs.class_constructor_type_cache
239            .insert(NodeIndex(42), TypeId::STRING);
240
241        lhs.merge(rhs);
242
243        assert_eq!(
244            lhs.class_constructor_type_cache.get(&NodeIndex(42)),
245            Some(&TypeId::STRING)
246        );
247    }
248
249    #[test]
250    fn type_cache_merge_keeps_error_class_type_cache_entries() {
251        let mut lhs = empty_cache();
252        let mut rhs = empty_cache();
253
254        rhs.class_instance_type_cache
255            .insert(NodeIndex(10), TypeId::ERROR);
256        rhs.class_constructor_type_cache
257            .insert(NodeIndex(11), TypeId::ERROR);
258
259        lhs.merge(rhs);
260
261        assert_eq!(
262            lhs.class_instance_type_cache.get(&NodeIndex(10)),
263            Some(&TypeId::ERROR)
264        );
265        assert_eq!(
266            lhs.class_constructor_type_cache.get(&NodeIndex(11)),
267            Some(&TypeId::ERROR)
268        );
269    }
270
271    #[test]
272    fn invalidate_symbols_clears_class_type_caches() {
273        let mut cache = empty_cache();
274        let sym = SymbolId(7);
275        cache
276            .symbol_dependencies
277            .insert(sym, FxHashSet::<SymbolId>::default());
278        cache
279            .class_instance_type_cache
280            .insert(NodeIndex(1), TypeId::NUMBER);
281        cache
282            .class_constructor_type_cache
283            .insert(NodeIndex(2), TypeId::STRING);
284        cache
285            .class_instance_type_to_decl
286            .insert(TypeId::BOOLEAN, NodeIndex(3));
287
288        let affected = cache.invalidate_symbols(&[sym]);
289
290        assert_eq!(affected, 1);
291        assert!(cache.class_instance_type_cache.is_empty());
292        assert!(cache.class_constructor_type_cache.is_empty());
293        assert!(cache.class_instance_type_to_decl.is_empty());
294    }
295}
296
297/// Info about a symbol that came from destructuring a union type.
298/// Info about a symbol that came from destructuring a union type.
299/// Used for correlated discriminant narrowing: when `const { data, isSuccess } = getResult()`,
300/// narrowing `isSuccess` should also narrow `data`.
301#[derive(Clone, Debug)]
302pub struct DestructuredBindingInfo {
303    /// The source type of the entire destructured expression (the union)
304    pub source_type: TypeId,
305    /// The property name that this symbol corresponds to (for object patterns)
306    pub property_name: String,
307    /// The element index for array/tuple patterns (`u32::MAX` if object pattern)
308    pub element_index: u32,
309    /// The binding group ID — all symbols from the same destructuring share this
310    pub group_id: u32,
311    /// Whether this is a const binding (only const bindings support correlated narrowing)
312    pub is_const: bool,
313}
314
315/// Shared state for type checking.
316pub struct CheckerContext<'a> {
317    /// The `NodeArena` containing the AST.
318    pub arena: &'a NodeArena,
319
320    /// The binder state with symbols.
321    pub binder: &'a BinderState,
322
323    /// Query database for type interning and memoized type operations.
324    /// Supports both `TypeInterner` (via trait upcasting) and `QueryCache`.
325    pub types: &'a dyn QueryDatabase,
326    /// Current file name.
327    pub file_name: String,
328
329    /// Compiler options for type checking.
330    pub compiler_options: CheckerOptions,
331
332    /// Whether `noImplicitOverride` diagnostics are enabled for this source file.
333    pub no_implicit_override: bool,
334
335    /// Whether unresolved import diagnostics should be emitted by the checker.
336    /// The CLI driver handles module resolution in multi-file mode.
337    ///
338    /// Checker invariant: when driver-provided resolution context is available,
339    /// checker should consume that context and avoid ad-hoc module-existence inference.
340    pub report_unresolved_imports: bool,
341
342    /// Tracking the current computed property name node for TS2467
343    pub checking_computed_property_name: Option<NodeIndex>,
344
345    /// Count of spelling suggestions (TS2552) emitted to limit output size.
346    pub spelling_suggestions_emitted: u32,
347
348    // --- Caches ---
349    /// Cached types for symbols.
350    pub symbol_types: FxHashMap<SymbolId, TypeId>,
351
352    /// Cached instance types for class symbols (for TYPE position).
353    /// Distinguishes from `symbol_types` which holds constructor types for VALUE position.
354    pub symbol_instance_types: FxHashMap<SymbolId, TypeId>,
355
356    /// Cached types for variable declarations (used for TS2403 checks).
357    pub var_decl_types: FxHashMap<SymbolId, TypeId>,
358
359    /// Cache for `resolve_lib_type_by_name` results.
360    /// Keyed by type name and stores both hits (`Some(TypeId)`) and misses (`None`).
361    pub lib_type_resolution_cache: FxHashMap<String, Option<TypeId>>,
362
363    /// Cached types for nodes.
364    pub node_types: FxHashMap<u32, TypeId>,
365
366    /// Cached type environment for resolving Ref types during assignability checks.
367    pub type_environment: Rc<RefCell<TypeEnvironment>>,
368
369    /// Recursion guard for application evaluation.
370    pub application_eval_set: FxHashSet<TypeId>,
371
372    /// Recursion guard for mapped type evaluation with resolution.
373    pub mapped_eval_set: FxHashSet<TypeId>,
374
375    /// Cache for control flow analysis results.
376    /// Key: (`FlowNodeId`, `SymbolId`, `InitialTypeId`) -> `NarrowedTypeId`
377    /// Prevents re-traversing the flow graph for the same symbol/flow combination.
378    /// Fixes performance regression on binaryArithmeticControlFlowGraphNotTooLarge.ts
379    /// where each operand in a + b + c was triggering fresh graph traversals.
380    pub flow_analysis_cache:
381        RefCell<FxHashMap<(tsz_binder::FlowNodeId, tsz_binder::SymbolId, TypeId), TypeId>>,
382
383    /// Reusable buffers for flow analysis to avoid frequent heap allocations in `check_flow`.
384    pub flow_worklist: RefCell<VecDeque<(tsz_binder::FlowNodeId, TypeId)>>,
385    pub flow_in_worklist: RefCell<FxHashSet<tsz_binder::FlowNodeId>>,
386    pub flow_visited: RefCell<FxHashSet<tsz_binder::FlowNodeId>>,
387    pub flow_results: RefCell<FxHashMap<tsz_binder::FlowNodeId, TypeId>>,
388
389    /// Shared cache for narrowing operations (type resolution, property lookup).
390    /// Reused across flow analysis passes to prevent O(N^2) behavior in CFA chains.
391    pub narrowing_cache: tsz_solver::NarrowingCache,
392
393    /// Cache for switch-reference relevance checks.
394    /// Reused across `FlowAnalyzer` instances within a single file check.
395    pub flow_switch_reference_cache: RefCell<FxHashMap<(u32, u32), bool>>,
396
397    /// Cache numeric atom conversions during flow analysis.
398    /// Reused across `FlowAnalyzer` instances within a single file check.
399    pub flow_numeric_atom_cache: RefCell<FxHashMap<u64, Atom>>,
400
401    /// Shared reference-equivalence cache used by flow narrowing.
402    /// Key: (`node_a`, `node_b`) -> whether they reference the same symbol/property chain.
403    /// Reused across `FlowAnalyzer` instances within a single file check.
404    pub flow_reference_match_cache: RefCell<FxHashMap<(u32, u32), bool>>,
405
406    /// Instantiated type predicates from generic call resolutions.
407    /// Keyed by call expression node index. Used by flow narrowing to get
408    /// predicates with inferred type arguments applied (e.g., `T` -> `string`).
409    pub call_type_predicates: crate::control_flow::CallPredicateMap,
410
411    /// `TypeIds` whose application/lazy symbol references are fully resolved in `type_env`.
412    /// This avoids repeated deep traversals in assignability hot paths.
413    pub application_symbols_resolved: FxHashSet<TypeId>,
414
415    /// Recursion guard for application symbol resolution traversal.
416    pub application_symbols_resolution_set: FxHashSet<TypeId>,
417
418    /// Maps class instance `TypeIds` to their class declaration `NodeIndex`.
419    /// Used by `get_class_decl_from_type` to correctly identify the class
420    /// for derived classes that have no private/protected members (and thus no brand).
421    /// Populated by `get_class_instance_type_inner` when creating class instance types.
422    pub class_instance_type_to_decl: FxHashMap<TypeId, NodeIndex>,
423
424    /// Forward cache: class declaration `NodeIndex` -> computed instance `TypeId`.
425    /// Avoids recomputing the full class instance type on every member check.
426    pub class_instance_type_cache: FxHashMap<NodeIndex, TypeId>,
427
428    /// Forward cache: class declaration `NodeIndex` -> computed constructor `TypeId`.
429    /// Avoids recomputing constructor inheritance checks in class-heavy programs.
430    pub class_constructor_type_cache: FxHashMap<NodeIndex, TypeId>,
431
432    /// Cache class symbol -> class declaration node lookups used in inheritance queries.
433    /// Stores misses as `None` to avoid repeated declaration scans on hot paths.
434    pub class_symbol_to_decl_cache: RefCell<FxHashMap<SymbolId, Option<NodeIndex>>>,
435
436    /// Cache heritage expression node -> resolved symbol lookups.
437    /// Stores misses as `None` to avoid repeating namespace/alias walks across
438    /// class and interface inheritance passes.
439    pub heritage_symbol_cache: RefCell<FxHashMap<NodeIndex, Option<SymbolId>>>,
440
441    /// Cache constructor type fallback for heritage expressions with no explicit type args.
442    /// Avoids repeatedly re-evaluating anonymous/complex `extends` expressions.
443    pub base_constructor_expr_cache: RefCell<FxHashMap<NodeIndex, Option<TypeId>>>,
444
445    /// Cache instance type fallback for heritage expressions with no explicit type args.
446    /// Reuses constructor->instance fallback work across class instance checks.
447    pub base_instance_expr_cache: RefCell<FxHashMap<NodeIndex, Option<TypeId>>>,
448
449    /// Cache of non-class `TypeId`s for `get_class_decl_from_type`.
450    /// Avoids repeating private-brand scans on hot miss paths.
451    pub class_decl_miss_cache: RefCell<FxHashSet<TypeId>>,
452
453    /// Cache for JSX intrinsic element evaluated props types.
454    /// Maps (`intrinsic_elements_type`, `tag_atom`) -> `evaluated_props_type`.
455    /// Avoids re-evaluating `JSX.IntrinsicElements['div']` for every `<div>` element.
456    pub jsx_intrinsic_props_cache: FxHashMap<(TypeId, tsz_common::interner::Atom), TypeId>,
457
458    /// Symbol dependency graph (symbol -> referenced symbols).
459    pub symbol_dependencies: FxHashMap<SymbolId, FxHashSet<SymbolId>>,
460
461    /// Stack of symbols currently being evaluated for dependency tracking.
462    pub symbol_dependency_stack: Vec<SymbolId>,
463
464    /// Set of symbols that have been referenced (used for TS6133 unused checking).
465    /// Uses `RefCell` to allow tracking from &self methods (e.g., `resolve_identifier_symbol`).
466    pub referenced_symbols: std::cell::RefCell<FxHashSet<SymbolId>>,
467
468    /// Set of symbols written to (assignment targets).
469    /// Tracked separately from references for flow/usage checks.
470    pub written_symbols: std::cell::RefCell<FxHashSet<SymbolId>>,
471
472    // --- Destructured Binding Tracking ---
473    /// Maps destructured const binding symbols to their source union type info.
474    /// Used for correlated discriminant narrowing (TS 4.6+ feature).
475    pub destructured_bindings: FxHashMap<SymbolId, DestructuredBindingInfo>,
476    /// Counter for generating unique binding group IDs.
477    pub next_binding_group_id: u32,
478
479    // --- Diagnostics ---
480    /// Whether the source file has parse errors.
481    /// Set by the driver before type checking to suppress noise-sensitive diagnostics
482    /// (e.g., TS2695 for comma operators in malformed JSON files).
483    pub has_parse_errors: bool,
484    /// Whether the source file has real syntax errors (not just conflict markers TS1185).
485    /// Used to suppress TS2304 only when there are genuine parse errors.
486    pub has_syntax_parse_errors: bool,
487    /// Positions (start) of syntax parse errors (excluding conflict markers TS1185).
488    /// Used for targeted TS2304 suppression near parse error sites.
489    pub syntax_parse_error_positions: Vec<u32>,
490    /// Whether the file has "real" syntax errors (TS1005, TS1109, TS1127, TS1128,
491    /// TS1135, etc.) that indicate actual parse failure, as opposed to grammar
492    /// checks (TS1100, TS1173, TS1212, etc.) which are semantic errors emitted
493    /// during parsing. Used for broader TS2304 suppression matching tsc behavior.
494    pub has_real_syntax_errors: bool,
495
496    /// Diagnostics produced during type checking.
497    pub diagnostics: Vec<Diagnostic>,
498    /// Set of already-emitted diagnostics (start, code) for deduplication.
499    pub emitted_diagnostics: FxHashSet<(u32, u32)>,
500    /// Set of modules that have already had TS2307 emitted (prevents duplicate emissions).
501    pub modules_with_ts2307_emitted: FxHashSet<String>,
502
503    // --- Recursion Guards ---
504    /// Stack of symbols being resolved.
505    pub symbol_resolution_stack: Vec<SymbolId>,
506    /// O(1) lookup set for symbol resolution stack.
507    pub symbol_resolution_set: FxHashSet<SymbolId>,
508    /// O(1) lookup set for class instance type resolution to avoid recursion.
509    pub class_instance_resolution_set: FxHashSet<SymbolId>,
510    /// O(1) lookup set for class constructor type resolution to avoid recursion.
511    pub class_constructor_resolution_set: FxHashSet<SymbolId>,
512    /// Deferred TS7034 candidates: non-ambient variables with no annotation, no init, and type ANY.
513    /// Maps symbol ID → declaration name node. Consumed when a capture is detected.
514    pub pending_implicit_any_vars: FxHashMap<SymbolId, NodeIndex>,
515    /// Variables that have already had TS7034 emitted.
516    /// Used to emit TS7005 on subsequent usages.
517    pub reported_implicit_any_vars: FxHashSet<SymbolId>,
518
519    /// Inheritance graph tracking class/interface relationships
520    pub inheritance_graph: tsz_solver::classes::inheritance::InheritanceGraph,
521
522    /// Stack of nodes being resolved.
523    pub node_resolution_stack: Vec<NodeIndex>,
524    /// O(1) lookup set for node resolution stack.
525    pub node_resolution_set: FxHashSet<NodeIndex>,
526
527    /// Set of class declaration nodes currently being checked.
528    /// Used to prevent infinite recursion in `check_class_declaration` when
529    /// class checking triggers type resolution that circles back to the same class.
530    pub checking_classes: FxHashSet<NodeIndex>,
531
532    /// Set of class declaration nodes that have been fully checked.
533    /// Used to avoid re-checking the same class multiple times (e.g. once via
534    /// dependency resolution and once via the main source file traversal).
535    pub checked_classes: FxHashSet<NodeIndex>,
536
537    // --- Scopes & Context ---
538    /// Current type parameter scope.
539    pub type_parameter_scope: FxHashMap<String, TypeId>,
540
541    /// Temporary scope for value parameters visible to `typeof` in return type annotations.
542    /// Populated during signature processing so `typeof paramName` in return types
543    /// can resolve to the parameter's type.
544    pub typeof_param_scope: FxHashMap<String, TypeId>,
545
546    /// Contextual type for expression being checked.
547    pub contextual_type: Option<TypeId>,
548
549    /// Whether we're in the statement checking phase (vs type environment building).
550    /// During `build_type_environment`, closure parameter types may not have contextual types
551    /// yet, so TS7006 should be deferred until the checking phase.
552    pub is_checking_statements: bool,
553
554    /// Whether the current file is a declaration file (.d.ts/.d.tsx/.d.mts/.d.cts).
555    /// Used to suppress statement-specific errors (TS1105, TS1108, TS1104) in favor of TS1036.
556    pub is_in_ambient_declaration_file: bool,
557
558    /// Whether we are currently evaluating the LHS of a destructuring assignment.
559    /// Used to suppress TS1117 (duplicate property) checks in object patterns.
560    pub in_destructuring_target: bool,
561
562    /// Whether to skip flow narrowing when computing types.
563    /// Used in assignment target type resolution to get declared types instead of narrowed types.
564    /// When checking `foo[x] = 1` after `if (foo[x] === undefined)`, we need the declared type
565    /// (e.g., `number | undefined`) not the narrowed type (e.g., `undefined`).
566    pub skip_flow_narrowing: bool,
567
568    /// Current depth of recursive type instantiation.
569    pub instantiation_depth: RefCell<u32>,
570
571    /// Whether type instantiation depth was exceeded (for TS2589 emission).
572    pub depth_exceeded: RefCell<bool>,
573
574    /// General recursion depth counter for type checking.
575    /// Prevents stack overflow by bailing out when depth exceeds the limit.
576    pub recursion_depth: RefCell<tsz_solver::recursion::DepthCounter>,
577
578    /// Current depth of call expression resolution.
579    pub call_depth: RefCell<tsz_solver::recursion::DepthCounter>,
580
581    /// Stack of expected return types for functions.
582    pub return_type_stack: Vec<TypeId>,
583    /// Stack of contextual yield types for generator functions.
584    /// Used to contextually type yield expressions (prevents false TS7006).
585    pub yield_type_stack: Vec<Option<TypeId>>,
586    /// Stack of current `this` types for class member bodies.
587    pub this_type_stack: Vec<TypeId>,
588
589    /// Current enclosing class info.
590    pub enclosing_class: Option<EnclosingClassInfo>,
591
592    /// Type environment for symbol resolution with type parameters.
593    /// Used by the evaluator to expand Application types.
594    pub type_env: RefCell<TypeEnvironment>,
595
596    // --- DefId Migration Infrastructure ---
597    /// Storage for type definitions (interfaces, classes, type aliases).
598    /// Part of the `DefId` migration to decouple Solver from Binder.
599    pub definition_store: Arc<DefinitionStore>,
600
601    /// Mapping from Binder `SymbolId` to Solver `DefId`.
602    /// Used during migration to avoid creating duplicate `DefIds` for the same symbol.
603    /// Wrapped in `RefCell` to allow mutation through shared references (for use in Fn closures).
604    pub symbol_to_def: RefCell<FxHashMap<SymbolId, DefId>>,
605
606    /// Reverse mapping from Solver `DefId` to Binder `SymbolId`.
607    /// Used to look up binder symbols from DefId-based types (e.g., namespace exports).
608    /// Wrapped in `RefCell` to allow mutation through shared references (for use in Fn closures).
609    pub def_to_symbol: RefCell<FxHashMap<DefId, SymbolId>>,
610
611    /// Type parameters for `DefIds` (used for type aliases, classes, interfaces).
612    /// Enables the Solver to expand Application(Lazy(DefId), Args) by providing
613    /// the type parameters needed for generic substitution.
614    /// Wrapped in `RefCell` to allow mutation through shared references.
615    pub def_type_params: RefCell<FxHashMap<DefId, Vec<tsz_solver::TypeParamInfo>>>,
616
617    /// `DefIds` known to have no type parameters.
618    /// This avoids repeated cross-arena lookups for non-generic symbols.
619    pub def_no_type_params: RefCell<FxHashSet<DefId>>,
620
621    /// Abstract constructor types (`TypeIds`) produced for abstract classes.
622    pub abstract_constructor_types: FxHashSet<TypeId>,
623
624    /// Protected constructor types (`TypeIds`) produced for protected constructors.
625    pub protected_constructor_types: FxHashSet<TypeId>,
626
627    /// Private constructor types (`TypeIds`) produced for private constructors.
628    pub private_constructor_types: FxHashSet<TypeId>,
629
630    /// Maps cross-file `SymbolIds` to their source file index.
631    /// Populated by `resolve_cross_file_export/resolve_cross_file_namespace_exports`
632    /// so `delegate_cross_arena_symbol_resolution` can find the correct arena.
633    pub cross_file_symbol_targets: RefCell<FxHashMap<SymbolId, usize>>,
634
635    /// All arenas for cross-file resolution (indexed by `file_idx` from `Symbol.decl_file_idx`).
636    /// Set during multi-file type checking to allow resolving declarations across files.
637    pub all_arenas: Option<Arc<Vec<Arc<NodeArena>>>>,
638
639    /// All binders for cross-file resolution (indexed by `file_idx`).
640    /// Enables looking up exported symbols from other files during import resolution.
641    pub all_binders: Option<Arc<Vec<Arc<BinderState>>>>,
642
643    /// Resolved module paths map: (`source_file_idx`, specifier) -> `target_file_idx`.
644    /// Used by `get_type_of_symbol` to resolve imports to their target file and symbol.
645    ///
646    /// Key invariant: all specifier lookups should use
647    /// `module_resolution::module_specifier_candidates` for canonical variants.
648    pub resolved_module_paths: Option<Arc<ResolvedModulePathMap>>,
649
650    /// Current file index in multi-file mode (index into `all_arenas/all_binders`).
651    /// Used with `resolved_module_paths` to look up cross-file imports.
652    pub current_file_idx: usize,
653
654    /// Resolved module specifiers for this file (multi-file CLI mode).
655    pub resolved_modules: Option<FxHashSet<String>>,
656
657    /// Track value exports declared in module augmentations for duplicate detection.
658    /// Keyed by a canonical module key (resolved file index or specifier).
659    pub module_augmentation_value_decls: FxHashMap<String, FxHashMap<String, NodeIndex>>,
660
661    /// Per-file cache of `is_external_module` values to preserve state across files.
662    /// Maps file path -> whether that file is an external module (has imports/exports).
663    /// This prevents state corruption when binding multiple files sequentially.
664    pub is_external_module_by_file: Option<Arc<FxHashMap<String, bool>>>,
665
666    /// Map of resolution errors: (`source_file_idx`, specifier) -> Error details.
667    /// Populated by the driver when `ModuleResolver` returns a specific error.
668    /// Contains structured error information (code, message) for TS2834, TS2835, TS2792, etc.
669    ///
670    /// Diagnostic-source invariant: module-not-found-family code/message selection
671    /// should come from resolver outcomes when present.
672    pub resolved_module_errors: Option<Arc<ResolvedModuleErrorMap>>,
673
674    /// Import resolution stack for circular import detection.
675    /// Tracks the chain of modules being resolved to detect circular dependencies.
676    pub import_resolution_stack: Vec<String>,
677
678    /// Set of import specifier nodes that should be elided from JavaScript output.
679    /// These are imports that reference type-only declarations (interfaces, type aliases).
680    /// Populated during type checking and consulted by the emitter.
681    pub type_only_nodes: FxHashSet<NodeIndex>,
682
683    /// Symbol resolution depth counter for preventing stack overflow.
684    /// Tracks how many nested `get_type_of_symbol` calls we've made.
685    pub symbol_resolution_depth: Cell<u32>,
686
687    /// Maximum symbol resolution depth before we give up (prevents stack overflow).
688    pub max_symbol_resolution_depth: u32,
689
690    /// Lib file contexts for global type resolution (lib.es5.d.ts, lib.dom.d.ts, etc.).
691    /// Each entry is a (arena, binder) pair from a pre-parsed lib file.
692    /// Used as a fallback when resolving type references not found in the main file.
693    pub lib_contexts: Vec<LibContext>,
694
695    /// Number of actual lib files loaded (not including user files).
696    /// Used by `has_lib_loaded()` to correctly determine if standard library is available.
697    /// This is separate from `lib_contexts.len()` because `lib_contexts` may also include
698    /// user file contexts for cross-file type resolution in multi-file tests.
699    pub actual_lib_file_count: usize,
700
701    /// Control flow graph for definite assignment analysis and type narrowing.
702    /// This is built during the binding phase and used by the checker.
703    pub flow_graph: Option<FlowGraph<'a>>,
704
705    /// Async context depth - tracks nesting of async functions.
706    /// Used to check if await expressions are within async context (TS1359).
707    pub async_depth: u32,
708
709    /// Stack of symbols being resolved via typeof to detect cycles.
710    /// Prevents infinite loops in typeof X where X's type computation depends on typeof X.
711    pub typeof_resolution_stack: RefCell<FxHashSet<u32>>,
712
713    /// Closure depth - tracks nesting of function expressions, arrow functions, and method expressions.
714    /// Used to apply Rule #42: CFA Invalidation in Closures.
715    /// When > 0, mutable variables (let/var) lose narrowing in closures.
716    pub inside_closure_depth: u32,
717
718    /// When true, we're inside a const assertion (as const) and should preserve literal types.
719    /// This prevents widening of literal types in object/array literals.
720    pub in_const_assertion: bool,
721
722    /// When true, preserve literal types instead of widening.
723    /// Set during evaluation of compound expression branches (conditional `?:`,
724    /// logical `||`/`&&`/`??`) so that `const x = cond ? "a" : "b"` infers
725    /// `"a" | "b"` instead of `string`.
726    pub preserve_literal_types: bool,
727
728    // --- Control Flow Validation ---
729    /// Depth of nested iteration statements (for/while/do-while).
730    /// Used to validate break/continue statements.
731    pub iteration_depth: u32,
732
733    /// Depth of nested switch statements.
734    /// Used to validate break statements (break is valid in switch).
735    pub switch_depth: u32,
736
737    /// Depth of nested functions.
738    /// Used to detect when labeled jumps cross function boundaries.
739    pub function_depth: u32,
740
741    /// Track whether current code path is syntactically unreachable.
742    pub is_unreachable: bool,
743
744    /// Track whether we have already reported an unreachable error in this block/scope.
745    pub has_reported_unreachable: bool,
746
747    /// Stack of labels in scope.
748    /// Each entry contains (`label_name`, `is_iteration`, `function_depth_when_defined`).
749    /// Used for labeled break/continue validation.
750    pub label_stack: Vec<LabelInfo>,
751
752    /// Whether there was a loop/switch in an outer function scope.
753    /// Used to determine TS1107 vs TS1105 for unlabeled break statements.
754    /// When true, an unlabeled break inside a function should emit TS1107,
755    /// because the break is "trying" to exit the outer loop but can't cross
756    /// the function boundary.
757    pub had_outer_loop: bool,
758
759    /// When true, suppress definite assignment errors (TS2454).
760    /// This is used during return type inference to avoid duplicate errors.
761    /// The function body is checked twice: once for return type inference
762    /// and once for actual statement checking. We only want to emit TS2454
763    /// errors during the second pass.
764    pub suppress_definite_assignment_errors: bool,
765
766    /// Set to true during function body checking when the body references `arguments`.
767    /// Used in JS files to add an implicit rest parameter, allowing extra arguments.
768    /// Save/restore pattern ensures correct handling across nested functions.
769    pub js_body_uses_arguments: bool,
770
771    /// Track which (node, symbol) pairs have already emitted TS2454 errors
772    /// to avoid duplicate errors when the same usage is checked multiple times.
773    /// Key: (`node_position`, `symbol_id`)
774    pub emitted_ts2454_errors: FxHashSet<(u32, SymbolId)>,
775
776    /// Fuel counter for type resolution operations.
777    /// Decremented on each type resolution to prevent timeout on pathological types.
778    /// When exhausted, type resolution returns ERROR to prevent infinite loops.
779    pub type_resolution_fuel: RefCell<u32>,
780
781    /// Whether type resolution fuel was exhausted (for timeout detection).
782    pub fuel_exhausted: RefCell<bool>,
783    // NOTE: Freshness is now tracked on the TypeId via ObjectFlags.
784    // This fixes the "Zombie Freshness" bug by interning fresh vs non-fresh
785    // object shapes distinctly.
786}
787
788/// Context for a lib file (arena + binder) for global type resolution.
789#[derive(Clone, Debug)]
790pub struct LibContext {
791    /// The AST arena for this lib file.
792    pub arena: Arc<NodeArena>,
793    /// The binder state with symbols from this lib file.
794    pub binder: Arc<BinderState>,
795}
796
797impl<'a> CheckerContext<'a> {
798    /// Set lib contexts for global type resolution.
799    /// Note: `lib_contexts` may include both actual lib files AND user files for cross-file
800    /// resolution. Use `set_actual_lib_file_count()` to track how many are actual lib files.
801    pub fn set_lib_contexts(&mut self, lib_contexts: Vec<LibContext>) {
802        self.lib_contexts = lib_contexts;
803    }
804
805    /// Set the count of actual lib files loaded (not including user files).
806    /// This is used by `has_lib_loaded()` to correctly determine if standard library is available.
807    pub const fn set_actual_lib_file_count(&mut self, count: usize) {
808        self.actual_lib_file_count = count;
809    }
810
811    /// Set all arenas for cross-file resolution.
812    pub fn set_all_arenas(&mut self, arenas: Arc<Vec<Arc<NodeArena>>>) {
813        self.all_arenas = Some(arenas);
814    }
815
816    /// Set all binders for cross-file resolution.
817    pub fn set_all_binders(&mut self, binders: Arc<Vec<Arc<BinderState>>>) {
818        self.all_binders = Some(binders);
819    }
820
821    /// Set resolved module paths map for cross-file import resolution.
822    pub fn set_resolved_module_paths(&mut self, paths: Arc<FxHashMap<(usize, String), usize>>) {
823        self.resolved_module_paths = Some(paths);
824    }
825
826    /// Set resolved module specifiers (module names that exist in the project).
827    /// Used to suppress TS2307 errors for known modules.
828    pub fn set_resolved_modules(&mut self, modules: FxHashSet<String>) {
829        self.resolved_modules = Some(modules);
830    }
831
832    /// Set resolved module errors map for cross-file import resolution.
833    /// Populated by the driver when `ModuleResolver` returns specific errors (TS2834, TS2835, TS2792, etc.).
834    pub fn set_resolved_module_errors(
835        &mut self,
836        errors: Arc<FxHashMap<(usize, String), ResolutionError>>,
837    ) {
838        self.resolved_module_errors = Some(errors);
839    }
840
841    /// Get the resolution error for a specifier, if any.
842    /// Returns the specific error (TS2834, TS2835, TS2792, etc.) if the module resolution failed with a known error.
843    pub fn get_resolution_error(&self, specifier: &str) -> Option<&ResolutionError> {
844        let errors = self.resolved_module_errors.as_ref()?;
845
846        for candidate in module_specifier_candidates(specifier) {
847            if let Some(error) = errors.get(&(self.current_file_idx, candidate)) {
848                return Some(error);
849            }
850        }
851        None
852    }
853
854    /// Set the current file index.
855    pub const fn set_current_file_idx(&mut self, idx: usize) {
856        self.current_file_idx = idx;
857    }
858
859    /// Get the arena for a specific file index.
860    /// Returns the current arena if `file_idx` is `u32::MAX` (single-file mode).
861    pub fn get_arena_for_file(&self, file_idx: u32) -> &NodeArena {
862        if file_idx == u32::MAX {
863            return self.arena;
864        }
865        if let Some(arenas) = self.all_arenas.as_ref()
866            && let Some(arena) = arenas.get(file_idx as usize)
867        {
868            return arena.as_ref();
869        }
870        self.arena
871    }
872
873    /// Get the binder for a specific file index.
874    /// Returns None if `file_idx` is out of bounds or `all_binders` is not set.
875    pub fn get_binder_for_file(&self, file_idx: usize) -> Option<&BinderState> {
876        self.all_binders
877            .as_ref()
878            .and_then(|binders| binders.get(file_idx))
879            .map(Arc::as_ref)
880    }
881
882    /// Resolve an import specifier to its target file index.
883    /// Uses the `resolved_module_paths` map populated by the driver.
884    /// Returns None if the import cannot be resolved (e.g., external module).
885    pub fn resolve_import_target(&self, specifier: &str) -> Option<usize> {
886        self.resolve_import_target_from_file(self.current_file_idx, specifier)
887    }
888
889    /// Resolve an import specifier from a specific file to its target file index.
890    /// Like `resolve_import_target` but for any source file, not just the current one.
891    pub fn resolve_import_target_from_file(
892        &self,
893        source_file_idx: usize,
894        specifier: &str,
895    ) -> Option<usize> {
896        let paths = self.resolved_module_paths.as_ref()?;
897        for candidate in module_specifier_candidates(specifier) {
898            if let Some(target_idx) = paths.get(&(source_file_idx, candidate)) {
899                return Some(*target_idx);
900            }
901        }
902        None
903    }
904
905    /// Returns true if an augmentation target resolves to an `export =` value without
906    /// namespace/module shape (TS2671/TS2649 cases).
907    pub fn module_resolves_to_non_module_entity(&self, module_specifier: &str) -> bool {
908        let candidates = module_specifier_candidates(module_specifier);
909
910        let lookup_cached = |binder: &BinderState, key: &str| {
911            binder.module_export_equals_non_module.get(key).copied()
912        };
913
914        if let Some(target_idx) = self.resolve_import_target(module_specifier)
915            && let Some(target_binder) = self.get_binder_for_file(target_idx)
916        {
917            for candidate in &candidates {
918                if let Some(non_module) = lookup_cached(target_binder, candidate) {
919                    return non_module;
920                }
921            }
922        }
923
924        for candidate in &candidates {
925            if let Some(non_module) = lookup_cached(self.binder, candidate) {
926                return non_module;
927            }
928        }
929
930        if let Some(all_binders) = self.all_binders.as_ref() {
931            for binder in all_binders.iter() {
932                for candidate in &candidates {
933                    if let Some(non_module) = lookup_cached(binder, candidate) {
934                        return non_module;
935                    }
936                }
937            }
938        }
939
940        let export_equals_is_non_module = |binder: &BinderState,
941                                           exports: &tsz_binder::SymbolTable|
942         -> Option<bool> {
943            let export_equals_sym_id = exports.get("export=")?;
944            let has_named_exports = exports.iter().any(|(name, _)| name != "export=");
945            tracing::trace!(
946                module_specifier = module_specifier,
947                export_equals_sym_id = export_equals_sym_id.0,
948                has_named_exports,
949                "module_resolves_to_non_module_entity: checking exports table"
950            );
951
952            let mut candidate_symbols = Vec::with_capacity(2);
953            if let Some(sym) = binder.get_symbol(export_equals_sym_id) {
954                candidate_symbols.push((binder, sym));
955            } else if let Some(sym) = self.binder.get_symbol(export_equals_sym_id) {
956                candidate_symbols.push((self.binder, sym));
957            } else if let Some(all_binders) = self.all_binders.as_ref() {
958                for other in all_binders.iter() {
959                    if let Some(sym) = other.get_symbol(export_equals_sym_id) {
960                        candidate_symbols.push((other.as_ref(), sym));
961                        break;
962                    }
963                }
964            }
965
966            let has_namespace_shape = |sym_binder: &BinderState, sym: &tsz_binder::Symbol| {
967                let has_namespace_decl = sym.declarations.iter().any(|decl_idx| {
968                    if decl_idx.is_none() {
969                        return false;
970                    }
971                    sym_binder
972                        .declaration_arenas
973                        .get(&(sym.id, *decl_idx))
974                        .and_then(|v| v.first())
975                        .is_some_and(|arena| {
976                            let Some(node) = arena.get(*decl_idx) else {
977                                return false;
978                            };
979                            if node.kind != syntax_kind_ext::MODULE_DECLARATION {
980                                return false;
981                            }
982                            let Some(module_decl) = arena.get_module(node) else {
983                                return false;
984                            };
985                            if module_decl.body.is_none() {
986                                return false;
987                            }
988                            let Some(body_node) = arena.get(module_decl.body) else {
989                                return false;
990                            };
991                            if body_node.kind == syntax_kind_ext::MODULE_BLOCK
992                                && let Some(block) = arena.get_module_block(body_node)
993                                && let Some(statements) = block.statements.as_ref()
994                            {
995                                return !statements.nodes.is_empty();
996                            }
997                            true
998                        })
999                });
1000
1001                sym.exports.as_ref().is_some_and(|tbl| !tbl.is_empty())
1002                    || sym.members.as_ref().is_some_and(|tbl| !tbl.is_empty())
1003                    || has_namespace_decl
1004            };
1005
1006            let export_assignment_target_name =
1007                |sym_binder: &BinderState, sym: &tsz_binder::Symbol| -> Option<String> {
1008                    let mut decls = sym.declarations.clone();
1009                    if sym.value_declaration.is_some() {
1010                        decls.push(sym.value_declaration);
1011                    }
1012
1013                    for decl_idx in decls {
1014                        if decl_idx.is_none() {
1015                            continue;
1016                        }
1017                        let Some(arena) = sym_binder
1018                            .declaration_arenas
1019                            .get(&(sym.id, decl_idx))
1020                            .and_then(|v| v.first())
1021                        else {
1022                            continue;
1023                        };
1024                        let Some(node) = arena.get(decl_idx) else {
1025                            continue;
1026                        };
1027                        if node.kind != syntax_kind_ext::EXPORT_ASSIGNMENT {
1028                            continue;
1029                        }
1030                        let Some(assign) = arena.get_export_assignment(node) else {
1031                            continue;
1032                        };
1033                        if !assign.is_export_equals {
1034                            continue;
1035                        }
1036                        let Some(expr_node) = arena.get(assign.expression) else {
1037                            continue;
1038                        };
1039                        if let Some(id) = arena.get_identifier(expr_node) {
1040                            return Some(id.escaped_text.clone());
1041                        }
1042                    }
1043
1044                    None
1045                };
1046
1047            let symbol_has_namespace_shape =
1048                candidate_symbols.into_iter().any(|(sym_binder, sym)| {
1049                    tracing::trace!(
1050                        module_specifier = module_specifier,
1051                        symbol_name = sym.escaped_name.as_str(),
1052                        symbol_flags = sym.flags,
1053                        "module_resolves_to_non_module_entity: candidate symbol"
1054                    );
1055                    if has_namespace_shape(sym_binder, sym) {
1056                        return true;
1057                    }
1058
1059                    if sym_binder
1060                        .get_symbols()
1061                        .find_all_by_name(&sym.escaped_name)
1062                        .into_iter()
1063                        .filter_map(|candidate_id| sym_binder.get_symbol(candidate_id))
1064                        .any(|candidate| has_namespace_shape(sym_binder, candidate))
1065                    {
1066                        return true;
1067                    }
1068
1069                    let Some(target_name) = export_assignment_target_name(sym_binder, sym) else {
1070                        return false;
1071                    };
1072                    tracing::trace!(
1073                        module_specifier = module_specifier,
1074                        target_name = target_name.as_str(),
1075                        "module_resolves_to_non_module_entity: export assignment target"
1076                    );
1077
1078                    sym_binder
1079                        .get_symbols()
1080                        .find_all_by_name(&target_name)
1081                        .into_iter()
1082                        .filter_map(|target_sym_id| sym_binder.get_symbol(target_sym_id))
1083                        .any(|target_sym| has_namespace_shape(sym_binder, target_sym))
1084                });
1085
1086            tracing::trace!(
1087                module_specifier = module_specifier,
1088                symbol_has_namespace_shape,
1089                "module_resolves_to_non_module_entity: namespace shape computed"
1090            );
1091            Some(!has_named_exports && !symbol_has_namespace_shape)
1092        };
1093        let has_namespace_shape = |binder: &BinderState, sym: &tsz_binder::Symbol| {
1094            let has_namespace_decl = sym.declarations.iter().any(|decl_idx| {
1095                if decl_idx.is_none() {
1096                    return false;
1097                }
1098                binder
1099                    .declaration_arenas
1100                    .get(&(sym.id, *decl_idx))
1101                    .and_then(|v| v.first())
1102                    .is_some_and(|arena| {
1103                        let Some(node) = arena.get(*decl_idx) else {
1104                            return false;
1105                        };
1106                        if node.kind != syntax_kind_ext::MODULE_DECLARATION {
1107                            return false;
1108                        }
1109                        let Some(module_decl) = arena.get_module(node) else {
1110                            return false;
1111                        };
1112                        if module_decl.body.is_none() {
1113                            return false;
1114                        }
1115                        let Some(body_node) = arena.get(module_decl.body) else {
1116                            return false;
1117                        };
1118                        if body_node.kind == syntax_kind_ext::MODULE_BLOCK
1119                            && let Some(block) = arena.get_module_block(body_node)
1120                            && let Some(statements) = block.statements.as_ref()
1121                        {
1122                            return !statements.nodes.is_empty();
1123                        }
1124                        true
1125                    })
1126            });
1127
1128            sym.exports.as_ref().is_some_and(|tbl| !tbl.is_empty())
1129                || sym.members.as_ref().is_some_and(|tbl| !tbl.is_empty())
1130                || has_namespace_decl
1131        };
1132        fn contains_namespace_decl_named(
1133            arena: &NodeArena,
1134            idx: NodeIndex,
1135            target_name: &str,
1136            depth: usize,
1137        ) -> bool {
1138            if depth > 128 {
1139                return false;
1140            }
1141            let Some(node) = arena.get(idx) else {
1142                return false;
1143            };
1144
1145            if node.kind == syntax_kind_ext::MODULE_DECLARATION {
1146                let Some(module_decl) = arena.get_module(node) else {
1147                    return false;
1148                };
1149                if let Some(name_node) = arena.get(module_decl.name)
1150                    && let Some(id) = arena.get_identifier(name_node)
1151                    && id.escaped_text == target_name
1152                {
1153                    if module_decl.body.is_none() {
1154                        return false;
1155                    }
1156                    if let Some(body_node) = arena.get(module_decl.body)
1157                        && body_node.kind == syntax_kind_ext::MODULE_BLOCK
1158                        && let Some(block) = arena.get_module_block(body_node)
1159                        && let Some(stmts) = block.statements.as_ref()
1160                    {
1161                        return !stmts.nodes.is_empty();
1162                    }
1163                    return true;
1164                }
1165                if module_decl.body.is_some() {
1166                    return contains_namespace_decl_named(
1167                        arena,
1168                        module_decl.body,
1169                        target_name,
1170                        depth + 1,
1171                    );
1172                }
1173                return false;
1174            }
1175
1176            if node.kind == syntax_kind_ext::MODULE_BLOCK
1177                && let Some(block) = arena.get_module_block(node)
1178                && let Some(statements) = block.statements.as_ref()
1179            {
1180                for &stmt in &statements.nodes {
1181                    if contains_namespace_decl_named(arena, stmt, target_name, depth + 1) {
1182                        return true;
1183                    }
1184                }
1185            }
1186
1187            false
1188        }
1189        fn collect_export_equals_targets(
1190            arena: &NodeArena,
1191            idx: NodeIndex,
1192            out: &mut Vec<String>,
1193            depth: usize,
1194        ) {
1195            if depth > 128 {
1196                return;
1197            }
1198            let Some(node) = arena.get(idx) else {
1199                return;
1200            };
1201
1202            if node.kind == syntax_kind_ext::EXPORT_ASSIGNMENT {
1203                if let Some(assign) = arena.get_export_assignment(node)
1204                    && assign.is_export_equals
1205                    && let Some(expr_node) = arena.get(assign.expression)
1206                    && let Some(id) = arena.get_identifier(expr_node)
1207                {
1208                    out.push(id.escaped_text.clone());
1209                }
1210                return;
1211            }
1212
1213            if node.kind == syntax_kind_ext::MODULE_DECLARATION {
1214                if let Some(module_decl) = arena.get_module(node)
1215                    && module_decl.body.is_some()
1216                {
1217                    collect_export_equals_targets(arena, module_decl.body, out, depth + 1);
1218                }
1219                return;
1220            }
1221
1222            if node.kind == syntax_kind_ext::MODULE_BLOCK
1223                && let Some(block) = arena.get_module_block(node)
1224                && let Some(statements) = block.statements.as_ref()
1225            {
1226                for &stmt in &statements.nodes {
1227                    collect_export_equals_targets(arena, stmt, out, depth + 1);
1228                }
1229            }
1230        }
1231        let export_assignment_targets_namespace_via_source =
1232            |binder: &BinderState, arena: &NodeArena| {
1233                for source_file in &arena.source_files {
1234                    let mut export_targets = Vec::new();
1235                    for &stmt_idx in &source_file.statements.nodes {
1236                        collect_export_equals_targets(arena, stmt_idx, &mut export_targets, 0);
1237                    }
1238                    for target_name in export_targets {
1239                        let has_matching_namespace_decl = source_file
1240                            .statements
1241                            .nodes
1242                            .iter()
1243                            .copied()
1244                            .any(|top_stmt| {
1245                                contains_namespace_decl_named(arena, top_stmt, &target_name, 0)
1246                            });
1247                        if has_matching_namespace_decl {
1248                            return true;
1249                        }
1250                        if binder
1251                            .get_symbols()
1252                            .find_all_by_name(&target_name)
1253                            .into_iter()
1254                            .filter_map(|target_id| binder.get_symbol(target_id))
1255                            .any(|target_sym| has_namespace_shape(binder, target_sym))
1256                        {
1257                            return true;
1258                        }
1259                    }
1260                }
1261                false
1262            };
1263
1264        if let Some(target_idx) = self.resolve_import_target(module_specifier)
1265            && let Some(target_binder) = self.get_binder_for_file(target_idx)
1266        {
1267            let target_arena = self.get_arena_for_file(target_idx as u32);
1268            for candidate in &candidates {
1269                if let Some(exports) = target_binder.module_exports.get(candidate)
1270                    && let Some(non_module) = export_equals_is_non_module(target_binder, exports)
1271                {
1272                    tracing::trace!(
1273                        module_specifier = module_specifier,
1274                        candidate = candidate.as_str(),
1275                        branch = "target_specifier_key",
1276                        non_module,
1277                        "module_resolves_to_non_module_entity: branch result"
1278                    );
1279                    if non_module
1280                        && export_assignment_targets_namespace_via_source(
1281                            target_binder,
1282                            target_arena,
1283                        )
1284                    {
1285                        tracing::trace!(
1286                            module_specifier = module_specifier,
1287                            candidate = candidate.as_str(),
1288                            branch = "target_specifier_key",
1289                            "module_resolves_to_non_module_entity: source fallback override"
1290                        );
1291                        return false;
1292                    }
1293                    return non_module;
1294                }
1295            }
1296
1297            if let Some(target_file_name) = self
1298                .get_arena_for_file(target_idx as u32)
1299                .source_files
1300                .first()
1301                .map(|sf| sf.file_name.as_str())
1302                && let Some(exports) = target_binder.module_exports.get(target_file_name)
1303                && let Some(non_module) = export_equals_is_non_module(target_binder, exports)
1304            {
1305                tracing::trace!(
1306                    module_specifier = module_specifier,
1307                    branch = "target_file_key",
1308                    non_module,
1309                    "module_resolves_to_non_module_entity: branch result"
1310                );
1311                if non_module
1312                    && export_assignment_targets_namespace_via_source(target_binder, target_arena)
1313                {
1314                    tracing::trace!(
1315                        module_specifier = module_specifier,
1316                        branch = "target_file_key",
1317                        "module_resolves_to_non_module_entity: source fallback override"
1318                    );
1319                    return false;
1320                }
1321                return non_module;
1322            }
1323        }
1324
1325        let mut saw_non_module = false;
1326        if let Some(exports) = self.binder.module_exports.get(module_specifier)
1327            && let Some(non_module) = export_equals_is_non_module(self.binder, exports)
1328        {
1329            tracing::trace!(
1330                module_specifier = module_specifier,
1331                branch = "self_binder",
1332                non_module,
1333                "module_resolves_to_non_module_entity: branch result"
1334            );
1335            if non_module && export_assignment_targets_namespace_via_source(self.binder, self.arena)
1336            {
1337                tracing::trace!(
1338                    module_specifier = module_specifier,
1339                    branch = "self_binder",
1340                    "module_resolves_to_non_module_entity: source fallback override"
1341                );
1342                return false;
1343            }
1344            if !non_module {
1345                return false;
1346            }
1347            saw_non_module = true;
1348        }
1349
1350        if let Some(all_binders) = self.all_binders.as_ref() {
1351            for (idx, binder) in all_binders.iter().enumerate() {
1352                if let Some(exports) = binder.module_exports.get(module_specifier)
1353                    && let Some(non_module) = export_equals_is_non_module(binder, exports)
1354                {
1355                    tracing::trace!(
1356                        module_specifier = module_specifier,
1357                        branch = "all_binders",
1358                        binder_idx = idx,
1359                        non_module,
1360                        "module_resolves_to_non_module_entity: branch result"
1361                    );
1362                    if non_module
1363                        && let Some(all_arenas) = self.all_arenas.as_ref()
1364                        && let Some(arena) = all_arenas.get(idx)
1365                        && export_assignment_targets_namespace_via_source(binder, arena.as_ref())
1366                    {
1367                        tracing::trace!(
1368                            module_specifier = module_specifier,
1369                            branch = "all_binders",
1370                            binder_idx = idx,
1371                            "module_resolves_to_non_module_entity: source fallback override"
1372                        );
1373                        return false;
1374                    }
1375                    if !non_module {
1376                        return false;
1377                    }
1378                    saw_non_module = true;
1379                }
1380            }
1381        }
1382
1383        saw_non_module
1384    }
1385
1386    /// Extract the persistent cache from this context.
1387    /// This allows saving type checking results for future queries.
1388    pub fn extract_cache(self) -> TypeCache {
1389        TypeCache {
1390            symbol_types: self.symbol_types,
1391            symbol_instance_types: self.symbol_instance_types,
1392            node_types: self.node_types,
1393            symbol_dependencies: self.symbol_dependencies,
1394            def_to_symbol: self.def_to_symbol.into_inner(),
1395            flow_analysis_cache: self.flow_analysis_cache.into_inner(),
1396            class_instance_type_to_decl: self.class_instance_type_to_decl,
1397            class_instance_type_cache: self.class_instance_type_cache,
1398            class_constructor_type_cache: self.class_constructor_type_cache,
1399            type_only_nodes: self.type_only_nodes,
1400        }
1401    }
1402
1403    /// Add an error diagnostic (with deduplication).
1404    /// Diagnostics with the same (start, code) are only emitted once.
1405    pub fn error(&mut self, start: u32, length: u32, message: String, code: u32) {
1406        // Check if we've already emitted this diagnostic
1407        let key = (start, code);
1408        if self.emitted_diagnostics.contains(&key) {
1409            return;
1410        }
1411        self.emitted_diagnostics.insert(key);
1412        tracing::debug!(
1413            code,
1414            start,
1415            length,
1416            file = %self.file_name,
1417            message = %message,
1418            "diagnostic"
1419        );
1420        self.diagnostics.push(Diagnostic::error(
1421            self.file_name.clone(),
1422            start,
1423            length,
1424            message,
1425            code,
1426        ));
1427    }
1428
1429    /// Push a diagnostic with deduplication.
1430    /// Diagnostics with the same (start, code) are only emitted once.
1431    /// Exception: TS2318 (missing global type) at position 0 uses message hash
1432    /// to allow multiple distinct global type errors.
1433    pub fn push_diagnostic(&mut self, diag: Diagnostic) {
1434        // For TS2318 at position 0, include message hash in key to allow distinct errors
1435        // (e.g., "Cannot find global type 'Array'" vs "Cannot find global type 'Object'")
1436        let key = if diag.code == 2318 && diag.start == 0 {
1437            // Use a hash of the message to distinguish different TS2318 errors
1438            use std::hash::{Hash, Hasher};
1439            let mut hasher = std::collections::hash_map::DefaultHasher::new();
1440            diag.message_text.hash(&mut hasher);
1441            (hasher.finish() as u32, diag.code)
1442        } else {
1443            (diag.start, diag.code)
1444        };
1445
1446        if self.emitted_diagnostics.contains(&key) {
1447            return;
1448        }
1449        self.emitted_diagnostics.insert(key);
1450        tracing::debug!(
1451            code = diag.code,
1452            start = diag.start,
1453            length = diag.length,
1454            file = %diag.file,
1455            message = %diag.message_text,
1456            "diagnostic"
1457        );
1458        self.diagnostics.push(diag);
1459    }
1460
1461    /// Get node span (pos, end) from index.
1462    pub fn get_node_span(&self, idx: NodeIndex) -> Option<(u32, u32)> {
1463        let node = self.arena.get(idx)?;
1464        Some((node.pos, node.end))
1465    }
1466
1467    /// Push an expected return type onto the stack.
1468    pub fn push_return_type(&mut self, return_type: TypeId) {
1469        self.return_type_stack.push(return_type);
1470    }
1471
1472    /// Pop the expected return type from the stack.
1473    pub fn pop_return_type(&mut self) {
1474        self.return_type_stack.pop();
1475    }
1476
1477    /// Get the current expected return type.
1478    pub fn current_return_type(&self) -> Option<TypeId> {
1479        self.return_type_stack.last().copied()
1480    }
1481
1482    /// Push a contextual yield type for a generator function.
1483    pub fn push_yield_type(&mut self, yield_type: Option<TypeId>) {
1484        self.yield_type_stack.push(yield_type);
1485    }
1486
1487    /// Pop the contextual yield type from the stack.
1488    pub fn pop_yield_type(&mut self) {
1489        self.yield_type_stack.pop();
1490    }
1491
1492    /// Get the current contextual yield type for the enclosing generator.
1493    pub fn current_yield_type(&self) -> Option<TypeId> {
1494        self.yield_type_stack.last().copied().flatten()
1495    }
1496
1497    /// Enter an async context (increment async depth).
1498    pub const fn enter_async_context(&mut self) {
1499        self.async_depth += 1;
1500    }
1501
1502    /// Exit an async context (decrement async depth).
1503    pub const fn exit_async_context(&mut self) {
1504        if self.async_depth > 0 {
1505            self.async_depth -= 1;
1506        }
1507    }
1508
1509    /// Check if we're currently inside an async function.
1510    pub const fn in_async_context(&self) -> bool {
1511        self.async_depth > 0
1512    }
1513
1514    /// Consume one unit of type resolution fuel.
1515    /// Returns true if fuel is still available, false if exhausted.
1516    /// When exhausted, type resolution should return ERROR to prevent timeout.
1517    pub fn consume_fuel(&self) -> bool {
1518        let mut fuel = self.type_resolution_fuel.borrow_mut();
1519        if *fuel == 0 {
1520            *self.fuel_exhausted.borrow_mut() = true;
1521            return false;
1522        }
1523        *fuel -= 1;
1524        true
1525    }
1526
1527    /// Check if type resolution fuel has been exhausted.
1528    pub fn is_fuel_exhausted(&self) -> bool {
1529        *self.fuel_exhausted.borrow()
1530    }
1531
1532    /// Enter a recursive call. Returns true if recursion is allowed,
1533    /// false if the depth limit has been reached (caller should bail out).
1534    #[inline]
1535    pub fn enter_recursion(&self) -> bool {
1536        self.recursion_depth.borrow_mut().enter()
1537    }
1538
1539    /// Leave a recursive call (decrement depth counter).
1540    #[inline]
1541    pub fn leave_recursion(&self) {
1542        self.recursion_depth.borrow_mut().leave();
1543    }
1544
1545    // =========================================================================
1546    // Flow Graph Queries
1547    // =========================================================================
1548
1549    /// Check flow usage at a specific AST node.
1550    ///
1551    /// This method queries the control flow graph to determine flow-sensitive
1552    /// information at a given node. Returns `None` if flow graph is not available.
1553    ///
1554    /// # Arguments
1555    /// * `node_idx` - The AST node to query flow information for
1556    ///
1557    /// # Returns
1558    /// * `Some(FlowNodeId)` - The flow node ID at this location
1559    /// * `None` - If flow graph is not available or node has no flow info
1560    pub fn check_flow_usage(&self, node_idx: NodeIndex) -> Option<tsz_binder::FlowNodeId> {
1561        if let Some(ref _graph) = self.flow_graph {
1562            // Look up the flow node for this AST node from the binder's node_flow mapping
1563            self.binder.node_flow.get(&node_idx.0).copied()
1564        } else {
1565            None
1566        }
1567    }
1568
1569    /// Get a reference to the flow graph.
1570    pub const fn flow_graph(&self) -> Option<&FlowGraph<'a>> {
1571        self.flow_graph.as_ref()
1572    }
1573}