tsz_solver/operations/
iterators.rs1use super::property::PropertyAccessEvaluator;
8use crate::TypeDatabase;
9use crate::types::{PropertyInfo, TypeData, TypeId};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct IteratorInfo {
20 pub iterator_type: TypeId,
22 pub yield_type: TypeId,
24 pub return_type: TypeId,
26 pub next_type: TypeId,
28}
29
30pub fn get_iterator_info(
46 db: &dyn crate::caches::db::QueryDatabase,
47 type_id: TypeId,
48 is_async: bool,
49) -> Option<IteratorInfo> {
50 use crate::type_queries::is_callable_type;
51
52 if type_id == TypeId::ANY {
55 return Some(IteratorInfo {
56 iterator_type: TypeId::ANY,
57 yield_type: TypeId::ANY,
58 return_type: TypeId::ANY,
59 next_type: TypeId::ANY,
60 });
61 }
62
63 if let Some(key) = db.lookup(type_id) {
65 match key {
66 TypeData::Array(elem_type) => {
67 return get_array_iterator_info(type_id, elem_type);
68 }
69 TypeData::Tuple(_) => {
70 return get_tuple_iterator_info(db, type_id);
71 }
72 _ => {}
73 }
74 }
75
76 let symbol_name = if is_async {
78 "[Symbol.asyncIterator]"
79 } else {
80 "[Symbol.iterator]"
81 };
82
83 let evaluator = PropertyAccessEvaluator::new(db);
84 let iterator_method_type = evaluator
85 .resolve_property_access(type_id, symbol_name)
86 .success_type()?;
87
88 use crate::type_queries::get_return_type;
91 let iterator_type = if is_callable_type(db, iterator_method_type) {
92 get_return_type(db, iterator_method_type).unwrap_or(TypeId::ANY)
95 } else {
96 iterator_method_type
98 };
99
100 let next_method_type = evaluator
102 .resolve_property_access(iterator_type, "next")
103 .success_type()?;
104
105 extract_iterator_result_types(db, iterator_type, next_method_type, is_async)
107}
108
109const fn get_array_iterator_info(array_type: TypeId, elem_type: TypeId) -> Option<IteratorInfo> {
111 Some(IteratorInfo {
117 iterator_type: array_type,
118 yield_type: elem_type,
119 return_type: TypeId::UNDEFINED,
120 next_type: TypeId::UNDEFINED,
121 })
122}
123
124fn get_tuple_iterator_info(db: &dyn TypeDatabase, tuple_type: TypeId) -> Option<IteratorInfo> {
126 match db.lookup(tuple_type) {
128 Some(TypeData::Tuple(list_id)) => {
129 let elements = db.tuple_list(list_id);
130 let elem_types: Vec<TypeId> = elements.iter().map(|e| e.type_id).collect();
131
132 let yield_type = if elem_types.is_empty() {
134 TypeId::NEVER
135 } else {
136 elem_types
137 .into_iter()
138 .reduce(|acc, elem| db.union2(acc, elem))
139 .unwrap_or(TypeId::NEVER)
140 };
141
142 Some(IteratorInfo {
143 iterator_type: tuple_type,
144 yield_type,
145 return_type: TypeId::UNDEFINED,
146 next_type: TypeId::UNDEFINED,
147 })
148 }
149 _ => None,
150 }
151}
152
153fn extract_promise_inner_type(
161 db: &dyn crate::caches::db::QueryDatabase,
162 type_id: TypeId,
163) -> Option<TypeId> {
164 match db.lookup(type_id) {
165 Some(TypeData::Application(app_id)) => {
167 let app = db.type_application(app_id);
168 if app.base == TypeId::PROMISE_BASE {
169 return app.args.first().copied();
170 }
171 app.args.first().copied()
174 }
175 Some(TypeData::Object(shape_id)) => {
177 let shape = db.object_shape(shape_id);
178 let then_atom = db.intern_string("then");
179 let then_prop = PropertyInfo::find_in_slice(&shape.properties, then_atom)?;
180 match db.lookup(then_prop.type_id) {
183 Some(TypeData::Function(fn_id)) => {
184 let fn_shape = db.function_shape(fn_id);
185 let onfulfilled = fn_shape.params.first()?;
186 match db.lookup(onfulfilled.type_id) {
188 Some(TypeData::Function(inner_fn_id)) => {
189 let inner_shape = db.function_shape(inner_fn_id);
190 inner_shape.params.first().map(|p| p.type_id)
191 }
192 _ => None,
193 }
194 }
195 Some(TypeData::Callable(callable_id)) => {
196 let callable = db.callable_shape(callable_id);
197 let sig = callable.call_signatures.first()?;
198 let onfulfilled = sig.params.first()?;
199 match db.lookup(onfulfilled.type_id) {
200 Some(TypeData::Function(inner_fn_id)) => {
201 let inner_shape = db.function_shape(inner_fn_id);
202 inner_shape.params.first().map(|p| p.type_id)
203 }
204 _ => None,
205 }
206 }
207 _ => Some(then_prop.type_id),
209 }
210 }
211 _ => None,
212 }
213}
214
215fn extract_iterator_result_types(
220 db: &dyn crate::caches::db::QueryDatabase,
221 iterator_type: TypeId,
222 next_method_type: TypeId,
223 is_async: bool,
224) -> Option<IteratorInfo> {
225 use crate::type_queries::is_promise_like;
226
227 let (next_return_type, next_params) = match db.lookup(next_method_type) {
229 Some(TypeData::Function(shape_id)) => {
230 let shape = db.function_shape(shape_id);
231 (shape.return_type, shape.params.clone())
232 }
233 Some(TypeData::Callable(shape_id)) => {
234 let shape = db.callable_shape(shape_id);
235 let sig = shape.call_signatures.first()?;
236 (sig.return_type, sig.params.clone())
237 }
238 _ => return None,
239 };
240
241 let iterator_result_type = if is_async {
243 if is_promise_like(db, next_return_type) {
244 extract_promise_inner_type(db, next_return_type).unwrap_or(next_return_type)
245 } else {
246 return None;
247 }
248 } else {
249 next_return_type
250 };
251
252 let (yield_type, return_type) = extract_iterator_result_value_types(db, iterator_result_type);
255
256 let next_type = next_params.first().map_or(TypeId::UNDEFINED, |p| p.type_id);
258
259 Some(IteratorInfo {
260 iterator_type,
261 yield_type,
262 return_type,
263 next_type,
264 })
265}
266
267fn extract_iterator_result_value_types(
275 db: &dyn crate::caches::db::QueryDatabase,
276 iterator_result_type: TypeId,
277) -> (TypeId, TypeId) {
278 let done_atom = db.intern_string("done");
279 let value_atom = db.intern_string("value");
280
281 match db.lookup(iterator_result_type) {
282 Some(TypeData::Union(list_id)) => {
283 let members = db.type_list(list_id);
284 let mut yield_types = Vec::new();
285 let mut return_types = Vec::new();
286
287 for &member_id in members.iter() {
288 if let Some(TypeData::Object(shape_id)) = db.lookup(member_id) {
289 let shape = db.object_shape(shape_id);
290 let value_type = shape
291 .properties
292 .iter()
293 .find(|p| p.name == value_atom)
294 .map(|p| p.type_id);
295 let done_type = shape
296 .properties
297 .iter()
298 .find(|p| p.name == done_atom)
299 .map(|p| p.type_id);
300
301 match done_type {
302 Some(t) if t == TypeId::BOOLEAN_TRUE => {
304 if let Some(v) = value_type {
305 return_types.push(v);
306 }
307 }
308 Some(t) if t == TypeId::BOOLEAN_FALSE => {
310 if let Some(v) = value_type {
311 yield_types.push(v);
312 }
313 }
314 _ => {
316 if let Some(v) = value_type {
317 yield_types.push(v);
318 }
319 }
320 }
321 }
322 }
323
324 let yield_type = if yield_types.is_empty() {
325 TypeId::ANY
326 } else {
327 yield_types
328 .into_iter()
329 .reduce(|acc, t| db.union2(acc, t))
330 .unwrap_or(TypeId::ANY)
331 };
332
333 let return_type = if return_types.is_empty() {
334 TypeId::ANY
335 } else {
336 return_types
337 .into_iter()
338 .reduce(|acc, t| db.union2(acc, t))
339 .unwrap_or(TypeId::ANY)
340 };
341
342 (yield_type, return_type)
343 }
344 Some(TypeData::Object(shape_id)) => {
345 let shape = db.object_shape(shape_id);
346 let value_type = shape
347 .properties
348 .iter()
349 .find(|p| p.name == value_atom)
350 .map_or(TypeId::ANY, |p| p.type_id);
351 (value_type, TypeId::ANY)
352 }
353 _ => (TypeId::ANY, TypeId::ANY),
354 }
355}
356
357pub fn get_async_iterable_element_type(
362 db: &dyn crate::caches::db::QueryDatabase,
363 type_id: TypeId,
364) -> TypeId {
365 match get_iterator_info(db, type_id, true) {
366 Some(info) => info.yield_type,
367 None => match get_iterator_info(db, type_id, false) {
368 Some(info) => info.yield_type,
369 None => TypeId::ANY,
370 },
371 }
372}