Skip to main content

tsz_solver/
visitor.rs

1//! Type Visitor Pattern
2//!
3//! This module implements the Visitor pattern for `TypeData` operations,
4//! providing a clean alternative to repetitive match statements.
5//!
6//! # Benefits
7//!
8//! - **Centralized type logic**: All type handling in one place
9//! - **Easier to extend**: Add new visitors without modifying existing code
10//! - **Type-safe**: Compiler ensures all variants are handled
11//! - **Composable**: Visitors can be combined and chained
12//!
13//! # Usage
14//!
15//! ```rust,ignore
16//! use crate::visitor::{TypeVisitor, TypeKind, is_type_kind};
17//! use crate::types::{IntrinsicKind, LiteralValue};
18//!
19//! struct MyVisitor;
20//!
21//! impl TypeVisitor for MyVisitor {
22//!     type Output = bool;
23//!
24//!     fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> Self::Output {
25//!         matches!(kind, IntrinsicKind::Any)
26//!     }
27//!
28//!     fn visit_literal(&mut self, value: &LiteralValue) -> Self::Output {
29//!         matches!(value, LiteralValue::Boolean(true))
30//!     }
31//!
32//!     fn default_output() -> Self::Output {
33//!         false
34//!     }
35//! }
36//!
37//! // Or use convenience functions:
38//! let is_object = is_type_kind(&types, type_id, TypeKind::Object);
39//! ```
40
41use crate::def::DefId;
42use crate::types::{
43    IntrinsicKind, ObjectShapeId, StringIntrinsicKind, TupleElement, TypeParamInfo,
44};
45use crate::{LiteralValue, SymbolRef, TypeData, TypeDatabase, TypeId};
46use rustc_hash::{FxHashMap, FxHashSet};
47
48// Re-export type data extraction helpers (extracted to visitor_extract.rs)
49pub use crate::visitor_extract::*;
50
51// =============================================================================
52// Type Visitor Trait
53// =============================================================================
54
55/// Visitor pattern for `TypeData` traversal and transformation.
56///
57/// Implement this trait to perform custom operations on types without
58/// writing repetitive match statements. Each method corresponds to a
59/// `TypeData` variant and receives the relevant data for that type.
60pub trait TypeVisitor: Sized {
61    /// The output type produced by visiting.
62    type Output;
63
64    // =========================================================================
65    // Core Type Visitors
66    // =========================================================================
67
68    /// Visit an intrinsic type (any, unknown, never, void, etc.).
69    fn visit_intrinsic(&mut self, kind: IntrinsicKind) -> Self::Output;
70
71    /// Visit a literal type (string, number, boolean, bigint literals).
72    fn visit_literal(&mut self, value: &LiteralValue) -> Self::Output;
73
74    // =========================================================================
75    // Composite Types - Default implementations provided
76    // =========================================================================
77
78    /// Visit an object type with properties.
79    fn visit_object(&mut self, _shape_id: u32) -> Self::Output {
80        Self::default_output()
81    }
82
83    /// Visit an object type with index signatures.
84    fn visit_object_with_index(&mut self, _shape_id: u32) -> Self::Output {
85        Self::default_output()
86    }
87
88    /// Visit a union type (A | B | C).
89    fn visit_union(&mut self, _list_id: u32) -> Self::Output {
90        Self::default_output()
91    }
92
93    /// Visit an intersection type (A & B & C).
94    fn visit_intersection(&mut self, _list_id: u32) -> Self::Output {
95        Self::default_output()
96    }
97
98    /// Visit an array type T[].
99    fn visit_array(&mut self, _element_type: TypeId) -> Self::Output {
100        Self::default_output()
101    }
102
103    /// Visit a tuple type [T, U, V].
104    fn visit_tuple(&mut self, _list_id: u32) -> Self::Output {
105        Self::default_output()
106    }
107
108    /// Visit a function type.
109    fn visit_function(&mut self, _shape_id: u32) -> Self::Output {
110        Self::default_output()
111    }
112
113    /// Visit a callable type with call/construct signatures.
114    fn visit_callable(&mut self, _shape_id: u32) -> Self::Output {
115        Self::default_output()
116    }
117
118    /// Visit a type parameter (generic type variable).
119    fn visit_type_parameter(&mut self, _param_info: &TypeParamInfo) -> Self::Output {
120        Self::default_output()
121    }
122
123    /// Visit a bound type parameter using De Bruijn index for alpha-equivalence.
124    ///
125    /// This is used for canonicalizing generic types to achieve structural identity,
126    /// where `type F<T> = T` and `type G<U> = U` are considered identical.
127    /// The index represents which parameter in the binding scope (0 = innermost).
128    fn visit_bound_parameter(&mut self, _de_bruijn_index: u32) -> Self::Output {
129        Self::default_output()
130    }
131
132    /// Visit a named type reference (interface, class, type alias).
133    fn visit_ref(&mut self, _symbol_ref: u32) -> Self::Output {
134        Self::default_output()
135    }
136
137    /// Visit an enum type with nominal identity and structural member types.
138    fn visit_enum(&mut self, _def_id: u32, _member_type: TypeId) -> Self::Output {
139        Self::default_output()
140    }
141
142    /// Visit a lazy type reference using `DefId`.
143    fn visit_lazy(&mut self, _def_id: u32) -> Self::Output {
144        Self::default_output()
145    }
146
147    /// Visit a recursive type reference using De Bruijn index.
148    ///
149    /// This is used for canonicalizing recursive types to achieve O(1) equality.
150    /// The index represents how many levels up the nesting chain to refer to.
151    fn visit_recursive(&mut self, _de_bruijn_index: u32) -> Self::Output {
152        Self::default_output()
153    }
154
155    /// Visit a generic type application Base<Args>.
156    fn visit_application(&mut self, _app_id: u32) -> Self::Output {
157        Self::default_output()
158    }
159
160    /// Visit a conditional type T extends U ? X : Y.
161    fn visit_conditional(&mut self, _cond_id: u32) -> Self::Output {
162        Self::default_output()
163    }
164
165    /// Visit a mapped type { [K in Keys]: V }.
166    fn visit_mapped(&mut self, _mapped_id: u32) -> Self::Output {
167        Self::default_output()
168    }
169
170    /// Visit an indexed access type T[K].
171    fn visit_index_access(&mut self, _object_type: TypeId, _key_type: TypeId) -> Self::Output {
172        Self::default_output()
173    }
174
175    /// Visit a template literal type `hello${x}world`.
176    fn visit_template_literal(&mut self, _template_id: u32) -> Self::Output {
177        Self::default_output()
178    }
179
180    /// Visit a type query (typeof expr).
181    fn visit_type_query(&mut self, _symbol_ref: u32) -> Self::Output {
182        Self::default_output()
183    }
184
185    /// Visit a keyof type.
186    fn visit_keyof(&mut self, _type_id: TypeId) -> Self::Output {
187        Self::default_output()
188    }
189
190    /// Visit a readonly type modifier.
191    fn visit_readonly_type(&mut self, _inner_type: TypeId) -> Self::Output {
192        Self::default_output()
193    }
194
195    /// Visit a unique symbol type.
196    fn visit_unique_symbol(&mut self, _symbol_ref: u32) -> Self::Output {
197        Self::default_output()
198    }
199
200    /// Visit an infer type (for type inference in conditional types).
201    fn visit_infer(&mut self, _param_info: &TypeParamInfo) -> Self::Output {
202        Self::default_output()
203    }
204
205    /// Visit a this type (polymorphic this parameter).
206    fn visit_this_type(&mut self) -> Self::Output {
207        Self::default_output()
208    }
209
210    /// Visit a string manipulation intrinsic type.
211    fn visit_string_intrinsic(
212        &mut self,
213        _kind: StringIntrinsicKind,
214        _type_arg: TypeId,
215    ) -> Self::Output {
216        Self::default_output()
217    }
218
219    /// Visit an error type.
220    fn visit_error(&mut self) -> Self::Output {
221        Self::default_output()
222    }
223
224    /// Visit a `NoInfer`<T> type (TypeScript 5.4+).
225    /// Traverses the inner type (`NoInfer` is transparent for traversal).
226    fn visit_no_infer(&mut self, _inner: TypeId) -> Self::Output {
227        Self::default_output()
228    }
229
230    /// Visit a module namespace type (import * as ns).
231    fn visit_module_namespace(&mut self, _symbol_ref: u32) -> Self::Output {
232        Self::default_output()
233    }
234
235    // =========================================================================
236    // Helper Methods
237    // =========================================================================
238
239    /// Default output for unimplemented variants.
240    fn default_output() -> Self::Output;
241
242    /// Visit a type by dispatching to the appropriate method.
243    ///
244    /// This is the main entry point for using the visitor.
245    fn visit_type(&mut self, types: &dyn TypeDatabase, type_id: TypeId) -> Self::Output {
246        match types.lookup(type_id) {
247            Some(ref type_key) => self.visit_type_key(types, type_key),
248            None => Self::default_output(),
249        }
250    }
251
252    /// Visit a `TypeData` by dispatching to the appropriate method.
253    fn visit_type_key(&mut self, _types: &dyn TypeDatabase, type_key: &TypeData) -> Self::Output {
254        match type_key {
255            TypeData::Intrinsic(kind) => self.visit_intrinsic(*kind),
256            TypeData::Literal(value) => self.visit_literal(value),
257            TypeData::Object(id) => self.visit_object(id.0),
258            TypeData::ObjectWithIndex(id) => self.visit_object_with_index(id.0),
259            TypeData::Union(id) => self.visit_union(id.0),
260            TypeData::Intersection(id) => self.visit_intersection(id.0),
261            TypeData::Array(element_type) => self.visit_array(*element_type),
262            TypeData::Tuple(id) => self.visit_tuple(id.0),
263            TypeData::Function(id) => self.visit_function(id.0),
264            TypeData::Callable(id) => self.visit_callable(id.0),
265            TypeData::TypeParameter(info) => self.visit_type_parameter(info),
266            TypeData::BoundParameter(index) => self.visit_bound_parameter(*index),
267            TypeData::Lazy(def_id) => self.visit_lazy(def_id.0),
268            TypeData::Recursive(index) => self.visit_recursive(*index),
269            TypeData::Enum(def_id, member_type) => self.visit_enum(def_id.0, *member_type),
270            TypeData::Application(id) => self.visit_application(id.0),
271            TypeData::Conditional(id) => self.visit_conditional(id.0),
272            TypeData::Mapped(id) => self.visit_mapped(id.0),
273            TypeData::IndexAccess(obj, key) => self.visit_index_access(*obj, *key),
274            TypeData::TemplateLiteral(id) => self.visit_template_literal(id.0),
275            TypeData::TypeQuery(sym_ref) => self.visit_type_query(sym_ref.0),
276            TypeData::KeyOf(type_id) => self.visit_keyof(*type_id),
277            TypeData::ReadonlyType(inner) => self.visit_readonly_type(*inner),
278            TypeData::UniqueSymbol(sym_ref) => self.visit_unique_symbol(sym_ref.0),
279            TypeData::Infer(info) => self.visit_infer(info),
280            TypeData::ThisType => self.visit_this_type(),
281            TypeData::StringIntrinsic { kind, type_arg } => {
282                self.visit_string_intrinsic(*kind, *type_arg)
283            }
284            TypeData::ModuleNamespace(sym_ref) => self.visit_module_namespace(sym_ref.0),
285            TypeData::NoInfer(inner) => self.visit_no_infer(*inner),
286            TypeData::Error => self.visit_error(),
287        }
288    }
289}
290
291// =============================================================================
292// Type Traversal Helpers
293// =============================================================================
294
295/// Invoke a function on each immediate child `TypeId` of a `TypeData`.
296///
297/// This function provides a simple way to traverse the type graph without
298/// requiring the full Visitor pattern. It's useful for operations like:
299/// - Populating caches (ensuring all nested types are resolved)
300/// - Collecting dependencies
301/// - Type environment population
302///
303/// # Parameters
304///
305/// * `db` - The type database to look up type structures
306/// * `key` - The `TypeData` whose children should be visited
307/// * `f` - Function to call for each child `TypeId`
308///
309/// # Examples
310///
311/// ```rust,ignore
312/// use crate::visitor::for_each_child;
313///
314/// for_each_child(types, &type_key, |child_id| {
315///     // Process each nested type
316/// });
317/// ```
318///
319/// # `TypeData` Variants Handled
320///
321/// This function handles ALL `TypeData` variants to ensure complete traversal:
322/// - **Single nested types**: Array, `ReadonlyType`, `KeyOf`, etc.
323/// - **Multiple members**: Union, Intersection
324/// - **Structured types**: Object, Tuple, Function, Callable
325/// - **Complex types**: Application, Conditional, Mapped, `IndexAccess`
326/// - **Template literals**: Iterates over template spans
327/// - **String intrinsics**: Visits type argument
328/// - **Leaf types**: Intrinsic, Literal, Lazy, `TypeQuery`, etc. (no children)
329pub fn for_each_child<F>(db: &dyn TypeDatabase, key: &TypeData, mut f: F)
330where
331    F: FnMut(TypeId),
332{
333    match key {
334        // Single nested type
335        TypeData::Array(inner)
336        | TypeData::ReadonlyType(inner)
337        | TypeData::KeyOf(inner)
338        | TypeData::NoInfer(inner) => {
339            f(*inner);
340        }
341
342        // Composite types with multiple members
343        TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
344            for &member in db.type_list(*list_id).iter() {
345                f(member);
346            }
347        }
348
349        // Object types with properties and index signatures
350        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
351            let shape = db.object_shape(*shape_id);
352            for prop in &shape.properties {
353                f(prop.type_id);
354                f(prop.write_type); // IMPORTANT: Must visit both read and write types
355            }
356            if let Some(ref sig) = shape.string_index {
357                f(sig.key_type);
358                f(sig.value_type);
359            }
360            if let Some(ref sig) = shape.number_index {
361                f(sig.key_type);
362                f(sig.value_type);
363            }
364        }
365
366        // Tuple types
367        TypeData::Tuple(tuple_id) => {
368            for elem in db.tuple_list(*tuple_id).iter() {
369                f(elem.type_id);
370            }
371        }
372
373        // Function types
374        TypeData::Function(func_id) => {
375            let sig = db.function_shape(*func_id);
376            f(sig.return_type);
377            if let Some(this_type) = sig.this_type {
378                f(this_type);
379            }
380            if let Some(ref type_predicate) = sig.type_predicate
381                && let Some(type_id) = type_predicate.type_id
382            {
383                f(type_id);
384            }
385            for param in &sig.params {
386                f(param.type_id);
387            }
388            for type_param in &sig.type_params {
389                if let Some(constraint) = type_param.constraint {
390                    f(constraint);
391                }
392                if let Some(default) = type_param.default {
393                    f(default);
394                }
395            }
396        }
397
398        // Callable types
399        TypeData::Callable(callable_id) => {
400            let callable = db.callable_shape(*callable_id);
401            for sig in &callable.call_signatures {
402                f(sig.return_type);
403                if let Some(this_type) = sig.this_type {
404                    f(this_type);
405                }
406                if let Some(ref type_predicate) = sig.type_predicate
407                    && let Some(type_id) = type_predicate.type_id
408                {
409                    f(type_id);
410                }
411                for param in &sig.params {
412                    f(param.type_id);
413                }
414                for type_param in &sig.type_params {
415                    if let Some(constraint) = type_param.constraint {
416                        f(constraint);
417                    }
418                    if let Some(default) = type_param.default {
419                        f(default);
420                    }
421                }
422            }
423            for sig in &callable.construct_signatures {
424                f(sig.return_type);
425                if let Some(this_type) = sig.this_type {
426                    f(this_type);
427                }
428                if let Some(ref type_predicate) = sig.type_predicate
429                    && let Some(type_id) = type_predicate.type_id
430                {
431                    f(type_id);
432                }
433                for param in &sig.params {
434                    f(param.type_id);
435                }
436                for type_param in &sig.type_params {
437                    if let Some(constraint) = type_param.constraint {
438                        f(constraint);
439                    }
440                    if let Some(default) = type_param.default {
441                        f(default);
442                    }
443                }
444            }
445            // Visit prototype properties
446            for prop in &callable.properties {
447                f(prop.type_id);
448                f(prop.write_type);
449            }
450            if let Some(ref sig) = callable.string_index {
451                f(sig.key_type);
452                f(sig.value_type);
453            }
454            if let Some(ref sig) = callable.number_index {
455                f(sig.key_type);
456                f(sig.value_type);
457            }
458        }
459
460        // Type applications
461        TypeData::Application(app_id) => {
462            let app = db.type_application(*app_id);
463            f(app.base);
464            for &arg in &app.args {
465                f(arg);
466            }
467        }
468
469        // Conditional types
470        TypeData::Conditional(cond_id) => {
471            let cond = db.conditional_type(*cond_id);
472            f(cond.check_type);
473            f(cond.extends_type);
474            f(cond.true_type);
475            f(cond.false_type);
476        }
477
478        // Mapped types
479        TypeData::Mapped(mapped_id) => {
480            let mapped = db.mapped_type(*mapped_id);
481            if let Some(constraint) = mapped.type_param.constraint {
482                f(constraint);
483            }
484            if let Some(default) = mapped.type_param.default {
485                f(default);
486            }
487            f(mapped.constraint);
488            f(mapped.template);
489            if let Some(name_type) = mapped.name_type {
490                f(name_type);
491            }
492        }
493
494        // Index access types
495        TypeData::IndexAccess(obj, idx) => {
496            f(*obj);
497            f(*idx);
498        }
499
500        // Template literal types
501        TypeData::TemplateLiteral(template_id) => {
502            for span in db.template_list(*template_id).iter() {
503                match span {
504                    crate::types::TemplateSpan::Text(_) => {}
505                    crate::types::TemplateSpan::Type(type_id) => {
506                        f(*type_id);
507                    }
508                }
509            }
510        }
511
512        // String intrinsics
513        TypeData::StringIntrinsic { type_arg, .. } => {
514            f(*type_arg);
515        }
516
517        // Type parameters with constraints
518        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
519            if let Some(constraint) = info.constraint {
520                f(constraint);
521            }
522        }
523
524        // Enum types
525        TypeData::Enum(_def_id, member_type) => {
526            f(*member_type);
527        }
528
529        // Leaf types - no children to visit
530        TypeData::Intrinsic(_)
531        | TypeData::Literal(_)
532        | TypeData::Lazy(_)
533        | TypeData::Recursive(_)
534        | TypeData::BoundParameter(_)
535        | TypeData::TypeQuery(_)
536        | TypeData::UniqueSymbol(_)
537        | TypeData::ThisType
538        | TypeData::ModuleNamespace(_)
539        | TypeData::Error => {}
540    }
541}
542
543/// Walk all transitively referenced type IDs from `root`.
544///
545/// The callback is invoked once per unique reachable type (including `root`).
546pub fn walk_referenced_types<F>(types: &dyn TypeDatabase, root: TypeId, mut f: F)
547where
548    F: FnMut(TypeId),
549{
550    let mut visited = FxHashSet::default();
551    let mut stack = vec![root];
552
553    while let Some(current) = stack.pop() {
554        if !visited.insert(current) {
555            continue;
556        }
557        f(current);
558
559        let Some(key) = types.lookup(current) else {
560            continue;
561        };
562        for_each_child(types, &key, |child| stack.push(child));
563    }
564}
565
566/// Collect all unique lazy `DefIds` reachable from `root`.
567pub fn collect_lazy_def_ids(types: &dyn TypeDatabase, root: TypeId) -> Vec<DefId> {
568    let mut out = Vec::new();
569    let mut seen = FxHashSet::default();
570
571    walk_referenced_types(types, root, |type_id| {
572        if let Some(TypeData::Lazy(def_id)) = types.lookup(type_id)
573            && seen.insert(def_id)
574        {
575            out.push(def_id);
576        }
577    });
578
579    out
580}
581
582/// Collect all unique enum `DefIds` reachable from `root`.
583pub fn collect_enum_def_ids(types: &dyn TypeDatabase, root: TypeId) -> Vec<DefId> {
584    let mut out = Vec::new();
585    let mut seen = FxHashSet::default();
586
587    walk_referenced_types(types, root, |type_id| {
588        if let Some(TypeData::Enum(def_id, _)) = types.lookup(type_id)
589            && seen.insert(def_id)
590        {
591            out.push(def_id);
592        }
593    });
594
595    out
596}
597
598/// Collect all unique type-query symbol references reachable from `root`.
599pub fn collect_type_queries(types: &dyn TypeDatabase, root: TypeId) -> Vec<SymbolRef> {
600    let mut out = Vec::new();
601    let mut seen = FxHashSet::default();
602
603    walk_referenced_types(types, root, |type_id| {
604        if let Some(TypeData::TypeQuery(symbol_ref)) = types.lookup(type_id)
605            && seen.insert(symbol_ref)
606        {
607            out.push(symbol_ref);
608        }
609    });
610
611    out
612}
613
614// =============================================================================
615// Common Visitor Implementations
616// =============================================================================
617
618/// Classification of types into broad categories.
619#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
620pub enum TypeKind {
621    /// Primitive types (string, number, boolean, etc.)
622    Primitive,
623    /// Literal types ("hello", 42, true)
624    Literal,
625    /// Object types
626    Object,
627    /// Array types
628    Array,
629    /// Tuple types
630    Tuple,
631    /// Union types
632    Union,
633    /// Intersection types
634    Intersection,
635    /// Function/callable types
636    Function,
637    /// Generic types (type applications)
638    Generic,
639    /// Type parameters (T, K, etc.)
640    TypeParameter,
641    /// Conditional types (T extends U ? X : Y)
642    Conditional,
643    /// Mapped types ({ [K in Keys]: V })
644    Mapped,
645    /// Index access types (T[K])
646    IndexAccess,
647    /// Template literal types (`hello${T}`)
648    TemplateLiteral,
649    /// Type query (typeof expr)
650    TypeQuery,
651    /// `KeyOf` types (keyof T)
652    KeyOf,
653    /// Named type references (interfaces, type aliases)
654    Reference,
655    /// Error types
656    Error,
657    /// Other/unknown
658    Other,
659}
660
661/// Visitor that checks if a type is a specific `TypeKind`.
662pub struct TypeKindVisitor {
663    /// The kind to check for.
664    pub target_kind: TypeKind,
665}
666
667impl TypeKindVisitor {
668    /// Create a new `TypeKindVisitor`.
669    pub const fn new(target_kind: TypeKind) -> Self {
670        Self { target_kind }
671    }
672
673    /// Get the kind of a type from its `TypeData`.
674    pub const fn get_kind(type_key: &TypeData) -> TypeKind {
675        match type_key {
676            TypeData::Intrinsic(_)
677            | TypeData::Enum(_, _)
678            | TypeData::UniqueSymbol(_)
679            | TypeData::StringIntrinsic { .. } => TypeKind::Primitive,
680            TypeData::Literal(_) => TypeKind::Literal,
681            TypeData::Object(_) | TypeData::ObjectWithIndex(_) | TypeData::ModuleNamespace(_) => {
682                TypeKind::Object
683            }
684            TypeData::Array(_) => TypeKind::Array,
685            TypeData::Tuple(_) => TypeKind::Tuple,
686            TypeData::Union(_) => TypeKind::Union,
687            TypeData::Intersection(_) => TypeKind::Intersection,
688            TypeData::Function(_) | TypeData::Callable(_) => TypeKind::Function,
689            TypeData::Application(_) => TypeKind::Generic,
690            TypeData::TypeParameter(_) | TypeData::Infer(_) | TypeData::BoundParameter(_) => {
691                TypeKind::TypeParameter
692            }
693            TypeData::Conditional(_) => TypeKind::Conditional,
694            TypeData::Lazy(_) | TypeData::Recursive(_) => TypeKind::Reference,
695            TypeData::Mapped(_) => TypeKind::Mapped,
696            TypeData::IndexAccess(_, _) => TypeKind::IndexAccess,
697            TypeData::TemplateLiteral(_) => TypeKind::TemplateLiteral,
698            TypeData::TypeQuery(_) => TypeKind::TypeQuery,
699            TypeData::KeyOf(_) => TypeKind::KeyOf,
700            TypeData::ReadonlyType(_inner) => {
701                // Readonly doesn't change the kind - look through it
702                // Note: This requires lookup which we don't have here
703                // For now, return Other and let callers handle it
704                TypeKind::Other
705            }
706            TypeData::NoInfer(_inner) => {
707                // NoInfer doesn't change the kind - look through it
708                TypeKind::Other
709            }
710            TypeData::ThisType => TypeKind::TypeParameter, // this is type-parameter-like
711            TypeData::Error => TypeKind::Error,
712        }
713    }
714
715    /// Get the kind of a type by `TypeId`.
716    pub fn get_kind_of(types: &dyn TypeDatabase, type_id: TypeId) -> TypeKind {
717        match types.lookup(type_id) {
718            Some(ref type_key) => Self::get_kind(type_key),
719            None => TypeKind::Other,
720        }
721    }
722}
723
724impl TypeVisitor for TypeKindVisitor {
725    type Output = bool;
726
727    fn visit_intrinsic(&mut self, _kind: IntrinsicKind) -> Self::Output {
728        self.target_kind == TypeKind::Primitive
729    }
730
731    fn visit_literal(&mut self, _value: &LiteralValue) -> Self::Output {
732        self.target_kind == TypeKind::Literal
733    }
734
735    fn visit_type_key(&mut self, _types: &dyn TypeDatabase, type_key: &TypeData) -> Self::Output {
736        Self::get_kind(type_key) == self.target_kind
737    }
738
739    fn default_output() -> Self::Output {
740        false
741    }
742}
743
744/// Visitor that collects all `TypeIds` referenced by a type.
745///
746/// Useful for finding dependencies or tracking type usage.
747pub struct TypeCollectorVisitor {
748    /// Set of collected type IDs.
749    pub types: FxHashSet<TypeId>,
750    /// Maximum depth to traverse.
751    pub max_depth: usize,
752}
753
754impl TypeCollectorVisitor {
755    /// Create a new `TypeCollectorVisitor`.
756    pub fn new() -> Self {
757        Self {
758            types: FxHashSet::default(),
759            max_depth: 10,
760        }
761    }
762
763    /// Create a new `TypeCollectorVisitor` with custom max depth.
764    pub fn with_max_depth(max_depth: usize) -> Self {
765        Self {
766            types: FxHashSet::default(),
767            max_depth,
768        }
769    }
770}
771
772impl Default for TypeCollectorVisitor {
773    fn default() -> Self {
774        Self::new()
775    }
776}
777
778impl TypeVisitor for TypeCollectorVisitor {
779    type Output = ();
780
781    fn visit_intrinsic(&mut self, _kind: IntrinsicKind) -> Self::Output {
782        // Intrinsic types have no child types to collect
783    }
784
785    fn visit_literal(&mut self, _value: &LiteralValue) -> Self::Output {
786        // Literal types have no child types to collect
787    }
788
789    fn visit_array(&mut self, element_type: TypeId) -> Self::Output {
790        if self.max_depth > 0 {
791            self.types.insert(element_type);
792        }
793    }
794
795    fn visit_index_access(&mut self, object_type: TypeId, key_type: TypeId) -> Self::Output {
796        if self.max_depth > 0 {
797            self.types.insert(object_type);
798            self.types.insert(key_type);
799        }
800    }
801
802    fn visit_keyof(&mut self, type_id: TypeId) -> Self::Output {
803        if self.max_depth > 0 {
804            self.types.insert(type_id);
805        }
806    }
807
808    fn visit_readonly_type(&mut self, inner_type: TypeId) -> Self::Output {
809        if self.max_depth > 0 {
810            self.types.insert(inner_type);
811        }
812    }
813
814    fn visit_string_intrinsic(
815        &mut self,
816        _kind: StringIntrinsicKind,
817        type_arg: TypeId,
818    ) -> Self::Output {
819        if self.max_depth > 0 {
820            self.types.insert(type_arg);
821        }
822    }
823
824    fn visit_enum(&mut self, _def_id: u32, member_type: TypeId) -> Self::Output {
825        if self.max_depth > 0 {
826            self.types.insert(member_type);
827        }
828    }
829
830    fn default_output() -> Self::Output {}
831}
832
833/// Visitor that checks if a type matches a specific predicate.
834pub struct TypePredicateVisitor<F>
835where
836    F: Fn(&TypeData) -> bool,
837{
838    /// Predicate function to test against `TypeData`.
839    pub predicate: F,
840}
841
842impl<F> TypePredicateVisitor<F>
843where
844    F: Fn(&TypeData) -> bool,
845{
846    /// Create a new `TypePredicateVisitor`.
847    pub const fn new(predicate: F) -> Self {
848        Self { predicate }
849    }
850}
851
852impl<F> TypeVisitor for TypePredicateVisitor<F>
853where
854    F: Fn(&TypeData) -> bool,
855{
856    type Output = bool;
857
858    fn visit_type_key(&mut self, _types: &dyn TypeDatabase, type_key: &TypeData) -> Self::Output {
859        (self.predicate)(type_key)
860    }
861
862    fn visit_intrinsic(&mut self, _kind: IntrinsicKind) -> Self::Output {
863        false
864    }
865
866    fn visit_literal(&mut self, _value: &LiteralValue) -> Self::Output {
867        false
868    }
869
870    fn default_output() -> Self::Output {
871        false
872    }
873}
874
875// =============================================================================
876// Convenience Functions
877// =============================================================================
878
879/// Check if a type is a specific kind using the `TypeKindVisitor`.
880///
881/// # Example
882///
883/// ```rust,ignore
884/// use crate::visitor::{is_type_kind, TypeKind};
885///
886/// let is_object = is_type_kind(&types, type_id, TypeKind::Object);
887/// ```
888pub fn is_type_kind(types: &dyn TypeDatabase, type_id: TypeId, kind: TypeKind) -> bool {
889    let mut visitor = TypeKindVisitor::new(kind);
890    visitor.visit_type(types, type_id)
891}
892
893/// Collect all types referenced by a type.
894///
895/// # Example
896///
897/// ```rust,ignore
898/// use crate::visitor::collect_referenced_types;
899///
900/// let types = collect_referenced_types(&type_interner, type_id);
901/// ```
902pub fn collect_referenced_types(types: &dyn TypeDatabase, type_id: TypeId) -> FxHashSet<TypeId> {
903    collect_all_types(types, type_id)
904}
905
906/// Test a type against a predicate function.
907///
908/// # Example
909///
910/// ```rust,ignore
911/// use crate::{TypeData, LiteralValue, visitor::test_type};
912///
913/// let is_string_literal = test_type(&types, type_id, |key| {
914///     matches!(key, TypeData::Literal(LiteralValue::String(_)))
915/// });
916/// ```
917pub fn test_type<F>(types: &dyn TypeDatabase, type_id: TypeId, predicate: F) -> bool
918where
919    F: Fn(&TypeData) -> bool,
920{
921    let mut visitor = TypePredicateVisitor::new(predicate);
922    visitor.visit_type(types, type_id)
923}
924
925// =============================================================================
926// Specialized Type Predicate Visitors
927// =============================================================================
928
929/// Check if a type is a literal type.
930///
931/// Matches: `TypeData::Literal`(_)
932pub fn is_literal_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
933    matches!(types.lookup(type_id), Some(TypeData::Literal(_)))
934}
935
936/// Check if a type is a module namespace type (import * as ns).
937///
938/// Matches: `TypeData::ModuleNamespace`(_)
939pub fn is_module_namespace_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
940    matches!(types.lookup(type_id), Some(TypeData::ModuleNamespace(_)))
941}
942
943/// Check if a type is a function type (Function or Callable).
944///
945/// This also handles intersections containing function types.
946pub fn is_function_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
947    is_function_type_impl(types, type_id)
948}
949
950fn is_function_type_impl(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
951    match types.lookup(type_id) {
952        Some(TypeData::Function(_) | TypeData::Callable(_)) => true,
953        Some(TypeData::Intersection(members)) => {
954            let members = types.type_list(members);
955            members
956                .iter()
957                .any(|&member| is_function_type_impl(types, member))
958        }
959        _ => false,
960    }
961}
962
963/// Check if a type is an object-like type (suitable for typeof "object").
964///
965/// Returns true for: Object, `ObjectWithIndex`, Array, Tuple, Mapped, `ReadonlyType` (of object)
966pub fn is_object_like_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
967    is_object_like_type_impl(types, type_id)
968}
969
970fn is_object_like_type_impl(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
971    match types.lookup(type_id) {
972        Some(
973            TypeData::Object(_)
974            | TypeData::ObjectWithIndex(_)
975            | TypeData::Array(_)
976            | TypeData::Tuple(_)
977            | TypeData::Mapped(_)
978            | TypeData::Function(_)
979            | TypeData::Callable(_)
980            | TypeData::Intrinsic(IntrinsicKind::Object | IntrinsicKind::Function),
981        ) => true,
982        Some(TypeData::ReadonlyType(inner)) => is_object_like_type_impl(types, inner),
983        Some(TypeData::Intersection(members)) => {
984            let members = types.type_list(members);
985            members
986                .iter()
987                .all(|&member| is_object_like_type_impl(types, member))
988        }
989        Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info
990            .constraint
991            .is_some_and(|constraint| is_object_like_type_impl(types, constraint)),
992        _ => false,
993    }
994}
995
996/// Check if a type is an empty object type (no properties, no index signatures).
997pub fn is_empty_object_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
998    match types.lookup(type_id) {
999        Some(TypeData::Object(shape_id)) => {
1000            let shape = types.object_shape(shape_id);
1001            shape.properties.is_empty()
1002        }
1003        Some(TypeData::ObjectWithIndex(shape_id)) => {
1004            let shape = types.object_shape(shape_id);
1005            shape.properties.is_empty()
1006                && shape.string_index.is_none()
1007                && shape.number_index.is_none()
1008        }
1009        _ => false,
1010    }
1011}
1012
1013/// Check if a type is a primitive type (intrinsic or literal).
1014pub fn is_primitive_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1015    // Check well-known intrinsic TypeIds first
1016    if type_id.is_intrinsic() {
1017        return true;
1018    }
1019    matches!(
1020        types.lookup(type_id),
1021        Some(TypeData::Intrinsic(_) | TypeData::Literal(_))
1022    )
1023}
1024
1025/// Check if a type is a union type.
1026pub fn is_union_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1027    matches!(types.lookup(type_id), Some(TypeData::Union(_)))
1028}
1029
1030/// Check if a type is an intersection type.
1031pub fn is_intersection_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1032    matches!(types.lookup(type_id), Some(TypeData::Intersection(_)))
1033}
1034
1035/// Check if a type is an array type.
1036pub fn is_array_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1037    matches!(types.lookup(type_id), Some(TypeData::Array(_)))
1038}
1039
1040/// Check if a type is a tuple type.
1041pub fn is_tuple_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1042    matches!(types.lookup(type_id), Some(TypeData::Tuple(_)))
1043}
1044
1045/// Check if a type is a type parameter.
1046pub fn is_type_parameter(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1047    matches!(
1048        types.lookup(type_id),
1049        Some(TypeData::TypeParameter(_) | TypeData::Infer(_))
1050    )
1051}
1052
1053/// Check if a type is a conditional type.
1054pub fn is_conditional_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1055    matches!(types.lookup(type_id), Some(TypeData::Conditional(_)))
1056}
1057
1058/// Check if a type is a mapped type.
1059pub fn is_mapped_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1060    matches!(types.lookup(type_id), Some(TypeData::Mapped(_)))
1061}
1062
1063/// Check if a type is an index access type.
1064pub fn is_index_access_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1065    matches!(types.lookup(type_id), Some(TypeData::IndexAccess(_, _)))
1066}
1067
1068/// Check if a type is a template literal type.
1069pub fn is_template_literal_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1070    matches!(types.lookup(type_id), Some(TypeData::TemplateLiteral(_)))
1071}
1072
1073/// Check if a type is a type reference (Lazy/DefId).
1074pub fn is_type_reference(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1075    matches!(
1076        types.lookup(type_id),
1077        Some(TypeData::Lazy(_) | TypeData::Recursive(_))
1078    )
1079}
1080
1081/// Check if a type is a generic type application.
1082pub fn is_generic_application(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1083    matches!(types.lookup(type_id), Some(TypeData::Application(_)))
1084}
1085
1086/// Check if a type is a "unit type" - a type that represents exactly one value.
1087///
1088/// Unit types are types where subtyping reduces to identity: two different unit types
1089/// are always disjoint (neither is a subtype of the other, except for identity).
1090///
1091/// This is used as an optimization to skip structural recursion in subtype checking.
1092/// For example, comparing `[E.A, E.B]` vs `[E.C, E.D]` can return `source == target`
1093/// in O(1) instead of walking into each tuple element.
1094///
1095/// Unit types include:
1096/// - Literal types (string, number, boolean, bigint literals)
1097/// - Enum members (`TypeData::Enum`)
1098/// - Unique symbols
1099/// - null, undefined, void
1100/// - Tuples where ALL elements are unit types (and no rest elements)
1101///
1102/// NOTE: This does NOT handle `ReadonlyType` - readonly tuples must be checked separately
1103/// because `["a"]` is a subtype of `readonly ["a"]` even though they have different `TypeIds`.
1104pub fn is_unit_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1105    is_unit_type_impl(types, type_id, 0)
1106}
1107
1108const MAX_UNIT_TYPE_DEPTH: u32 = 10;
1109
1110fn is_unit_type_impl(types: &dyn TypeDatabase, type_id: TypeId, depth: u32) -> bool {
1111    // Prevent stack overflow on pathological types
1112    if depth > MAX_UNIT_TYPE_DEPTH {
1113        return false;
1114    }
1115
1116    // Check well-known singleton types first
1117    if type_id == TypeId::NULL
1118        || type_id == TypeId::UNDEFINED
1119        || type_id == TypeId::VOID
1120        || type_id == TypeId::NEVER
1121    {
1122        return true;
1123    }
1124
1125    match types.lookup(type_id) {
1126        // Unit-like scalar types are handled together.
1127        Some(TypeData::Literal(_))
1128        | Some(TypeData::Enum(_, _))
1129        | Some(TypeData::UniqueSymbol(_)) => true,
1130
1131        // Tuples are unit types if ALL elements are unit types (no rest elements)
1132        Some(TypeData::Tuple(list_id)) => {
1133            let elements = types.tuple_list(list_id);
1134            // Check for rest elements - if any, not a unit type
1135            if elements.iter().any(|e| e.rest) {
1136                return false;
1137            }
1138            // All elements must be unit types
1139            elements
1140                .iter()
1141                .all(|e| is_unit_type_impl(types, e.type_id, depth + 1))
1142        }
1143
1144        // Everything else is not a unit type
1145        // ReadonlyType of a unit tuple is NOT considered a unit type for optimization purposes
1146        // because ["a"] <: readonly ["a"] but they have different TypeIds.
1147        _ => false,
1148    }
1149}
1150
1151// =============================================================================
1152// Recursive Type Visitor - Traverses into nested types
1153// =============================================================================
1154
1155/// A visitor that recursively collects all types referenced by a root type.
1156/// Unlike `TypeCollectorVisitor`, this properly traverses into nested structures.
1157pub struct RecursiveTypeCollector<'a> {
1158    types: &'a dyn TypeDatabase,
1159    collected: FxHashSet<TypeId>,
1160    guard: crate::recursion::RecursionGuard<TypeId>,
1161}
1162
1163impl<'a> RecursiveTypeCollector<'a> {
1164    pub fn new(types: &'a dyn TypeDatabase) -> Self {
1165        Self {
1166            types,
1167            collected: FxHashSet::default(),
1168            guard: crate::recursion::RecursionGuard::with_profile(
1169                crate::recursion::RecursionProfile::ShallowTraversal,
1170            ),
1171        }
1172    }
1173
1174    pub fn with_max_depth(types: &'a dyn TypeDatabase, max_depth: usize) -> Self {
1175        Self {
1176            types,
1177            collected: FxHashSet::default(),
1178            guard: crate::recursion::RecursionGuard::new(max_depth as u32, 100_000),
1179        }
1180    }
1181
1182    /// Collect all types reachable from the given type.
1183    pub fn collect(&mut self, type_id: TypeId) -> FxHashSet<TypeId> {
1184        self.visit(type_id);
1185        std::mem::take(&mut self.collected)
1186    }
1187
1188    fn visit(&mut self, type_id: TypeId) {
1189        // Already collected
1190        if self.collected.contains(&type_id) {
1191            return;
1192        }
1193
1194        // Unified enter: checks depth, cycle, iterations
1195        match self.guard.enter(type_id) {
1196            crate::recursion::RecursionResult::Entered => {}
1197            _ => return,
1198        }
1199
1200        self.collected.insert(type_id);
1201
1202        if let Some(key) = self.types.lookup(type_id) {
1203            self.visit_key(&key);
1204        }
1205
1206        self.guard.leave(type_id);
1207    }
1208
1209    fn visit_key(&mut self, key: &TypeData) {
1210        match key {
1211            TypeData::Intrinsic(_)
1212            | TypeData::Literal(_)
1213            | TypeData::Error
1214            | TypeData::ThisType
1215            | TypeData::BoundParameter(_)
1216            | TypeData::Lazy(_)
1217            | TypeData::Recursive(_)
1218            | TypeData::TypeQuery(_)
1219            | TypeData::UniqueSymbol(_)
1220            | TypeData::ModuleNamespace(_) => {
1221                // Leaf types - nothing to traverse
1222            }
1223            TypeData::NoInfer(inner) => {
1224                // Traverse inner type
1225                self.visit(*inner);
1226            }
1227            TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
1228                let shape = self.types.object_shape(*shape_id);
1229                for prop in &shape.properties {
1230                    self.visit(prop.type_id);
1231                    self.visit(prop.write_type);
1232                }
1233                if let Some(ref idx) = shape.string_index {
1234                    self.visit(idx.key_type);
1235                    self.visit(idx.value_type);
1236                }
1237                if let Some(ref idx) = shape.number_index {
1238                    self.visit(idx.key_type);
1239                    self.visit(idx.value_type);
1240                }
1241            }
1242            TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
1243                let members = self.types.type_list(*list_id);
1244                for &member in members.iter() {
1245                    self.visit(member);
1246                }
1247            }
1248            TypeData::Array(elem) => {
1249                self.visit(*elem);
1250            }
1251            TypeData::Tuple(list_id) => {
1252                let elements = self.types.tuple_list(*list_id);
1253                for elem in elements.iter() {
1254                    self.visit(elem.type_id);
1255                }
1256            }
1257            TypeData::Function(shape_id) => {
1258                let shape = self.types.function_shape(*shape_id);
1259                for param in &shape.params {
1260                    self.visit(param.type_id);
1261                }
1262                self.visit(shape.return_type);
1263                if let Some(this_type) = shape.this_type {
1264                    self.visit(this_type);
1265                }
1266                if let Some(ref type_predicate) = shape.type_predicate
1267                    && let Some(type_id) = type_predicate.type_id
1268                {
1269                    self.visit(type_id);
1270                }
1271                for type_param in &shape.type_params {
1272                    if let Some(constraint) = type_param.constraint {
1273                        self.visit(constraint);
1274                    }
1275                    if let Some(default) = type_param.default {
1276                        self.visit(default);
1277                    }
1278                }
1279            }
1280            TypeData::Callable(shape_id) => {
1281                let shape = self.types.callable_shape(*shape_id);
1282                for sig in &shape.call_signatures {
1283                    for param in &sig.params {
1284                        self.visit(param.type_id);
1285                    }
1286                    self.visit(sig.return_type);
1287                    if let Some(this_type) = sig.this_type {
1288                        self.visit(this_type);
1289                    }
1290                    if let Some(ref type_predicate) = sig.type_predicate
1291                        && let Some(type_id) = type_predicate.type_id
1292                    {
1293                        self.visit(type_id);
1294                    }
1295                    for type_param in &sig.type_params {
1296                        if let Some(constraint) = type_param.constraint {
1297                            self.visit(constraint);
1298                        }
1299                        if let Some(default) = type_param.default {
1300                            self.visit(default);
1301                        }
1302                    }
1303                }
1304                for sig in &shape.construct_signatures {
1305                    for param in &sig.params {
1306                        self.visit(param.type_id);
1307                    }
1308                    self.visit(sig.return_type);
1309                    if let Some(this_type) = sig.this_type {
1310                        self.visit(this_type);
1311                    }
1312                    if let Some(ref type_predicate) = sig.type_predicate
1313                        && let Some(type_id) = type_predicate.type_id
1314                    {
1315                        self.visit(type_id);
1316                    }
1317                    for type_param in &sig.type_params {
1318                        if let Some(constraint) = type_param.constraint {
1319                            self.visit(constraint);
1320                        }
1321                        if let Some(default) = type_param.default {
1322                            self.visit(default);
1323                        }
1324                    }
1325                }
1326                for prop in &shape.properties {
1327                    self.visit(prop.type_id);
1328                    self.visit(prop.write_type);
1329                }
1330                if let Some(ref sig) = shape.string_index {
1331                    self.visit(sig.key_type);
1332                    self.visit(sig.value_type);
1333                }
1334                if let Some(ref sig) = shape.number_index {
1335                    self.visit(sig.key_type);
1336                    self.visit(sig.value_type);
1337                }
1338            }
1339            TypeData::TypeParameter(info) | TypeData::Infer(info) => {
1340                if let Some(constraint) = info.constraint {
1341                    self.visit(constraint);
1342                }
1343                if let Some(default) = info.default {
1344                    self.visit(default);
1345                }
1346            }
1347            TypeData::Application(app_id) => {
1348                let app = self.types.type_application(*app_id);
1349                self.visit(app.base);
1350                for &arg in &app.args {
1351                    self.visit(arg);
1352                }
1353            }
1354            TypeData::Conditional(cond_id) => {
1355                let cond = self.types.conditional_type(*cond_id);
1356                self.visit(cond.check_type);
1357                self.visit(cond.extends_type);
1358                self.visit(cond.true_type);
1359                self.visit(cond.false_type);
1360            }
1361            TypeData::Mapped(mapped_id) => {
1362                let mapped = self.types.mapped_type(*mapped_id);
1363                if let Some(constraint) = mapped.type_param.constraint {
1364                    self.visit(constraint);
1365                }
1366                if let Some(default) = mapped.type_param.default {
1367                    self.visit(default);
1368                }
1369                self.visit(mapped.constraint);
1370                self.visit(mapped.template);
1371                if let Some(name_type) = mapped.name_type {
1372                    self.visit(name_type);
1373                }
1374            }
1375            TypeData::IndexAccess(obj, idx) => {
1376                self.visit(*obj);
1377                self.visit(*idx);
1378            }
1379            TypeData::TemplateLiteral(list_id) => {
1380                let spans = self.types.template_list(*list_id);
1381                for span in spans.iter() {
1382                    if let crate::types::TemplateSpan::Type(type_id) = span {
1383                        self.visit(*type_id);
1384                    }
1385                }
1386            }
1387            TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner) => {
1388                self.visit(*inner);
1389            }
1390            TypeData::StringIntrinsic { type_arg, .. } => {
1391                self.visit(*type_arg);
1392            }
1393            TypeData::Enum(_def_id, member_type) => {
1394                // Traverse into the structural member type
1395                self.visit(*member_type);
1396            }
1397        }
1398    }
1399}
1400
1401/// Collect all types recursively reachable from a root type.
1402pub fn collect_all_types(types: &dyn TypeDatabase, type_id: TypeId) -> FxHashSet<TypeId> {
1403    let mut collector = RecursiveTypeCollector::new(types);
1404    collector.collect(type_id)
1405}
1406
1407// =============================================================================
1408// Type Contains Visitor - Check if a type contains specific types
1409// =============================================================================
1410
1411/// Check if a type contains any type parameters.
1412pub fn contains_type_parameters(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1413    contains_type_matching(types, type_id, |key| {
1414        matches!(key, TypeData::TypeParameter(_) | TypeData::Infer(_))
1415    })
1416}
1417
1418/// Check if a type contains any `infer` types.
1419pub fn contains_infer_types(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1420    contains_type_matching(types, type_id, |key| matches!(key, TypeData::Infer(_)))
1421}
1422
1423/// Check if a type contains the error type.
1424pub fn contains_error_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1425    if type_id == TypeId::ERROR {
1426        return true;
1427    }
1428    contains_type_matching(types, type_id, |key| matches!(key, TypeData::Error))
1429}
1430
1431/// Check if a type contains the `this` type anywhere.
1432pub fn contains_this_type(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1433    contains_type_matching(types, type_id, |key| matches!(key, TypeData::ThisType))
1434}
1435
1436/// Check if a type contains any type matching a predicate.
1437pub fn contains_type_matching<F>(types: &dyn TypeDatabase, type_id: TypeId, predicate: F) -> bool
1438where
1439    F: Fn(&TypeData) -> bool,
1440{
1441    let mut checker = ContainsTypeChecker {
1442        types,
1443        predicate,
1444        memo: FxHashMap::default(),
1445        guard: crate::recursion::RecursionGuard::with_profile(
1446            crate::recursion::RecursionProfile::ShallowTraversal,
1447        ),
1448    };
1449    checker.check(type_id)
1450}
1451
1452struct ContainsTypeChecker<'a, F>
1453where
1454    F: Fn(&TypeData) -> bool,
1455{
1456    types: &'a dyn TypeDatabase,
1457    predicate: F,
1458    memo: FxHashMap<TypeId, bool>,
1459    guard: crate::recursion::RecursionGuard<TypeId>,
1460}
1461
1462impl<'a, F> ContainsTypeChecker<'a, F>
1463where
1464    F: Fn(&TypeData) -> bool,
1465{
1466    fn check(&mut self, type_id: TypeId) -> bool {
1467        if let Some(&cached) = self.memo.get(&type_id) {
1468            return cached;
1469        }
1470
1471        match self.guard.enter(type_id) {
1472            crate::recursion::RecursionResult::Entered => {}
1473            _ => return false,
1474        }
1475
1476        let Some(key) = self.types.lookup(type_id) else {
1477            self.guard.leave(type_id);
1478            return false;
1479        };
1480
1481        if (self.predicate)(&key) {
1482            self.guard.leave(type_id);
1483            self.memo.insert(type_id, true);
1484            return true;
1485        }
1486
1487        let result = self.check_key(&key);
1488
1489        self.guard.leave(type_id);
1490        self.memo.insert(type_id, result);
1491
1492        result
1493    }
1494
1495    fn check_key(&mut self, key: &TypeData) -> bool {
1496        match key {
1497            TypeData::Intrinsic(_)
1498            | TypeData::Literal(_)
1499            | TypeData::Error
1500            | TypeData::ThisType
1501            | TypeData::BoundParameter(_)
1502            | TypeData::Lazy(_)
1503            | TypeData::Recursive(_)
1504            | TypeData::TypeQuery(_)
1505            | TypeData::UniqueSymbol(_)
1506            | TypeData::ModuleNamespace(_) => false,
1507            TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
1508                let shape = self.types.object_shape(*shape_id);
1509                shape.properties.iter().any(|p| self.check(p.type_id))
1510                    || shape
1511                        .string_index
1512                        .as_ref()
1513                        .is_some_and(|i| self.check(i.value_type))
1514                    || shape
1515                        .number_index
1516                        .as_ref()
1517                        .is_some_and(|i| self.check(i.value_type))
1518            }
1519            TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
1520                let members = self.types.type_list(*list_id);
1521                members.iter().any(|&m| self.check(m))
1522            }
1523            TypeData::Array(elem) => self.check(*elem),
1524            TypeData::Tuple(list_id) => {
1525                let elements = self.types.tuple_list(*list_id);
1526                elements.iter().any(|e| self.check(e.type_id))
1527            }
1528            TypeData::Function(shape_id) => {
1529                let shape = self.types.function_shape(*shape_id);
1530                shape.params.iter().any(|p| self.check(p.type_id))
1531                    || self.check(shape.return_type)
1532                    || shape.this_type.is_some_and(|t| self.check(t))
1533            }
1534            TypeData::Callable(shape_id) => {
1535                let shape = self.types.callable_shape(*shape_id);
1536                shape.call_signatures.iter().any(|s| {
1537                    s.params.iter().any(|p| self.check(p.type_id)) || self.check(s.return_type)
1538                }) || shape.construct_signatures.iter().any(|s| {
1539                    s.params.iter().any(|p| self.check(p.type_id)) || self.check(s.return_type)
1540                }) || shape.properties.iter().any(|p| self.check(p.type_id))
1541            }
1542            TypeData::TypeParameter(info) | TypeData::Infer(info) => {
1543                info.constraint.is_some_and(|c| self.check(c))
1544                    || info.default.is_some_and(|d| self.check(d))
1545            }
1546            TypeData::Application(app_id) => {
1547                let app = self.types.type_application(*app_id);
1548                self.check(app.base) || app.args.iter().any(|&a| self.check(a))
1549            }
1550            TypeData::Conditional(cond_id) => {
1551                let cond = self.types.conditional_type(*cond_id);
1552                self.check(cond.check_type)
1553                    || self.check(cond.extends_type)
1554                    || self.check(cond.true_type)
1555                    || self.check(cond.false_type)
1556            }
1557            TypeData::Mapped(mapped_id) => {
1558                let mapped = self.types.mapped_type(*mapped_id);
1559                mapped.type_param.constraint.is_some_and(|c| self.check(c))
1560                    || mapped.type_param.default.is_some_and(|d| self.check(d))
1561                    || self.check(mapped.constraint)
1562                    || self.check(mapped.template)
1563                    || mapped.name_type.is_some_and(|n| self.check(n))
1564            }
1565            TypeData::IndexAccess(obj, idx) => self.check(*obj) || self.check(*idx),
1566            TypeData::TemplateLiteral(list_id) => {
1567                let spans = self.types.template_list(*list_id);
1568                spans.iter().any(|span| {
1569                    if let crate::types::TemplateSpan::Type(type_id) = span {
1570                        self.check(*type_id)
1571                    } else {
1572                        false
1573                    }
1574                })
1575            }
1576            TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
1577                self.check(*inner)
1578            }
1579            TypeData::StringIntrinsic { type_arg, .. } => self.check(*type_arg),
1580            TypeData::Enum(_def_id, member_type) => self.check(*member_type),
1581        }
1582    }
1583}
1584
1585// =============================================================================
1586// TypeDatabase-based convenience functions
1587// =============================================================================
1588
1589/// Check if a type is a literal type (`TypeDatabase` version).
1590pub fn is_literal_type_db(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1591    LiteralTypeChecker::check(types, type_id)
1592}
1593
1594/// Check if a type is a module namespace type (`TypeDatabase` version).
1595pub fn is_module_namespace_type_db(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1596    matches!(types.lookup(type_id), Some(TypeData::ModuleNamespace(_)))
1597}
1598
1599/// Check if a type is a function type (`TypeDatabase` version).
1600pub fn is_function_type_db(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1601    FunctionTypeChecker::check(types, type_id)
1602}
1603
1604/// Check if a type is object-like (`TypeDatabase` version).
1605pub fn is_object_like_type_db(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1606    ObjectTypeChecker::check(types, type_id)
1607}
1608
1609/// Check if a type is an empty object type (`TypeDatabase` version).
1610pub fn is_empty_object_type_db(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1611    let checker = EmptyObjectChecker::new(types);
1612    checker.check(type_id)
1613}
1614
1615// =============================================================================
1616// Object Type Classification
1617// =============================================================================
1618
1619/// Classification of object types for freshness tracking.
1620pub enum ObjectTypeKind {
1621    /// A regular object type (no index signatures).
1622    Object(ObjectShapeId),
1623    /// An object type with index signatures.
1624    ObjectWithIndex(ObjectShapeId),
1625    /// Not an object type.
1626    NotObject,
1627}
1628
1629/// Classify a type as an object type kind.
1630///
1631/// This is used by the freshness tracking system to determine if a type
1632/// is a fresh object literal that needs special handling.
1633pub fn classify_object_type(types: &dyn TypeDatabase, type_id: TypeId) -> ObjectTypeKind {
1634    match types.lookup(type_id) {
1635        Some(TypeData::Object(shape_id)) => ObjectTypeKind::Object(shape_id),
1636        Some(TypeData::ObjectWithIndex(shape_id)) => ObjectTypeKind::ObjectWithIndex(shape_id),
1637        _ => ObjectTypeKind::NotObject,
1638    }
1639}
1640
1641// =============================================================================
1642// Visitor Pattern Implementations for Helper Functions
1643// =============================================================================
1644
1645/// Visitor to check if a type is a literal type.
1646struct LiteralTypeChecker;
1647
1648impl LiteralTypeChecker {
1649    fn check(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1650        match types.lookup(type_id) {
1651            Some(TypeData::Literal(_)) => true,
1652            Some(TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner)) => {
1653                Self::check(types, inner)
1654            }
1655            Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
1656                info.constraint.is_some_and(|c| Self::check(types, c))
1657            }
1658            _ => false,
1659        }
1660    }
1661}
1662
1663/// Visitor to check if a type is a function type.
1664struct FunctionTypeChecker;
1665
1666impl FunctionTypeChecker {
1667    fn check(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1668        match types.lookup(type_id) {
1669            Some(TypeData::Function(_) | TypeData::Callable(_)) => true,
1670            Some(TypeData::Intersection(members)) => {
1671                let members = types.type_list(members);
1672                members.iter().any(|&member| Self::check(types, member))
1673            }
1674            Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
1675                info.constraint.is_some_and(|c| Self::check(types, c))
1676            }
1677            _ => false,
1678        }
1679    }
1680}
1681
1682/// Visitor to check if a type is object-like.
1683struct ObjectTypeChecker;
1684
1685impl ObjectTypeChecker {
1686    fn check(types: &dyn TypeDatabase, type_id: TypeId) -> bool {
1687        match types.lookup(type_id) {
1688            Some(
1689                TypeData::Object(_)
1690                | TypeData::ObjectWithIndex(_)
1691                | TypeData::Array(_)
1692                | TypeData::Tuple(_)
1693                | TypeData::Mapped(_),
1694            ) => true,
1695            Some(TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner)) => {
1696                Self::check(types, inner)
1697            }
1698            Some(TypeData::Intersection(members)) => {
1699                let members = types.type_list(members);
1700                members.iter().all(|&member| Self::check(types, member))
1701            }
1702            Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info
1703                .constraint
1704                .is_some_and(|constraint| Self::check(types, constraint)),
1705            _ => false,
1706        }
1707    }
1708}
1709
1710/// Visitor to check if a type is an empty object type.
1711struct EmptyObjectChecker<'a> {
1712    db: &'a dyn TypeDatabase,
1713}
1714
1715impl<'a> EmptyObjectChecker<'a> {
1716    fn new(db: &'a dyn TypeDatabase) -> Self {
1717        Self { db }
1718    }
1719
1720    fn check(&self, type_id: TypeId) -> bool {
1721        match self.db.lookup(type_id) {
1722            Some(TypeData::Object(shape_id)) => {
1723                let shape = self.db.object_shape(shape_id);
1724                shape.properties.is_empty()
1725            }
1726            Some(TypeData::ObjectWithIndex(shape_id)) => {
1727                let shape = self.db.object_shape(shape_id);
1728                shape.properties.is_empty()
1729                    && shape.string_index.is_none()
1730                    && shape.number_index.is_none()
1731            }
1732            Some(TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner)) => self.check(inner),
1733            Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
1734                info.constraint.is_some_and(|c| self.check(c))
1735            }
1736            _ => false,
1737        }
1738    }
1739}
1740
1741// =============================================================================
1742// Const Assertion Visitor
1743// =============================================================================
1744
1745/// Visitor that applies `as const` transformation to a type.
1746///
1747/// This visitor implements the const assertion logic from TypeScript:
1748/// - Literals: Preserved as-is
1749/// - Arrays: Converted to readonly tuples
1750/// - Tuples: Marked readonly, elements recursively const-asserted
1751/// - Objects: All properties marked readonly, recursively const-asserted
1752/// - Other types: Preserved as-is (any, unknown, primitives, etc.)
1753pub struct ConstAssertionVisitor<'a> {
1754    /// The type database/interner.
1755    pub db: &'a dyn TypeDatabase,
1756    /// Unified recursion guard for cycle detection.
1757    pub guard: crate::recursion::RecursionGuard<TypeId>,
1758}
1759
1760impl<'a> ConstAssertionVisitor<'a> {
1761    /// Create a new `ConstAssertionVisitor`.
1762    pub fn new(db: &'a dyn TypeDatabase) -> Self {
1763        Self {
1764            db,
1765            guard: crate::recursion::RecursionGuard::with_profile(
1766                crate::recursion::RecursionProfile::ConstAssertion,
1767            ),
1768        }
1769    }
1770
1771    /// Apply const assertion to a type, returning the transformed type ID.
1772    pub fn apply_const_assertion(&mut self, type_id: TypeId) -> TypeId {
1773        // Prevent infinite recursion
1774        match self.guard.enter(type_id) {
1775            crate::recursion::RecursionResult::Entered => {}
1776            _ => return type_id,
1777        }
1778
1779        let result = match self.db.lookup(type_id) {
1780            // Arrays: Convert to readonly tuple
1781            Some(TypeData::Array(element_type)) => {
1782                let const_element = self.apply_const_assertion(element_type);
1783                // Arrays become readonly tuples when const-asserted
1784                let tuple_elem = TupleElement {
1785                    type_id: const_element,
1786                    name: None,
1787                    optional: false,
1788                    rest: false,
1789                };
1790                let tuple_type = self.db.tuple(vec![tuple_elem]);
1791                self.db.readonly_type(tuple_type)
1792            }
1793
1794            // Tuples: Mark readonly and recurse on elements
1795            Some(TypeData::Tuple(list_id)) => {
1796                let elements = self.db.tuple_list(list_id);
1797                let const_elements: Vec<TupleElement> = elements
1798                    .iter()
1799                    .map(|elem| {
1800                        let const_type = self.apply_const_assertion(elem.type_id);
1801                        TupleElement {
1802                            type_id: const_type,
1803                            name: elem.name,
1804                            optional: elem.optional,
1805                            rest: elem.rest,
1806                        }
1807                    })
1808                    .collect();
1809                let tuple_type = self.db.tuple(const_elements);
1810                self.db.readonly_type(tuple_type)
1811            }
1812
1813            // Objects: Mark all properties readonly and recurse
1814            Some(TypeData::Object(shape_id)) => {
1815                let shape = self.db.object_shape(shape_id);
1816                let mut new_props = Vec::with_capacity(shape.properties.len());
1817
1818                for prop in &shape.properties {
1819                    let const_prop_type = self.apply_const_assertion(prop.type_id);
1820                    let const_write_type = self.apply_const_assertion(prop.write_type);
1821                    new_props.push(crate::types::PropertyInfo {
1822                        name: prop.name,
1823                        type_id: const_prop_type,
1824                        write_type: const_write_type,
1825                        optional: prop.optional,
1826                        readonly: true, // Mark as readonly
1827                        is_method: prop.is_method,
1828                        visibility: prop.visibility,
1829                        parent_id: prop.parent_id,
1830                    });
1831                }
1832
1833                self.db.object(new_props)
1834            }
1835
1836            // Objects with index signatures
1837            Some(TypeData::ObjectWithIndex(shape_id)) => {
1838                let shape = self.db.object_shape(shape_id);
1839                let mut new_props = Vec::with_capacity(shape.properties.len());
1840
1841                for prop in &shape.properties {
1842                    let const_prop_type = self.apply_const_assertion(prop.type_id);
1843                    let const_write_type = self.apply_const_assertion(prop.write_type);
1844                    new_props.push(crate::types::PropertyInfo {
1845                        name: prop.name,
1846                        type_id: const_prop_type,
1847                        write_type: const_write_type,
1848                        optional: prop.optional,
1849                        readonly: true, // Mark as readonly
1850                        is_method: prop.is_method,
1851                        visibility: prop.visibility,
1852                        parent_id: prop.parent_id,
1853                    });
1854                }
1855
1856                // Mark index signatures as readonly
1857                let string_index =
1858                    shape
1859                        .string_index
1860                        .as_ref()
1861                        .map(|idx| crate::types::IndexSignature {
1862                            key_type: idx.key_type,
1863                            value_type: self.apply_const_assertion(idx.value_type),
1864                            readonly: true,
1865                        });
1866
1867                let number_index =
1868                    shape
1869                        .number_index
1870                        .as_ref()
1871                        .map(|idx| crate::types::IndexSignature {
1872                            key_type: idx.key_type,
1873                            value_type: self.apply_const_assertion(idx.value_type),
1874                            readonly: true,
1875                        });
1876
1877                let mut new_shape = (*shape).clone();
1878                new_shape.properties = new_props;
1879                new_shape.string_index = string_index;
1880                new_shape.number_index = number_index;
1881
1882                self.db.object_with_index(new_shape)
1883            }
1884
1885            // Readonly types: Unwrap, process, re-wrap
1886            Some(TypeData::ReadonlyType(inner)) => {
1887                let const_inner = self.apply_const_assertion(inner);
1888                self.db.readonly_type(const_inner)
1889            }
1890
1891            // Unions: Recursively apply to all members
1892            Some(TypeData::Union(list_id)) => {
1893                let members = self.db.type_list(list_id);
1894                let const_members: Vec<TypeId> = members
1895                    .iter()
1896                    .map(|&m| self.apply_const_assertion(m))
1897                    .collect();
1898                self.db.union(const_members)
1899            }
1900
1901            // Intersections: Recursively apply to all members
1902            Some(TypeData::Intersection(list_id)) => {
1903                let members = self.db.type_list(list_id);
1904                let const_members: Vec<TypeId> = members
1905                    .iter()
1906                    .map(|&m| self.apply_const_assertion(m))
1907                    .collect();
1908                self.db.intersection(const_members)
1909            }
1910
1911            // All other types: preserved as-is
1912            _ => type_id,
1913        };
1914
1915        self.guard.leave(type_id);
1916        result
1917    }
1918}