boa_engine/builtins/iterable/mod.rs
1//! Boa's implementation of ECMAScript's `IteratorRecord` and iterator prototype objects.
2
3use crate::{
4 Context, JsResult, JsValue,
5 builtins::{ArrayIterator, BuiltInBuilder, IntrinsicObject},
6 context::intrinsics::Intrinsics,
7 error::JsNativeError,
8 js_string,
9 native_function::{CoroutineBranch, CoroutineState},
10 object::JsObject,
11 realm::Realm,
12 symbol::JsSymbol,
13 vm::CompletionRecord,
14};
15use boa_gc::{Finalize, Trace};
16
17mod async_from_sync_iterator;
18pub(crate) mod iterator_constructor;
19pub(crate) mod iterator_helper;
20mod iterator_prototype;
21pub(crate) mod wrap_for_valid_iterator;
22
23#[cfg(test)]
24mod tests;
25
26pub(crate) use async_from_sync_iterator::AsyncFromSyncIterator;
27pub(crate) use iterator_prototype::Iterator;
28
29/// `IfAbruptCloseIterator ( value, iteratorRecord )`
30///
31/// `IfAbruptCloseIterator` is a shorthand for a sequence of algorithm steps that use an `Iterator`
32/// Record.
33///
34/// More information:
35/// - [ECMA reference][spec]
36///
37/// [spec]: https://tc39.es/ecma262/#sec-ifabruptcloseiterator
38macro_rules! if_abrupt_close_iterator {
39 ($value:expr, $iterator_record:expr, $context:expr) => {
40 match $value {
41 // 1. If value is an abrupt completion, return ? IteratorClose(iteratorRecord, value).
42 Err(err) => return $iterator_record.close(Err(err), $context),
43 // 2. Else if value is a Completion Record, set value to value.
44 Ok(value) => value,
45 }
46 };
47}
48
49// Export macro to crate level
50pub(crate) use if_abrupt_close_iterator;
51
52use super::OrdinaryObject;
53
54/// The built-in iterator prototypes.
55#[derive(Debug, Trace, Finalize)]
56pub struct IteratorPrototypes {
57 /// The `IteratorPrototype` object.
58 iterator: JsObject,
59
60 /// The `AsyncIteratorPrototype` object.
61 async_iterator: JsObject,
62
63 /// The `AsyncFromSyncIteratorPrototype` prototype object.
64 async_from_sync_iterator: JsObject,
65
66 /// The `ArrayIteratorPrototype` prototype object.
67 array: JsObject,
68
69 /// The `SetIteratorPrototype` prototype object.
70 set: JsObject,
71
72 /// The `StringIteratorPrototype` prototype object.
73 string: JsObject,
74
75 /// The `RegExpStringIteratorPrototype` prototype object.
76 regexp_string: JsObject,
77
78 /// The `MapIteratorPrototype` prototype object.
79 map: JsObject,
80
81 /// The `%SegmentIteratorPrototype%` prototype object.
82 #[cfg(feature = "intl")]
83 segment: JsObject,
84
85 /// The `%IteratorHelperPrototype%` prototype object.
86 iterator_helper: JsObject,
87
88 /// The `%WrapForValidIteratorPrototype%` prototype object.
89 wrap_for_valid_iterator: JsObject,
90}
91
92impl Default for IteratorPrototypes {
93 fn default() -> Self {
94 Self {
95 iterator: JsObject::with_null_proto(),
96 async_iterator: JsObject::with_null_proto(),
97 async_from_sync_iterator: JsObject::with_null_proto(),
98 array: JsObject::with_null_proto(),
99 set: JsObject::with_null_proto(),
100 string: JsObject::with_null_proto(),
101 regexp_string: JsObject::with_null_proto(),
102 map: JsObject::with_null_proto(),
103 #[cfg(feature = "intl")]
104 segment: JsObject::with_null_proto(),
105 iterator_helper: JsObject::with_null_proto(),
106 wrap_for_valid_iterator: JsObject::with_null_proto(),
107 }
108 }
109}
110
111impl IteratorPrototypes {
112 /// Returns the `ArrayIteratorPrototype` object.
113 #[inline]
114 #[must_use]
115 pub fn array(&self) -> JsObject {
116 self.array.clone()
117 }
118
119 /// Returns the `AsyncIteratorPrototype` object.
120 #[inline]
121 #[must_use]
122 pub fn async_iterator(&self) -> JsObject {
123 self.async_iterator.clone()
124 }
125
126 /// Returns the `AsyncFromSyncIteratorPrototype` object.
127 #[inline]
128 #[must_use]
129 pub fn async_from_sync_iterator(&self) -> JsObject {
130 self.async_from_sync_iterator.clone()
131 }
132
133 /// Returns the `SetIteratorPrototype` object.
134 #[inline]
135 #[must_use]
136 pub fn set(&self) -> JsObject {
137 self.set.clone()
138 }
139
140 /// Returns the `StringIteratorPrototype` object.
141 #[inline]
142 #[must_use]
143 pub fn string(&self) -> JsObject {
144 self.string.clone()
145 }
146
147 /// Returns the `RegExpStringIteratorPrototype` object.
148 #[inline]
149 #[must_use]
150 pub fn regexp_string(&self) -> JsObject {
151 self.regexp_string.clone()
152 }
153
154 /// Returns the `MapIteratorPrototype` object.
155 #[inline]
156 #[must_use]
157 pub fn map(&self) -> JsObject {
158 self.map.clone()
159 }
160
161 /// Returns the `%SegmentIteratorPrototype%` object.
162 #[inline]
163 #[must_use]
164 #[cfg(feature = "intl")]
165 pub fn segment(&self) -> JsObject {
166 self.segment.clone()
167 }
168
169 /// Returns the `%IteratorHelperPrototype%` object.
170 #[inline]
171 #[must_use]
172 pub fn iterator_helper(&self) -> JsObject {
173 self.iterator_helper.clone()
174 }
175
176 /// Returns the `%WrapForValidIteratorPrototype%` object.
177 #[inline]
178 #[must_use]
179 pub fn wrap_for_valid_iterator(&self) -> JsObject {
180 self.wrap_for_valid_iterator.clone()
181 }
182}
183
184/// `%AsyncIteratorPrototype%` object
185///
186/// More information:
187/// - [ECMA reference][spec]
188///
189/// [spec]: https://tc39.es/ecma262/#sec-asynciteratorprototype
190pub(crate) struct AsyncIterator;
191
192impl IntrinsicObject for AsyncIterator {
193 fn init(realm: &Realm) {
194 BuiltInBuilder::with_intrinsic::<Self>(realm)
195 .static_method(|v, _, _| Ok(v.clone()), JsSymbol::async_iterator(), 0)
196 .build();
197 }
198
199 fn get(intrinsics: &Intrinsics) -> JsObject {
200 intrinsics.objects().iterator_prototypes().async_iterator()
201 }
202}
203
204/// `CreateIterResultObject( value, done )`
205///
206/// Generates an object supporting the `IteratorResult` interface.
207pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Context) -> JsValue {
208 // 1. Assert: Type(done) is Boolean.
209 // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
210 // 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value).
211 // 4. Perform ! CreateDataPropertyOrThrow(obj, "done", done).
212 let obj = context
213 .intrinsics()
214 .templates()
215 .iterator_result()
216 .create(OrdinaryObject, vec![value, done.into()]);
217
218 // 5. Return obj.
219 obj.into()
220}
221
222/// Iterator hint for `GetIterator`.
223#[derive(Debug, Clone, Copy, PartialEq, Eq)]
224pub enum IteratorHint {
225 /// Hints that the iterator should be sync.
226 Sync,
227
228 /// Hints that the iterator should be async.
229 Async,
230}
231
232impl JsValue {
233 /// `GetIteratorFromMethod ( obj, method )`
234 ///
235 /// More information:
236 /// - [ECMA reference][spec]
237 ///
238 /// [spec]: https://tc39.es/ecma262/#sec-getiteratorfrommethod
239 pub fn get_iterator_from_method(
240 &self,
241 method: &JsObject,
242 context: &mut Context,
243 ) -> JsResult<IteratorRecord> {
244 // 1. Let iterator be ? Call(method, obj).
245 let iterator = method.call(self, &[], context)?;
246 // 2. If iterator is not an Object, throw a TypeError exception.
247 let iterator_obj = iterator.as_object().ok_or_else(|| {
248 JsNativeError::typ().with_message("returned iterator is not an object")
249 })?;
250 // 3. Let nextMethod be ? Get(iterator, "next").
251 let next_method = iterator_obj.get(js_string!("next"), context)?;
252 // 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
253 // 5. Return iteratorRecord.
254 Ok(IteratorRecord::new(iterator_obj.clone(), next_method))
255 }
256
257 /// `GetIterator ( obj, kind )`
258 ///
259 /// More information:
260 /// - [ECMA reference][spec]
261 ///
262 /// [spec]: https://tc39.es/ecma262/#sec-getiterator
263 pub fn get_iterator(
264 &self,
265 hint: IteratorHint,
266 context: &mut Context,
267 ) -> JsResult<IteratorRecord> {
268 let method = match hint {
269 // 1. If kind is async, then
270 IteratorHint::Async => {
271 // a. Let method be ? GetMethod(obj, %Symbol.asyncIterator%).
272 let Some(method) = self.get_method(JsSymbol::async_iterator(), context)? else {
273 // b. If method is undefined, then
274 // i. Let syncMethod be ? GetMethod(obj, %Symbol.iterator%).
275 let sync_method =
276 self.get_method(JsSymbol::iterator(), context)?
277 .ok_or_else(|| {
278 // ii. If syncMethod is undefined, throw a TypeError exception.
279 JsNativeError::typ().with_message(format!(
280 "value with type `{}` is not iterable",
281 self.type_of()
282 ))
283 })?;
284 // iii. Let syncIteratorRecord be ? GetIteratorFromMethod(obj, syncMethod).
285 let sync_iterator_record =
286 self.get_iterator_from_method(&sync_method, context)?;
287 // iv. Return CreateAsyncFromSyncIterator(syncIteratorRecord).
288 return Ok(AsyncFromSyncIterator::create(sync_iterator_record, context));
289 };
290
291 Some(method)
292 }
293 // 2. Else,
294 IteratorHint::Sync => {
295 if let Some(object) = self.as_object()
296 && let Some(record) = ArrayIterator::fast_iterator_record(&object, context)
297 {
298 return Ok(record);
299 }
300 // a. Let method be ? GetMethod(obj, %Symbol.iterator%).
301 self.get_method(JsSymbol::iterator(), context)?
302 }
303 };
304
305 let method = method.ok_or_else(|| {
306 // 3. If method is undefined, throw a TypeError exception.
307 JsNativeError::typ().with_message(format!(
308 "value with type `{}` is not iterable",
309 self.type_of()
310 ))
311 })?;
312
313 // 4. Return ? GetIteratorFromMethod(obj, method).
314 self.get_iterator_from_method(&method, context)
315 }
316}
317
318/// The result of the iteration process.
319#[derive(Debug, Clone, Trace, Finalize)]
320pub struct IteratorResult {
321 object: JsObject,
322}
323
324impl IteratorResult {
325 /// Gets a new `IteratorResult` from a value. Returns `Err` if
326 /// the value is not a [`JsObject`]
327 pub(crate) fn from_value(value: JsValue) -> JsResult<Self> {
328 if let Some(object) = value.into_object() {
329 Ok(Self { object })
330 } else {
331 Err(JsNativeError::typ()
332 .with_message("next value should be an object")
333 .into())
334 }
335 }
336
337 /// Gets the inner object of this `IteratorResult`.
338 pub(crate) const fn object(&self) -> &JsObject {
339 &self.object
340 }
341
342 /// `IteratorComplete ( iterResult )`
343 ///
344 /// The abstract operation `IteratorComplete` takes argument `iterResult` (an `Object`) and
345 /// returns either a normal completion containing a `Boolean` or a throw completion.
346 ///
347 /// More information:
348 /// - [ECMA reference][spec]
349 ///
350 /// [spec]: https://tc39.es/ecma262/#sec-iteratorcomplete
351 #[inline]
352 pub fn complete(&self, context: &mut Context) -> JsResult<bool> {
353 // 1. Return ToBoolean(? Get(iterResult, "done")).
354 Ok(self.object.get(js_string!("done"), context)?.to_boolean())
355 }
356
357 /// `IteratorValue ( iterResult )`
358 ///
359 /// The abstract operation `IteratorValue` takes argument `iterResult` (an `Object`) and
360 /// returns either a normal completion containing an ECMAScript language value or a throw
361 /// completion.
362 ///
363 /// More information:
364 /// - [ECMA reference][spec]
365 ///
366 /// [spec]: https://tc39.es/ecma262/#sec-iteratorvalue
367 #[inline]
368 pub fn value(&self, context: &mut Context) -> JsResult<JsValue> {
369 // 1. Return ? Get(iterResult, "value").
370 self.object.get(js_string!("value"), context)
371 }
372}
373
374/// Iterator Record
375///
376/// An Iterator Record is a Record value used to encapsulate an
377/// `Iterator` or `AsyncIterator` along with the `next` method.
378///
379/// More information:
380/// - [ECMA reference][spec]
381///
382/// [spec]: https://tc39.es/ecma262/#sec-iterator-records
383#[derive(Clone, Debug, Finalize, Trace)]
384pub struct IteratorRecord {
385 /// `[[Iterator]]`
386 ///
387 /// An object that conforms to the `Iterator` or `AsyncIterator` interface.
388 iterator: JsObject,
389
390 /// `[[NextMethod]]`
391 ///
392 /// The `next` method of the `[[Iterator]]` object.
393 next_method: JsValue,
394
395 /// `[[Done]]`
396 ///
397 /// Whether the iterator has been closed.
398 done: bool,
399
400 /// The result of the last call to `next`; `None` until there is one, which reads as an empty
401 /// result object.
402 last_result: Option<IteratorResult>,
403
404 /// Whether `next_method` is the intrinsic `%ArrayIteratorPrototype%.next`, so `step` can run
405 /// it directly without allocating a result object.
406 array_next: bool,
407
408 /// The value of the last non-done fast `step`; `last_result` is stale while this is `Some`.
409 fast_value: Option<JsValue>,
410
411 /// Whether the last fast `step` was done; its `{ value: undefined, done: true }` result
412 /// object is only created if bytecode asks for it.
413 fast_done: bool,
414}
415
416impl IteratorRecord {
417 /// Creates a new `IteratorRecord` with the given iterator object, next method and `done` flag.
418 #[inline]
419 #[must_use]
420 pub fn new(iterator: JsObject, next_method: JsValue) -> Self {
421 let array_next = ArrayIterator::is_intrinsic_next(&next_method);
422 Self {
423 iterator,
424 next_method,
425 done: false,
426 last_result: None,
427 array_next,
428 fast_value: None,
429 fast_done: false,
430 }
431 }
432
433 /// Get the `[[Iterator]]` field of the `IteratorRecord`.
434 pub(crate) const fn iterator(&self) -> &JsObject {
435 &self.iterator
436 }
437
438 /// Gets the `[[NextMethod]]` field of the `IteratorRecord`.
439 pub(crate) const fn next_method(&self) -> &JsValue {
440 &self.next_method
441 }
442
443 /// Gets the last result object of the iterator record.
444 pub(crate) fn last_result(&mut self, context: &mut Context) -> &IteratorResult {
445 if let Some(value) = self.fast_value.take() {
446 let object = create_iter_result_object(value, false, context);
447 self.last_result = Some(IteratorResult {
448 object: object
449 .as_object()
450 .expect("iter result is an object")
451 .clone(),
452 });
453 }
454 if self.fast_done {
455 self.fast_done = false;
456 let object = create_iter_result_object(JsValue::undefined(), true, context);
457 self.last_result = Some(IteratorResult {
458 object: object
459 .as_object()
460 .expect("iter result is an object")
461 .clone(),
462 });
463 }
464 self.last_result.get_or_insert_with(|| IteratorResult {
465 object: JsObject::with_null_proto(),
466 })
467 }
468
469 /// Runs `f`, setting the `done` field of this `IteratorRecord` to `true` if `f` returns
470 /// an error.
471 fn set_done_on_err<R, F>(&mut self, f: F) -> JsResult<R>
472 where
473 F: FnOnce(&mut Self) -> JsResult<R>,
474 {
475 let result = f(self);
476 if result.is_err() {
477 self.done = true;
478 }
479 result
480 }
481
482 /// Gets the current value of the `IteratorRecord`.
483 pub(crate) fn value(&mut self, context: &mut Context) -> JsResult<JsValue> {
484 if let Some(value) = &self.fast_value {
485 return Ok(value.clone());
486 }
487 self.set_done_on_err(|iter| {
488 iter.last_result
489 .as_ref()
490 .map_or(Ok(JsValue::undefined()), |result| result.value(context))
491 })
492 }
493
494 /// Get the `[[Done]]` field of the `IteratorRecord`.
495 pub(crate) const fn done(&self) -> bool {
496 self.done
497 }
498
499 /// Updates the current result value of this iterator record.
500 pub(crate) fn update_result(&mut self, result: JsValue, context: &mut Context) -> JsResult<()> {
501 self.set_done_on_err(|iter| {
502 // 3. If Type(result) is not Object, throw a TypeError exception.
503 // 4. Return result.
504 // `IteratorResult::from_value` does this for us.
505
506 // `IteratorStep(iteratorRecord)`
507 // https://tc39.es/ecma262/#sec-iteratorstep
508
509 // 1. Let result be ? IteratorNext(iteratorRecord).
510 let result = IteratorResult::from_value(result)?;
511 // 2. Let done be ? IteratorComplete(result).
512 // 3. If done is true, return false.
513 iter.done = result.complete(context)?;
514
515 iter.last_result = Some(result);
516 iter.fast_value = None;
517 iter.fast_done = false;
518
519 Ok(())
520 })
521 }
522
523 /// `IteratorNext ( iteratorRecord [ , value ] )`
524 ///
525 /// The abstract operation `IteratorNext` takes argument `iteratorRecord` (an `Iterator`
526 /// Record) and optional argument `value` (an ECMAScript language value) and returns either a
527 /// normal completion containing an `Object` or a throw completion.
528 ///
529 /// More information:
530 /// - [ECMA reference][spec]
531 ///
532 /// [spec]: https://tc39.es/ecma262/#sec-iteratornext
533 pub(crate) fn next(
534 &mut self,
535 value: Option<&JsValue>,
536 context: &mut Context,
537 ) -> JsResult<IteratorResult> {
538 // 1. If value is not present, then
539 // a. Let result be Completion(Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]])).
540 // 2. Else,
541 // a. Let result be Completion(Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »)).
542 // 3. If result is a throw completion, then
543 // a. Set iteratorRecord.[[Done]] to true.
544 // b. Return ? result.
545 // 4. Set result to ! result.
546 // 5. If result is not an Object, then
547 // a. Set iteratorRecord.[[Done]] to true.
548 // b. Throw a TypeError exception.
549 // 6. Return result.
550 // NOTE: In this case, `set_done_on_err` does all the heavylifting for us, which
551 // simplifies the instructions below.
552 self.set_done_on_err(|iter| {
553 iter.next_method
554 .call(
555 &iter.iterator.clone().into(),
556 value.map_or(&[], std::slice::from_ref),
557 context,
558 )
559 .and_then(IteratorResult::from_value)
560 })
561 }
562
563 /// `IteratorStep ( iteratorRecord )`
564 ///
565 /// Updates the `IteratorRecord` and returns `true` if the next result record returned
566 /// `done: true`, otherwise returns `false`. This differs slightly from the spec, but also
567 /// simplifies some logic around iterators.
568 ///
569 /// More information:
570 /// - [ECMA reference][spec]
571 ///
572 /// [spec]: https://tc39.es/ecma262/#sec-iteratorstep
573 pub(crate) fn step(&mut self, context: &mut Context) -> JsResult<bool> {
574 if self.array_next {
575 // The intrinsic `next` returns a fresh ordinary result object nobody else sees, so
576 // skipping it is unobservable; `last_result` materializes one if bytecode asks.
577 return self.set_done_on_err(|iter| {
578 let (value, done) =
579 ArrayIterator::next_value(&iter.iterator.clone().into(), context)?;
580 iter.done = done;
581 if done {
582 iter.fast_value = None;
583 iter.last_result = None;
584 iter.fast_done = true;
585 } else {
586 iter.fast_value = Some(value);
587 }
588 Ok(done)
589 });
590 }
591 self.set_done_on_err(|iter| {
592 // 1. Let result be ? IteratorNext(iteratorRecord).
593 let result = iter.next(None, context)?;
594
595 // 2. Let done be Completion(IteratorComplete(result)).
596 // 3. If done is a throw completion, then
597 // a. Set iteratorRecord.[[Done]] to true.
598 // b. Return ? done.
599 // 4. Set done to ! done.
600 // 5. If done is true, then
601 // a. Set iteratorRecord.[[Done]] to true.
602 // b. Return done.
603 iter.done = result.complete(context)?;
604
605 iter.last_result = Some(result);
606
607 // 6. Return result.
608 Ok(iter.done)
609 })
610 }
611
612 /// `IteratorStepValue ( iteratorRecord )`
613 ///
614 /// Updates the `IteratorRecord` and returns `Some(value)` if the next result record returned
615 /// `done: true`, otherwise returns `None`.
616 ///
617 /// More information:
618 /// - [ECMA reference][spec]
619 ///
620 /// [spec]: https://tc39.es/ecma262/#sec-iteratorstepvalue
621 pub(crate) fn step_value(&mut self, context: &mut Context) -> JsResult<Option<JsValue>> {
622 // 1. Let result be ? IteratorStep(iteratorRecord).
623 if self.step(context)? {
624 // 2. If result is done, then
625 // a. Return done.
626 Ok(None)
627 } else {
628 // 3. Let value be Completion(IteratorValue(result)).
629 // 4. If value is a throw completion, then
630 // a. Set iteratorRecord.[[Done]] to true.
631 // 5. Return ? value.
632 self.value(context).map(Some)
633 }
634 }
635
636 /// [`IfAbruptCloseIterator( value, iteratorRecord )`][spec], but
637 /// adapted to be used inside `NativeCoroutine`.
638 ///
639 /// [spec]: https://tc39.es/ecma262/#sec-ifabruptcloseiterator
640 pub(crate) fn if_abrupt_close_iterator(
641 &self,
642 completion: CompletionRecord,
643 context: &mut Context,
644 ) -> CoroutineState {
645 // 1. Assert: value is a Completion Record.
646 // 2. If value is an abrupt completion, return ? IteratorClose(iteratorRecord, value).
647 // 3. Set value to ! value.
648 match completion {
649 CompletionRecord::Return(value) => {
650 self.close(Ok(value), context).branch()?;
651 CoroutineState::Break(Ok(()))
652 }
653 CompletionRecord::Throw(err) => self.close(Err(err), context).branch(),
654 CompletionRecord::Normal(value) => CoroutineState::Continue(value),
655 }
656 }
657
658 /// `IteratorClose ( iteratorRecord, completion )`
659 ///
660 /// The abstract operation `IteratorClose` takes arguments `iteratorRecord` (an
661 /// [Iterator Record][Self]) and `completion` (a `Completion` Record) and returns a
662 /// `Completion` Record. It is used to notify an iterator that it should perform any actions it
663 /// would normally perform when it has reached its completed state.
664 ///
665 /// More information:
666 /// - [ECMA reference][spec]
667 ///
668 /// [spec]: https://tc39.es/ecma262/#sec-iteratorclose
669 pub(crate) fn close(
670 &self,
671 completion: JsResult<JsValue>,
672 context: &mut Context,
673 ) -> JsResult<JsValue> {
674 // 1. Assert: Type(iteratorRecord.[[Iterator]]) is Object.
675
676 // 2. Let iterator be iteratorRecord.[[Iterator]].
677 let iterator = &self.iterator;
678
679 // 3. Let innerResult be Completion(GetMethod(iterator, "return")).
680 let inner_result = iterator.get_method(js_string!("return"), context);
681
682 // 4. If innerResult.[[Type]] is normal, then
683 let inner_result = match inner_result {
684 Ok(inner_result) => {
685 // a. Let return be innerResult.[[Value]].
686 let r#return = inner_result;
687
688 if let Some(r#return) = r#return {
689 // c. Set innerResult to Completion(Call(return, iterator)).
690 r#return.call(&iterator.clone().into(), &[], context)
691 } else {
692 // b. If return is undefined, return ? completion.
693 return completion;
694 }
695 }
696 Err(inner_result) => {
697 // 5. If completion.[[Type]] is throw, return ? completion.
698 completion?;
699
700 // 6. If innerResult.[[Type]] is throw, return ? innerResult.
701 return Err(inner_result);
702 }
703 };
704
705 // 5. If completion.[[Type]] is throw, return ? completion.
706 let completion = completion?;
707
708 // 6. If innerResult.[[Type]] is throw, return ? innerResult.
709 let inner_result = inner_result?;
710
711 if inner_result.is_object() {
712 // 8. Return ? completion.
713 Ok(completion)
714 } else {
715 // 7. If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception.
716 Err(JsNativeError::typ()
717 .with_message("inner result was not an object")
718 .into())
719 }
720 }
721
722 /// `IteratorToList ( iteratorRecord )`
723 ///
724 /// More information:
725 /// - [ECMA reference][spec]
726 ///
727 /// [spec]: https://tc39.es/ecma262/#sec-iteratortolist
728 pub(crate) fn into_list(mut self, context: &mut Context) -> JsResult<Vec<JsValue>> {
729 // 1. Let values be a new empty List.
730 let mut values = Vec::new();
731
732 // 2. Repeat,
733 // a. Let next be ? IteratorStepValue(iteratorRecord).
734 while let Some(value) = self.step_value(context)? {
735 // c. Append next to values.
736 values.push(value);
737 }
738
739 // b. If next is done, then
740 // i. Return values.
741 Ok(values)
742 }
743}
744
745/// `GetIteratorDirect ( obj )`
746///
747/// The abstract operation `GetIteratorDirect` takes argument `obj` (an Object)
748/// and returns either a normal completion containing an Iterator Record or a
749/// throw completion.
750///
751/// More information:
752/// - [ECMAScript reference][spec]
753///
754/// [spec]: https://tc39.es/ecma262/#sec-getiteratordirect
755pub(crate) fn get_iterator_direct(
756 obj: &JsObject,
757 context: &mut Context,
758) -> JsResult<IteratorRecord> {
759 // 1. Let nextMethod be ? Get(obj, "next").
760 let next_method = obj.get(js_string!("next"), context)?;
761 // 2. Let iteratorRecord be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }.
762 // 3. Return iteratorRecord.
763 Ok(IteratorRecord::new(obj.clone(), next_method))
764}
765
766/// `GetIteratorFlattenable ( obj, stringHandling )`
767///
768/// The abstract operation `GetIteratorFlattenable` takes arguments `obj` (an ECMAScript
769/// language value) and `stringHandling` (iterate-strings or reject-strings) and returns
770/// either a normal completion containing an Iterator Record or a throw completion.
771///
772/// More information:
773/// - [ECMAScript reference][spec]
774///
775/// [spec]: https://tc39.es/ecma262/#sec-getiteratorflattenable
776pub(crate) fn get_iterator_flattenable(
777 obj: &JsValue,
778 iterate_strings: bool,
779 context: &mut Context,
780) -> JsResult<IteratorRecord> {
781 // 1. If obj is not an Object, then
782 if !obj.is_object() {
783 // a. If stringHandling is reject-strings or obj is not a String, throw a TypeError exception.
784 if !iterate_strings || !obj.is_string() {
785 return Err(JsNativeError::typ()
786 .with_message("GetIteratorFlattenable: value is not an object")
787 .into());
788 }
789 }
790
791 // 2. Let method be ? GetMethod(obj, @@iterator).
792 let method = obj.get_method(JsSymbol::iterator(), context)?;
793
794 match method {
795 // 3. If method is undefined, then
796 None => {
797 // a. Let iterator be obj.
798 let iterator = obj.as_object().ok_or_else(|| {
799 JsNativeError::typ()
800 .with_message("GetIteratorFlattenable: value is not iterable and not an object")
801 })?;
802
803 // b. Return ? GetIteratorDirect(iterator).
804 get_iterator_direct(&iterator, context)
805 }
806 // 4. Else,
807 Some(method) => {
808 // a. Let iterator be ? Call(method, obj).
809 let iterator = method.call(obj, &[], context)?;
810
811 // b. If iterator is not an Object, throw a TypeError exception.
812 let iterator_obj = iterator.as_object().ok_or_else(|| {
813 JsNativeError::typ()
814 .with_message("GetIteratorFlattenable: iterator result is not an object")
815 })?;
816
817 // c. Return ? GetIteratorDirect(iterator).
818 get_iterator_direct(&iterator_obj, context)
819 }
820 }
821}