Skip to main content

tsz_solver/type_queries/
iterable.rs

1//! Iterable Type Classification
2//!
3//! Classification enums and functions for iterable types, used for spread handling,
4//! `for-of` element type computation, and async iterable checking.
5
6use crate::{TypeData, TypeDatabase, TypeId};
7
8// =============================================================================
9// Iterable Type Classification (Spread Handling)
10// =============================================================================
11
12/// Classification for iterable types (used for spread element handling).
13#[derive(Debug, Clone)]
14pub enum IterableTypeKind {
15    /// Tuple type - elements can be expanded
16    Tuple(Vec<crate::types::TupleElement>),
17    /// Array type - element type for variadic handling
18    Array(TypeId),
19    /// Not a directly iterable type (caller should handle as-is)
20    Other,
21}
22
23/// Classify a type for iterable/spread handling.
24pub fn classify_iterable_type(db: &dyn TypeDatabase, type_id: TypeId) -> IterableTypeKind {
25    let Some(key) = db.lookup(type_id) else {
26        return IterableTypeKind::Other;
27    };
28
29    match key {
30        TypeData::Tuple(tuple_id) => {
31            let elements = db.tuple_list(tuple_id);
32            IterableTypeKind::Tuple(elements.to_vec())
33        }
34        TypeData::Array(elem_type) => IterableTypeKind::Array(elem_type),
35        _ => IterableTypeKind::Other,
36    }
37}
38
39// =============================================================================
40// Full Iterable Type Classification (For is_iterable_type checks)
41// =============================================================================
42
43/// Comprehensive classification for iterable type checking.
44///
45/// This enum is used by `is_iterable_type` and related functions to determine
46/// if a type is iterable (has Symbol.iterator protocol) without directly
47/// matching on `TypeData` in the checker layer.
48#[derive(Debug, Clone)]
49pub enum FullIterableTypeKind {
50    /// Array type - always iterable
51    Array(TypeId),
52    /// Tuple type - always iterable
53    Tuple(Vec<crate::types::TupleElement>),
54    /// String literal - always iterable
55    StringLiteral(tsz_common::interner::Atom),
56    /// Union type - all members must be iterable
57    Union(Vec<TypeId>),
58    /// Intersection type - at least one member must be iterable
59    Intersection(Vec<TypeId>),
60    /// Object type - check for [Symbol.iterator] method
61    Object(crate::types::ObjectShapeId),
62    /// Application type (Set<T>, Map<K,V>, etc.) - check base type
63    Application { base: TypeId },
64    /// Type parameter - check constraint if present
65    TypeParameter { constraint: Option<TypeId> },
66    /// Readonly wrapper - check inner type
67    Readonly(TypeId),
68    /// Function or Callable - not iterable
69    FunctionOrCallable,
70    /// Index access, Conditional, Mapped - not directly iterable
71    ComplexType,
72    /// Unknown type - not iterable (or needs special handling)
73    NotIterable,
74}
75
76/// Classify a type for full iterable checking.
77///
78/// This is used by `is_iterable_type` and related functions.
79pub fn classify_full_iterable_type(db: &dyn TypeDatabase, type_id: TypeId) -> FullIterableTypeKind {
80    let Some(key) = db.lookup(type_id) else {
81        return FullIterableTypeKind::NotIterable;
82    };
83
84    match key {
85        TypeData::Array(elem) => FullIterableTypeKind::Array(elem),
86        TypeData::Tuple(tuple_id) => {
87            let elements = db.tuple_list(tuple_id);
88            FullIterableTypeKind::Tuple(elements.to_vec())
89        }
90        TypeData::Literal(crate::LiteralValue::String(s)) => FullIterableTypeKind::StringLiteral(s),
91        TypeData::Union(members_id) => {
92            let members = db.type_list(members_id);
93            FullIterableTypeKind::Union(members.to_vec())
94        }
95        TypeData::Intersection(members_id) => {
96            let members = db.type_list(members_id);
97            FullIterableTypeKind::Intersection(members.to_vec())
98        }
99        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
100            FullIterableTypeKind::Object(shape_id)
101        }
102        TypeData::Application(app_id) => {
103            let app = db.type_application(app_id);
104            FullIterableTypeKind::Application { base: app.base }
105        }
106        TypeData::TypeParameter(info) | TypeData::Infer(info) => {
107            FullIterableTypeKind::TypeParameter {
108                constraint: info.constraint,
109            }
110        }
111        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
112            FullIterableTypeKind::Readonly(inner)
113        }
114        TypeData::Function(_) | TypeData::Callable(_) => FullIterableTypeKind::FunctionOrCallable,
115        TypeData::IndexAccess(_, _) | TypeData::Conditional(_) | TypeData::Mapped(_) => {
116            FullIterableTypeKind::ComplexType
117        }
118        // All other types are not directly iterable
119        TypeData::BoundParameter(_)
120        | TypeData::Intrinsic(_)
121        | TypeData::Literal(_)
122        | TypeData::Lazy(_)
123        | TypeData::Recursive(_)
124        | TypeData::TemplateLiteral(_)
125        | TypeData::UniqueSymbol(_)
126        | TypeData::ThisType
127        | TypeData::TypeQuery(_)
128        | TypeData::KeyOf(_)
129        | TypeData::StringIntrinsic { .. }
130        | TypeData::ModuleNamespace(_)
131        | TypeData::Enum(_, _)
132        | TypeData::Error => FullIterableTypeKind::NotIterable,
133    }
134}
135
136/// Classification for async iterable type checking.
137#[derive(Debug, Clone)]
138pub enum AsyncIterableTypeKind {
139    /// Union type - all members must be async iterable
140    Union(Vec<TypeId>),
141    /// Object type - check for [Symbol.asyncIterator] method
142    Object(crate::types::ObjectShapeId),
143    /// Readonly wrapper - check inner type
144    Readonly(TypeId),
145    /// Not async iterable
146    NotAsyncIterable,
147}
148
149/// Classify a type for async iterable checking.
150pub fn classify_async_iterable_type(
151    db: &dyn TypeDatabase,
152    type_id: TypeId,
153) -> AsyncIterableTypeKind {
154    let Some(key) = db.lookup(type_id) else {
155        return AsyncIterableTypeKind::NotAsyncIterable;
156    };
157
158    match key {
159        TypeData::Union(members_id) => {
160            let members = db.type_list(members_id);
161            AsyncIterableTypeKind::Union(members.to_vec())
162        }
163        TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id) => {
164            AsyncIterableTypeKind::Object(shape_id)
165        }
166        TypeData::ReadonlyType(inner) => AsyncIterableTypeKind::Readonly(inner),
167        _ => AsyncIterableTypeKind::NotAsyncIterable,
168    }
169}
170
171/// Classification for for-of element type computation.
172#[derive(Debug, Clone)]
173pub enum ForOfElementKind {
174    /// Array type - element is the array element type
175    Array(TypeId),
176    /// Tuple type - element is union of tuple element types
177    Tuple(Vec<crate::types::TupleElement>),
178    /// Union type - compute element type for each member
179    Union(Vec<TypeId>),
180    /// Readonly wrapper - unwrap and compute
181    Readonly(TypeId),
182    /// String type - iteration yields string
183    String,
184    /// Other types - resolve via iterator protocol or return ANY as fallback
185    Other,
186}
187
188/// Classify a type for for-of element type computation.
189pub fn classify_for_of_element_type(db: &dyn TypeDatabase, type_id: TypeId) -> ForOfElementKind {
190    let Some(key) = db.lookup(type_id) else {
191        return ForOfElementKind::Other;
192    };
193
194    match key {
195        TypeData::Array(elem) => ForOfElementKind::Array(elem),
196        TypeData::Tuple(tuple_id) => {
197            let elements = db.tuple_list(tuple_id);
198            ForOfElementKind::Tuple(elements.to_vec())
199        }
200        TypeData::Union(members_id) => {
201            let members = db.type_list(members_id);
202            ForOfElementKind::Union(members.to_vec())
203        }
204        TypeData::ReadonlyType(inner) | TypeData::NoInfer(inner) => {
205            ForOfElementKind::Readonly(inner)
206        }
207        // String literals iterate to produce `string`
208        TypeData::Literal(crate::LiteralValue::String(_)) => ForOfElementKind::String,
209        _ => ForOfElementKind::Other,
210    }
211}