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).is_some()
118}
119
120/// Returns the specific `TypeId` within the given type (e.g., inside a union)
121/// that makes it invalid for indexing.
122pub fn get_invalid_index_type_member(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
123    let mut visited = FxHashSet::default();
124    is_invalid_index_type_inner(db, type_id, &mut visited)
125}
126
127fn is_invalid_index_type_inner(
128    db: &dyn TypeDatabase,
129    type_id: TypeId,
130    visited: &mut FxHashSet<TypeId>,
131) -> Option<TypeId> {
132    if !visited.insert(type_id) {
133        return None;
134    }
135
136    if matches!(
137        type_id,
138        TypeId::ANY | TypeId::UNKNOWN | TypeId::ERROR | TypeId::NEVER
139    ) {
140        return None;
141    }
142
143    let is_invalid = match db.lookup(type_id) {
144        Some(TypeData::Intrinsic(kind)) => matches!(
145            kind,
146            crate::IntrinsicKind::Void
147                | crate::IntrinsicKind::Null
148                | crate::IntrinsicKind::Undefined
149                | crate::IntrinsicKind::Boolean
150                | crate::IntrinsicKind::Bigint
151                | crate::IntrinsicKind::Object
152                | crate::IntrinsicKind::Function
153        ),
154        Some(TypeData::Literal(value)) => matches!(
155            value,
156            crate::LiteralValue::Boolean(_) | crate::LiteralValue::BigInt(_)
157        ),
158        Some(
159            TypeData::Array(_)
160            | TypeData::Tuple(_)
161            | TypeData::Object(_)
162            | TypeData::ObjectWithIndex(_)
163            | TypeData::Function(_)
164            | TypeData::Callable(_)
165            | TypeData::Lazy(_),
166        ) => true,
167        Some(TypeData::Union(list_id) | TypeData::Intersection(list_id)) => {
168            for &member in db.type_list(list_id).iter() {
169                if let Some(invalid_member) = is_invalid_index_type_inner(db, member, visited) {
170                    return Some(invalid_member);
171                }
172            }
173            false
174        }
175        Some(TypeData::TypeParameter(info)) => {
176            if let Some(constraint) = info.constraint
177                && let Some(invalid_member) = is_invalid_index_type_inner(db, constraint, visited)
178            {
179                return Some(invalid_member);
180            }
181            false
182        }
183        _ => false,
184    };
185
186    if is_invalid { Some(type_id) } else { None }
187}
188
189// =============================================================================
190// Spread Type Classification
191// =============================================================================
192
193/// Classification for spread operations.
194///
195/// This enum provides a structured way to handle spread types without
196/// directly matching on `TypeData` in the checker layer.
197#[derive(Debug, Clone)]
198pub enum SpreadTypeKind {
199    /// Array type - element type for spread
200    Array(TypeId),
201    /// Tuple type - can expand individual elements
202    Tuple(crate::types::TupleListId),
203    /// Object type - properties can be spread
204    Object(crate::types::ObjectShapeId),
205    /// Object with index signature
206    ObjectWithIndex(crate::types::ObjectShapeId),
207    /// String literal - can be spread as characters
208    StringLiteral(tsz_common::interner::Atom),
209    /// Lazy reference (`DefId`) - needs resolution to actual spreadable type
210    Lazy(DefId),
211    /// Type that needs further checks for iterability
212    Other,
213    /// Type that cannot be spread
214    NotSpreadable,
215}
216
217/// Classify a type for spread operations.
218///
219/// This function examines a type and returns information about how to handle it
220/// when used in a spread context.
221pub fn classify_spread_type(db: &dyn TypeDatabase, type_id: TypeId) -> SpreadTypeKind {
222    // Handle intrinsic types first
223    if type_id.is_any() || type_id == TypeId::STRING {
224        return SpreadTypeKind::Other;
225    }
226    if type_id.is_unknown() {
227        return SpreadTypeKind::NotSpreadable;
228    }
229
230    let Some(key) = db.lookup(type_id) else {
231        return SpreadTypeKind::NotSpreadable;
232    };
233
234    match key {
235        TypeData::Array(element_type) => SpreadTypeKind::Array(element_type),
236        TypeData::Tuple(tuple_id) => SpreadTypeKind::Tuple(tuple_id),
237        TypeData::Object(shape_id) => SpreadTypeKind::Object(shape_id),
238        TypeData::ObjectWithIndex(shape_id) => SpreadTypeKind::ObjectWithIndex(shape_id),
239        TypeData::Literal(crate::LiteralValue::String(atom)) => SpreadTypeKind::StringLiteral(atom),
240        TypeData::Lazy(def_id) => SpreadTypeKind::Lazy(def_id),
241        _ => SpreadTypeKind::Other,
242    }
243}
244
245/// Check if a type has Symbol.iterator or is otherwise iterable.
246///
247/// This is a helper for checking iterability without matching on `TypeData`.
248pub fn is_iterable_type_kind(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
249    // Handle intrinsic string type
250    if type_id == TypeId::STRING {
251        return true;
252    }
253
254    let Some(key) = db.lookup(type_id) else {
255        return false;
256    };
257
258    match key {
259        TypeData::Array(_)
260        | TypeData::Tuple(_)
261        | TypeData::Literal(crate::LiteralValue::String(_)) => true,
262        TypeData::Object(shape_id) => {
263            // Check for [Symbol.iterator] method
264            let shape = db.object_shape(shape_id);
265            shape.properties.iter().any(|prop| {
266                let prop_name = db.resolve_atom_ref(prop.name);
267                (prop_name.as_ref() == "[Symbol.iterator]" || prop_name.as_ref() == "next")
268                    && prop.is_method
269            })
270        }
271        _ => false,
272    }
273}
274
275/// Get the iterable element type for a type if it's iterable.
276pub fn get_iterable_element_type_from_db(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
277    // Handle intrinsic string type
278    if type_id == TypeId::STRING {
279        return Some(TypeId::STRING);
280    }
281
282    let key = db.lookup(type_id)?;
283
284    match key {
285        TypeData::Array(elem_type) => Some(elem_type),
286        TypeData::Tuple(tuple_list_id) => {
287            let elements = db.tuple_list(tuple_list_id);
288            if elements.is_empty() {
289                Some(TypeId::NEVER)
290            } else {
291                let types: Vec<TypeId> = elements.iter().map(|e| e.type_id).collect();
292                Some(db.union(types))
293            }
294        }
295        TypeData::Literal(crate::LiteralValue::String(_)) => Some(TypeId::STRING),
296        TypeData::Object(shape_id) => {
297            // For objects with [Symbol.iterator], we'd need to infer the element type
298            // from the iterator's return type. For now, return Any as a fallback.
299            let shape = db.object_shape(shape_id);
300            let has_iterator = shape.properties.iter().any(|prop| {
301                let prop_name = db.resolve_atom_ref(prop.name);
302                (prop_name.as_ref() == "[Symbol.iterator]" || prop_name.as_ref() == "next")
303                    && prop.is_method
304            });
305            has_iterator.then_some(TypeId::ANY)
306        }
307        _ => None,
308    }
309}
310
311// =============================================================================
312// Type Parameter Classification (Extended)
313// =============================================================================
314
315/// Classification for type parameter types.
316///
317/// This enum provides a structured way to handle type parameters without
318/// directly matching on `TypeData` in the checker layer.
319#[derive(Debug, Clone)]
320pub enum TypeParameterKind {
321    /// Type parameter with info
322    TypeParameter(crate::types::TypeParamInfo),
323    /// Infer type with info
324    Infer(crate::types::TypeParamInfo),
325    /// Type application - may contain type parameters
326    Application(crate::types::TypeApplicationId),
327    /// Union - may contain type parameters in members
328    Union(Vec<TypeId>),
329    /// Intersection - may contain type parameters in members
330    Intersection(Vec<TypeId>),
331    /// Callable - may have type parameters
332    Callable(crate::types::CallableShapeId),
333    /// Not a type parameter or type containing type parameters
334    NotTypeParameter,
335}
336
337/// Classify a type for type parameter handling.
338///
339/// Returns detailed information about type parameter types.
340pub fn classify_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> TypeParameterKind {
341    let Some(key) = db.lookup(type_id) else {
342        return TypeParameterKind::NotTypeParameter;
343    };
344
345    match key {
346        TypeData::TypeParameter(info) => TypeParameterKind::TypeParameter(info),
347        TypeData::Infer(info) => TypeParameterKind::Infer(info),
348        TypeData::Application(app_id) => TypeParameterKind::Application(app_id),
349        TypeData::Union(list_id) => {
350            let members = db.type_list(list_id);
351            TypeParameterKind::Union(members.to_vec())
352        }
353        TypeData::Intersection(list_id) => {
354            let members = db.type_list(list_id);
355            TypeParameterKind::Intersection(members.to_vec())
356        }
357        TypeData::Callable(shape_id) => TypeParameterKind::Callable(shape_id),
358        _ => TypeParameterKind::NotTypeParameter,
359    }
360}
361
362/// Check if a type is directly a type parameter (`TypeParameter` or Infer).
363pub fn is_direct_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
364    matches!(
365        classify_type_parameter(db, type_id),
366        TypeParameterKind::TypeParameter(_) | TypeParameterKind::Infer(_)
367    )
368}
369
370/// Get the type parameter default if this is a type parameter.
371pub fn get_type_param_default(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
372    match db.lookup(type_id) {
373        Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info.default,
374        _ => None,
375    }
376}
377
378/// Get the callable type parameter count.
379pub fn get_callable_type_param_count(db: &dyn TypeDatabase, type_id: TypeId) -> usize {
380    match db.lookup(type_id) {
381        Some(TypeData::Callable(shape_id)) => {
382            let shape = db.callable_shape(shape_id);
383            shape
384                .call_signatures
385                .iter()
386                .map(|sig| sig.type_params.len())
387                .max()
388                .unwrap_or(0)
389        }
390        _ => 0,
391    }
392}
393
394// =============================================================================
395// Promise Type Classification
396// =============================================================================
397
398/// Classification for promise-like types.
399///
400/// This enum provides a structured way to handle promise types without
401/// directly matching on `TypeData` in the checker layer.
402#[derive(Debug, Clone)]
403pub enum PromiseTypeKind {
404    /// Type application (like Promise<T>) - contains base and args
405    Application {
406        app_id: crate::types::TypeApplicationId,
407        base: TypeId,
408        args: Vec<TypeId>,
409    },
410    /// Lazy reference (`DefId`) - needs resolution to check if it's Promise
411    Lazy(crate::def::DefId),
412    /// Object type (might be Promise interface from lib)
413    Object(crate::types::ObjectShapeId),
414    /// Union type - check each member
415    Union(Vec<TypeId>),
416    /// Not a promise type
417    NotPromise,
418}
419
420/// Classify a type for promise handling.
421///
422/// This function examines a type and returns information about how to handle it
423/// when checking for promise-like types.
424pub fn classify_promise_type(db: &dyn TypeDatabase, type_id: TypeId) -> PromiseTypeKind {
425    let Some(key) = db.lookup(type_id) else {
426        return PromiseTypeKind::NotPromise;
427    };
428
429    match key {
430        TypeData::Application(app_id) => {
431            let app = db.type_application(app_id);
432            PromiseTypeKind::Application {
433                app_id,
434                base: app.base,
435                args: app.args.clone(),
436            }
437        }
438        TypeData::Lazy(def_id) => PromiseTypeKind::Lazy(def_id),
439        TypeData::Object(shape_id) => PromiseTypeKind::Object(shape_id),
440        TypeData::Union(list_id) => {
441            let members = db.type_list(list_id);
442            PromiseTypeKind::Union(members.to_vec())
443        }
444        _ => PromiseTypeKind::NotPromise,
445    }
446}
447
448// =============================================================================
449// KeyOf Type Classification
450// =============================================================================
451
452/// Classification for computing keyof types.
453#[derive(Debug, Clone)]
454pub enum KeyOfTypeKind {
455    /// Object type with properties
456    Object(crate::types::ObjectShapeId),
457    /// No keys available
458    NoKeys,
459}
460
461/// Classify a type for keyof computation.
462pub fn classify_for_keyof(db: &dyn TypeDatabase, type_id: TypeId) -> KeyOfTypeKind {
463    let Some(key) = db.lookup(type_id) else {
464        return KeyOfTypeKind::NoKeys;
465    };
466
467    match key {
468        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
469            KeyOfTypeKind::Object(shape_id)
470        }
471        _ => KeyOfTypeKind::NoKeys,
472    }
473}
474
475// =============================================================================
476// String Literal Key Extraction
477// =============================================================================
478
479/// Classification for extracting string literal keys.
480#[derive(Debug, Clone)]
481pub enum StringLiteralKeyKind {
482    /// Single string literal
483    SingleString(tsz_common::interner::Atom),
484    /// Union of types - check each member
485    Union(Vec<TypeId>),
486    /// Not a string literal
487    NotStringLiteral,
488}
489
490/// Classify a type for string literal key extraction.
491pub fn classify_for_string_literal_keys(
492    db: &dyn TypeDatabase,
493    type_id: TypeId,
494) -> StringLiteralKeyKind {
495    let Some(key) = db.lookup(type_id) else {
496        return StringLiteralKeyKind::NotStringLiteral;
497    };
498
499    match key {
500        TypeData::Literal(crate::types::LiteralValue::String(name)) => {
501            StringLiteralKeyKind::SingleString(name)
502        }
503        TypeData::Union(list_id) => {
504            let members = db.type_list(list_id);
505            StringLiteralKeyKind::Union(members.to_vec())
506        }
507        _ => StringLiteralKeyKind::NotStringLiteral,
508    }
509}
510
511/// Extract string literal from a Literal type.
512/// Returns None if not a string literal.
513pub fn get_string_literal_value(
514    db: &dyn TypeDatabase,
515    type_id: TypeId,
516) -> Option<tsz_common::interner::Atom> {
517    match db.lookup(type_id) {
518        Some(TypeData::Literal(crate::types::LiteralValue::String(name))) => Some(name),
519        _ => None,
520    }
521}
522
523/// Extract string, numeric, enum, or unique symbol property name from a type.
524pub fn get_literal_property_name(
525    db: &dyn TypeDatabase,
526    type_id: TypeId,
527) -> Option<tsz_common::interner::Atom> {
528    match db.lookup(type_id) {
529        Some(TypeData::Literal(crate::types::LiteralValue::String(name))) => Some(name),
530        Some(TypeData::Literal(crate::types::LiteralValue::Number(num))) => {
531            // Format number exactly like TS (e.g. 1.0 -> "1")
532            let s = format!("{}", num.0);
533            Some(db.intern_string(&s))
534        }
535        Some(TypeData::UniqueSymbol(sym)) => {
536            let s = format!("__unique_{}", sym.0);
537            Some(db.intern_string(&s))
538        }
539        Some(TypeData::Enum(_, member_type)) => get_literal_property_name(db, member_type),
540        _ => None,
541    }
542}
543
544// =============================================================================
545// Call Expression Overload Classification
546// =============================================================================
547
548/// Classification for extracting call signatures from a type.
549#[derive(Debug, Clone)]
550pub enum CallSignaturesKind {
551    /// Callable type with signatures
552    Callable(crate::types::CallableShapeId),
553    /// Multiple call signatures (e.g., from union of callables)
554    MultipleSignatures(Vec<crate::CallSignature>),
555    /// Other type - no call signatures
556    NoSignatures,
557}
558
559/// Classify a type for call signature extraction.
560pub fn classify_for_call_signatures(db: &dyn TypeDatabase, type_id: TypeId) -> CallSignaturesKind {
561    let Some(key) = db.lookup(type_id) else {
562        return CallSignaturesKind::NoSignatures;
563    };
564
565    match key {
566        TypeData::Callable(shape_id) => CallSignaturesKind::Callable(shape_id),
567        TypeData::Union(list_id) => {
568            // For unions, collect call signatures from all callable members
569            let members = db.type_list(list_id);
570            let mut call_signatures = Vec::new();
571
572            for &member in members.iter() {
573                match db.lookup(member) {
574                    Some(TypeData::Callable(shape_id)) => {
575                        let shape = db.callable_shape(shape_id);
576                        // Extend with call signatures from this member
577                        call_signatures.extend(shape.call_signatures.iter().cloned());
578                    }
579                    _ => continue,
580                }
581            }
582
583            if call_signatures.is_empty() {
584                CallSignaturesKind::NoSignatures
585            } else {
586                CallSignaturesKind::MultipleSignatures(call_signatures)
587            }
588        }
589        _ => CallSignaturesKind::NoSignatures,
590    }
591}
592
593// =============================================================================
594// Generic Application Type Extraction
595// =============================================================================
596
597/// Get the base and args from an Application type.
598/// Returns None if not an Application.
599pub fn get_application_info(
600    db: &dyn TypeDatabase,
601    type_id: TypeId,
602) -> Option<(TypeId, Vec<TypeId>)> {
603    match db.lookup(type_id) {
604        Some(TypeData::Application(app_id)) => {
605            let app = db.type_application(app_id);
606            Some((app.base, app.args.clone()))
607        }
608        _ => None,
609    }
610}
611
612// =============================================================================
613// Type Parameter Content Classification
614// =============================================================================
615
616/// Classification for types when checking for type parameters.
617#[derive(Debug, Clone)]
618pub enum TypeParameterContentKind {
619    /// Is a type parameter or infer type
620    IsTypeParameter,
621    /// Array - check element type
622    Array(TypeId),
623    /// Tuple - check element types
624    Tuple(crate::types::TupleListId),
625    /// Union - check all members
626    Union(Vec<TypeId>),
627    /// Intersection - check all members
628    Intersection(Vec<TypeId>),
629    /// Application - check base and args
630    Application { base: TypeId, args: Vec<TypeId> },
631    /// Not a type parameter and no nested types to check
632    NotTypeParameter,
633}
634
635/// Classify a type for type parameter checking.
636pub fn classify_for_type_parameter_content(
637    db: &dyn TypeDatabase,
638    type_id: TypeId,
639) -> TypeParameterContentKind {
640    let Some(key) = db.lookup(type_id) else {
641        return TypeParameterContentKind::NotTypeParameter;
642    };
643
644    match key {
645        TypeData::TypeParameter(_) | TypeData::Infer(_) => {
646            TypeParameterContentKind::IsTypeParameter
647        }
648        TypeData::Array(elem) => TypeParameterContentKind::Array(elem),
649        TypeData::Tuple(list_id) => TypeParameterContentKind::Tuple(list_id),
650        TypeData::Union(list_id) => {
651            let members = db.type_list(list_id);
652            TypeParameterContentKind::Union(members.to_vec())
653        }
654        TypeData::Intersection(list_id) => {
655            let members = db.type_list(list_id);
656            TypeParameterContentKind::Intersection(members.to_vec())
657        }
658        TypeData::Application(app_id) => {
659            let app = db.type_application(app_id);
660            TypeParameterContentKind::Application {
661                base: app.base,
662                args: app.args.clone(),
663            }
664        }
665        _ => TypeParameterContentKind::NotTypeParameter,
666    }
667}
668
669// =============================================================================
670// Type Depth Classification
671// =============================================================================
672
673/// Classification for computing type depth.
674#[derive(Debug, Clone)]
675pub enum TypeDepthKind {
676    /// Array - depth = 1 + element depth
677    Array(TypeId),
678    /// Tuple - depth = 1 + max element depth
679    Tuple(crate::types::TupleListId),
680    /// Union or Intersection - depth = 1 + max member depth
681    Members(Vec<TypeId>),
682    /// Application - depth = 1 + max(base depth, arg depths)
683    Application { base: TypeId, args: Vec<TypeId> },
684    /// Terminal type - depth = 1
685    Terminal,
686}
687
688/// Classify a type for depth computation.
689pub fn classify_for_type_depth(db: &dyn TypeDatabase, type_id: TypeId) -> TypeDepthKind {
690    let Some(key) = db.lookup(type_id) else {
691        return TypeDepthKind::Terminal;
692    };
693
694    match key {
695        TypeData::Array(elem) => TypeDepthKind::Array(elem),
696        TypeData::Tuple(list_id) => TypeDepthKind::Tuple(list_id),
697        TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
698            let members = db.type_list(list_id);
699            TypeDepthKind::Members(members.to_vec())
700        }
701        TypeData::Application(app_id) => {
702            let app = db.type_application(app_id);
703            TypeDepthKind::Application {
704                base: app.base,
705                args: app.args.clone(),
706            }
707        }
708        _ => TypeDepthKind::Terminal,
709    }
710}
711
712// =============================================================================
713// Object Spread Property Classification
714// =============================================================================
715
716/// Classification for collecting properties from spread expressions.
717#[derive(Debug, Clone)]
718pub enum SpreadPropertyKind {
719    /// Object type with properties
720    Object(crate::types::ObjectShapeId),
721    /// Callable type with properties
722    Callable(crate::types::CallableShapeId),
723    /// Intersection - collect from all members
724    Intersection(Vec<TypeId>),
725    /// No properties to spread
726    NoProperties,
727}
728
729/// Classify a type for spread property collection.
730pub fn classify_for_spread_properties(
731    db: &dyn TypeDatabase,
732    type_id: TypeId,
733) -> SpreadPropertyKind {
734    let Some(key) = db.lookup(type_id) else {
735        return SpreadPropertyKind::NoProperties;
736    };
737
738    match key {
739        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
740            SpreadPropertyKind::Object(shape_id)
741        }
742        TypeData::Callable(shape_id) => SpreadPropertyKind::Callable(shape_id),
743        TypeData::Intersection(list_id) => {
744            let members = db.type_list(list_id);
745            SpreadPropertyKind::Intersection(members.to_vec())
746        }
747        _ => SpreadPropertyKind::NoProperties,
748    }
749}
750
751// =============================================================================
752// Ref Type Resolution
753// =============================================================================
754
755/// Classification for Lazy type resolution.
756#[derive(Debug, Clone)]
757pub enum LazyTypeKind {
758    /// `DefId` - resolve to actual type
759    Lazy(crate::def::DefId),
760    /// Not a Lazy type
761    NotLazy,
762}
763
764/// Classify a type for Lazy resolution.
765pub fn classify_for_lazy_resolution(db: &dyn TypeDatabase, type_id: TypeId) -> LazyTypeKind {
766    let Some(key) = db.lookup(type_id) else {
767        return LazyTypeKind::NotLazy;
768    };
769
770    match key {
771        TypeData::Lazy(def_id) => LazyTypeKind::Lazy(def_id),
772        _ => LazyTypeKind::NotLazy,
773    }
774}
775
776/// Check if a type is narrowable (union or type parameter).
777pub fn is_narrowable_type_key(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
778    // CRITICAL FIX: Lazy types are also narrowable!
779    // Lazy types are DefId references that need to be resolved, but they represent
780    // types that can be narrowed (unions, type parameters, etc.)
781    // Without this, discriminant narrowing fails for types stored as lazy references
782    matches!(
783        db.lookup(type_id),
784        Some(
785            TypeData::Union(_)
786                | TypeData::TypeParameter(_)
787                | TypeData::Infer(_)
788                | TypeData::Lazy(_)
789        )
790    )
791}
792
793// =============================================================================
794// Private Brand Classification (for get_private_brand)
795// =============================================================================
796
797/// Classification for types when extracting private brands.
798#[derive(Debug, Clone)]
799pub enum PrivateBrandKind {
800    /// Object type with `shape_id` - check properties for brand
801    Object(crate::types::ObjectShapeId),
802    /// Callable type with `shape_id` - check properties for brand
803    Callable(crate::types::CallableShapeId),
804    /// No private brand possible
805    None,
806}
807
808/// Classify a type for private brand extraction.
809pub fn classify_for_private_brand(db: &dyn TypeDatabase, type_id: TypeId) -> PrivateBrandKind {
810    match db.lookup(type_id) {
811        Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
812            PrivateBrandKind::Object(shape_id)
813        }
814        Some(TypeData::Callable(shape_id)) => PrivateBrandKind::Callable(shape_id),
815        _ => PrivateBrandKind::None,
816    }
817}
818
819/// Get the widened type for a literal type.
820pub fn get_widened_literal_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
821    match db.lookup(type_id) {
822        Some(TypeData::Literal(ref lit)) => Some(lit.primitive_type_id()),
823        _ => None,
824    }
825}
826
827/// Get tuple elements list ID if the type is a tuple.
828pub fn get_tuple_list_id(
829    db: &dyn TypeDatabase,
830    type_id: TypeId,
831) -> Option<crate::types::TupleListId> {
832    match db.lookup(type_id) {
833        Some(TypeData::Tuple(list_id)) => Some(list_id),
834        _ => None,
835    }
836}
837
838/// Get the base type of an application type.
839pub fn get_application_base(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
840    match db.lookup(type_id) {
841        Some(TypeData::Application(app_id)) => Some(db.type_application(app_id).base),
842        _ => None,
843    }
844}
845
846// =============================================================================
847// Literal Key Classification (for get_literal_key_union_from_type)
848// =============================================================================
849
850/// Classification for literal key extraction from types.
851#[derive(Debug, Clone)]
852pub enum LiteralKeyKind {
853    StringLiteral(tsz_common::interner::Atom),
854    NumberLiteral(f64),
855    Union(Vec<TypeId>),
856    Other,
857}
858
859/// Classify a type for literal key extraction.
860pub fn classify_literal_key(db: &dyn TypeDatabase, type_id: TypeId) -> LiteralKeyKind {
861    match db.lookup(type_id) {
862        Some(TypeData::Literal(crate::LiteralValue::String(atom))) => {
863            LiteralKeyKind::StringLiteral(atom)
864        }
865        Some(TypeData::Literal(crate::LiteralValue::Number(num))) => {
866            LiteralKeyKind::NumberLiteral(num.0)
867        }
868        Some(TypeData::Union(members_id)) => {
869            LiteralKeyKind::Union(db.type_list(members_id).to_vec())
870        }
871        _ => LiteralKeyKind::Other,
872    }
873}
874
875/// Get literal value from a type if it's a literal.
876pub fn get_literal_value(db: &dyn TypeDatabase, type_id: TypeId) -> Option<crate::LiteralValue> {
877    match db.lookup(type_id) {
878        Some(TypeData::Literal(value)) => Some(value),
879        _ => None,
880    }
881}
882
883/// Widen a literal type to its corresponding primitive type.
884///
885/// - `1` -> `number`
886/// - `"hello"` -> `string`
887/// - `true` -> `boolean`
888/// - `1n` -> `bigint`
889///
890/// Non-literal types are returned unchanged.
891pub fn widen_literal_to_primitive(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
892    match db.lookup(type_id) {
893        Some(TypeData::Literal(ref lit)) => lit.primitive_type_id(),
894        _ => type_id,
895    }
896}
897
898/// Get the type of a named property from an object type.
899///
900/// Returns `None` if the type is not an object or the property doesn't exist.
901pub fn get_object_property_type(
902    db: &dyn TypeDatabase,
903    type_id: TypeId,
904    property_name: &str,
905) -> Option<TypeId> {
906    match db.lookup(type_id) {
907        Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
908            let shape = db.object_shape(shape_id);
909            for prop in &shape.properties {
910                let prop_name = db.resolve_atom_ref(prop.name);
911                if prop_name.as_ref() == property_name {
912                    return Some(prop.type_id);
913                }
914            }
915            None
916        }
917        _ => None,
918    }
919}
920
921/// Check if a type is a Function (not Callable) and get its return type.
922///
923/// Used for checking if a function's return type is promise-like in async iterator contexts.
924pub fn get_function_return_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
925    match db.lookup(type_id) {
926        Some(TypeData::Function(shape_id)) => {
927            let shape = db.function_shape(shape_id);
928            Some(shape.return_type)
929        }
930        _ => None,
931    }
932}
933
934/// Check if a type is specifically an object type with index signatures.
935///
936/// Returns true only for `TypeData::ObjectWithIndex`, not for `TypeData::Object`.
937pub fn is_object_with_index_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
938    matches!(db.lookup(type_id), Some(TypeData::ObjectWithIndex(_)))
939}
940
941// =============================================================================
942// Array-Like Type Classification (for is_array_like_type)
943// =============================================================================
944
945/// Classification for array-like types.
946#[derive(Debug, Clone)]
947pub enum ArrayLikeKind {
948    Array(TypeId),
949    Tuple,
950    Readonly(TypeId),
951    Union(Vec<TypeId>),
952    Intersection(Vec<TypeId>),
953    Other,
954}
955
956/// Classify a type for array-like checking.
957pub fn classify_array_like(db: &dyn TypeDatabase, type_id: TypeId) -> ArrayLikeKind {
958    match db.lookup(type_id) {
959        Some(TypeData::Array(elem)) => ArrayLikeKind::Array(elem),
960        Some(TypeData::Tuple(_)) => ArrayLikeKind::Tuple,
961        Some(TypeData::ReadonlyType(inner)) => ArrayLikeKind::Readonly(inner),
962        Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
963            info.constraint.map_or(ArrayLikeKind::Other, |constraint| {
964                classify_array_like(db, constraint)
965            })
966        }
967        Some(TypeData::Union(members_id)) => {
968            ArrayLikeKind::Union(db.type_list(members_id).to_vec())
969        }
970        Some(TypeData::Intersection(members_id)) => {
971            ArrayLikeKind::Intersection(db.type_list(members_id).to_vec())
972        }
973        _ => ArrayLikeKind::Other,
974    }
975}
976
977// =============================================================================
978// Index Key Classification (for get_index_key_kind)
979// =============================================================================
980
981/// Classification for index key types.
982#[derive(Debug, Clone)]
983pub enum IndexKeyKind {
984    String,
985    Number,
986    StringLiteral,
987    NumberLiteral,
988    Union(Vec<TypeId>),
989    Other,
990}
991
992/// Classify a type for index key checking.
993pub fn classify_index_key(db: &dyn TypeDatabase, type_id: TypeId) -> IndexKeyKind {
994    match db.lookup(type_id) {
995        Some(TypeData::Intrinsic(crate::IntrinsicKind::String)) => IndexKeyKind::String,
996        Some(TypeData::Intrinsic(crate::IntrinsicKind::Number)) => IndexKeyKind::Number,
997        Some(TypeData::Literal(crate::LiteralValue::String(_))) => IndexKeyKind::StringLiteral,
998        Some(TypeData::Literal(crate::LiteralValue::Number(_))) => IndexKeyKind::NumberLiteral,
999        Some(TypeData::Union(members_id)) => IndexKeyKind::Union(db.type_list(members_id).to_vec()),
1000        _ => IndexKeyKind::Other,
1001    }
1002}
1003
1004// =============================================================================
1005// Element Indexable Classification (for is_element_indexable_key)
1006// =============================================================================
1007
1008/// Classification for element indexable types.
1009#[derive(Debug, Clone)]
1010pub enum ElementIndexableKind {
1011    Array,
1012    Tuple,
1013    ObjectWithIndex { has_string: bool, has_number: bool },
1014    Union(Vec<TypeId>),
1015    Intersection(Vec<TypeId>),
1016    StringLike,
1017    Other,
1018}
1019
1020/// Classify a type for element indexing capability.
1021pub fn classify_element_indexable(db: &dyn TypeDatabase, type_id: TypeId) -> ElementIndexableKind {
1022    match db.lookup(type_id) {
1023        Some(TypeData::Array(_)) => ElementIndexableKind::Array,
1024        Some(TypeData::Tuple(_)) => ElementIndexableKind::Tuple,
1025        Some(TypeData::ObjectWithIndex(shape_id)) => {
1026            let shape = db.object_shape(shape_id);
1027            ElementIndexableKind::ObjectWithIndex {
1028                has_string: shape.string_index.is_some(),
1029                has_number: shape.number_index.is_some(),
1030            }
1031        }
1032        Some(TypeData::Union(members_id)) => {
1033            ElementIndexableKind::Union(db.type_list(members_id).to_vec())
1034        }
1035        Some(TypeData::Intersection(members_id)) => {
1036            ElementIndexableKind::Intersection(db.type_list(members_id).to_vec())
1037        }
1038        Some(TypeData::Literal(crate::LiteralValue::String(_)))
1039        | Some(TypeData::Intrinsic(crate::IntrinsicKind::String)) => {
1040            ElementIndexableKind::StringLike
1041        }
1042        // Enums support reverse mapping: E[value] returns the name, E["name"] returns the value.
1043        // Treat them as having both string and number index signatures.
1044        Some(TypeData::Enum(_, _)) => ElementIndexableKind::ObjectWithIndex {
1045            has_string: true,
1046            has_number: true,
1047        },
1048        _ => ElementIndexableKind::Other,
1049    }
1050}
1051
1052// =============================================================================
1053// Type Query Classification (for resolve_type_query_type)
1054// =============================================================================
1055
1056/// Classification for type query resolution.
1057#[derive(Debug, Clone)]
1058pub enum TypeQueryKind {
1059    TypeQuery(crate::types::SymbolRef),
1060    ApplicationWithTypeQuery {
1061        base_sym_ref: crate::types::SymbolRef,
1062        args: Vec<TypeId>,
1063    },
1064    Application {
1065        app_id: crate::types::TypeApplicationId,
1066    },
1067    Other,
1068}
1069
1070/// Classify a type for type query resolution.
1071pub fn classify_type_query(db: &dyn TypeDatabase, type_id: TypeId) -> TypeQueryKind {
1072    match db.lookup(type_id) {
1073        Some(TypeData::TypeQuery(sym_ref)) => TypeQueryKind::TypeQuery(sym_ref),
1074        Some(TypeData::Application(app_id)) => {
1075            let app = db.type_application(app_id);
1076            match db.lookup(app.base) {
1077                Some(TypeData::TypeQuery(base_sym_ref)) => {
1078                    TypeQueryKind::ApplicationWithTypeQuery {
1079                        base_sym_ref,
1080                        args: app.args.clone(),
1081                    }
1082                }
1083                _ => TypeQueryKind::Application { app_id },
1084            }
1085        }
1086        _ => TypeQueryKind::Other,
1087    }
1088}
1089
1090// =============================================================================
1091// Symbol Reference Classification (for enum_symbol_from_value_type)
1092// =============================================================================
1093
1094/// Classification for symbol reference types.
1095#[derive(Debug, Clone)]
1096pub enum SymbolRefKind {
1097    /// Lazy reference (`DefId`)
1098    Lazy(crate::def::DefId),
1099    TypeQuery(crate::types::SymbolRef),
1100    Other,
1101}
1102
1103/// Classify a type as a symbol reference.
1104pub fn classify_symbol_ref(db: &dyn TypeDatabase, type_id: TypeId) -> SymbolRefKind {
1105    match db.lookup(type_id) {
1106        Some(TypeData::Lazy(def_id)) => SymbolRefKind::Lazy(def_id),
1107        Some(TypeData::TypeQuery(sym_ref)) => SymbolRefKind::TypeQuery(sym_ref),
1108        _ => SymbolRefKind::Other,
1109    }
1110}
1111
1112// =============================================================================
1113// Type Contains Classification (for type_contains_any_inner)
1114// =============================================================================
1115
1116/// Classification for recursive type traversal.
1117#[derive(Debug, Clone)]
1118pub enum TypeContainsKind {
1119    Array(TypeId),
1120    Tuple(crate::types::TupleListId),
1121    Members(Vec<TypeId>),
1122    Object(crate::types::ObjectShapeId),
1123    Function(crate::types::FunctionShapeId),
1124    Callable(crate::types::CallableShapeId),
1125    Application(crate::types::TypeApplicationId),
1126    Conditional(crate::types::ConditionalTypeId),
1127    Mapped(crate::types::MappedTypeId),
1128    IndexAccess {
1129        base: TypeId,
1130        index: TypeId,
1131    },
1132    TemplateLiteral(crate::types::TemplateLiteralId),
1133    Inner(TypeId),
1134    TypeParam {
1135        constraint: Option<TypeId>,
1136        default: Option<TypeId>,
1137    },
1138    Terminal,
1139}
1140
1141/// Classify a type for recursive traversal.
1142pub fn classify_for_contains_traversal(db: &dyn TypeDatabase, type_id: TypeId) -> TypeContainsKind {
1143    match db.lookup(type_id) {
1144        Some(TypeData::Array(elem)) => TypeContainsKind::Array(elem),
1145        Some(TypeData::Tuple(list_id)) => TypeContainsKind::Tuple(list_id),
1146        Some(TypeData::Union(list_id) | TypeData::Intersection(list_id)) => {
1147            TypeContainsKind::Members(db.type_list(list_id).to_vec())
1148        }
1149        Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
1150            TypeContainsKind::Object(shape_id)
1151        }
1152        Some(TypeData::Function(shape_id)) => TypeContainsKind::Function(shape_id),
1153        Some(TypeData::Callable(shape_id)) => TypeContainsKind::Callable(shape_id),
1154        Some(TypeData::Application(app_id)) => TypeContainsKind::Application(app_id),
1155        Some(TypeData::Conditional(cond_id)) => TypeContainsKind::Conditional(cond_id),
1156        Some(TypeData::Mapped(mapped_id)) => TypeContainsKind::Mapped(mapped_id),
1157        Some(TypeData::IndexAccess(base, index)) => TypeContainsKind::IndexAccess { base, index },
1158        Some(TypeData::TemplateLiteral(template_id)) => {
1159            TypeContainsKind::TemplateLiteral(template_id)
1160        }
1161        Some(TypeData::KeyOf(inner) | TypeData::ReadonlyType(inner)) => {
1162            TypeContainsKind::Inner(inner)
1163        }
1164        Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => {
1165            TypeContainsKind::TypeParam {
1166                constraint: info.constraint,
1167                default: info.default,
1168            }
1169        }
1170        _ => TypeContainsKind::Terminal,
1171    }
1172}
1173
1174// =============================================================================
1175// Namespace Member Classification (for resolve_namespace_value_member)
1176// =============================================================================
1177
1178/// Classification for namespace member resolution.
1179#[derive(Debug, Clone)]
1180pub enum NamespaceMemberKind {
1181    Lazy(DefId),
1182    ModuleNamespace(crate::types::SymbolRef),
1183    Callable(crate::types::CallableShapeId),
1184    // TSZ-4: Added Enum variant to handle enum member property access (E.A)
1185    Enum(DefId),
1186    Other,
1187}
1188
1189/// Classify a type for namespace member resolution.
1190pub fn classify_namespace_member(db: &dyn TypeDatabase, type_id: TypeId) -> NamespaceMemberKind {
1191    match db.lookup(type_id) {
1192        Some(TypeData::Callable(shape_id)) => NamespaceMemberKind::Callable(shape_id),
1193        Some(TypeData::Lazy(def_id)) => NamespaceMemberKind::Lazy(def_id),
1194        Some(TypeData::ModuleNamespace(sym_ref)) => NamespaceMemberKind::ModuleNamespace(sym_ref),
1195        // TSZ-4: Handle TypeData::Enum for enum member property access (E.A)
1196        Some(TypeData::Enum(def_id, _)) => NamespaceMemberKind::Enum(def_id),
1197        _ => NamespaceMemberKind::Other,
1198    }
1199}
1200
1201/// Unwrap readonly type wrapper if present.
1202pub fn unwrap_readonly_for_lookup(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
1203    match db.lookup(type_id) {
1204        Some(TypeData::ReadonlyType(inner)) => inner,
1205        _ => type_id,
1206    }
1207}
1208
1209// =============================================================================
1210// Literal Type Creation Helpers
1211// =============================================================================
1212
1213/// Create a string literal type from a string value.
1214///
1215/// This abstracts away the `TypeData` construction from the checker layer.
1216pub fn create_string_literal_type(db: &dyn TypeDatabase, value: &str) -> TypeId {
1217    let atom = db.intern_string(value);
1218    db.literal_string_atom(atom)
1219}
1220
1221/// Create a number literal type from a numeric value.
1222///
1223/// This abstracts away the `TypeData` construction from the checker layer.
1224pub fn create_number_literal_type(db: &dyn TypeDatabase, value: f64) -> TypeId {
1225    db.literal_number(value)
1226}
1227
1228/// Create a boolean literal type.
1229///
1230/// This abstracts away the `TypeData` construction from the checker layer.
1231pub fn create_boolean_literal_type(db: &dyn TypeDatabase, value: bool) -> TypeId {
1232    db.literal_boolean(value)
1233}
1234
1235// =============================================================================
1236// Property Access Resolution Classification
1237// =============================================================================
1238
1239/// Classification for resolving types for property access.
1240#[derive(Debug, Clone)]
1241pub enum PropertyAccessResolutionKind {
1242    /// Lazy type (`DefId`) - needs resolution to actual type
1243    Lazy(DefId),
1244    /// `TypeQuery` (typeof) - resolve the symbol
1245    TypeQuery(crate::types::SymbolRef),
1246    /// Application - needs evaluation
1247    Application(crate::types::TypeApplicationId),
1248    /// Type parameter - follow constraint
1249    TypeParameter { constraint: Option<TypeId> },
1250    /// Complex types that need evaluation
1251    NeedsEvaluation,
1252    /// Union - resolve each member
1253    Union(Vec<TypeId>),
1254    /// Intersection - resolve each member
1255    Intersection(Vec<TypeId>),
1256    /// Readonly wrapper - unwrap
1257    Readonly(TypeId),
1258    /// Function or Callable - may need Function interface
1259    FunctionLike,
1260    /// Already resolved
1261    Resolved,
1262}
1263
1264/// Classify a type for property access resolution.
1265pub fn classify_for_property_access_resolution(
1266    db: &dyn TypeDatabase,
1267    type_id: TypeId,
1268) -> PropertyAccessResolutionKind {
1269    let Some(key) = db.lookup(type_id) else {
1270        return PropertyAccessResolutionKind::Resolved;
1271    };
1272
1273    match key {
1274        TypeData::TypeQuery(sym_ref) => PropertyAccessResolutionKind::TypeQuery(sym_ref),
1275        TypeData::Lazy(def_id) => PropertyAccessResolutionKind::Lazy(def_id),
1276        TypeData::Application(app_id) => PropertyAccessResolutionKind::Application(app_id),
1277        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
1278            PropertyAccessResolutionKind::TypeParameter {
1279                constraint: info.constraint,
1280            }
1281        }
1282        TypeData::Conditional(_)
1283        | TypeData::Mapped(_)
1284        | TypeData::IndexAccess(_, _)
1285        | TypeData::KeyOf(_) => PropertyAccessResolutionKind::NeedsEvaluation,
1286        TypeData::Union(list_id) => {
1287            let members = db.type_list(list_id);
1288            PropertyAccessResolutionKind::Union(members.to_vec())
1289        }
1290        TypeData::Intersection(list_id) => {
1291            let members = db.type_list(list_id);
1292            PropertyAccessResolutionKind::Intersection(members.to_vec())
1293        }
1294        TypeData::ReadonlyType(inner) => PropertyAccessResolutionKind::Readonly(inner),
1295        TypeData::Function(_) | TypeData::Callable(_) => PropertyAccessResolutionKind::FunctionLike,
1296        _ => PropertyAccessResolutionKind::Resolved,
1297    }
1298}
1299
1300// =============================================================================
1301// Contextual Type Literal Allow Classification
1302// =============================================================================
1303
1304/// Classification for checking if contextual type allows literals.
1305#[derive(Debug, Clone)]
1306pub enum ContextualLiteralAllowKind {
1307    /// Union or Intersection - check all members
1308    Members(Vec<TypeId>),
1309    /// Type parameter - check constraint
1310    TypeParameter { constraint: Option<TypeId> },
1311    /// Application - needs evaluation
1312    Application,
1313    /// Mapped type - needs evaluation
1314    Mapped,
1315    /// Template literal type - always allows string literals (pattern matching check
1316    /// happens later during assignability). This prevents premature widening of string
1317    /// literals like `"*hello*"` to `string` when the target is `` `*${string}*` ``.
1318    TemplateLiteral,
1319    /// Does not allow literal
1320    NotAllowed,
1321}
1322
1323/// Classify a type for contextual literal checking.
1324pub fn classify_for_contextual_literal(
1325    db: &dyn TypeDatabase,
1326    type_id: TypeId,
1327) -> ContextualLiteralAllowKind {
1328    let Some(key) = db.lookup(type_id) else {
1329        return ContextualLiteralAllowKind::NotAllowed;
1330    };
1331
1332    match key {
1333        TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
1334            let members = db.type_list(list_id);
1335            ContextualLiteralAllowKind::Members(members.to_vec())
1336        }
1337        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
1338            ContextualLiteralAllowKind::TypeParameter {
1339                constraint: info.constraint,
1340            }
1341        }
1342        TypeData::Application(_) => ContextualLiteralAllowKind::Application,
1343        TypeData::Mapped(_) => ContextualLiteralAllowKind::Mapped,
1344        TypeData::TemplateLiteral(_) => ContextualLiteralAllowKind::TemplateLiteral,
1345        _ => ContextualLiteralAllowKind::NotAllowed,
1346    }
1347}
1348
1349// =============================================================================
1350// Mapped Constraint Resolution Classification
1351// =============================================================================
1352
1353/// Classification for evaluating mapped type constraints.
1354#[derive(Debug, Clone)]
1355pub enum MappedConstraintKind {
1356    /// `KeyOf` type - evaluate operand
1357    KeyOf(TypeId),
1358    /// Union or Literal - return as-is
1359    Resolved,
1360    /// Other type - return as-is
1361    Other,
1362}
1363
1364/// Classify a constraint type for mapped type evaluation.
1365pub fn classify_mapped_constraint(db: &dyn TypeDatabase, type_id: TypeId) -> MappedConstraintKind {
1366    let Some(key) = db.lookup(type_id) else {
1367        return MappedConstraintKind::Other;
1368    };
1369
1370    match key {
1371        TypeData::KeyOf(operand) => MappedConstraintKind::KeyOf(operand),
1372        TypeData::Union(_) | TypeData::Literal(_) => MappedConstraintKind::Resolved,
1373        _ => MappedConstraintKind::Other,
1374    }
1375}
1376
1377// =============================================================================
1378// Type Resolution Classification
1379// =============================================================================
1380
1381/// Classification for evaluating types with symbol resolution.
1382#[derive(Debug, Clone)]
1383pub enum TypeResolutionKind {
1384    /// Lazy - resolve to symbol type via `DefId`
1385    Lazy(DefId),
1386    /// Application - evaluate the application
1387    Application,
1388    /// Already resolved
1389    Resolved,
1390}
1391
1392/// Classify a type for resolution.
1393pub fn classify_for_type_resolution(db: &dyn TypeDatabase, type_id: TypeId) -> TypeResolutionKind {
1394    let Some(key) = db.lookup(type_id) else {
1395        return TypeResolutionKind::Resolved;
1396    };
1397
1398    match key {
1399        TypeData::Lazy(def_id) => TypeResolutionKind::Lazy(def_id),
1400        TypeData::Application(_) => TypeResolutionKind::Application,
1401        _ => TypeResolutionKind::Resolved,
1402    }
1403}
1404
1405// =============================================================================
1406// Type Argument Extraction Classification
1407// =============================================================================
1408
1409/// Classification for extracting type parameters from a type for instantiation.
1410#[derive(Debug, Clone)]
1411pub enum TypeArgumentExtractionKind {
1412    /// Function type with type params
1413    Function(crate::types::FunctionShapeId),
1414    /// Callable type with signatures potentially having type params
1415    Callable(crate::types::CallableShapeId),
1416    /// Not applicable
1417    Other,
1418}
1419
1420/// Classify a type for type argument extraction.
1421pub fn classify_for_type_argument_extraction(
1422    db: &dyn TypeDatabase,
1423    type_id: TypeId,
1424) -> TypeArgumentExtractionKind {
1425    let Some(key) = db.lookup(type_id) else {
1426        return TypeArgumentExtractionKind::Other;
1427    };
1428
1429    match key {
1430        TypeData::Function(shape_id) => TypeArgumentExtractionKind::Function(shape_id),
1431        TypeData::Callable(shape_id) => TypeArgumentExtractionKind::Callable(shape_id),
1432        _ => TypeArgumentExtractionKind::Other,
1433    }
1434}