Skip to main content

tsz_solver/
type_queries.rs

1//! Type Query Functions
2//!
3//! This module provides high-level query functions for inspecting type characteristics.
4//! These functions abstract away the internal `TypeData` representation and provide
5//! a stable API for the checker to query type properties.
6//!
7//! # Design Principles
8//!
9//! - **Abstraction**: Checker code should use these functions instead of matching on `TypeData`
10//! - **TypeDatabase-based**: All queries work through the `TypeDatabase` trait
11//! - **Comprehensive**: Covers all common type checking scenarios
12//! - **Efficient**: Simple lookups with minimal overhead
13//!
14//! # Usage
15//!
16//! ```rust,ignore
17//! use crate::type_queries::*;
18//!
19//! // Check if a type is callable
20//! if is_callable_type(&db, type_id) {
21//!     // Handle callable type
22//! }
23//!
24//! // Check if a type is a tuple
25//! if is_tuple_type(&db, type_id) {
26//!     // Handle tuple type
27//! }
28//! ```
29
30use crate::{QueryDatabase, TypeData, TypeDatabase, TypeId};
31use tsz_common::Atom;
32
33// Re-export extended type queries so callers can use `type_queries::*`
34pub use crate::type_queries_classifiers::{
35    AssignabilityEvalKind, AugmentationTargetKind, BindingElementTypeKind, ConstructorAccessKind,
36    ExcessPropertiesKind, InterfaceMergeKind, SymbolResolutionTraversalKind,
37    classify_for_assignability_eval, classify_for_augmentation, classify_for_binding_element,
38    classify_for_constructor_access, classify_for_excess_properties, classify_for_interface_merge,
39    classify_for_symbol_resolution_traversal, get_conditional_type_id, get_def_id,
40    get_enum_components, get_keyof_inner, get_lazy_def_id, get_mapped_type_id, get_type_identity,
41};
42pub use crate::type_queries_extended::get_application_info;
43pub use crate::type_queries_extended::{
44    AbstractClassCheckKind, AbstractConstructorKind, ArrayLikeKind, BaseInstanceMergeKind,
45    CallSignaturesKind, ClassDeclTypeKind, ConstructorCheckKind, ConstructorReturnMergeKind,
46    ContextualLiteralAllowKind, ElementIndexableKind, IndexKeyKind, InstanceTypeKind,
47    KeyOfTypeKind, LazyTypeKind, LiteralKeyKind, LiteralTypeKind, MappedConstraintKind,
48    NamespaceMemberKind, PrivateBrandKind, PromiseTypeKind, PropertyAccessResolutionKind,
49    StringLiteralKeyKind, TypeArgumentExtractionKind, TypeParameterKind, TypeQueryKind,
50    TypeResolutionKind, classify_array_like, classify_element_indexable,
51    classify_for_abstract_check, classify_for_base_instance_merge, classify_for_call_signatures,
52    classify_for_class_decl, classify_for_constructor_check, classify_for_constructor_return_merge,
53    classify_for_contextual_literal, classify_for_instance_type, classify_for_lazy_resolution,
54    classify_for_private_brand, classify_for_property_access_resolution,
55    classify_for_string_literal_keys, classify_for_type_argument_extraction,
56    classify_for_type_resolution, classify_index_key, classify_literal_key, classify_literal_type,
57    classify_mapped_constraint, classify_namespace_member, classify_promise_type,
58    classify_type_parameter, classify_type_query, create_boolean_literal_type,
59    create_number_literal_type, create_string_literal_type, get_application_base,
60    get_boolean_literal_value, get_callable_type_param_count, get_literal_property_name,
61    get_number_literal_value, get_string_literal_atom, get_string_literal_value, get_tuple_list_id,
62    get_type_param_default, get_widened_literal_type, is_boolean_literal, is_direct_type_parameter,
63    is_invalid_index_type, is_number_literal, is_object_with_index_type, is_string_literal,
64    unwrap_readonly_for_lookup, widen_literal_to_primitive,
65};
66
67pub use crate::type_queries_data::*;
68pub use crate::type_queries_flow::*;
69
70pub fn get_keyof_type(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
71    get_keyof_inner(db, type_id)
72}
73
74pub fn get_allowed_keys(db: &dyn TypeDatabase, type_id: TypeId) -> rustc_hash::FxHashSet<String> {
75    let atoms = collect_property_name_atoms_for_diagnostics(db, type_id, 10);
76    atoms.into_iter().map(|a| db.resolve_atom(a)).collect()
77}
78
79// =============================================================================
80// Core Type Queries
81// =============================================================================
82
83/// Check if a type is a callable type (function or callable with signatures).
84///
85/// Returns true for `TypeData::Callable` and `TypeData::Function` types.
86pub fn is_callable_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
87    matches!(
88        db.lookup(type_id),
89        Some(TypeData::Callable(_) | TypeData::Function(_))
90    )
91}
92
93/// Check if a type is invokable (has call signatures, not just construct signatures).
94///
95/// This is more specific than `is_callable_type` - it ensures the type can be called
96/// as a function (not just constructed with `new`).
97///
98/// # Arguments
99///
100/// * `db` - The type database/interner
101/// * `type_id` - The type to check
102///
103/// # Returns
104///
105/// * `true` - If the type has call signatures
106/// * `false` - Otherwise
107///
108/// # Examples
109///
110/// ```ignore
111/// // Functions are invokable
112/// assert!(is_invokable_type(&db, function_type));
113///
114/// // Callables with call signatures are invokable
115/// assert!(is_invokable_type(&db, callable_with_call_sigs));
116///
117/// // Callables with ONLY construct signatures are NOT invokable
118/// assert!(!is_invokable_type(&db, class_constructor_only));
119/// ```
120pub fn is_invokable_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
121    match db.lookup(type_id) {
122        Some(TypeData::Function(_)) => true,
123        Some(TypeData::Callable(shape_id)) => {
124            let shape = db.callable_shape(shape_id);
125            // Must have at least one call signature (not just construct signatures)
126            !shape.call_signatures.is_empty()
127        }
128        // Intersections might contain a callable
129        Some(TypeData::Intersection(list_id)) => {
130            let members = db.type_list(list_id);
131            members.iter().any(|&m| is_invokable_type(db, m))
132        }
133        _ => false,
134    }
135}
136
137/// Check if a type is a tuple type.
138///
139/// Returns true for `TypeData::Tuple`.
140pub fn is_tuple_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
141    matches!(db.lookup(type_id), Some(TypeData::Tuple(_)))
142}
143
144/// Check if a type is a union type (A | B).
145///
146/// Returns true for `TypeData::Union`.
147pub fn is_union_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
148    matches!(db.lookup(type_id), Some(TypeData::Union(_)))
149}
150
151/// Check if a type is an intersection type (A & B).
152///
153/// Returns true for `TypeData::Intersection`.
154pub fn is_intersection_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
155    matches!(db.lookup(type_id), Some(TypeData::Intersection(_)))
156}
157
158/// Check if a type is an object type (with or without index signatures).
159///
160/// Returns true for `TypeData::Object` and `TypeData::ObjectWithIndex`.
161pub fn is_object_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
162    matches!(
163        db.lookup(type_id),
164        Some(TypeData::Object(_) | TypeData::ObjectWithIndex(_))
165    )
166}
167
168/// Check if a type is an array type (T[]).
169///
170/// Returns true for `TypeData::Array`.
171pub fn is_array_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
172    matches!(db.lookup(type_id), Some(TypeData::Array(_)))
173}
174
175/// Check if a type is a literal type (specific value).
176///
177/// Returns true for `TypeData::Literal`.
178pub fn is_literal_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
179    matches!(db.lookup(type_id), Some(TypeData::Literal(_)))
180}
181
182/// Check if a type is a generic type application (Base<Args>).
183///
184/// Returns true for `TypeData::Application`.
185pub fn is_generic_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
186    matches!(db.lookup(type_id), Some(TypeData::Application(_)))
187}
188
189/// Check if a type is a named type reference.
190///
191/// Returns true for `TypeData::Lazy(DefId)` (interfaces, classes, type aliases).
192pub fn is_type_reference(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
193    matches!(
194        db.lookup(type_id),
195        Some(TypeData::Lazy(_) | TypeData::Recursive(_) | TypeData::BoundParameter(_))
196    )
197}
198
199/// Check if a type is a conditional type (T extends U ? X : Y).
200///
201/// Returns true for `TypeData::Conditional`.
202pub fn is_conditional_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
203    matches!(db.lookup(type_id), Some(TypeData::Conditional(_)))
204}
205
206/// Check if a type is a mapped type ({ [K in Keys]: V }).
207///
208/// Returns true for `TypeData::Mapped`.
209pub fn is_mapped_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
210    matches!(db.lookup(type_id), Some(TypeData::Mapped(_)))
211}
212
213/// Check if a type is a template literal type (`hello${T}world`).
214///
215/// Returns true for `TypeData::TemplateLiteral`.
216pub fn is_template_literal_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
217    matches!(db.lookup(type_id), Some(TypeData::TemplateLiteral(_)))
218}
219
220/// Check if a type is a type parameter, bound parameter, or infer type.
221///
222/// Check if a type is a type parameter, or a union/intersection containing one.
223/// This acts as a more specific check than `contains_type_parameters_db` and is useful
224/// for preventing indexed writes to pure type variables (TS2862) while allowing
225/// writes to instantiated generics like mapped types.
226pub fn is_uninstantiated_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
227    match db.lookup(type_id) {
228        Some(
229            TypeData::TypeParameter(_)
230            | TypeData::BoundParameter(_)
231            | TypeData::Infer(_)
232            | TypeData::Lazy(_),
233        ) => true,
234        Some(TypeData::Intersection(list_id) | TypeData::Union(list_id)) => {
235            let elements = db.type_list(list_id);
236            elements
237                .iter()
238                .any(|&e| is_uninstantiated_type_parameter(db, e))
239        }
240        _ => false,
241    }
242}
243
244/// Returns true for `TypeData::TypeParameter`, `TypeData::BoundParameter`,
245/// and `TypeData::Infer`. `BoundParameter` is included because it represents
246/// a type parameter that has been bound to a specific index in a generic
247/// signature — it should still be treated as "unresolved" for purposes like
248/// excess property checking and constraint validation.
249pub fn is_type_parameter(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
250    matches!(
251        db.lookup(type_id),
252        Some(TypeData::TypeParameter(_) | TypeData::BoundParameter(_) | TypeData::Infer(_))
253    )
254}
255
256/// Check if a type is an index access type (T[K]).
257///
258/// Returns true for `TypeData::IndexAccess`.
259pub fn is_index_access_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
260    matches!(db.lookup(type_id), Some(TypeData::IndexAccess(_, _)))
261}
262
263/// Check if a type is a keyof type.
264///
265/// Returns true for `TypeData::KeyOf`.
266pub fn is_keyof_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
267    matches!(db.lookup(type_id), Some(TypeData::KeyOf(_)))
268}
269
270/// Check if a type is a type query (typeof expr).
271///
272/// Returns true for `TypeData::TypeQuery`.
273pub fn is_type_query(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
274    matches!(db.lookup(type_id), Some(TypeData::TypeQuery(_)))
275}
276
277/// Check if a type is a readonly type modifier.
278///
279/// Returns true for `TypeData::ReadonlyType`.
280pub fn is_readonly_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
281    matches!(db.lookup(type_id), Some(TypeData::ReadonlyType(_)))
282}
283
284/// Check if a type is a unique symbol type.
285///
286/// Returns true for `TypeData::UniqueSymbol`.
287pub fn is_unique_symbol_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
288    matches!(db.lookup(type_id), Some(TypeData::UniqueSymbol(_)))
289}
290
291/// Check if a type is usable as a property name (TS1166/TS1165/TS1169).
292///
293/// Returns true for string literals, number literals, and unique symbol types.
294/// This corresponds to TypeScript's `isTypeUsableAsPropertyName` check.
295pub fn is_type_usable_as_property_name(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
296    if type_id == TypeId::ANY {
297        return true;
298    }
299    matches!(
300        db.lookup(type_id),
301        Some(
302            TypeData::Literal(crate::LiteralValue::String(_))
303                | TypeData::Literal(crate::LiteralValue::Number(_))
304                | TypeData::UniqueSymbol(_)
305        )
306    )
307}
308
309/// Check if a type is the this type.
310///
311/// Returns true for `TypeData::ThisType`.
312pub fn is_this_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
313    matches!(db.lookup(type_id), Some(TypeData::ThisType))
314}
315
316/// Check if a type is an error type.
317///
318/// Returns true for `TypeData::Error` or `TypeId::ERROR`.
319pub fn is_error_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
320    type_id == TypeId::ERROR || matches!(db.lookup(type_id), Some(TypeData::Error))
321}
322
323/// Check if a type needs evaluation before interface merging.
324///
325/// Returns true for Application and Lazy types, which are meta-types that
326/// may resolve to Object/Callable types when evaluated. Used before
327/// `classify_for_interface_merge` to ensure that type-alias-based heritage
328/// (e.g., `interface X extends TypeAlias<T>`) is properly resolved.
329pub fn needs_evaluation_for_merge(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
330    matches!(
331        db.lookup(type_id),
332        Some(TypeData::Application(_) | TypeData::Lazy(_))
333    )
334}
335
336/// Get the return type of a function type.
337///
338/// Returns `TypeId::ERROR` if the type is not a Function.
339pub fn get_function_return_type(db: &dyn TypeDatabase, type_id: TypeId) -> TypeId {
340    match db.lookup(type_id) {
341        Some(TypeData::Function(shape_id)) => db.function_shape(shape_id).return_type,
342        _ => TypeId::ERROR,
343    }
344}
345
346/// Get the parameter types of a function type.
347///
348/// Returns an empty vector if the type is not a Function.
349pub fn get_function_parameter_types(db: &dyn TypeDatabase, type_id: TypeId) -> Vec<TypeId> {
350    match db.lookup(type_id) {
351        Some(TypeData::Function(shape_id)) => db
352            .function_shape(shape_id)
353            .params
354            .iter()
355            .map(|p| p.type_id)
356            .collect(),
357        _ => Vec::new(),
358    }
359}
360
361/// Check if a type is an intrinsic type (any, unknown, never, void, etc.).
362///
363/// Returns true for `TypeData::Intrinsic`.
364pub fn is_intrinsic_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
365    matches!(db.lookup(type_id), Some(TypeData::Intrinsic(_)))
366}
367
368/// Check if a type is a primitive type (intrinsic or literal).
369///
370/// Returns true for intrinsic types and literal types.
371pub fn is_primitive_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
372    // Check well-known intrinsic TypeIds first
373    if type_id.is_intrinsic() {
374        return true;
375    }
376    matches!(
377        db.lookup(type_id),
378        Some(TypeData::Intrinsic(_) | TypeData::Literal(_))
379    )
380}
381
382// =============================================================================
383// Intrinsic Type Queries
384// =============================================================================
385//
386// These functions provide TypeData-free checking for intrinsic types.
387// Checker code should use these instead of matching on TypeData::Intrinsic.
388//
389// ## Important Usage Notes
390//
391// These are TYPE IDENTITY checks, NOT compatibility checks:
392//
393// - Identity: `is_string_type(TypeId::STRING)` -> TRUE
394// - Identity: `is_string_type(literal "hello")` -> FALSE (literal, not intrinsic)
395// - Identity: `is_string_type(string & {tag: 1})` -> FALSE (intersection, not intrinsic)
396//
397// For assignability/compatibility checks, use Solver subtyping:
398// - `solver.is_subtype_of(literal, TypeId::STRING)` -> TRUE
399// - `solver.is_subtype_of(branded, TypeId::STRING)` -> TRUE (if assignable)
400//
401// ### When to use these helpers
402// - Checking if a type annotation is explicitly the intrinsic keyword
403// - Validating type constructor arguments
404// - Distinguishing `void` from `undefined` in return types
405//
406// ### When NOT to use these helpers
407// - Assignment/compatibility checks -> Use `is_subtype_of` instead
408// - Type narrowing -> Use Solver's narrowing analysis
409// - Checking if a value IS a string (not literal) -> Use `is_subtype_of`
410//
411// ## Implementation Notes
412// - Shallow queries: do NOT resolve Lazy/Ref (caller's responsibility)
413// - Defensive pattern: check both TypeId constants AND TypeData::Intrinsic
414// - Fast-path O(1) using TypeId integer comparison
415
416use crate::types::IntrinsicKind;
417
418/// Check if a type is the `any` type.
419///
420/// Generate an intrinsic type checker function that checks both the well-known
421/// `TypeId` constant and the `TypeData::Intrinsic` variant.
422macro_rules! define_intrinsic_check {
423    ($fn_name:ident, $type_id:ident, $kind:ident) => {
424        pub fn $fn_name(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
425            type_id == TypeId::$type_id
426                || matches!(
427                    db.lookup(type_id),
428                    Some(TypeData::Intrinsic(IntrinsicKind::$kind))
429                )
430        }
431    };
432}
433
434define_intrinsic_check!(is_any_type, ANY, Any);
435define_intrinsic_check!(is_unknown_type, UNKNOWN, Unknown);
436define_intrinsic_check!(is_never_type, NEVER, Never);
437define_intrinsic_check!(is_void_type, VOID, Void);
438define_intrinsic_check!(is_undefined_type, UNDEFINED, Undefined);
439define_intrinsic_check!(is_null_type, NULL, Null);
440define_intrinsic_check!(is_string_type, STRING, String);
441define_intrinsic_check!(is_number_type, NUMBER, Number);
442define_intrinsic_check!(is_bigint_type, BIGINT, Bigint);
443define_intrinsic_check!(is_boolean_type, BOOLEAN, Boolean);
444define_intrinsic_check!(is_symbol_type, SYMBOL, Symbol);
445
446// =============================================================================
447// Composite Type Queries
448// =============================================================================
449
450/// Check if a type is an object-like type suitable for typeof "object".
451///
452/// Returns true for: Object, `ObjectWithIndex`, Array, Tuple, Mapped
453pub fn is_object_like_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
454    is_object_like_type_impl(db, type_id)
455}
456
457fn is_object_like_type_impl(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
458    match db.lookup(type_id) {
459        Some(
460            TypeData::Object(_)
461            | TypeData::ObjectWithIndex(_)
462            | TypeData::Array(_)
463            | TypeData::Tuple(_)
464            | TypeData::Mapped(_)
465            | TypeData::Function(_)
466            | TypeData::Callable(_)
467            | TypeData::Intrinsic(IntrinsicKind::Object | IntrinsicKind::Function),
468        ) => true,
469        Some(TypeData::ReadonlyType(inner)) => is_object_like_type_impl(db, inner),
470        Some(TypeData::Intersection(members)) => {
471            let members = db.type_list(members);
472            members
473                .iter()
474                .all(|&member| is_object_like_type_impl(db, member))
475        }
476        Some(TypeData::TypeParameter(info) | TypeData::Infer(info)) => info
477            .constraint
478            .is_some_and(|constraint| is_object_like_type_impl(db, constraint)),
479        _ => false,
480    }
481}
482
483/// Check if a type is a function type (Function or Callable).
484///
485/// This also handles intersections containing function types.
486pub fn is_function_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
487    is_function_type_impl(db, type_id)
488}
489
490fn is_function_type_impl(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
491    match db.lookup(type_id) {
492        Some(TypeData::Function(_) | TypeData::Callable(_)) => true,
493        Some(TypeData::Intersection(members)) => {
494            let members = db.type_list(members);
495            members
496                .iter()
497                .any(|&member| is_function_type_impl(db, member))
498        }
499        _ => false,
500    }
501}
502
503/// Check if a type is valid for object spreading (`{...x}`).
504///
505/// Returns `true` for types that can be spread into an object literal:
506/// - `any`, `never`, `error` (always spreadable)
507/// - Object types, arrays, tuples, functions, callables, mapped types
508/// - `object` intrinsic (non-primitive)
509/// - Type parameters (spreadable by default; constraint checked separately)
510/// - Unions: all members must be spreadable
511/// - Intersections: all members must be spreadable
512///
513/// Returns `false` for primitive types (`number`, `string`, `boolean`, etc.),
514/// literals, `null`, `undefined`, `void`, and `unknown`.
515pub fn is_valid_spread_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
516    is_valid_spread_type_impl(db, type_id, 0)
517}
518
519fn is_valid_spread_type_impl(db: &dyn TypeDatabase, type_id: TypeId, depth: u32) -> bool {
520    if depth > 20 {
521        return true;
522    }
523    match type_id {
524        TypeId::ANY | TypeId::NEVER | TypeId::ERROR => return true,
525        _ => {}
526    }
527    match db.lookup(type_id) {
528        // Primitives and literals are not spreadable
529        Some(
530            TypeData::Intrinsic(
531                IntrinsicKind::String
532                | IntrinsicKind::Number
533                | IntrinsicKind::Boolean
534                | IntrinsicKind::Bigint
535                | IntrinsicKind::Symbol
536                | IntrinsicKind::Void
537                | IntrinsicKind::Null
538                | IntrinsicKind::Undefined
539                | IntrinsicKind::Unknown,
540            )
541            | TypeData::Literal(_),
542        ) => false,
543        // Union: all members must be spreadable
544        Some(TypeData::Union(members)) => {
545            let members = db.type_list(members);
546            members
547                .iter()
548                .all(|&m| is_valid_spread_type_impl(db, m, depth + 1))
549        }
550        // Intersection: all members must be spreadable
551        Some(TypeData::Intersection(members)) => {
552            let members = db.type_list(members);
553            members
554                .iter()
555                .all(|&m| is_valid_spread_type_impl(db, m, depth + 1))
556        }
557        Some(TypeData::ReadonlyType(inner)) => is_valid_spread_type_impl(db, inner, depth + 1),
558        // Everything else is spreadable: object types, arrays, tuples, functions,
559        // callables, mapped types, type parameters, lazy refs, applications, etc.
560        _ => true,
561    }
562}
563
564/// Check if a type is an empty object type (no properties, no index signatures).
565pub fn is_empty_object_type(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
566    match db.lookup(type_id) {
567        Some(TypeData::Object(shape_id)) => {
568            let shape = db.object_shape(shape_id);
569            shape.properties.is_empty()
570        }
571        Some(TypeData::ObjectWithIndex(shape_id)) => {
572            let shape = db.object_shape(shape_id);
573            shape.properties.is_empty()
574                && shape.string_index.is_none()
575                && shape.number_index.is_none()
576        }
577        _ => false,
578    }
579}
580
581// =============================================================================
582// Constructor Type Collection Helpers
583// =============================================================================
584
585/// Result of classifying a type for constructor collection.
586///
587/// This enum tells the caller what kind of type this is and how to proceed
588/// when collecting constructor types from a composite type structure.
589#[derive(Debug, Clone, PartialEq, Eq)]
590pub enum ConstructorTypeKind {
591    /// This is a Callable type - always a constructor type
592    Callable,
593    /// This is a Function type - check `is_constructor` flag on the shape
594    Function(crate::types::FunctionShapeId),
595    /// Recurse into these member types (Union, Intersection)
596    Members(Vec<TypeId>),
597    /// Recurse into the inner type (`ReadonlyType`)
598    Inner(TypeId),
599    /// Recurse into the constraint (`TypeParameter`, Infer)
600    Constraint(Option<TypeId>),
601    /// This type needs full type evaluation (Conditional, Mapped, `IndexAccess`, `KeyOf`)
602    NeedsTypeEvaluation,
603    /// This is a generic application that needs instantiation
604    NeedsApplicationEvaluation,
605    /// This is a `TypeQuery` - resolve the symbol reference to get its type
606    TypeQuery(crate::types::SymbolRef),
607    /// This type cannot be a constructor (primitives, literals, etc.)
608    NotConstructor,
609}
610
611/// Classify a type for constructor type collection.
612///
613/// This function examines a `TypeData` and returns information about how to handle it
614/// when collecting constructor types. The caller is responsible for:
615/// - Checking the `is_constructor` flag for Function types
616/// - Evaluating types when `NeedsTypeEvaluation` or `NeedsApplicationEvaluation` is returned
617/// - Resolving symbol references for `TypeQuery`
618/// - Recursing into members/inner types
619///
620/// # Example
621///
622/// ```rust,ignore
623/// use crate::type_queries::{classify_constructor_type, ConstructorTypeKind};
624///
625/// match classify_constructor_type(db, type_id) {
626///     ConstructorTypeKind::Callable => {
627///         // This is a constructor type
628///         ctor_types.push(type_id);
629///     }
630///     ConstructorTypeKind::Function(shape_id) => {
631///         let shape = db.function_shape(shape_id);
632///         if shape.is_constructor {
633///             ctor_types.push(type_id);
634///         }
635///     }
636///     ConstructorTypeKind::Members(members) => {
637///         for member in members {
638///             // Recurse
639///         }
640///     }
641///     ConstructorTypeKind::NeedsTypeEvaluation => {
642///         // Use evaluate_type_with_env
643///     }
644///     ConstructorTypeKind::NeedsApplicationEvaluation => {
645///         // Use evaluate_application_type
646///     }
647///     // ... handle other cases
648/// }
649/// ```
650pub fn classify_constructor_type(db: &dyn TypeDatabase, type_id: TypeId) -> ConstructorTypeKind {
651    let Some(key) = db.lookup(type_id) else {
652        return ConstructorTypeKind::NotConstructor;
653    };
654
655    match key {
656        TypeData::Callable(_) => ConstructorTypeKind::Callable,
657        TypeData::Function(shape_id) => ConstructorTypeKind::Function(shape_id),
658        TypeData::Intersection(members_id) | TypeData::Union(members_id) => {
659            let members = db.type_list(members_id);
660            ConstructorTypeKind::Members(members.to_vec())
661        }
662        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
663            ConstructorTypeKind::Inner(inner)
664        }
665        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
666            ConstructorTypeKind::Constraint(info.constraint)
667        }
668        TypeData::Conditional(_)
669        | TypeData::Mapped(_)
670        | TypeData::IndexAccess(_, _)
671        | TypeData::KeyOf(_) => ConstructorTypeKind::NeedsTypeEvaluation,
672        TypeData::Application(_) => ConstructorTypeKind::NeedsApplicationEvaluation,
673        TypeData::TypeQuery(sym_ref) => ConstructorTypeKind::TypeQuery(sym_ref),
674        // All other types cannot be constructors
675        TypeData::Enum(_, _)
676        | TypeData::BoundParameter(_)
677        | TypeData::Intrinsic(_)
678        | TypeData::Literal(_)
679        | TypeData::Object(_)
680        | TypeData::ObjectWithIndex(_)
681        | TypeData::Array(_)
682        | TypeData::Tuple(_)
683        | TypeData::Lazy(_)
684        | TypeData::Recursive(_)
685        | TypeData::TemplateLiteral(_)
686        | TypeData::UniqueSymbol(_)
687        | TypeData::ThisType
688        | TypeData::StringIntrinsic { .. }
689        | TypeData::ModuleNamespace(_)
690        | TypeData::Error => ConstructorTypeKind::NotConstructor,
691    }
692}
693
694// =============================================================================
695// Static Property Collection Helpers
696// =============================================================================
697
698/// Result of extracting static properties from a type.
699///
700/// This enum allows the caller to handle recursion and type evaluation
701/// while keeping the `TypeData` matching logic in the solver layer.
702#[derive(Debug, Clone)]
703pub enum StaticPropertySource {
704    /// Direct properties from Callable, Object, or `ObjectWithIndex` types.
705    Properties(Vec<crate::PropertyInfo>),
706    /// Member types that should be recursively processed (Union/Intersection).
707    RecurseMembers(Vec<TypeId>),
708    /// Single type to recurse into (`TypeParameter` constraint, `ReadonlyType` inner).
709    RecurseSingle(TypeId),
710    /// Type that needs evaluation before property extraction (Conditional, Mapped, etc.).
711    NeedsEvaluation,
712    /// Type that needs application evaluation (Application type).
713    NeedsApplicationEvaluation,
714    /// No properties available (primitives, error types, etc.).
715    None,
716}
717
718/// Extract static property information from a type.
719///
720/// This function handles the `TypeData` matching for property collection,
721/// returning a `StaticPropertySource` that tells the caller how to proceed.
722/// The caller is responsible for:
723/// - Handling recursion for `RecurseMembers` and `RecurseSingle` cases
724/// - Evaluating types for `NeedsEvaluation` and `NeedsApplicationEvaluation` cases
725/// - Tracking visited types to prevent infinite loops
726///
727/// # Example
728///
729/// ```ignore
730/// match get_static_property_source(&db, type_id) {
731///     StaticPropertySource::Properties(props) => {
732///         for prop in props {
733///             properties.entry(prop.name).or_insert(prop);
734///         }
735///     }
736///     StaticPropertySource::RecurseMembers(members) => {
737///         for member in members {
738///             // Recursively collect from member
739///         }
740///     }
741///     // ... handle other cases
742/// }
743/// ```
744pub fn get_static_property_source(db: &dyn TypeDatabase, type_id: TypeId) -> StaticPropertySource {
745    let Some(key) = db.lookup(type_id) else {
746        return StaticPropertySource::None;
747    };
748
749    match key {
750        TypeData::Callable(shape_id) => {
751            let shape = db.callable_shape(shape_id);
752            StaticPropertySource::Properties(shape.properties.to_vec())
753        }
754        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
755            let shape = db.object_shape(shape_id);
756            StaticPropertySource::Properties(shape.properties.to_vec())
757        }
758        TypeData::Intersection(members_id) | TypeData::Union(members_id) => {
759            let members = db.type_list(members_id);
760            StaticPropertySource::RecurseMembers(members.to_vec())
761        }
762        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
763            if let Some(constraint) = info.constraint {
764                StaticPropertySource::RecurseSingle(constraint)
765            } else {
766                StaticPropertySource::None
767            }
768        }
769        TypeData::ReadonlyType(inner) => StaticPropertySource::RecurseSingle(inner),
770        TypeData::Conditional(_)
771        | TypeData::Mapped(_)
772        | TypeData::IndexAccess(_, _)
773        | TypeData::KeyOf(_) => StaticPropertySource::NeedsEvaluation,
774        TypeData::Application(_) => StaticPropertySource::NeedsApplicationEvaluation,
775        _ => StaticPropertySource::None,
776    }
777}
778
779// =============================================================================
780// Construct Signature Queries
781// =============================================================================
782
783/// Check if a Callable type has construct signatures.
784///
785/// Returns true only for Callable types that have non-empty `construct_signatures`.
786/// This is a direct check and does not resolve through Ref or `TypeQuery` types.
787pub fn has_construct_signatures(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
788    match db.lookup(type_id) {
789        Some(TypeData::Callable(shape_id)) => {
790            let shape = db.callable_shape(shape_id);
791            !shape.construct_signatures.is_empty()
792        }
793        _ => false,
794    }
795}
796
797/// Get the symbol reference from a `TypeQuery` type.
798///
799/// Returns None if the type is not a `TypeQuery`.
800pub fn get_symbol_ref_from_type(
801    db: &dyn TypeDatabase,
802    type_id: TypeId,
803) -> Option<crate::types::SymbolRef> {
804    match db.lookup(type_id) {
805        Some(TypeData::TypeQuery(sym_ref)) => Some(sym_ref),
806        _ => None,
807    }
808}
809
810/// Kind of constructable type for `get_construct_type_from_type`.
811///
812/// This enum represents the different ways a type can be constructable,
813/// allowing the caller to handle each case appropriately without matching
814/// directly on `TypeData`.
815#[derive(Debug, Clone)]
816pub enum ConstructableTypeKind {
817    /// Callable type with construct signatures - return transformed callable
818    CallableWithConstruct,
819    /// Callable type without construct signatures - check for prototype property
820    CallableMaybePrototype,
821    /// Function type - always constructable
822    Function,
823    /// Reference to a symbol - need to check symbol flags
824    SymbolRef(crate::types::SymbolRef),
825    /// `TypeQuery` (typeof expr) - need to check symbol flags
826    TypeQueryRef(crate::types::SymbolRef),
827    /// Type parameter with a constraint to check recursively
828    TypeParameterWithConstraint(TypeId),
829    /// Type parameter without constraint - not constructable
830    TypeParameterNoConstraint,
831    /// Intersection type - all members must be constructable
832    Intersection(Vec<TypeId>),
833    /// Application (generic instantiation) - return as-is
834    Application,
835    /// Object type - return as-is (may have construct signatures)
836    Object,
837    /// Not constructable
838    NotConstructable,
839}
840
841/// Classify a type for constructability checking.
842///
843/// This function examines a type and returns information about how to handle it
844/// when determining if it can be used with `new`. This is specifically for
845/// the `get_construct_type_from_type` use case.
846///
847/// The caller is responsible for:
848/// - Checking symbol flags for SymbolRef/TypeQueryRef cases
849/// - Checking prototype property for `CallableMaybePrototype`
850/// - Recursing into constraint for `TypeParameterWithConstraint`
851/// - Checking all members for Intersection
852pub fn classify_for_constructability(
853    db: &dyn TypeDatabase,
854    type_id: TypeId,
855) -> ConstructableTypeKind {
856    let Some(key) = db.lookup(type_id) else {
857        return ConstructableTypeKind::NotConstructable;
858    };
859
860    match key {
861        TypeData::Callable(shape_id) => {
862            let shape = db.callable_shape(shape_id);
863            if shape.construct_signatures.is_empty() {
864                ConstructableTypeKind::CallableMaybePrototype
865            } else {
866                ConstructableTypeKind::CallableWithConstruct
867            }
868        }
869        TypeData::Function(_) => ConstructableTypeKind::Function,
870        TypeData::TypeQuery(sym_ref) => ConstructableTypeKind::TypeQueryRef(sym_ref),
871        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
872            if let Some(constraint) = info.constraint {
873                ConstructableTypeKind::TypeParameterWithConstraint(constraint)
874            } else {
875                ConstructableTypeKind::TypeParameterNoConstraint
876            }
877        }
878        TypeData::Intersection(members_id) => {
879            let members = db.type_list(members_id);
880            ConstructableTypeKind::Intersection(members.to_vec())
881        }
882        TypeData::Application(_) => ConstructableTypeKind::Application,
883        TypeData::Object(_) | TypeData::ObjectWithIndex(_) => ConstructableTypeKind::Object,
884        _ => ConstructableTypeKind::NotConstructable,
885    }
886}
887
888/// Create a callable type with construct signatures converted to call signatures.
889///
890/// This is used when resolving `new` expressions where we need to treat
891/// construct signatures as call signatures for type checking purposes.
892/// Returns None if the type doesn't have construct signatures.
893pub fn construct_to_call_callable(db: &dyn TypeDatabase, type_id: TypeId) -> Option<TypeId> {
894    match db.lookup(type_id) {
895        Some(TypeData::Callable(shape_id)) => {
896            let shape = db.callable_shape(shape_id);
897            if shape.construct_signatures.is_empty() {
898                None
899            } else {
900                Some(db.callable(crate::types::CallableShape {
901                    call_signatures: shape.construct_signatures.clone(),
902                    construct_signatures: Vec::new(),
903                    properties: Vec::new(),
904                    string_index: None,
905                    number_index: None,
906                    symbol: None,
907                }))
908            }
909        }
910        _ => None,
911    }
912}
913
914// =============================================================================
915// Constraint Type Classification Helpers
916// =============================================================================
917
918/// Classification for constraint types.
919#[derive(Debug, Clone)]
920pub enum ConstraintTypeKind {
921    /// Type parameter or infer with constraint
922    TypeParameter {
923        constraint: Option<TypeId>,
924        default: Option<TypeId>,
925    },
926    /// Union - get constraint from each member
927    Union(Vec<TypeId>),
928    /// Intersection - get constraint from each member
929    Intersection(Vec<TypeId>),
930    /// Symbol reference - resolve first
931    SymbolRef(crate::types::SymbolRef),
932    /// Application - evaluate first
933    Application { app_id: u32 },
934    /// Mapped type - evaluate constraint
935    Mapped { mapped_id: u32 },
936    /// `KeyOf` - special handling
937    KeyOf(TypeId),
938    /// Literal or resolved constraint
939    Resolved(TypeId),
940    /// No constraint
941    NoConstraint,
942}
943
944/// Classify a type for constraint extraction.
945pub fn classify_for_constraint(db: &dyn TypeDatabase, type_id: TypeId) -> ConstraintTypeKind {
946    let Some(key) = db.lookup(type_id) else {
947        return ConstraintTypeKind::NoConstraint;
948    };
949    match key {
950        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
951            ConstraintTypeKind::TypeParameter {
952                constraint: info.constraint,
953                default: info.default,
954            }
955        }
956        TypeData::Union(list_id) => {
957            let members = db.type_list(list_id);
958            ConstraintTypeKind::Union(members.to_vec())
959        }
960        TypeData::Intersection(list_id) => {
961            let members = db.type_list(list_id);
962            ConstraintTypeKind::Intersection(members.to_vec())
963        }
964        TypeData::Application(app_id) => ConstraintTypeKind::Application { app_id: app_id.0 },
965        TypeData::Mapped(mapped_id) => ConstraintTypeKind::Mapped {
966            mapped_id: mapped_id.0,
967        },
968        TypeData::KeyOf(operand) => ConstraintTypeKind::KeyOf(operand),
969        TypeData::Literal(_) => ConstraintTypeKind::Resolved(type_id),
970        TypeData::BoundParameter(_)
971        | TypeData::Intrinsic(_)
972        | TypeData::Object(_)
973        | TypeData::ObjectWithIndex(_)
974        | TypeData::Array(_)
975        | TypeData::Tuple(_)
976        | TypeData::Function(_)
977        | TypeData::Callable(_)
978        | TypeData::Conditional(_)
979        | TypeData::IndexAccess(_, _)
980        | TypeData::TemplateLiteral(_)
981        | TypeData::UniqueSymbol(_)
982        | TypeData::ThisType
983        | TypeData::ReadonlyType(_)
984        | TypeData::NoInfer(_)
985        | TypeData::TypeQuery(_)
986        | TypeData::StringIntrinsic { .. }
987        | TypeData::ModuleNamespace(_)
988        | TypeData::Enum(_, _)
989        | TypeData::Lazy(_)
990        | TypeData::Recursive(_)
991        | TypeData::Error => ConstraintTypeKind::NoConstraint,
992    }
993}
994
995// =============================================================================
996// Signature Classification
997// =============================================================================
998
999/// Classification for types when extracting call/construct signatures.
1000#[derive(Debug, Clone)]
1001pub enum SignatureTypeKind {
1002    /// Callable type with `shape_id` - has `call_signatures` and `construct_signatures`
1003    Callable(crate::types::CallableShapeId),
1004    /// Function type with `shape_id` - has single signature
1005    Function(crate::types::FunctionShapeId),
1006    /// Union type - get signatures from each member
1007    Union(Vec<TypeId>),
1008    /// Intersection type - get signatures from each member
1009    Intersection(Vec<TypeId>),
1010    /// Readonly wrapper - unwrap and get signatures from inner type
1011    ReadonlyType(TypeId),
1012    /// Type parameter with optional constraint - may need to check constraint
1013    TypeParameter { constraint: Option<TypeId> },
1014    /// Types that need evaluation before signature extraction (Conditional, Mapped, `IndexAccess`, `KeyOf`)
1015    NeedsEvaluation(TypeId),
1016    /// Types without signatures (Intrinsic, Literal, Object without callable, etc.)
1017    NoSignatures,
1018}
1019
1020/// Classify a type for signature extraction.
1021pub fn classify_for_signatures(db: &dyn TypeDatabase, type_id: TypeId) -> SignatureTypeKind {
1022    // Handle special TypeIds first
1023    if type_id == TypeId::ERROR || type_id == TypeId::NEVER {
1024        return SignatureTypeKind::NoSignatures;
1025    }
1026    if type_id == TypeId::ANY {
1027        // any is callable but has no concrete signatures
1028        return SignatureTypeKind::NoSignatures;
1029    }
1030
1031    let Some(key) = db.lookup(type_id) else {
1032        return SignatureTypeKind::NoSignatures;
1033    };
1034
1035    match key {
1036        // Callable types - have call_signatures and construct_signatures
1037        TypeData::Callable(shape_id) => SignatureTypeKind::Callable(shape_id),
1038
1039        // Function types - have a single signature
1040        TypeData::Function(shape_id) => SignatureTypeKind::Function(shape_id),
1041
1042        // Union type - get signatures from each member
1043        TypeData::Union(members_id) => {
1044            let members = db.type_list(members_id);
1045            SignatureTypeKind::Union(members.to_vec())
1046        }
1047
1048        // Intersection type - get signatures from each member
1049        TypeData::Intersection(members_id) => {
1050            let members = db.type_list(members_id);
1051            SignatureTypeKind::Intersection(members.to_vec())
1052        }
1053
1054        // Readonly wrapper - unwrap and recurse
1055        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
1056            SignatureTypeKind::ReadonlyType(inner)
1057        }
1058
1059        // Type parameter - may have constraint with signatures
1060        TypeData::TypeParameter(info) | TypeData::Infer(info) => SignatureTypeKind::TypeParameter {
1061            constraint: info.constraint,
1062        },
1063
1064        // Complex types that need evaluation before signature extraction
1065        TypeData::Conditional(_)
1066        | TypeData::Mapped(_)
1067        | TypeData::IndexAccess(_, _)
1068        | TypeData::KeyOf(_) => SignatureTypeKind::NeedsEvaluation(type_id),
1069
1070        // All other types don't have callable signatures
1071        TypeData::BoundParameter(_)
1072        | TypeData::Intrinsic(_)
1073        | TypeData::Literal(_)
1074        | TypeData::Object(_)
1075        | TypeData::ObjectWithIndex(_)
1076        | TypeData::Array(_)
1077        | TypeData::Tuple(_)
1078        | TypeData::Lazy(_)
1079        | TypeData::Recursive(_)
1080        | TypeData::Application(_)
1081        | TypeData::TemplateLiteral(_)
1082        | TypeData::UniqueSymbol(_)
1083        | TypeData::ThisType
1084        | TypeData::TypeQuery(_)
1085        | TypeData::StringIntrinsic { .. }
1086        | TypeData::ModuleNamespace(_)
1087        | TypeData::Enum(_, _)
1088        | TypeData::Error => SignatureTypeKind::NoSignatures,
1089    }
1090}
1091
1092// =============================================================================
1093// Iterable Type Classification (Spread Handling)
1094// =============================================================================
1095
1096/// Classification for iterable types (used for spread element handling).
1097#[derive(Debug, Clone)]
1098pub enum IterableTypeKind {
1099    /// Tuple type - elements can be expanded
1100    Tuple(Vec<crate::types::TupleElement>),
1101    /// Array type - element type for variadic handling
1102    Array(TypeId),
1103    /// Not a directly iterable type (caller should handle as-is)
1104    Other,
1105}
1106
1107/// Classify a type for iterable/spread handling.
1108pub fn classify_iterable_type(db: &dyn TypeDatabase, type_id: TypeId) -> IterableTypeKind {
1109    let Some(key) = db.lookup(type_id) else {
1110        return IterableTypeKind::Other;
1111    };
1112
1113    match key {
1114        TypeData::Tuple(tuple_id) => {
1115            let elements = db.tuple_list(tuple_id);
1116            IterableTypeKind::Tuple(elements.to_vec())
1117        }
1118        TypeData::Array(elem_type) => IterableTypeKind::Array(elem_type),
1119        _ => IterableTypeKind::Other,
1120    }
1121}
1122
1123// =============================================================================
1124// Full Iterable Type Classification (For is_iterable_type checks)
1125// =============================================================================
1126
1127/// Comprehensive classification for iterable type checking.
1128///
1129/// This enum is used by `is_iterable_type` and related functions to determine
1130/// if a type is iterable (has Symbol.iterator protocol) without directly
1131/// matching on `TypeData` in the checker layer.
1132#[derive(Debug, Clone)]
1133pub enum FullIterableTypeKind {
1134    /// Array type - always iterable
1135    Array(TypeId),
1136    /// Tuple type - always iterable
1137    Tuple(Vec<crate::types::TupleElement>),
1138    /// String literal - always iterable
1139    StringLiteral(tsz_common::interner::Atom),
1140    /// Union type - all members must be iterable
1141    Union(Vec<TypeId>),
1142    /// Intersection type - at least one member must be iterable
1143    Intersection(Vec<TypeId>),
1144    /// Object type - check for [Symbol.iterator] method
1145    Object(crate::types::ObjectShapeId),
1146    /// Application type (Set<T>, Map<K,V>, etc.) - check base type
1147    Application { base: TypeId },
1148    /// Type parameter - check constraint if present
1149    TypeParameter { constraint: Option<TypeId> },
1150    /// Readonly wrapper - check inner type
1151    Readonly(TypeId),
1152    /// Function or Callable - not iterable
1153    FunctionOrCallable,
1154    /// Index access, Conditional, Mapped - not directly iterable
1155    ComplexType,
1156    /// Unknown type - not iterable (or needs special handling)
1157    NotIterable,
1158}
1159
1160/// Classify a type for full iterable checking.
1161///
1162/// This is used by `is_iterable_type` and related functions.
1163pub fn classify_full_iterable_type(db: &dyn TypeDatabase, type_id: TypeId) -> FullIterableTypeKind {
1164    let Some(key) = db.lookup(type_id) else {
1165        return FullIterableTypeKind::NotIterable;
1166    };
1167
1168    match key {
1169        TypeData::Array(elem) => FullIterableTypeKind::Array(elem),
1170        TypeData::Tuple(tuple_id) => {
1171            let elements = db.tuple_list(tuple_id);
1172            FullIterableTypeKind::Tuple(elements.to_vec())
1173        }
1174        TypeData::Literal(crate::LiteralValue::String(s)) => FullIterableTypeKind::StringLiteral(s),
1175        TypeData::Union(members_id) => {
1176            let members = db.type_list(members_id);
1177            FullIterableTypeKind::Union(members.to_vec())
1178        }
1179        TypeData::Intersection(members_id) => {
1180            let members = db.type_list(members_id);
1181            FullIterableTypeKind::Intersection(members.to_vec())
1182        }
1183        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
1184            FullIterableTypeKind::Object(shape_id)
1185        }
1186        TypeData::Application(app_id) => {
1187            let app = db.type_application(app_id);
1188            FullIterableTypeKind::Application { base: app.base }
1189        }
1190        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
1191            FullIterableTypeKind::TypeParameter {
1192                constraint: info.constraint,
1193            }
1194        }
1195        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
1196            FullIterableTypeKind::Readonly(inner)
1197        }
1198        TypeData::Function(_) | TypeData::Callable(_) => FullIterableTypeKind::FunctionOrCallable,
1199        TypeData::IndexAccess(_, _) | TypeData::Conditional(_) | TypeData::Mapped(_) => {
1200            FullIterableTypeKind::ComplexType
1201        }
1202        // All other types are not directly iterable
1203        TypeData::BoundParameter(_)
1204        | TypeData::Intrinsic(_)
1205        | TypeData::Literal(_)
1206        | TypeData::Lazy(_)
1207        | TypeData::Recursive(_)
1208        | TypeData::TemplateLiteral(_)
1209        | TypeData::UniqueSymbol(_)
1210        | TypeData::ThisType
1211        | TypeData::TypeQuery(_)
1212        | TypeData::KeyOf(_)
1213        | TypeData::StringIntrinsic { .. }
1214        | TypeData::ModuleNamespace(_)
1215        | TypeData::Enum(_, _)
1216        | TypeData::Error => FullIterableTypeKind::NotIterable,
1217    }
1218}
1219
1220/// Classification for async iterable type checking.
1221#[derive(Debug, Clone)]
1222pub enum AsyncIterableTypeKind {
1223    /// Union type - all members must be async iterable
1224    Union(Vec<TypeId>),
1225    /// Object type - check for [Symbol.asyncIterator] method
1226    Object(crate::types::ObjectShapeId),
1227    /// Readonly wrapper - check inner type
1228    Readonly(TypeId),
1229    /// Not async iterable
1230    NotAsyncIterable,
1231}
1232
1233/// Classify a type for async iterable checking.
1234pub fn classify_async_iterable_type(
1235    db: &dyn TypeDatabase,
1236    type_id: TypeId,
1237) -> AsyncIterableTypeKind {
1238    let Some(key) = db.lookup(type_id) else {
1239        return AsyncIterableTypeKind::NotAsyncIterable;
1240    };
1241
1242    match key {
1243        TypeData::Union(members_id) => {
1244            let members = db.type_list(members_id);
1245            AsyncIterableTypeKind::Union(members.to_vec())
1246        }
1247        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
1248            AsyncIterableTypeKind::Object(shape_id)
1249        }
1250        TypeData::ReadonlyType(inner) => AsyncIterableTypeKind::Readonly(inner),
1251        _ => AsyncIterableTypeKind::NotAsyncIterable,
1252    }
1253}
1254
1255/// Classification for for-of element type computation.
1256#[derive(Debug, Clone)]
1257pub enum ForOfElementKind {
1258    /// Array type - element is the array element type
1259    Array(TypeId),
1260    /// Tuple type - element is union of tuple element types
1261    Tuple(Vec<crate::types::TupleElement>),
1262    /// Union type - compute element type for each member
1263    Union(Vec<TypeId>),
1264    /// Readonly wrapper - unwrap and compute
1265    Readonly(TypeId),
1266    /// String type - iteration yields string
1267    String,
1268    /// Other types - resolve via iterator protocol or return ANY as fallback
1269    Other,
1270}
1271
1272/// Classify a type for for-of element type computation.
1273pub fn classify_for_of_element_type(db: &dyn TypeDatabase, type_id: TypeId) -> ForOfElementKind {
1274    let Some(key) = db.lookup(type_id) else {
1275        return ForOfElementKind::Other;
1276    };
1277
1278    match key {
1279        TypeData::Array(elem) => ForOfElementKind::Array(elem),
1280        TypeData::Tuple(tuple_id) => {
1281            let elements = db.tuple_list(tuple_id);
1282            ForOfElementKind::Tuple(elements.to_vec())
1283        }
1284        TypeData::Union(members_id) => {
1285            let members = db.type_list(members_id);
1286            ForOfElementKind::Union(members.to_vec())
1287        }
1288        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
1289            ForOfElementKind::Readonly(inner)
1290        }
1291        // String literals iterate to produce `string`
1292        TypeData::Literal(crate::LiteralValue::String(_)) => ForOfElementKind::String,
1293        _ => ForOfElementKind::Other,
1294    }
1295}
1296
1297// =============================================================================
1298// Property Lookup Type Classification
1299// =============================================================================
1300
1301/// Classification for types when looking up properties.
1302///
1303/// This enum provides a structured way to handle property lookups on different
1304/// type kinds, abstracting away the internal `TypeData` representation.
1305///
1306/// # Design Principles
1307///
1308/// - **No Symbol Resolution**: Keeps solver layer pure
1309/// - **No Type Evaluation**: Returns classification for caller to handle
1310/// - **Complete Coverage**: Handles all common property access patterns
1311#[derive(Debug, Clone)]
1312pub enum PropertyLookupKind {
1313    /// Object type with `shape_id` - has properties
1314    Object(crate::types::ObjectShapeId),
1315    /// Object with index signature - has properties and index signatures
1316    ObjectWithIndex(crate::types::ObjectShapeId),
1317    /// Union type - lookup on each member
1318    Union(Vec<TypeId>),
1319    /// Intersection type - lookup on each member
1320    Intersection(Vec<TypeId>),
1321    /// Array type - element type for numeric access
1322    Array(TypeId),
1323    /// Tuple type - element types
1324    Tuple(Vec<crate::types::TupleElement>),
1325    /// Type that doesn't have direct properties (Intrinsic, Literal, etc.)
1326    NoProperties,
1327}
1328
1329/// Classify a type for property lookup operations.
1330///
1331/// This function examines a type and returns information about how to handle it
1332/// when looking up properties. This is used for:
1333/// - Merging base type properties
1334/// - Checking excess properties in object literals
1335/// - Getting binding element types from destructuring patterns
1336///
1337/// The caller is responsible for:
1338/// - Recursing into Union/Intersection members
1339/// - Handling Array/Tuple element access appropriately
1340/// - Accessing the object shape using the returned `shape_id`
1341///
1342/// # Example
1343///
1344/// ```ignore
1345/// use crate::type_queries::{classify_for_property_lookup, PropertyLookupKind};
1346///
1347/// match classify_for_property_lookup(&db, type_id) {
1348///     PropertyLookupKind::Object(shape_id) | PropertyLookupKind::ObjectWithIndex(shape_id) => {
1349///         let shape = db.object_shape(shape_id);
1350///         for prop in shape.properties.iter() {
1351///             // Process property
1352///         }
1353///     }
1354///     PropertyLookupKind::Union(members) | PropertyLookupKind::Intersection(members) => {
1355///         for member in members {
1356///             // Recurse
1357///         }
1358///     }
1359///     PropertyLookupKind::Array(elem_type) => {
1360///         // Use element type for numeric index access
1361///     }
1362///     PropertyLookupKind::Tuple(elements) => {
1363///         // Use specific element type by index
1364///     }
1365///     PropertyLookupKind::NoProperties => {
1366///         // Handle types without properties
1367///     }
1368/// }
1369/// ```
1370pub fn classify_for_property_lookup(db: &dyn TypeDatabase, type_id: TypeId) -> PropertyLookupKind {
1371    let Some(key) = db.lookup(type_id) else {
1372        return PropertyLookupKind::NoProperties;
1373    };
1374
1375    match key {
1376        TypeData::Object(shape_id) => PropertyLookupKind::Object(shape_id),
1377        TypeData::ObjectWithIndex(shape_id) => PropertyLookupKind::ObjectWithIndex(shape_id),
1378        TypeData::Union(list_id) => {
1379            let members = db.type_list(list_id);
1380            PropertyLookupKind::Union(members.to_vec())
1381        }
1382        TypeData::Intersection(list_id) => {
1383            let members = db.type_list(list_id);
1384            PropertyLookupKind::Intersection(members.to_vec())
1385        }
1386        TypeData::Array(elem_type) => PropertyLookupKind::Array(elem_type),
1387        TypeData::Tuple(tuple_id) => {
1388            let elements = db.tuple_list(tuple_id);
1389            PropertyLookupKind::Tuple(elements.to_vec())
1390        }
1391        // All other types don't have direct properties for this use case
1392        TypeData::BoundParameter(_)
1393        | TypeData::Intrinsic(_)
1394        | TypeData::Literal(_)
1395        | TypeData::Function(_)
1396        | TypeData::Callable(_)
1397        | TypeData::TypeParameter(_)
1398        | TypeData::Infer(_)
1399        | TypeData::Lazy(_)
1400        | TypeData::Recursive(_)
1401        | TypeData::Application(_)
1402        | TypeData::Conditional(_)
1403        | TypeData::Mapped(_)
1404        | TypeData::IndexAccess(_, _)
1405        | TypeData::KeyOf(_)
1406        | TypeData::TemplateLiteral(_)
1407        | TypeData::UniqueSymbol(_)
1408        | TypeData::ThisType
1409        | TypeData::TypeQuery(_)
1410        | TypeData::ReadonlyType(_)
1411        | TypeData::NoInfer(_)
1412        | TypeData::StringIntrinsic { .. }
1413        | TypeData::ModuleNamespace(_)
1414        | TypeData::Enum(_, _)
1415        | TypeData::Error => PropertyLookupKind::NoProperties,
1416    }
1417}
1418
1419// =============================================================================
1420// EvaluationNeeded - Classification for types that need evaluation
1421// =============================================================================
1422
1423/// Classification for types that need evaluation before use.
1424#[derive(Debug, Clone)]
1425pub enum EvaluationNeeded {
1426    /// Already resolved, no evaluation needed
1427    Resolved(TypeId),
1428    /// Symbol reference - resolve symbol first
1429    SymbolRef(crate::types::SymbolRef),
1430    /// Type query (typeof) - evaluate first
1431    TypeQuery(crate::types::SymbolRef),
1432    /// Generic application - instantiate first
1433    Application {
1434        app_id: crate::types::TypeApplicationId,
1435    },
1436    /// Index access T[K] - evaluate with environment
1437    IndexAccess { object: TypeId, index: TypeId },
1438    /// `KeyOf` type - evaluate
1439    KeyOf(TypeId),
1440    /// Mapped type - evaluate
1441    Mapped {
1442        mapped_id: crate::types::MappedTypeId,
1443    },
1444    /// Conditional type - evaluate
1445    Conditional {
1446        cond_id: crate::types::ConditionalTypeId,
1447    },
1448    /// Callable type (for contextual typing checks)
1449    Callable(crate::types::CallableShapeId),
1450    /// Function type
1451    Function(crate::types::FunctionShapeId),
1452    /// Union - may need per-member evaluation
1453    Union(Vec<TypeId>),
1454    /// Intersection - may need per-member evaluation
1455    Intersection(Vec<TypeId>),
1456    /// Type parameter with constraint
1457    TypeParameter { constraint: Option<TypeId> },
1458    /// Readonly wrapper - unwrap
1459    Readonly(TypeId),
1460}
1461
1462/// Classify a type for what kind of evaluation it needs.
1463pub fn classify_for_evaluation(db: &dyn TypeDatabase, type_id: TypeId) -> EvaluationNeeded {
1464    let Some(key) = db.lookup(type_id) else {
1465        return EvaluationNeeded::Resolved(type_id);
1466    };
1467
1468    match key {
1469        TypeData::TypeQuery(sym_ref) => EvaluationNeeded::TypeQuery(sym_ref),
1470        TypeData::Application(app_id) => EvaluationNeeded::Application { app_id },
1471        TypeData::IndexAccess(object, index) => EvaluationNeeded::IndexAccess { object, index },
1472        TypeData::KeyOf(inner) => EvaluationNeeded::KeyOf(inner),
1473        TypeData::Mapped(mapped_id) => EvaluationNeeded::Mapped { mapped_id },
1474        TypeData::Conditional(cond_id) => EvaluationNeeded::Conditional { cond_id },
1475        TypeData::Callable(shape_id) => EvaluationNeeded::Callable(shape_id),
1476        TypeData::Function(shape_id) => EvaluationNeeded::Function(shape_id),
1477        TypeData::Union(list_id) => {
1478            let members = db.type_list(list_id);
1479            EvaluationNeeded::Union(members.to_vec())
1480        }
1481        TypeData::Intersection(list_id) => {
1482            let members = db.type_list(list_id);
1483            EvaluationNeeded::Intersection(members.to_vec())
1484        }
1485        TypeData::TypeParameter(info) | TypeData::Infer(info) => EvaluationNeeded::TypeParameter {
1486            constraint: info.constraint,
1487        },
1488        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
1489            EvaluationNeeded::Readonly(inner)
1490        }
1491        // Already resolved types (Lazy needs special handling when DefId lookup is implemented)
1492        TypeData::BoundParameter(_)
1493        | TypeData::Intrinsic(_)
1494        | TypeData::Literal(_)
1495        | TypeData::Object(_)
1496        | TypeData::ObjectWithIndex(_)
1497        | TypeData::Array(_)
1498        | TypeData::Tuple(_)
1499        | TypeData::Lazy(_)
1500        | TypeData::Recursive(_)
1501        | TypeData::TemplateLiteral(_)
1502        | TypeData::UniqueSymbol(_)
1503        | TypeData::ThisType
1504        | TypeData::StringIntrinsic { .. }
1505        | TypeData::ModuleNamespace(_)
1506        | TypeData::Enum(_, _)
1507        | TypeData::Error => EvaluationNeeded::Resolved(type_id),
1508    }
1509}
1510
1511/// Evaluate contextual wrapper structure while delegating leaf evaluation.
1512///
1513/// Solver owns traversal over semantic type shape; caller provides the concrete
1514/// leaf evaluator (for example checker's judge-based environment evaluation).
1515pub fn evaluate_contextual_structure_with(
1516    db: &dyn QueryDatabase,
1517    type_id: TypeId,
1518    evaluate_leaf: &mut dyn FnMut(TypeId) -> TypeId,
1519) -> TypeId {
1520    fn visit(
1521        db: &dyn QueryDatabase,
1522        type_id: TypeId,
1523        evaluate_leaf: &mut dyn FnMut(TypeId) -> TypeId,
1524    ) -> TypeId {
1525        match classify_for_evaluation(db, type_id) {
1526            EvaluationNeeded::Union(members) => {
1527                let mut changed = false;
1528                let evaluated: Vec<TypeId> = members
1529                    .iter()
1530                    .map(|&member| {
1531                        let ev = visit(db, member, evaluate_leaf);
1532                        if ev != member {
1533                            changed = true;
1534                        }
1535                        ev
1536                    })
1537                    .collect();
1538                if changed {
1539                    db.factory().union(evaluated)
1540                } else {
1541                    type_id
1542                }
1543            }
1544            EvaluationNeeded::Intersection(members) => {
1545                let mut changed = false;
1546                let evaluated: Vec<TypeId> = members
1547                    .iter()
1548                    .map(|&member| {
1549                        let ev = visit(db, member, evaluate_leaf);
1550                        if ev != member {
1551                            changed = true;
1552                        }
1553                        ev
1554                    })
1555                    .collect();
1556                if changed {
1557                    db.factory().intersection(evaluated)
1558                } else {
1559                    type_id
1560                }
1561            }
1562            EvaluationNeeded::Application { .. }
1563            | EvaluationNeeded::Mapped { .. }
1564            | EvaluationNeeded::Conditional { .. } => {
1565                let evaluated = evaluate_leaf(type_id);
1566                if evaluated != type_id {
1567                    evaluated
1568                } else {
1569                    type_id
1570                }
1571            }
1572            _ if get_lazy_def_id(db, type_id).is_some() => {
1573                let evaluated = evaluate_leaf(type_id);
1574                if evaluated != type_id {
1575                    evaluated
1576                } else {
1577                    type_id
1578                }
1579            }
1580            _ => type_id,
1581        }
1582    }
1583
1584    visit(db, type_id, evaluate_leaf)
1585}
1586
1587// =============================================================================
1588// PropertyAccessClassification - Classification for property access resolution
1589// =============================================================================
1590
1591/// Classification for property access resolution.
1592#[derive(Debug, Clone)]
1593pub enum PropertyAccessClassification {
1594    /// Direct object type that can have properties accessed
1595    Direct(TypeId),
1596    /// Symbol reference - needs resolution first
1597    SymbolRef(crate::types::SymbolRef),
1598    /// Type query (typeof) - needs symbol resolution
1599    TypeQuery(crate::types::SymbolRef),
1600    /// Generic application - needs instantiation
1601    Application {
1602        app_id: crate::types::TypeApplicationId,
1603    },
1604    /// Union - access on each member
1605    Union(Vec<TypeId>),
1606    /// Intersection - access on each member
1607    Intersection(Vec<TypeId>),
1608    /// Index access - needs evaluation
1609    IndexAccess { object: TypeId, index: TypeId },
1610    /// Readonly wrapper - unwrap and continue
1611    Readonly(TypeId),
1612    /// Callable type - may need Function interface expansion
1613    Callable(TypeId),
1614    /// Type parameter with constraint
1615    TypeParameter { constraint: Option<TypeId> },
1616    /// Needs evaluation (Conditional, Mapped, `KeyOf`)
1617    NeedsEvaluation(TypeId),
1618    /// Primitive or resolved type
1619    Resolved(TypeId),
1620}
1621
1622/// Classify a type for property access resolution.
1623pub fn classify_for_property_access(
1624    db: &dyn TypeDatabase,
1625    type_id: TypeId,
1626) -> PropertyAccessClassification {
1627    let Some(key) = db.lookup(type_id) else {
1628        return PropertyAccessClassification::Resolved(type_id);
1629    };
1630
1631    match key {
1632        TypeData::Object(_) | TypeData::ObjectWithIndex(_) => {
1633            PropertyAccessClassification::Direct(type_id)
1634        }
1635        TypeData::TypeQuery(sym_ref) => PropertyAccessClassification::TypeQuery(sym_ref),
1636        TypeData::Application(app_id) => PropertyAccessClassification::Application { app_id },
1637        TypeData::Union(list_id) => {
1638            let members = db.type_list(list_id);
1639            PropertyAccessClassification::Union(members.to_vec())
1640        }
1641        TypeData::Intersection(list_id) => {
1642            let members = db.type_list(list_id);
1643            PropertyAccessClassification::Intersection(members.to_vec())
1644        }
1645        TypeData::IndexAccess(object, index) => {
1646            PropertyAccessClassification::IndexAccess { object, index }
1647        }
1648        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => PropertyAccessClassification::Readonly(inner),
1649        TypeData::Function(_) | TypeData::Callable(_) => {
1650            PropertyAccessClassification::Callable(type_id)
1651        }
1652        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
1653            PropertyAccessClassification::TypeParameter {
1654                constraint: info.constraint,
1655            }
1656        }
1657        TypeData::Conditional(_) | TypeData::Mapped(_) | TypeData::KeyOf(_) => {
1658            PropertyAccessClassification::NeedsEvaluation(type_id)
1659        }
1660        // BoundParameter is a resolved type (leaf node)
1661        TypeData::BoundParameter(_)
1662        // Primitives and resolved types (Lazy needs special handling when DefId lookup is implemented)
1663        | TypeData::Intrinsic(_)
1664        | TypeData::Literal(_)
1665        | TypeData::Array(_)
1666        | TypeData::Tuple(_)
1667        | TypeData::Lazy(_)
1668        | TypeData::Recursive(_)
1669        | TypeData::TemplateLiteral(_)
1670        | TypeData::UniqueSymbol(_)
1671        | TypeData::ThisType
1672        | TypeData::StringIntrinsic { .. }
1673        | TypeData::ModuleNamespace(_)
1674        | TypeData::Error
1675        | TypeData::Enum(_, _) => PropertyAccessClassification::Resolved(type_id),
1676    }
1677}
1678
1679// =============================================================================
1680// TypeTraversalKind - Classification for type structure traversal
1681// =============================================================================
1682
1683/// Classification for traversing type structure to resolve symbols.
1684///
1685/// This enum is used by `ensure_application_symbols_resolved_inner` to
1686/// determine how to traverse into nested types without directly matching
1687/// on `TypeData` in the checker layer.
1688#[derive(Debug, Clone)]
1689pub enum TypeTraversalKind {
1690    /// Application type - resolve base symbol and recurse into base and args
1691    Application {
1692        app_id: crate::types::TypeApplicationId,
1693        base: TypeId,
1694        args: Vec<TypeId>,
1695    },
1696    /// Symbol reference - resolve the symbol
1697    SymbolRef(crate::types::SymbolRef),
1698    /// Lazy type reference (`DefId`) - needs resolution before traversal
1699    Lazy(crate::def::DefId),
1700    /// Type query (typeof X) - value-space reference that needs resolution
1701    TypeQuery(crate::types::SymbolRef),
1702    /// Type parameter - recurse into constraint and default if present
1703    TypeParameter {
1704        constraint: Option<TypeId>,
1705        default: Option<TypeId>,
1706    },
1707    /// Union or intersection - recurse into members
1708    Members(Vec<TypeId>),
1709    /// Function type - recurse into type params, params, return type, etc.
1710    Function(crate::types::FunctionShapeId),
1711    /// Callable type - recurse into signatures and properties
1712    Callable(crate::types::CallableShapeId),
1713    /// Object type - recurse into properties and index signatures
1714    Object(crate::types::ObjectShapeId),
1715    /// Array type - recurse into element type
1716    Array(TypeId),
1717    /// Tuple type - recurse into element types
1718    Tuple(crate::types::TupleListId),
1719    /// Conditional type - recurse into check, extends, true, and false types
1720    Conditional(crate::types::ConditionalTypeId),
1721    /// Mapped type - recurse into constraint, template, and name type
1722    Mapped(crate::types::MappedTypeId),
1723    /// Readonly wrapper - recurse into inner type
1724    Readonly(TypeId),
1725    /// Index access - recurse into object and index types
1726    IndexAccess { object: TypeId, index: TypeId },
1727    /// `KeyOf` - recurse into inner type
1728    KeyOf(TypeId),
1729    /// Template literal - extract types from spans
1730    TemplateLiteral(Vec<TypeId>),
1731    /// String intrinsic - traverse the type argument
1732    StringIntrinsic(TypeId),
1733    /// Terminal type - no further traversal needed
1734    Terminal,
1735}
1736
1737/// Classify a type for structure traversal (symbol resolution).
1738///
1739/// This function examines a type and returns information about how to
1740/// traverse into its nested types. Used by `ensure_application_symbols_resolved_inner`.
1741pub fn classify_for_traversal(db: &dyn TypeDatabase, type_id: TypeId) -> TypeTraversalKind {
1742    let Some(key) = db.lookup(type_id) else {
1743        return TypeTraversalKind::Terminal;
1744    };
1745
1746    match key {
1747        TypeData::Application(app_id) => {
1748            let app = db.type_application(app_id);
1749            TypeTraversalKind::Application {
1750                app_id,
1751                base: app.base,
1752                args: app.args.clone(),
1753            }
1754        }
1755        TypeData::TypeParameter(info) | TypeData::Infer(info) => TypeTraversalKind::TypeParameter {
1756            constraint: info.constraint,
1757            default: info.default,
1758        },
1759        TypeData::Union(list_id) | TypeData::Intersection(list_id) => {
1760            let members = db.type_list(list_id);
1761            TypeTraversalKind::Members(members.to_vec())
1762        }
1763        TypeData::Function(shape_id) => TypeTraversalKind::Function(shape_id),
1764        TypeData::Callable(shape_id) => TypeTraversalKind::Callable(shape_id),
1765        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
1766            TypeTraversalKind::Object(shape_id)
1767        }
1768        TypeData::Array(elem) => TypeTraversalKind::Array(elem),
1769        TypeData::Tuple(list_id) => TypeTraversalKind::Tuple(list_id),
1770        TypeData::Conditional(cond_id) => TypeTraversalKind::Conditional(cond_id),
1771        TypeData::Mapped(mapped_id) => TypeTraversalKind::Mapped(mapped_id),
1772        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
1773            TypeTraversalKind::Readonly(inner)
1774        }
1775        TypeData::IndexAccess(object, index) => TypeTraversalKind::IndexAccess { object, index },
1776        TypeData::KeyOf(inner) => TypeTraversalKind::KeyOf(inner),
1777        // Template literal - extract types from spans for traversal
1778        TypeData::TemplateLiteral(list_id) => {
1779            let spans = db.template_list(list_id);
1780            let types: Vec<TypeId> = spans
1781                .iter()
1782                .filter_map(|span| match span {
1783                    crate::types::TemplateSpan::Type(id) => Some(*id),
1784                    _ => None,
1785                })
1786                .collect();
1787            if types.is_empty() {
1788                TypeTraversalKind::Terminal
1789            } else {
1790                TypeTraversalKind::TemplateLiteral(types)
1791            }
1792        }
1793        // String intrinsic - traverse the type argument
1794        TypeData::StringIntrinsic { type_arg, .. } => TypeTraversalKind::StringIntrinsic(type_arg),
1795        // Lazy type reference - needs resolution before traversal
1796        TypeData::Lazy(def_id) => TypeTraversalKind::Lazy(def_id),
1797        // Type query (typeof X) - value-space reference
1798        TypeData::TypeQuery(symbol_ref) => TypeTraversalKind::TypeQuery(symbol_ref),
1799        // Terminal types - no nested types to traverse
1800        TypeData::BoundParameter(_)
1801        | TypeData::Intrinsic(_)
1802        | TypeData::Literal(_)
1803        | TypeData::Recursive(_)
1804        | TypeData::UniqueSymbol(_)
1805        | TypeData::ThisType
1806        | TypeData::ModuleNamespace(_)
1807        | TypeData::Error
1808        | TypeData::Enum(_, _) => TypeTraversalKind::Terminal,
1809    }
1810}
1811
1812/// Check if a type is a lazy type and return the `DefId`.
1813///
1814/// This is a helper for checking if the base of an Application is a Lazy type.
1815pub fn get_lazy_if_def(db: &dyn TypeDatabase, type_id: TypeId) -> Option<crate::def::DefId> {
1816    match db.lookup(type_id) {
1817        Some(TypeData::Lazy(def_id)) => Some(def_id),
1818        _ => None,
1819    }
1820}
1821
1822/// High-level property traversal classification for diagnostics/reporting.
1823///
1824/// This keeps traversal-shape branching inside solver queries so checker code
1825/// can remain thin orchestration.
1826#[derive(Debug, Clone)]
1827pub enum PropertyTraversalKind {
1828    Object(std::sync::Arc<crate::types::ObjectShape>),
1829    Callable(std::sync::Arc<crate::types::CallableShape>),
1830    Members(Vec<TypeId>),
1831    Other,
1832}
1833
1834/// Classify a type into a property traversal shape for checker diagnostics.
1835pub fn classify_property_traversal(
1836    db: &dyn TypeDatabase,
1837    type_id: TypeId,
1838) -> PropertyTraversalKind {
1839    match classify_for_traversal(db, type_id) {
1840        TypeTraversalKind::Object(_) => get_object_shape(db, type_id)
1841            .map_or(PropertyTraversalKind::Other, PropertyTraversalKind::Object),
1842        TypeTraversalKind::Callable(_) => get_callable_shape(db, type_id).map_or(
1843            PropertyTraversalKind::Other,
1844            PropertyTraversalKind::Callable,
1845        ),
1846        TypeTraversalKind::Members(members) => PropertyTraversalKind::Members(members),
1847        _ => PropertyTraversalKind::Other,
1848    }
1849}
1850
1851/// Collect property names reachable from a type for diagnostics/suggestions.
1852///
1853/// Traversal shape decisions stay in solver so checker can remain orchestration-only.
1854pub fn collect_property_name_atoms_for_diagnostics(
1855    db: &dyn TypeDatabase,
1856    type_id: TypeId,
1857    max_depth: usize,
1858) -> Vec<Atom> {
1859    fn collect_inner(
1860        db: &dyn TypeDatabase,
1861        type_id: TypeId,
1862        out: &mut Vec<Atom>,
1863        depth: usize,
1864        max_depth: usize,
1865    ) {
1866        if depth > max_depth {
1867            return;
1868        }
1869        match classify_property_traversal(db, type_id) {
1870            PropertyTraversalKind::Object(shape) => {
1871                for prop in &shape.properties {
1872                    out.push(prop.name);
1873                }
1874            }
1875            PropertyTraversalKind::Callable(shape) => {
1876                for prop in &shape.properties {
1877                    out.push(prop.name);
1878                }
1879            }
1880            PropertyTraversalKind::Members(members) => {
1881                for member in members {
1882                    collect_inner(db, member, out, depth + 1, max_depth);
1883                }
1884            }
1885            PropertyTraversalKind::Other => {}
1886        }
1887    }
1888
1889    let mut atoms = Vec::new();
1890    collect_inner(db, type_id, &mut atoms, 0, max_depth);
1891    atoms.sort_unstable();
1892    atoms.dedup();
1893    atoms
1894}
1895
1896/// Collect property names accessible on a type for spelling suggestions.
1897///
1898/// For union types, only properties present in ALL members are returned (intersection).
1899/// This matches tsc: "did you mean" for union access uses only common/accessible properties.
1900pub fn collect_accessible_property_names_for_suggestion(
1901    db: &dyn TypeDatabase,
1902    type_id: TypeId,
1903    max_depth: usize,
1904) -> Vec<Atom> {
1905    if let Some(TypeData::Union(list_id)) = db.lookup(type_id) {
1906        let members = db.type_list(list_id).to_vec();
1907        if members.is_empty() {
1908            return vec![];
1909        }
1910        let mut common = collect_property_name_atoms_for_diagnostics(db, members[0], max_depth);
1911        common.sort_unstable();
1912        common.dedup();
1913        for &member in &members[1..] {
1914            let mut member_props =
1915                collect_property_name_atoms_for_diagnostics(db, member, max_depth);
1916            member_props.sort_unstable();
1917            member_props.dedup();
1918            common.retain(|a| member_props.binary_search(a).is_ok());
1919            if common.is_empty() {
1920                return vec![];
1921            }
1922        }
1923        return common;
1924    }
1925    collect_property_name_atoms_for_diagnostics(db, type_id, max_depth)
1926}
1927
1928/// Checks if a type is exclusively `null`, `undefined`, or a union of both.
1929pub fn is_only_null_or_undefined(db: &dyn TypeDatabase, type_id: TypeId) -> bool {
1930    if type_id == TypeId::NULL || type_id == TypeId::UNDEFINED {
1931        return true;
1932    }
1933    match db.lookup(type_id) {
1934        Some(TypeData::Intrinsic(IntrinsicKind::Null | IntrinsicKind::Undefined)) => true,
1935        Some(TypeData::Union(list_id)) => {
1936            let members = db.type_list(list_id);
1937            members.iter().all(|&m| is_only_null_or_undefined(db, m))
1938        }
1939        _ => false,
1940    }
1941}