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}