Skip to main content

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}