Skip to main content

tsz_solver/
type_queries_extended.rs

1//! Extended Type Query Functions
2//!
3//! This module contains additional type classification and query functions
4//! that complement the core type queries in `type_queries.rs`.
5//!
6//! These functions provide structured classification enums for various
7//! type-checking scenarios, allowing the checker layer to handle types
8//! without directly matching on `TypeData`.
9
10use crate::def::DefId;
11use crate::{TypeData, TypeDatabase, TypeId};
12use rustc_hash::FxHashSet;
13
14// =============================================================================
15// Full Literal Type Classification (includes boolean)
16// =============================================================================
17
18/// Classification for all literal types including boolean.
19/// Used by `literal_type.rs` for comprehensive literal handling.
20#[derive(Debug, Clone)]
21pub enum LiteralTypeKind {
22    /// String literal type with the atom for the string value
23    String(tsz_common::interner::Atom),
24    /// Number literal type with the numeric value
25    Number(f64),
26    /// `BigInt` literal type with the atom for the bigint value
27    BigInt(tsz_common::interner::Atom),
28    /// Boolean literal type with the boolean value
29    Boolean(bool),
30    /// Not a literal type
31    NotLiteral,
32}
33
34/// Classify a type for literal type handling.
35///
36/// This function examines a type and returns information about what kind
37/// of literal it is. Used for:
38/// - Detecting string/number/boolean literals
39/// - Extracting literal values
40/// - Literal type comparison
41pub fn classify_literal_type(db: &dyn TypeDatabase, type_id: TypeId) -> LiteralTypeKind {
42    let Some(key) = db.lookup(type_id) else {
43        return LiteralTypeKind::NotLiteral;
44    };
45
46    match key {
47        TypeData::Literal(crate::LiteralValue::String(atom)) => LiteralTypeKind::String(atom),
48        TypeData::Literal(crate::LiteralValue::Number(ordered_float)) => {
49            LiteralTypeKind::Number(ordered_float.0)
50        }
51        TypeData::Literal(crate::LiteralValue::BigInt(atom)) => LiteralTypeKind::BigInt(atom),
52        TypeData::Literal(crate::LiteralValue::Boolean(value)) => LiteralTypeKind::Boolean(value),
53        _ => LiteralTypeKind::NotLiteral,
54    }
55}
56
57/// Check if a type is a string literal type.
58pub fn is_string_literal(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
59    matches!(
60        classify_literal_type(db, type_id),
61        LiteralTypeKind::String(_)
62    )
63}
64
65/// Check if a type is a number literal type.
66pub fn is_number_literal(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
67    matches!(
68        classify_literal_type(db, type_id),
69        LiteralTypeKind::Number(_)
70    )
71}
72
73/// Check if a type is a boolean literal type.
74pub fn is_boolean_literal(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
75    matches!(
76        classify_literal_type(db, type_id),
77        LiteralTypeKind::Boolean(_)
78    )
79}
80
81/// Get string atom from a string literal type.
82pub fn get_string_literal_atom(
83    db: &dyn TypeDatabase,
84    type_id: TypeId,
85) -> Option<tsz_common::interner::Atom> {
86    match classify_literal_type(db, type_id) {
87        LiteralTypeKind::String(atom) => Some(atom),
88        _ => None,
89    }
90}
91
92/// Get number value from a number literal type.
93pub fn get_number_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> Option<f64> {
94    match classify_literal_type(db, type_id) {
95        LiteralTypeKind::Number(value) => Some(value),
96        _ => None,
97    }
98}
99
100/// Get boolean value from a boolean literal type.
101pub fn get_boolean_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> Option<bool> {
102    match classify_literal_type(db, type_id) {
103        LiteralTypeKind::Boolean(value) => Some(value),
104        _ => None,
105    }
106}
107
108// =============================================================================
109// Index Type Classification
110// =============================================================================
111
112/// Check if a type cannot be used as an index type (TS2538).
113///
114/// A valid index type must be string/number/symbol (or compatible literal forms).
115pub fn is_invalid_index_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
116    let mut visited = FxHashSet::default();
117    is_invalid_index_type_inner(db, type_id, &mut visited)
118}
119
120fn is_invalid_index_type_inner(
121    db: &dyn TypeDatabase,
122    type_id: TypeId,
123    visited: &mut FxHashSet<TypeId>,
124) -> bool {
125    if !visited.insert(type_id) {
126        return false;
127    }
128
129    if matches!(
130        type_id,
131        TypeId::ANY | TypeId::UNKNOWN | TypeId::ERROR | TypeId::NEVER
132    ) {
133        return false;
134    }
135
136    match db.lookup(type_id) {
137        Some(TypeData::Intrinsic(kind)) => matches!(
138            kind,
139            crate::IntrinsicKind::Void
140                | crate::IntrinsicKind::Null
141                | crate::IntrinsicKind::Undefined
142                | crate::IntrinsicKind::Boolean
143                | crate::IntrinsicKind::Bigint
144                | crate::IntrinsicKind::Object
145                | crate::IntrinsicKind::Function
146        ),
147        Some(TypeData::Literal(value)) => matches!(
148            value,
149            crate::LiteralValue::Boolean(_) | crate::LiteralValue::BigInt(_)
150        ),
151        Some(
152            TypeData::Array(_)
153            | TypeData::Tuple(_)
154            | TypeData::Object(_)
155            | TypeData::ObjectWithIndex(_)
156            | TypeData::Function(_)
157            | TypeData::Callable(_)
158            | TypeData::Lazy(_),
159        ) => true,
160        Some(TypeData::Union(list_id) | TypeData::Intersection(list_id)) => db
161            .type_list(list_id)
162            .iter()
163            .any(|&member| is_invalid_index_type_inner(db, member, visited)),
164        Some(TypeData::TypeParameter(info)) => info
165            .constraint
166            .is_some_and(|constraint| is_invalid_index_type_inner(db, constraint, visited)),
167        _ => false,
168    }
169}
170
171// =============================================================================
172// Spread Type Classification
173// =============================================================================
174
175/// Classification for spread operations.
176///
177/// This enum provides a structured way to handle spread types without
178/// directly matching on `TypeData` in the checker layer.
179#[derive(Debug, Clone)]
180pub enum SpreadTypeKind {
181    /// Array type - element type for spread
182    Array(TypeId),
183    /// Tuple type - can expand individual elements
184    Tuple(crate::types::TupleListId),
185    /// Object type - properties can be spread
186    Object(crate::types::ObjectShapeId),
187    /// Object with index signature
188    ObjectWithIndex(crate::types::ObjectShapeId),
189    /// String literal - can be spread as characters
190    StringLiteral(tsz_common::interner::Atom),
191    /// Lazy reference (`DefId`) - needs resolution to actual spreadable type
192    Lazy(DefId),
193    /// Type that needs further checks for iterability
194    Other,
195    /// Type that cannot be spread
196    NotSpreadable,
197}
198
199/// Classify a type for spread operations.
200///
201/// This function examines a type and returns information about how to handle it
202/// when used in a spread context.
203pub fn classify_spread_type(db: &dyn TypeDatabase, type_id: TypeId) -> SpreadTypeKind {
204    // Handle intrinsic types first
205    if type_id.is_any() || type_id == TypeId::STRING {
206        return SpreadTypeKind::Other;
207    }
208    if type_id.is_unknown() {
209        return SpreadTypeKind::NotSpreadable;
210    }
211
212    let Some(key) = db.lookup(type_id) else {
213        return SpreadTypeKind::NotSpreadable;
214    };
215
216    match key {
217        TypeData::Array(element_type) => SpreadTypeKind::Array(element_type),
218        TypeData::Tuple(tuple_id) => SpreadTypeKind::Tuple(tuple_id),
219        TypeData::Object(shape_id) => SpreadTypeKind::Object(shape_id),
220        TypeData::ObjectWithIndex(shape_id) => SpreadTypeKind::ObjectWithIndex(shape_id),
221        TypeData::Literal(crate::LiteralValue::String(atom)) => SpreadTypeKind::StringLiteral(atom),
222        TypeData::Lazy(def_id) => SpreadTypeKind::Lazy(def_id),
223        _ => SpreadTypeKind::Other,
224    }
225}
226
227/// Check if a type has Symbol.iterator or is otherwise iterable.
228///
229/// This is a helper for checking iterability without matching on `TypeData`.
230pub fn is_iterable_type_kind(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
231    // Handle intrinsic string type
232    if type_id == TypeId::STRING {
233        return true;
234    }
235
236    let Some(key) = db.lookup(type_id) else {
237        return false;
238    };
239
240    match key {
241        TypeData::Array(_)
242        | TypeData::Tuple(_)
243        | TypeData::Literal(crate::LiteralValue::String(_)) => true,
244        TypeData::Object(shape_id) => {
245            // Check for [Symbol.iterator] method
246            let shape = db.object_shape(shape_id);
247            shape.properties.iter().any(|prop| {
248                let prop_name = db.resolve_atom_ref(prop.name);
249                (prop_name.as_ref() == "[Symbol.iterator]" || prop_name.as_ref() == "next")
250                    && prop.is_method
251            })
252        }
253        _ => false,
254    }
255}
256
257/// Get the iterable element type for a type if it's iterable.
258pub fn get_iterable_element_type_from_db(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
259    // Handle intrinsic string type
260    if type_id == TypeId::STRING {
261        return Some(TypeId::STRING);
262    }
263
264    let key = db.lookup(type_id)?;
265
266    match key {
267        TypeData::Array(elem_type) => Some(elem_type),
268        TypeData::Tuple(tuple_list_id) => {
269            let elements = db.tuple_list(tuple_list_id);
270            if elements.is_empty() {
271                Some(TypeId::NEVER)
272            } else {
273                let types: Vec<TypeId> = elements.iter().map(|e| e.type_id).collect();
274                Some(db.union(types))
275            }
276        }
277        TypeData::Literal(crate::LiteralValue::String(_)) => Some(TypeId::STRING),
278        TypeData::Object(shape_id) => {
279            // For objects with [Symbol.iterator], we'd need to infer the element type
280            // from the iterator's return type. For now, return Any as a fallback.
281            let shape = db.object_shape(shape_id);
282            let has_iterator = shape.properties.iter().any(|prop| {
283                let prop_name = db.resolve_atom_ref(prop.name);
284                (prop_name.as_ref() == "[Symbol.iterator]" || prop_name.as_ref() == "next")
285                    && prop.is_method
286            });
287            has_iterator.then_some(TypeId::ANY)
288        }
289        _ => None,
290    }
291}
292
293// =============================================================================
294// Type Parameter Classification (Extended)
295// =============================================================================
296
297/// Classification for type parameter types.
298///
299/// This enum provides a structured way to handle type parameters without
300/// directly matching on `TypeData` in the checker layer.
301#[derive(Debug, Clone)]
302pub enum TypeParameterKind {
303    /// Type parameter with info
304    TypeParameter(crate::types::TypeParamInfo),
305    /// Infer type with info
306    Infer(crate::types::TypeParamInfo),
307    /// Type application - may contain type parameters
308    Application(crate::types::TypeApplicationId),
309    /// Union - may contain type parameters in members
310    Union(Vec<TypeId>),
311    /// Intersection - may contain type parameters in members
312    Intersection(Vec<TypeId>),
313    /// Callable - may have type parameters
314    Callable(crate::types::CallableShapeId),
315    /// Not a type parameter or type containing type parameters
316    NotTypeParameter,
317}
318
319/// Classify a type for type parameter handling.
320///
321/// Returns detailed information about type parameter types.
322pub fn classify_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> TypeParameterKind {
323    let Some(key) = db.lookup(type_id) else {
324        return TypeParameterKind::NotTypeParameter;
325    };
326
327    match key {
328        TypeData::TypeParameter(info) => TypeParameterKind::TypeParameter(info),
329        TypeData::Infer(info) => TypeParameterKind::Infer(info),
330        TypeData::Application(app_id) => TypeParameterKind::Application(app_id),
331        TypeData::Union(list_id) => {
332            let members = db.type_list(list_id);
333            TypeParameterKind::Union(members.to_vec())
334        }
335        TypeData::Intersection(list_id) => {
336            let members = db.type_list(list_id);
337            TypeParameterKind::Intersection(members.to_vec())
338        }
339        TypeData::Callable(shape_id) => TypeParameterKind::Callable(shape_id),
340        _ => TypeParameterKind::NotTypeParameter,
341    }
342}
343
344/// Check if a type is directly a type parameter (`TypeParameter` or Infer).
345pub fn is_direct_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
346    matches!(
347        classify_type_parameter(db, type_id),
348        TypeParameterKind::TypeParameter(_) | TypeParameterKind::Infer(_)
349    )
350}
351
352/// Get the type parameter default if this is a type parameter.
353pub fn get_type_param_default(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
354    match db.lookup(type_id) {
355        Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info.default,
356        _ => None,
357    }
358}
359
360/// Get the callable type parameter count.
361pub fn get_callable_type_param_count(db: &dyn TypeDatabase, type_id: TypeId) -> usize {
362    match db.lookup(type_id) {
363        Some(TypeData::Callable(shape_id)) => {
364            let shape = db.callable_shape(shape_id);
365            shape
366                .call_signatures
367                .iter()
368                .map(|sig| sig.type_params.len())
369                .max()
370                .unwrap_or(0)
371        }
372        _ => 0,
373    }
374}
375
376// =============================================================================
377// Promise Type Classification
378// =============================================================================
379
380/// Classification for promise-like types.
381///
382/// This enum provides a structured way to handle promise types without
383/// directly matching on `TypeData` in the checker layer.
384#[derive(Debug, Clone)]
385pub enum PromiseTypeKind {
386    /// Type application (like Promise<T>) - contains base and args
387    Application {
388        app_id: crate::types::TypeApplicationId,
389        base: TypeId,
390        args: Vec<TypeId>,
391    },
392    /// Lazy reference (`DefId`) - needs resolution to check if it's Promise
393    Lazy(crate::def::DefId),
394    /// Object type (might be Promise interface from lib)
395    Object(crate::types::ObjectShapeId),
396    /// Union type - check each member
397    Union(Vec<TypeId>),
398    /// Not a promise type
399    NotPromise,
400}
401
402/// Classify a type for promise handling.
403///
404/// This function examines a type and returns information about how to handle it
405/// when checking for promise-like types.
406pub fn classify_promise_type(db: &dyn TypeDatabase, type_id: TypeId) -> PromiseTypeKind {
407    let Some(key) = db.lookup(type_id) else {
408        return PromiseTypeKind::NotPromise;
409    };
410
411    match key {
412        TypeData::Application(app_id) => {
413            let app = db.type_application(app_id);
414            PromiseTypeKind::Application {
415                app_id,
416                base: app.base,
417                args: app.args.clone(),
418            }
419        }
420        TypeData::Lazy(def_id) => PromiseTypeKind::Lazy(def_id),
421        TypeData::Object(shape_id) => PromiseTypeKind::Object(shape_id),
422        TypeData::Union(list_id) => {
423            let members = db.type_list(list_id);
424            PromiseTypeKind::Union(members.to_vec())
425        }
426        _ => PromiseTypeKind::NotPromise,
427    }
428}
429
430// =============================================================================
431// New Expression Type Classification
432// =============================================================================
433
434/// Classification for types in `new` expressions.
435#[derive(Debug, Clone)]
436pub enum NewExpressionTypeKind {
437    /// Callable type - check for construct signatures
438    Callable(crate::types::CallableShapeId),
439    /// Function type - always constructable
440    Function(crate::types::FunctionShapeId),
441    /// `TypeQuery` (typeof X) - needs symbol resolution
442    TypeQuery(crate::types::SymbolRef),
443    /// Intersection type - check all members for construct signatures
444    Intersection(Vec<TypeId>),
445    /// Union type - all members must be constructable
446    Union(Vec<TypeId>),
447    /// Type parameter with constraint
448    TypeParameter { constraint: Option<TypeId> },
449    /// Not constructable
450    NotConstructable,
451}
452
453/// Classify a type for new expression handling.
454pub fn classify_for_new_expression(
455    db: &dyn TypeDatabase,
456    type_id: TypeId,
457) -> NewExpressionTypeKind {
458    let Some(key) = db.lookup(type_id) else {
459        return NewExpressionTypeKind::NotConstructable;
460    };
461
462    match key {
463        TypeData::Callable(shape_id) => NewExpressionTypeKind::Callable(shape_id),
464        TypeData::Function(shape_id) => NewExpressionTypeKind::Function(shape_id),
465        TypeData::TypeQuery(sym_ref) => NewExpressionTypeKind::TypeQuery(sym_ref),
466        TypeData::Intersection(list_id) => {
467            let members = db.type_list(list_id);
468            NewExpressionTypeKind::Intersection(members.to_vec())
469        }
470        TypeData::Union(list_id) => {
471            let members = db.type_list(list_id);
472            NewExpressionTypeKind::Union(members.to_vec())
473        }
474        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
475            NewExpressionTypeKind::TypeParameter {
476                constraint: info.constraint,
477            }
478        }
479        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
480            // Objects might contain callable properties that represent construct signatures
481            // Check if the object has a "new" property or if any property is callable with construct signatures
482            let shape = db.object_shape(shape_id);
483            for prop in &shape.properties {
484                // Check if this property is a callable type with construct signatures
485                if let Some(TypeData::Callable(callable_shape_id)) = db.lookup(prop.type_id) {
486                    let callable_shape = db.callable_shape(callable_shape_id);
487                    if !callable_shape.construct_signatures.is_empty() {
488                        // Found a callable property with construct signatures
489                        return NewExpressionTypeKind::Callable(callable_shape_id);
490                    }
491                }
492            }
493            NewExpressionTypeKind::NotConstructable
494        }
495        _ => NewExpressionTypeKind::NotConstructable,
496    }
497}
498
499// =============================================================================
500// Abstract Class Type Classification
501// =============================================================================
502
503/// Classification for checking if a type contains abstract classes.
504#[derive(Debug, Clone)]
505pub enum AbstractClassCheckKind {
506    /// `TypeQuery` - check if symbol is abstract
507    TypeQuery(crate::types::SymbolRef),
508    /// Union - check if any member is abstract
509    Union(Vec<TypeId>),
510    /// Intersection - check if any member is abstract
511    Intersection(Vec<TypeId>),
512    /// Other type - not an abstract class
513    NotAbstract,
514}
515
516/// Classify a type for abstract class checking.
517pub fn classify_for_abstract_check(
518    db: &dyn TypeDatabase,
519    type_id: TypeId,
520) -> AbstractClassCheckKind {
521    let Some(key) = db.lookup(type_id) else {
522        return AbstractClassCheckKind::NotAbstract;
523    };
524
525    match key {
526        TypeData::TypeQuery(sym_ref) => AbstractClassCheckKind::TypeQuery(sym_ref),
527        TypeData::Union(list_id) => {
528            let members = db.type_list(list_id);
529            AbstractClassCheckKind::Union(members.to_vec())
530        }
531        TypeData::Intersection(list_id) => {
532            let members = db.type_list(list_id);
533            AbstractClassCheckKind::Intersection(members.to_vec())
534        }
535        _ => AbstractClassCheckKind::NotAbstract,
536    }
537}
538
539// =============================================================================
540// Construct Signature Return Type Classification
541// =============================================================================
542
543/// Classification for extracting construct signature return types.
544#[derive(Debug, Clone)]
545pub enum ConstructSignatureKind {
546    /// Callable type with potential construct signatures
547    Callable(crate::types::CallableShapeId),
548    /// Lazy reference (`DefId`) - resolve and check
549    Lazy(crate::def::DefId),
550    /// `TypeQuery` (typeof X) - check if class
551    TypeQuery(crate::types::SymbolRef),
552    /// Application type - needs evaluation
553    Application(crate::types::TypeApplicationId),
554    /// Union - all members must have construct signatures
555    Union(Vec<TypeId>),
556    /// Intersection - any member with construct signature is sufficient
557    Intersection(Vec<TypeId>),
558    /// Type parameter with constraint
559    TypeParameter { constraint: Option<TypeId> },
560    /// Function type - check `is_constructor` flag
561    Function(crate::types::FunctionShapeId),
562    /// No construct signatures available
563    NoConstruct,
564}
565
566/// Classify a type for construct signature extraction.
567pub fn classify_for_construct_signature(
568    db: &dyn TypeDatabase,
569    type_id: TypeId,
570) -> ConstructSignatureKind {
571    let Some(key) = db.lookup(type_id) else {
572        return ConstructSignatureKind::NoConstruct;
573    };
574
575    match key {
576        TypeData::Callable(shape_id) => ConstructSignatureKind::Callable(shape_id),
577        TypeData::Lazy(def_id) => ConstructSignatureKind::Lazy(def_id),
578        TypeData::TypeQuery(sym_ref) => ConstructSignatureKind::TypeQuery(sym_ref),
579        TypeData::Application(app_id) => ConstructSignatureKind::Application(app_id),
580        TypeData::Union(list_id) => {
581            let members = db.type_list(list_id);
582            ConstructSignatureKind::Union(members.to_vec())
583        }
584        TypeData::Intersection(list_id) => {
585            let members = db.type_list(list_id);
586            ConstructSignatureKind::Intersection(members.to_vec())
587        }
588        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
589            ConstructSignatureKind::TypeParameter {
590                constraint: info.constraint,
591            }
592        }
593        TypeData::Function(shape_id) => ConstructSignatureKind::Function(shape_id),
594        _ => ConstructSignatureKind::NoConstruct,
595    }
596}
597
598// =============================================================================
599// KeyOf Type Classification
600// =============================================================================
601
602/// Classification for computing keyof types.
603#[derive(Debug, Clone)]
604pub enum KeyOfTypeKind {
605    /// Object type with properties
606    Object(crate::types::ObjectShapeId),
607    /// No keys available
608    NoKeys,
609}
610
611/// Classify a type for keyof computation.
612pub fn classify_for_keyof(db: &dyn TypeDatabase, type_id: TypeId) -> KeyOfTypeKind {
613    let Some(key) = db.lookup(type_id) else {
614        return KeyOfTypeKind::NoKeys;
615    };
616
617    match key {
618        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
619            KeyOfTypeKind::Object(shape_id)
620        }
621        _ => KeyOfTypeKind::NoKeys,
622    }
623}
624
625// =============================================================================
626// String Literal Key Extraction
627// =============================================================================
628
629/// Classification for extracting string literal keys.
630#[derive(Debug, Clone)]
631pub enum StringLiteralKeyKind {
632    /// Single string literal
633    SingleString(tsz_common::interner::Atom),
634    /// Union of types - check each member
635    Union(Vec<TypeId>),
636    /// Not a string literal
637    NotStringLiteral,
638}
639
640/// Classify a type for string literal key extraction.
641pub fn classify_for_string_literal_keys(
642    db: &dyn TypeDatabase,
643    type_id: TypeId,
644) -> StringLiteralKeyKind {
645    let Some(key) = db.lookup(type_id) else {
646        return StringLiteralKeyKind::NotStringLiteral;
647    };
648
649    match key {
650        TypeData::Literal(crate::types::LiteralValue::String(name)) => {
651            StringLiteralKeyKind::SingleString(name)
652        }
653        TypeData::Union(list_id) => {
654            let members = db.type_list(list_id);
655            StringLiteralKeyKind::Union(members.to_vec())
656        }
657        _ => StringLiteralKeyKind::NotStringLiteral,
658    }
659}
660
661/// Extract string literal from a Literal type.
662/// Returns None if not a string literal.
663pub fn get_string_literal_value(
664    db: &dyn TypeDatabase,
665    type_id: TypeId,
666) -> Option<tsz_common::interner::Atom> {
667    match db.lookup(type_id) {
668        Some(TypeData::Literal(crate::types::LiteralValue::String(name))) => Some(name),
669        _ => None,
670    }
671}
672
673/// Extract string, numeric, enum, or unique symbol property name from a type.
674pub fn get_literal_property_name(
675    db: &dyn TypeDatabase,
676    type_id: TypeId,
677) -> Option<tsz_common::interner::Atom> {
678    match db.lookup(type_id) {
679        Some(TypeData::Literal(crate::types::LiteralValue::String(name))) => Some(name),
680        Some(TypeData::Literal(crate::types::LiteralValue::Number(num))) => {
681            // Format number exactly like TS (e.g. 1.0 -> "1")
682            let s = format!("{}", num.0);
683            Some(db.intern_string(&s))
684        }
685        Some(TypeData::UniqueSymbol(sym)) => {
686            let s = format!("__unique_{}", sym.0);
687            Some(db.intern_string(&s))
688        }
689        Some(TypeData::Enum(_, member_type)) => get_literal_property_name(db, member_type),
690        _ => None,
691    }
692}
693
694// =============================================================================
695// Class Declaration from Type
696// =============================================================================
697
698/// Classification for extracting class declarations from types.
699#[derive(Debug, Clone)]
700pub enum ClassDeclTypeKind {
701    /// Object type with properties (may have brand)
702    Object(crate::types::ObjectShapeId),
703    /// Union/Intersection - check all members
704    Members(Vec<TypeId>),
705    /// Not an object type
706    NotObject,
707}
708
709/// Classify a type for class declaration extraction.
710pub fn classify_for_class_decl(db: &dyn TypeDatabase, type_id: TypeId) -> ClassDeclTypeKind {
711    let Some(key) = db.lookup(type_id) else {
712        return ClassDeclTypeKind::NotObject;
713    };
714
715    match key {
716        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
717            ClassDeclTypeKind::Object(shape_id)
718        }
719        TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
720            let members = db.type_list(list_id);
721            ClassDeclTypeKind::Members(members.to_vec())
722        }
723        _ => ClassDeclTypeKind::NotObject,
724    }
725}
726
727// =============================================================================
728// Call Expression Overload Classification
729// =============================================================================
730
731/// Classification for extracting call signatures from a type.
732#[derive(Debug, Clone)]
733pub enum CallSignaturesKind {
734    /// Callable type with signatures
735    Callable(crate::types::CallableShapeId),
736    /// Multiple call signatures (e.g., from union of callables)
737    MultipleSignatures(Vec<crate::CallSignature>),
738    /// Other type - no call signatures
739    NoSignatures,
740}
741
742/// Classify a type for call signature extraction.
743pub fn classify_for_call_signatures(db: &dyn TypeDatabase, type_id: TypeId) -> CallSignaturesKind {
744    let Some(key) = db.lookup(type_id) else {
745        return CallSignaturesKind::NoSignatures;
746    };
747
748    match key {
749        TypeData::Callable(shape_id) => CallSignaturesKind::Callable(shape_id),
750        TypeData::Union(list_id) => {
751            // For unions, collect call signatures from all callable members
752            let members = db.type_list(list_id);
753            let mut call_signatures = Vec::new();
754
755            for &member in members.iter() {
756                match db.lookup(member) {
757                    Some(TypeData::Callable(shape_id)) => {
758                        let shape = db.callable_shape(shape_id);
759                        // Extend with call signatures from this member
760                        call_signatures.extend(shape.call_signatures.iter().cloned());
761                    }
762                    _ => continue,
763                }
764            }
765
766            if call_signatures.is_empty() {
767                CallSignaturesKind::NoSignatures
768            } else {
769                CallSignaturesKind::MultipleSignatures(call_signatures)
770            }
771        }
772        _ => CallSignaturesKind::NoSignatures,
773    }
774}
775
776// =============================================================================
777// Generic Application Type Extraction
778// =============================================================================
779
780/// Get the base and args from an Application type.
781/// Returns None if not an Application.
782pub fn get_application_info(
783    db: &dyn TypeDatabase,
784    type_id: TypeId,
785) -> Option<(TypeId, Vec<TypeId>)> {
786    match db.lookup(type_id) {
787        Some(TypeData::Application(app_id)) => {
788            let app = db.type_application(app_id);
789            Some((app.base, app.args.clone()))
790        }
791        _ => None,
792    }
793}
794
795// =============================================================================
796// Type Parameter Content Classification
797// =============================================================================
798
799/// Classification for types when checking for type parameters.
800#[derive(Debug, Clone)]
801pub enum TypeParameterContentKind {
802    /// Is a type parameter or infer type
803    IsTypeParameter,
804    /// Array - check element type
805    Array(TypeId),
806    /// Tuple - check element types
807    Tuple(crate::types::TupleListId),
808    /// Union - check all members
809    Union(Vec<TypeId>),
810    /// Intersection - check all members
811    Intersection(Vec<TypeId>),
812    /// Application - check base and args
813    Application { base: TypeId, args: Vec<TypeId> },
814    /// Not a type parameter and no nested types to check
815    NotTypeParameter,
816}
817
818/// Classify a type for type parameter checking.
819pub fn classify_for_type_parameter_content(
820    db: &dyn TypeDatabase,
821    type_id: TypeId,
822) -> TypeParameterContentKind {
823    let Some(key) = db.lookup(type_id) else {
824        return TypeParameterContentKind::NotTypeParameter;
825    };
826
827    match key {
828        TypeData::TypeParameter(_) | TypeData::Infer(_) => {
829            TypeParameterContentKind::IsTypeParameter
830        }
831        TypeData::Array(elem) => TypeParameterContentKind::Array(elem),
832        TypeData::Tuple(list_id) => TypeParameterContentKind::Tuple(list_id),
833        TypeData::Union(list_id) => {
834            let members = db.type_list(list_id);
835            TypeParameterContentKind::Union(members.to_vec())
836        }
837        TypeData::Intersection(list_id) => {
838            let members = db.type_list(list_id);
839            TypeParameterContentKind::Intersection(members.to_vec())
840        }
841        TypeData::Application(app_id) => {
842            let app = db.type_application(app_id);
843            TypeParameterContentKind::Application {
844                base: app.base,
845                args: app.args.clone(),
846            }
847        }
848        _ => TypeParameterContentKind::NotTypeParameter,
849    }
850}
851
852// =============================================================================
853// Type Depth Classification
854// =============================================================================
855
856/// Classification for computing type depth.
857#[derive(Debug, Clone)]
858pub enum TypeDepthKind {
859    /// Array - depth = 1 + element depth
860    Array(TypeId),
861    /// Tuple - depth = 1 + max element depth
862    Tuple(crate::types::TupleListId),
863    /// Union or Intersection - depth = 1 + max member depth
864    Members(Vec<TypeId>),
865    /// Application - depth = 1 + max(base depth, arg depths)
866    Application { base: TypeId, args: Vec<TypeId> },
867    /// Terminal type - depth = 1
868    Terminal,
869}
870
871/// Classify a type for depth computation.
872pub fn classify_for_type_depth(db: &dyn TypeDatabase, type_id: TypeId) -> TypeDepthKind {
873    let Some(key) = db.lookup(type_id) else {
874        return TypeDepthKind::Terminal;
875    };
876
877    match key {
878        TypeData::Array(elem) => TypeDepthKind::Array(elem),
879        TypeData::Tuple(list_id) => TypeDepthKind::Tuple(list_id),
880        TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
881            let members = db.type_list(list_id);
882            TypeDepthKind::Members(members.to_vec())
883        }
884        TypeData::Application(app_id) => {
885            let app = db.type_application(app_id);
886            TypeDepthKind::Application {
887                base: app.base,
888                args: app.args.clone(),
889            }
890        }
891        _ => TypeDepthKind::Terminal,
892    }
893}
894
895// =============================================================================
896// Object Spread Property Classification
897// =============================================================================
898
899/// Classification for collecting properties from spread expressions.
900#[derive(Debug, Clone)]
901pub enum SpreadPropertyKind {
902    /// Object type with properties
903    Object(crate::types::ObjectShapeId),
904    /// Callable type with properties
905    Callable(crate::types::CallableShapeId),
906    /// Intersection - collect from all members
907    Intersection(Vec<TypeId>),
908    /// No properties to spread
909    NoProperties,
910}
911
912/// Classify a type for spread property collection.
913pub fn classify_for_spread_properties(
914    db: &dyn TypeDatabase,
915    type_id: TypeId,
916) -> SpreadPropertyKind {
917    let Some(key) = db.lookup(type_id) else {
918        return SpreadPropertyKind::NoProperties;
919    };
920
921    match key {
922        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
923            SpreadPropertyKind::Object(shape_id)
924        }
925        TypeData::Callable(shape_id) => SpreadPropertyKind::Callable(shape_id),
926        TypeData::Intersection(list_id) => {
927            let members = db.type_list(list_id);
928            SpreadPropertyKind::Intersection(members.to_vec())
929        }
930        _ => SpreadPropertyKind::NoProperties,
931    }
932}
933
934// =============================================================================
935// Ref Type Resolution
936// =============================================================================
937
938/// Classification for Lazy type resolution.
939#[derive(Debug, Clone)]
940pub enum LazyTypeKind {
941    /// `DefId` - resolve to actual type
942    Lazy(crate::def::DefId),
943    /// Not a Lazy type
944    NotLazy,
945}
946
947/// Classify a type for Lazy resolution.
948pub fn classify_for_lazy_resolution(db: &dyn TypeDatabase, type_id: TypeId) -> LazyTypeKind {
949    let Some(key) = db.lookup(type_id) else {
950        return LazyTypeKind::NotLazy;
951    };
952
953    match key {
954        TypeData::Lazy(def_id) => LazyTypeKind::Lazy(def_id),
955        _ => LazyTypeKind::NotLazy,
956    }
957}
958
959// =============================================================================
960// Constructor Check Classification (for is_constructor_type)
961// =============================================================================
962
963/// Classification for checking if a type is a constructor type.
964#[derive(Debug, Clone)]
965pub enum ConstructorCheckKind {
966    /// Type parameter with optional constraint - recurse into constraint
967    TypeParameter { constraint: Option<TypeId> },
968    /// Intersection type - check if any member is a constructor
969    Intersection(Vec<TypeId>),
970    /// Union type - check if all members are constructors
971    Union(Vec<TypeId>),
972    /// Application type - extract base and check
973    Application { base: TypeId },
974    /// Lazy reference (`DefId`) - resolve to check if it's a class/interface
975    Lazy(crate::def::DefId),
976    /// `TypeQuery` (typeof) - check referenced symbol
977    TypeQuery(crate::types::SymbolRef),
978    /// Not a constructor type or needs special handling
979    Other,
980}
981
982/// Classify a type for constructor type checking.
983pub fn classify_for_constructor_check(
984    db: &dyn TypeDatabase,
985    type_id: TypeId,
986) -> ConstructorCheckKind {
987    let Some(key) = db.lookup(type_id) else {
988        return ConstructorCheckKind::Other;
989    };
990
991    match key {
992        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
993            ConstructorCheckKind::TypeParameter {
994                constraint: info.constraint,
995            }
996        }
997        TypeData::Intersection(members_id) => {
998            let members = db.type_list(members_id);
999            ConstructorCheckKind::Intersection(members.to_vec())
1000        }
1001        TypeData::Union(members_id) => {
1002            let members = db.type_list(members_id);
1003            ConstructorCheckKind::Union(members.to_vec())
1004        }
1005        TypeData::Application(app_id) => {
1006            let app = db.type_application(app_id);
1007            ConstructorCheckKind::Application { base: app.base }
1008        }
1009        TypeData::Lazy(def_id) => ConstructorCheckKind::Lazy(def_id),
1010        TypeData::TypeQuery(sym_ref) => ConstructorCheckKind::TypeQuery(sym_ref),
1011        _ => ConstructorCheckKind::Other,
1012    }
1013}
1014
1015/// Check if a type is narrowable (union or type parameter).
1016pub fn is_narrowable_type_key(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
1017    // CRITICAL FIX: Lazy types are also narrowable!
1018    // Lazy types are DefId references that need to be resolved, but they represent
1019    // types that can be narrowed (unions, type parameters, etc.)
1020    // Without this, discriminant narrowing fails for types stored as lazy references
1021    matches!(
1022        db.lookup(type_id),
1023        Some(
1024            TypeData::Union(_)
1025                | TypeData::TypeParameter(_)
1026                | TypeData::Infer(_)
1027                | TypeData::Lazy(_)
1028        )
1029    )
1030}
1031
1032// =============================================================================
1033// Private Brand Classification (for get_private_brand)
1034// =============================================================================
1035
1036/// Classification for types when extracting private brands.
1037#[derive(Debug, Clone)]
1038pub enum PrivateBrandKind {
1039    /// Object type with `shape_id` - check properties for brand
1040    Object(crate::types::ObjectShapeId),
1041    /// Callable type with `shape_id` - check properties for brand
1042    Callable(crate::types::CallableShapeId),
1043    /// No private brand possible
1044    None,
1045}
1046
1047/// Classify a type for private brand extraction.
1048pub fn classify_for_private_brand(db: &dyn TypeDatabase, type_id: TypeId) -> PrivateBrandKind {
1049    match db.lookup(type_id) {
1050        Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
1051            PrivateBrandKind::Object(shape_id)
1052        }
1053        Some(TypeData::Callable(shape_id)) => PrivateBrandKind::Callable(shape_id),
1054        _ => PrivateBrandKind::None,
1055    }
1056}
1057
1058/// Get the widened type for a literal type.
1059pub fn get_widened_literal_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
1060    match db.lookup(type_id) {
1061        Some(TypeData::Literal(crate::LiteralValue::String(_))) => Some(TypeId::STRING),
1062        Some(TypeData::Literal(crate::LiteralValue::Number(_))) => Some(TypeId::NUMBER),
1063        Some(TypeData::Literal(crate::LiteralValue::BigInt(_))) => Some(TypeId::BIGINT),
1064        Some(TypeData::Literal(crate::LiteralValue::Boolean(_))) => Some(TypeId::BOOLEAN),
1065        _ => None,
1066    }
1067}
1068
1069/// Get tuple elements list ID if the type is a tuple.
1070pub fn get_tuple_list_id(
1071    db: &dyn TypeDatabase,
1072    type_id: TypeId,
1073) -> Option<crate::types::TupleListId> {
1074    match db.lookup(type_id) {
1075        Some(TypeData::Tuple(list_id)) => Some(list_id),
1076        _ => None,
1077    }
1078}
1079
1080/// Get the base type of an application type.
1081pub fn get_application_base(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
1082    match db.lookup(type_id) {
1083        Some(TypeData::Application(app_id)) => Some(db.type_application(app_id).base),
1084        _ => None,
1085    }
1086}
1087
1088// =============================================================================
1089// Literal Key Classification (for get_literal_key_union_from_type)
1090// =============================================================================
1091
1092/// Classification for literal key extraction from types.
1093#[derive(Debug, Clone)]
1094pub enum LiteralKeyKind {
1095    StringLiteral(tsz_common::interner::Atom),
1096    NumberLiteral(f64),
1097    Union(Vec<TypeId>),
1098    Other,
1099}
1100
1101/// Classify a type for literal key extraction.
1102pub fn classify_literal_key(db: &dyn TypeDatabase, type_id: TypeId) -> LiteralKeyKind {
1103    match db.lookup(type_id) {
1104        Some(TypeData::Literal(crate::LiteralValue::String(atom))) => {
1105            LiteralKeyKind::StringLiteral(atom)
1106        }
1107        Some(TypeData::Literal(crate::LiteralValue::Number(num))) => {
1108            LiteralKeyKind::NumberLiteral(num.0)
1109        }
1110        Some(TypeData::Union(members_id)) => {
1111            LiteralKeyKind::Union(db.type_list(members_id).to_vec())
1112        }
1113        _ => LiteralKeyKind::Other,
1114    }
1115}
1116
1117/// Get literal value from a type if it's a literal.
1118pub fn get_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> Option<crate::LiteralValue> {
1119    match db.lookup(type_id) {
1120        Some(TypeData::Literal(value)) => Some(value),
1121        _ => None,
1122    }
1123}
1124
1125/// Widen a literal type to its corresponding primitive type.
1126///
1127/// - `1` -> `number`
1128/// - `"hello"` -> `string`
1129/// - `true` -> `boolean`
1130/// - `1n` -> `bigint`
1131///
1132/// Non-literal types are returned unchanged.
1133pub fn widen_literal_to_primitive(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
1134    match db.lookup(type_id) {
1135        Some(TypeData::Literal(ref lit)) => match lit {
1136            crate::LiteralValue::String(_) => TypeId::STRING,
1137            crate::LiteralValue::Number(_) => TypeId::NUMBER,
1138            crate::LiteralValue::Boolean(_) => TypeId::BOOLEAN,
1139            crate::LiteralValue::BigInt(_) => TypeId::BIGINT,
1140        },
1141        _ => type_id,
1142    }
1143}
1144
1145/// Get the type of a named property from an object type.
1146///
1147/// Returns `None` if the type is not an object or the property doesn't exist.
1148pub fn get_object_property_type(
1149    db: &dyn TypeDatabase,
1150    type_id: TypeId,
1151    property_name: &str,
1152) -> Option<TypeId> {
1153    match db.lookup(type_id) {
1154        Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
1155            let shape = db.object_shape(shape_id);
1156            for prop in &shape.properties {
1157                let prop_name = db.resolve_atom_ref(prop.name);
1158                if prop_name.as_ref() == property_name {
1159                    return Some(prop.type_id);
1160                }
1161            }
1162            None
1163        }
1164        _ => None,
1165    }
1166}
1167
1168/// Check if a type is a Function (not Callable) and get its return type.
1169///
1170/// Used for checking if a function's return type is promise-like in async iterator contexts.
1171pub fn get_function_return_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
1172    match db.lookup(type_id) {
1173        Some(TypeData::Function(shape_id)) => {
1174            let shape = db.function_shape(shape_id);
1175            Some(shape.return_type)
1176        }
1177        _ => None,
1178    }
1179}
1180
1181/// Check if a type is specifically an object type with index signatures.
1182///
1183/// Returns true only for `TypeData::ObjectWithIndex`, not for `TypeData::Object`.
1184pub fn is_object_with_index_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
1185    matches!(db.lookup(type_id), Some(TypeData::ObjectWithIndex(_)))
1186}
1187
1188// =============================================================================
1189// Array-Like Type Classification (for is_array_like_type)
1190// =============================================================================
1191
1192/// Classification for array-like types.
1193#[derive(Debug, Clone)]
1194pub enum ArrayLikeKind {
1195    Array(TypeId),
1196    Tuple,
1197    Readonly(TypeId),
1198    Union(Vec<TypeId>),
1199    Intersection(Vec<TypeId>),
1200    Other,
1201}
1202
1203/// Classify a type for array-like checking.
1204pub fn classify_array_like(db: &dyn TypeDatabase, type_id: TypeId) -> ArrayLikeKind {
1205    match db.lookup(type_id) {
1206        Some(TypeData::Array(elem)) => ArrayLikeKind::Array(elem),
1207        Some(TypeData::Tuple(_)) => ArrayLikeKind::Tuple,
1208        Some(TypeData::ReadonlyType(inner)) => ArrayLikeKind::Readonly(inner),
1209        Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
1210            info.constraint.map_or(ArrayLikeKind::Other, |constraint| {
1211                classify_array_like(db, constraint)
1212            })
1213        }
1214        Some(TypeData::Union(members_id)) => {
1215            ArrayLikeKind::Union(db.type_list(members_id).to_vec())
1216        }
1217        Some(TypeData::Intersection(members_id)) => {
1218            ArrayLikeKind::Intersection(db.type_list(members_id).to_vec())
1219        }
1220        _ => ArrayLikeKind::Other,
1221    }
1222}
1223
1224// =============================================================================
1225// Index Key Classification (for get_index_key_kind)
1226// =============================================================================
1227
1228/// Classification for index key types.
1229#[derive(Debug, Clone)]
1230pub enum IndexKeyKind {
1231    String,
1232    Number,
1233    StringLiteral,
1234    NumberLiteral,
1235    Union(Vec<TypeId>),
1236    Other,
1237}
1238
1239/// Classify a type for index key checking.
1240pub fn classify_index_key(db: &dyn TypeDatabase, type_id: TypeId) -> IndexKeyKind {
1241    match db.lookup(type_id) {
1242        Some(TypeData::Intrinsic(crate::IntrinsicKind::String)) => IndexKeyKind::String,
1243        Some(TypeData::Intrinsic(crate::IntrinsicKind::Number)) => IndexKeyKind::Number,
1244        Some(TypeData::Literal(crate::LiteralValue::String(_))) => IndexKeyKind::StringLiteral,
1245        Some(TypeData::Literal(crate::LiteralValue::Number(_))) => IndexKeyKind::NumberLiteral,
1246        Some(TypeData::Union(members_id)) => IndexKeyKind::Union(db.type_list(members_id).to_vec()),
1247        _ => IndexKeyKind::Other,
1248    }
1249}
1250
1251// =============================================================================
1252// Element Indexable Classification (for is_element_indexable_key)
1253// =============================================================================
1254
1255/// Classification for element indexable types.
1256#[derive(Debug, Clone)]
1257pub enum ElementIndexableKind {
1258    Array,
1259    Tuple,
1260    ObjectWithIndex { has_string: bool, has_number: bool },
1261    Union(Vec<TypeId>),
1262    Intersection(Vec<TypeId>),
1263    StringLike,
1264    Other,
1265}
1266
1267/// Classify a type for element indexing capability.
1268pub fn classify_element_indexable(db: &dyn TypeDatabase, type_id: TypeId) -> ElementIndexableKind {
1269    match db.lookup(type_id) {
1270        Some(TypeData::Array(_)) => ElementIndexableKind::Array,
1271        Some(TypeData::Tuple(_)) => ElementIndexableKind::Tuple,
1272        Some(TypeData::ObjectWithIndex(shape_id)) => {
1273            let shape = db.object_shape(shape_id);
1274            ElementIndexableKind::ObjectWithIndex {
1275                has_string: shape.string_index.is_some(),
1276                has_number: shape.number_index.is_some(),
1277            }
1278        }
1279        Some(TypeData::Union(members_id)) => {
1280            ElementIndexableKind::Union(db.type_list(members_id).to_vec())
1281        }
1282        Some(TypeData::Intersection(members_id)) => {
1283            ElementIndexableKind::Intersection(db.type_list(members_id).to_vec())
1284        }
1285        Some(TypeData::Literal(crate::LiteralValue::String(_)))
1286        | Some(TypeData::Intrinsic(crate::IntrinsicKind::String)) => {
1287            ElementIndexableKind::StringLike
1288        }
1289        // Enums support reverse mapping: E[value] returns the name, E["name"] returns the value.
1290        // Treat them as having both string and number index signatures.
1291        Some(TypeData::Enum(_, _)) => ElementIndexableKind::ObjectWithIndex {
1292            has_string: true,
1293            has_number: true,
1294        },
1295        _ => ElementIndexableKind::Other,
1296    }
1297}
1298
1299// =============================================================================
1300// Type Query Classification (for resolve_type_query_type)
1301// =============================================================================
1302
1303/// Classification for type query resolution.
1304#[derive(Debug, Clone)]
1305pub enum TypeQueryKind {
1306    TypeQuery(crate::types::SymbolRef),
1307    ApplicationWithTypeQuery {
1308        base_sym_ref: crate::types::SymbolRef,
1309        args: Vec<TypeId>,
1310    },
1311    Application {
1312        app_id: crate::types::TypeApplicationId,
1313    },
1314    Other,
1315}
1316
1317/// Classify a type for type query resolution.
1318pub fn classify_type_query(db: &dyn TypeDatabase, type_id: TypeId) -> TypeQueryKind {
1319    match db.lookup(type_id) {
1320        Some(TypeData::TypeQuery(sym_ref)) => TypeQueryKind::TypeQuery(sym_ref),
1321        Some(TypeData::Application(app_id)) => {
1322            let app = db.type_application(app_id);
1323            match db.lookup(app.base) {
1324                Some(TypeData::TypeQuery(base_sym_ref)) => {
1325                    TypeQueryKind::ApplicationWithTypeQuery {
1326                        base_sym_ref,
1327                        args: app.args.clone(),
1328                    }
1329                }
1330                _ => TypeQueryKind::Application { app_id },
1331            }
1332        }
1333        _ => TypeQueryKind::Other,
1334    }
1335}
1336
1337// =============================================================================
1338// Symbol Reference Classification (for enum_symbol_from_value_type)
1339// =============================================================================
1340
1341/// Classification for symbol reference types.
1342#[derive(Debug, Clone)]
1343pub enum SymbolRefKind {
1344    /// Lazy reference (`DefId`)
1345    Lazy(crate::def::DefId),
1346    TypeQuery(crate::types::SymbolRef),
1347    Other,
1348}
1349
1350/// Classify a type as a symbol reference.
1351pub fn classify_symbol_ref(db: &dyn TypeDatabase, type_id: TypeId) -> SymbolRefKind {
1352    match db.lookup(type_id) {
1353        Some(TypeData::Lazy(def_id)) => SymbolRefKind::Lazy(def_id),
1354        Some(TypeData::TypeQuery(sym_ref)) => SymbolRefKind::TypeQuery(sym_ref),
1355        _ => SymbolRefKind::Other,
1356    }
1357}
1358
1359// =============================================================================
1360// Type Contains Classification (for type_contains_any_inner)
1361// =============================================================================
1362
1363/// Classification for recursive type traversal.
1364#[derive(Debug, Clone)]
1365pub enum TypeContainsKind {
1366    Array(TypeId),
1367    Tuple(crate::types::TupleListId),
1368    Members(Vec<TypeId>),
1369    Object(crate::types::ObjectShapeId),
1370    Function(crate::types::FunctionShapeId),
1371    Callable(crate::types::CallableShapeId),
1372    Application(crate::types::TypeApplicationId),
1373    Conditional(crate::types::ConditionalTypeId),
1374    Mapped(crate::types::MappedTypeId),
1375    IndexAccess {
1376        base: TypeId,
1377        index: TypeId,
1378    },
1379    TemplateLiteral(crate::types::TemplateLiteralId),
1380    Inner(TypeId),
1381    TypeParam {
1382        constraint: Option<TypeId>,
1383        default: Option<TypeId>,
1384    },
1385    Terminal,
1386}
1387
1388/// Classify a type for recursive traversal.
1389pub fn classify_for_contains_traversal(db: &dyn TypeDatabase, type_id: TypeId) -> TypeContainsKind {
1390    match db.lookup(type_id) {
1391        Some(TypeData::Array(elem)) => TypeContainsKind::Array(elem),
1392        Some(TypeData::Tuple(list_id)) => TypeContainsKind::Tuple(list_id),
1393        Some(TypeData::Union(list_id) | TypeData::Intersection(list_id)) => {
1394            TypeContainsKind::Members(db.type_list(list_id).to_vec())
1395        }
1396        Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
1397            TypeContainsKind::Object(shape_id)
1398        }
1399        Some(TypeData::Function(shape_id)) => TypeContainsKind::Function(shape_id),
1400        Some(TypeData::Callable(shape_id)) => TypeContainsKind::Callable(shape_id),
1401        Some(TypeData::Application(app_id)) => TypeContainsKind::Application(app_id),
1402        Some(TypeData::Conditional(cond_id)) => TypeContainsKind::Conditional(cond_id),
1403        Some(TypeData::Mapped(mapped_id)) => TypeContainsKind::Mapped(mapped_id),
1404        Some(TypeData::IndexAccess(base, index)) => TypeContainsKind::IndexAccess { base, index },
1405        Some(TypeData::TemplateLiteral(template_id)) => {
1406            TypeContainsKind::TemplateLiteral(template_id)
1407        }
1408        Some(TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner)) => {
1409            TypeContainsKind::Inner(inner)
1410        }
1411        Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
1412            TypeContainsKind::TypeParam {
1413                constraint: info.constraint,
1414                default: info.default,
1415            }
1416        }
1417        _ => TypeContainsKind::Terminal,
1418    }
1419}
1420
1421// =============================================================================
1422// Namespace Member Classification (for resolve_namespace_value_member)
1423// =============================================================================
1424
1425/// Classification for namespace member resolution.
1426#[derive(Debug, Clone)]
1427pub enum NamespaceMemberKind {
1428    Lazy(DefId),
1429    ModuleNamespace(crate::types::SymbolRef),
1430    Callable(crate::types::CallableShapeId),
1431    // TSZ-4: Added Enum variant to handle enum member property access (E.A)
1432    Enum(DefId),
1433    Other,
1434}
1435
1436/// Classify a type for namespace member resolution.
1437pub fn classify_namespace_member(db: &dyn TypeDatabase, type_id: TypeId) -> NamespaceMemberKind {
1438    match db.lookup(type_id) {
1439        Some(TypeData::Callable(shape_id)) => NamespaceMemberKind::Callable(shape_id),
1440        Some(TypeData::Lazy(def_id)) => NamespaceMemberKind::Lazy(def_id),
1441        Some(TypeData::ModuleNamespace(sym_ref)) => NamespaceMemberKind::ModuleNamespace(sym_ref),
1442        // TSZ-4: Handle TypeData::Enum for enum member property access (E.A)
1443        Some(TypeData::Enum(def_id, _)) => NamespaceMemberKind::Enum(def_id),
1444        _ => NamespaceMemberKind::Other,
1445    }
1446}
1447
1448/// Unwrap readonly type wrapper if present.
1449pub fn unwrap_readonly_for_lookup(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
1450    match db.lookup(type_id) {
1451        Some(TypeData::ReadonlyType(inner)) => inner,
1452        _ => type_id,
1453    }
1454}
1455
1456// =============================================================================
1457// Literal Type Creation Helpers
1458// =============================================================================
1459
1460/// Create a string literal type from a string value.
1461///
1462/// This abstracts away the `TypeData` construction from the checker layer.
1463pub fn create_string_literal_type(db: &dyn TypeDatabase, value: &str) -> TypeId {
1464    let atom = db.intern_string(value);
1465    db.literal_string_atom(atom)
1466}
1467
1468/// Create a number literal type from a numeric value.
1469///
1470/// This abstracts away the `TypeData` construction from the checker layer.
1471pub fn create_number_literal_type(db: &dyn TypeDatabase, value: f64) -> TypeId {
1472    db.literal_number(value)
1473}
1474
1475/// Create a boolean literal type.
1476///
1477/// This abstracts away the `TypeData` construction from the checker layer.
1478pub fn create_boolean_literal_type(db: &dyn TypeDatabase, value: bool) -> TypeId {
1479    db.literal_boolean(value)
1480}
1481
1482// =============================================================================
1483// Instance Type from Constructor Classification
1484// =============================================================================
1485
1486/// Classification for extracting instance types from constructor types.
1487#[derive(Debug, Clone)]
1488pub enum InstanceTypeKind {
1489    /// Callable type - extract from `construct_signatures` return types
1490    Callable(crate::types::CallableShapeId),
1491    /// Function type - check `is_constructor` flag
1492    Function(crate::types::FunctionShapeId),
1493    /// Intersection type - recursively extract instance types from members
1494    Intersection(Vec<TypeId>),
1495    /// Union type - recursively extract instance types from members
1496    Union(Vec<TypeId>),
1497    /// `ReadonlyType` - unwrap and recurse
1498    Readonly(TypeId),
1499    /// Type parameter with constraint - follow constraint
1500    TypeParameter { constraint: Option<TypeId> },
1501    /// Symbol reference (Ref or `TypeQuery`) - needs resolution to class instance type
1502    SymbolRef(crate::types::SymbolRef),
1503    /// Complex types (Conditional, Mapped, `IndexAccess`, `KeyOf`) - need evaluation
1504    NeedsEvaluation,
1505    /// Not a constructor type
1506    NotConstructor,
1507}
1508
1509/// Classify a type for instance type extraction.
1510pub fn classify_for_instance_type(db: &dyn TypeDatabase, type_id: TypeId) -> InstanceTypeKind {
1511    let Some(key) = db.lookup(type_id) else {
1512        return InstanceTypeKind::NotConstructor;
1513    };
1514
1515    match key {
1516        TypeData::Callable(shape_id) => InstanceTypeKind::Callable(shape_id),
1517        TypeData::Function(shape_id) => InstanceTypeKind::Function(shape_id),
1518        TypeData::Intersection(list_id) => {
1519            let members = db.type_list(list_id);
1520            InstanceTypeKind::Intersection(members.to_vec())
1521        }
1522        TypeData::Union(list_id) => {
1523            let members = db.type_list(list_id);
1524            InstanceTypeKind::Union(members.to_vec())
1525        }
1526        TypeData::ReadonlyType(inner) => InstanceTypeKind::Readonly(inner),
1527        TypeData::TypeParameter(info) | TypeData::Infer(info) => InstanceTypeKind::TypeParameter {
1528            constraint: info.constraint,
1529        },
1530        // TypeQuery (typeof expressions) needs resolution to instance type
1531        TypeData::TypeQuery(sym_ref) => InstanceTypeKind::SymbolRef(sym_ref),
1532        TypeData::Conditional(_)
1533        | TypeData::Mapped(_)
1534        | TypeData::IndexAccess(_, _)
1535        | TypeData::KeyOf(_)
1536        | TypeData::Application(_) => InstanceTypeKind::NeedsEvaluation,
1537        _ => InstanceTypeKind::NotConstructor,
1538    }
1539}
1540
1541// =============================================================================
1542// Constructor Return Merge Classification
1543// =============================================================================
1544
1545/// Classification for merging base instance into constructor return.
1546#[derive(Debug, Clone)]
1547pub enum ConstructorReturnMergeKind {
1548    /// Callable type - update `construct_signatures`
1549    Callable(crate::types::CallableShapeId),
1550    /// Function type - check `is_constructor` flag
1551    Function(crate::types::FunctionShapeId),
1552    /// Intersection type - update all members
1553    Intersection(Vec<TypeId>),
1554    /// Not mergeable
1555    Other,
1556}
1557
1558/// Classify a type for constructor return merging.
1559pub fn classify_for_constructor_return_merge(
1560    db: &dyn TypeDatabase,
1561    type_id: TypeId,
1562) -> ConstructorReturnMergeKind {
1563    let Some(key) = db.lookup(type_id) else {
1564        return ConstructorReturnMergeKind::Other;
1565    };
1566
1567    match key {
1568        TypeData::Callable(shape_id) => ConstructorReturnMergeKind::Callable(shape_id),
1569        TypeData::Function(shape_id) => ConstructorReturnMergeKind::Function(shape_id),
1570        TypeData::Intersection(list_id) => {
1571            let members = db.type_list(list_id);
1572            ConstructorReturnMergeKind::Intersection(members.to_vec())
1573        }
1574        _ => ConstructorReturnMergeKind::Other,
1575    }
1576}
1577
1578// =============================================================================
1579// Abstract Constructor Type Classification
1580// =============================================================================
1581
1582/// Classification for checking if a type is an abstract constructor type.
1583#[derive(Debug, Clone)]
1584pub enum AbstractConstructorKind {
1585    /// `TypeQuery` (typeof `AbstractClass`) - check if symbol is abstract
1586    TypeQuery(crate::types::SymbolRef),
1587    /// Callable - check if marked as abstract
1588    Callable(crate::types::CallableShapeId),
1589    /// Application - check base type
1590    Application(crate::types::TypeApplicationId),
1591    /// Not an abstract constructor type
1592    NotAbstract,
1593}
1594
1595/// Fully-resolved abstract-constructor anchor after peeling applications.
1596#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1597pub enum AbstractConstructorAnchor {
1598    /// `TypeQuery` (typeof `AbstractClass`) - checker resolves symbol flags.
1599    TypeQuery(crate::types::SymbolRef),
1600    /// Callable type id that checker can consult for abstract constructor metadata.
1601    CallableType(TypeId),
1602    /// Not an abstract constructor candidate.
1603    NotAbstract,
1604}
1605
1606/// Classify a type for abstract constructor checking.
1607pub fn classify_for_abstract_constructor(
1608    db: &dyn TypeDatabase,
1609    type_id: TypeId,
1610) -> AbstractConstructorKind {
1611    let Some(key) = db.lookup(type_id) else {
1612        return AbstractConstructorKind::NotAbstract;
1613    };
1614
1615    match key {
1616        TypeData::TypeQuery(sym_ref) => AbstractConstructorKind::TypeQuery(sym_ref),
1617        TypeData::Callable(shape_id) => AbstractConstructorKind::Callable(shape_id),
1618        TypeData::Application(app_id) => AbstractConstructorKind::Application(app_id),
1619        _ => AbstractConstructorKind::NotAbstract,
1620    }
1621}
1622
1623/// Resolve abstract-constructor candidates by unwrapping application types.
1624///
1625/// This keeps type-shape traversal in solver and lets checker only apply
1626/// source-context rules (e.g. symbol flags and diagnostics).
1627pub fn resolve_abstract_constructor_anchor(
1628    db: &dyn TypeDatabase,
1629    type_id: TypeId,
1630) -> AbstractConstructorAnchor {
1631    let mut current = type_id;
1632    let mut visited = FxHashSet::default();
1633
1634    while visited.insert(current) {
1635        match classify_for_abstract_constructor(db, current) {
1636            AbstractConstructorKind::TypeQuery(sym_ref) => {
1637                return AbstractConstructorAnchor::TypeQuery(sym_ref);
1638            }
1639            AbstractConstructorKind::Callable(_) => {
1640                return AbstractConstructorAnchor::CallableType(current);
1641            }
1642            AbstractConstructorKind::Application(app_id) => {
1643                let app = db.type_application(app_id);
1644                if app.base == current {
1645                    break;
1646                }
1647                current = app.base;
1648            }
1649            AbstractConstructorKind::NotAbstract => break,
1650        }
1651    }
1652
1653    AbstractConstructorAnchor::NotAbstract
1654}
1655
1656// =============================================================================
1657// Property Access Resolution Classification
1658// =============================================================================
1659
1660/// Classification for resolving types for property access.
1661#[derive(Debug, Clone)]
1662pub enum PropertyAccessResolutionKind {
1663    /// Lazy type (`DefId`) - needs resolution to actual type
1664    Lazy(DefId),
1665    /// `TypeQuery` (typeof) - resolve the symbol
1666    TypeQuery(crate::types::SymbolRef),
1667    /// Application - needs evaluation
1668    Application(crate::types::TypeApplicationId),
1669    /// Type parameter - follow constraint
1670    TypeParameter { constraint: Option<TypeId> },
1671    /// Complex types that need evaluation
1672    NeedsEvaluation,
1673    /// Union - resolve each member
1674    Union(Vec<TypeId>),
1675    /// Intersection - resolve each member
1676    Intersection(Vec<TypeId>),
1677    /// Readonly wrapper - unwrap
1678    Readonly(TypeId),
1679    /// Function or Callable - may need Function interface
1680    FunctionLike,
1681    /// Already resolved
1682    Resolved,
1683}
1684
1685/// Classify a type for property access resolution.
1686pub fn classify_for_property_access_resolution(
1687    db: &dyn TypeDatabase,
1688    type_id: TypeId,
1689) -> PropertyAccessResolutionKind {
1690    let Some(key) = db.lookup(type_id) else {
1691        return PropertyAccessResolutionKind::Resolved;
1692    };
1693
1694    match key {
1695        TypeData::TypeQuery(sym_ref) => PropertyAccessResolutionKind::TypeQuery(sym_ref),
1696        TypeData::Lazy(def_id) => PropertyAccessResolutionKind::Lazy(def_id),
1697        TypeData::Application(app_id) => PropertyAccessResolutionKind::Application(app_id),
1698        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
1699            PropertyAccessResolutionKind::TypeParameter {
1700                constraint: info.constraint,
1701            }
1702        }
1703        TypeData::Conditional(_)
1704        | TypeData::Mapped(_)
1705        | TypeData::IndexAccess(_, _)
1706        | TypeData::KeyOf(_) => PropertyAccessResolutionKind::NeedsEvaluation,
1707        TypeData::Union(list_id) => {
1708            let members = db.type_list(list_id);
1709            PropertyAccessResolutionKind::Union(members.to_vec())
1710        }
1711        TypeData::Intersection(list_id) => {
1712            let members = db.type_list(list_id);
1713            PropertyAccessResolutionKind::Intersection(members.to_vec())
1714        }
1715        TypeData::ReadonlyType(inner) => PropertyAccessResolutionKind::Readonly(inner),
1716        TypeData::Function(_) | TypeData::Callable(_) => PropertyAccessResolutionKind::FunctionLike,
1717        _ => PropertyAccessResolutionKind::Resolved,
1718    }
1719}
1720
1721// =============================================================================
1722// Contextual Type Literal Allow Classification
1723// =============================================================================
1724
1725/// Classification for checking if contextual type allows literals.
1726#[derive(Debug, Clone)]
1727pub enum ContextualLiteralAllowKind {
1728    /// Union or Intersection - check all members
1729    Members(Vec<TypeId>),
1730    /// Type parameter - check constraint
1731    TypeParameter { constraint: Option<TypeId> },
1732    /// Application - needs evaluation
1733    Application,
1734    /// Mapped type - needs evaluation
1735    Mapped,
1736    /// Template literal type - always allows string literals (pattern matching check
1737    /// happens later during assignability). This prevents premature widening of string
1738    /// literals like `"*hello*"` to `string` when the target is `` `*${string}*` ``.
1739    TemplateLiteral,
1740    /// Does not allow literal
1741    NotAllowed,
1742}
1743
1744/// Classify a type for contextual literal checking.
1745pub fn classify_for_contextual_literal(
1746    db: &dyn TypeDatabase,
1747    type_id: TypeId,
1748) -> ContextualLiteralAllowKind {
1749    let Some(key) = db.lookup(type_id) else {
1750        return ContextualLiteralAllowKind::NotAllowed;
1751    };
1752
1753    match key {
1754        TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
1755            let members = db.type_list(list_id);
1756            ContextualLiteralAllowKind::Members(members.to_vec())
1757        }
1758        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
1759            ContextualLiteralAllowKind::TypeParameter {
1760                constraint: info.constraint,
1761            }
1762        }
1763        TypeData::Application(_) => ContextualLiteralAllowKind::Application,
1764        TypeData::Mapped(_) => ContextualLiteralAllowKind::Mapped,
1765        TypeData::TemplateLiteral(_) => ContextualLiteralAllowKind::TemplateLiteral,
1766        _ => ContextualLiteralAllowKind::NotAllowed,
1767    }
1768}
1769
1770// =============================================================================
1771// Mapped Constraint Resolution Classification
1772// =============================================================================
1773
1774/// Classification for evaluating mapped type constraints.
1775#[derive(Debug, Clone)]
1776pub enum MappedConstraintKind {
1777    /// `KeyOf` type - evaluate operand
1778    KeyOf(TypeId),
1779    /// Union or Literal - return as-is
1780    Resolved,
1781    /// Other type - return as-is
1782    Other,
1783}
1784
1785/// Classify a constraint type for mapped type evaluation.
1786pub fn classify_mapped_constraint(db: &dyn TypeDatabase, type_id: TypeId) -> MappedConstraintKind {
1787    let Some(key) = db.lookup(type_id) else {
1788        return MappedConstraintKind::Other;
1789    };
1790
1791    match key {
1792        TypeData::KeyOf(operand) => MappedConstraintKind::KeyOf(operand),
1793        TypeData::Union(_) | TypeData::Literal(_) => MappedConstraintKind::Resolved,
1794        _ => MappedConstraintKind::Other,
1795    }
1796}
1797
1798// =============================================================================
1799// Type Resolution Classification
1800// =============================================================================
1801
1802/// Classification for evaluating types with symbol resolution.
1803#[derive(Debug, Clone)]
1804pub enum TypeResolutionKind {
1805    /// Lazy - resolve to symbol type via `DefId`
1806    Lazy(DefId),
1807    /// Application - evaluate the application
1808    Application,
1809    /// Already resolved
1810    Resolved,
1811}
1812
1813/// Classify a type for resolution.
1814pub fn classify_for_type_resolution(db: &dyn TypeDatabase, type_id: TypeId) -> TypeResolutionKind {
1815    let Some(key) = db.lookup(type_id) else {
1816        return TypeResolutionKind::Resolved;
1817    };
1818
1819    match key {
1820        TypeData::Lazy(def_id) => TypeResolutionKind::Lazy(def_id),
1821        TypeData::Application(_) => TypeResolutionKind::Application,
1822        _ => TypeResolutionKind::Resolved,
1823    }
1824}
1825
1826// =============================================================================
1827// Type Argument Extraction Classification
1828// =============================================================================
1829
1830/// Classification for extracting type parameters from a type for instantiation.
1831#[derive(Debug, Clone)]
1832pub enum TypeArgumentExtractionKind {
1833    /// Function type with type params
1834    Function(crate::types::FunctionShapeId),
1835    /// Callable type with signatures potentially having type params
1836    Callable(crate::types::CallableShapeId),
1837    /// Not applicable
1838    Other,
1839}
1840
1841/// Classify a type for type argument extraction.
1842pub fn classify_for_type_argument_extraction(
1843    db: &dyn TypeDatabase,
1844    type_id: TypeId,
1845) -> TypeArgumentExtractionKind {
1846    let Some(key) = db.lookup(type_id) else {
1847        return TypeArgumentExtractionKind::Other;
1848    };
1849
1850    match key {
1851        TypeData::Function(shape_id) => TypeArgumentExtractionKind::Function(shape_id),
1852        TypeData::Callable(shape_id) => TypeArgumentExtractionKind::Callable(shape_id),
1853        _ => TypeArgumentExtractionKind::Other,
1854    }
1855}
1856
1857// =============================================================================
1858// Base Instance Properties Merge Classification
1859// =============================================================================
1860
1861/// Classification for merging base instance properties.
1862#[derive(Debug, Clone)]
1863pub enum BaseInstanceMergeKind {
1864    /// Object type with shape
1865    Object(crate::types::ObjectShapeId),
1866    /// Intersection - merge all members
1867    Intersection(Vec<TypeId>),
1868    /// Union - find common properties
1869    Union(Vec<TypeId>),
1870    /// Not mergeable
1871    Other,
1872}
1873
1874/// Classify a type for base instance property merging.
1875pub fn classify_for_base_instance_merge(
1876    db: &dyn TypeDatabase,
1877    type_id: TypeId,
1878) -> BaseInstanceMergeKind {
1879    let Some(key) = db.lookup(type_id) else {
1880        return BaseInstanceMergeKind::Other;
1881    };
1882
1883    match key {
1884        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
1885            BaseInstanceMergeKind::Object(shape_id)
1886        }
1887        TypeData::Intersection(list_id) => {
1888            let members = db.type_list(list_id);
1889            BaseInstanceMergeKind::Intersection(members.to_vec())
1890        }
1891        TypeData::Union(list_id) => {
1892            let members = db.type_list(list_id);
1893            BaseInstanceMergeKind::Union(members.to_vec())
1894        }
1895        _ => BaseInstanceMergeKind::Other,
1896    }
1897}