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::{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                // a. Let method be ? GetMethod(obj, %Symbol.iterator%).
296                self.get_method(JsSymbol::iterator(), context)?
297            }
298        };
299
300        let method = method.ok_or_else(|| {
301            // 3. If method is undefined, throw a TypeError exception.
302            JsNativeError::typ().with_message(format!(
303                "value with type `{}` is not iterable",
304                self.type_of()
305            ))
306        })?;
307
308        // 4. Return ? GetIteratorFromMethod(obj, method).
309        self.get_iterator_from_method(&method, context)
310    }
311}
312
313/// The result of the iteration process.
314#[derive(Debug, Clone, Trace, Finalize)]
315pub struct IteratorResult {
316    object: JsObject,
317}
318
319impl IteratorResult {
320    /// Gets a new `IteratorResult` from a value. Returns `Err` if
321    /// the value is not a [`JsObject`]
322    pub(crate) fn from_value(value: JsValue) -> JsResult<Self> {
323        if let Some(object) = value.into_object() {
324            Ok(Self { object })
325        } else {
326            Err(JsNativeError::typ()
327                .with_message("next value should be an object")
328                .into())
329        }
330    }
331
332    /// Gets the inner object of this `IteratorResult`.
333    pub(crate) const fn object(&self) -> &JsObject {
334        &self.object
335    }
336
337    /// `IteratorComplete ( iterResult )`
338    ///
339    /// The abstract operation `IteratorComplete` takes argument `iterResult` (an `Object`) and
340    /// returns either a normal completion containing a `Boolean` or a throw completion.
341    ///
342    /// More information:
343    ///  - [ECMA reference][spec]
344    ///
345    /// [spec]: https://tc39.es/ecma262/#sec-iteratorcomplete
346    #[inline]
347    pub fn complete(&self, context: &mut Context) -> JsResult<bool> {
348        // 1. Return ToBoolean(? Get(iterResult, "done")).
349        Ok(self.object.get(js_string!("done"), context)?.to_boolean())
350    }
351
352    /// `IteratorValue ( iterResult )`
353    ///
354    /// The abstract operation `IteratorValue` takes argument `iterResult` (an `Object`) and
355    /// returns either a normal completion containing an ECMAScript language value or a throw
356    /// completion.
357    ///
358    /// More information:
359    ///  - [ECMA reference][spec]
360    ///
361    /// [spec]: https://tc39.es/ecma262/#sec-iteratorvalue
362    #[inline]
363    pub fn value(&self, context: &mut Context) -> JsResult<JsValue> {
364        // 1. Return ? Get(iterResult, "value").
365        self.object.get(js_string!("value"), context)
366    }
367}
368
369/// Iterator Record
370///
371/// An Iterator Record is a Record value used to encapsulate an
372/// `Iterator` or `AsyncIterator` along with the `next` method.
373///
374/// More information:
375///  - [ECMA reference][spec]
376///
377/// [spec]: https://tc39.es/ecma262/#sec-iterator-records
378#[derive(Clone, Debug, Finalize, Trace)]
379pub struct IteratorRecord {
380    /// `[[Iterator]]`
381    ///
382    /// An object that conforms to the `Iterator` or `AsyncIterator` interface.
383    iterator: JsObject,
384
385    /// `[[NextMethod]]`
386    ///
387    /// The `next` method of the `[[Iterator]]` object.
388    next_method: JsValue,
389
390    /// `[[Done]]`
391    ///
392    /// Whether the iterator has been closed.
393    done: bool,
394
395    /// The result of the last call to `next`.
396    last_result: IteratorResult,
397}
398
399impl IteratorRecord {
400    /// Creates a new `IteratorRecord` with the given iterator object, next method and `done` flag.
401    #[inline]
402    #[must_use]
403    pub fn new(iterator: JsObject, next_method: JsValue) -> Self {
404        Self {
405            iterator,
406            next_method,
407            done: false,
408            last_result: IteratorResult {
409                object: JsObject::with_null_proto(),
410            },
411        }
412    }
413
414    /// Get the `[[Iterator]]` field of the `IteratorRecord`.
415    pub(crate) const fn iterator(&self) -> &JsObject {
416        &self.iterator
417    }
418
419    /// Gets the `[[NextMethod]]` field of the `IteratorRecord`.
420    pub(crate) const fn next_method(&self) -> &JsValue {
421        &self.next_method
422    }
423
424    /// Gets the last result object of the iterator record.
425    pub(crate) const fn last_result(&self) -> &IteratorResult {
426        &self.last_result
427    }
428
429    /// Runs `f`, setting the `done` field of this `IteratorRecord` to `true` if `f` returns
430    /// an error.
431    fn set_done_on_err<R, F>(&mut self, f: F) -> JsResult<R>
432    where
433        F: FnOnce(&mut Self) -> JsResult<R>,
434    {
435        let result = f(self);
436        if result.is_err() {
437            self.done = true;
438        }
439        result
440    }
441
442    /// Gets the current value of the `IteratorRecord`.
443    pub(crate) fn value(&mut self, context: &mut Context) -> JsResult<JsValue> {
444        self.set_done_on_err(|iter| iter.last_result.value(context))
445    }
446
447    /// Get the `[[Done]]` field of the `IteratorRecord`.
448    pub(crate) const fn done(&self) -> bool {
449        self.done
450    }
451
452    /// Updates the current result value of this iterator record.
453    pub(crate) fn update_result(&mut self, result: JsValue, context: &mut Context) -> JsResult<()> {
454        self.set_done_on_err(|iter| {
455            // 3. If Type(result) is not Object, throw a TypeError exception.
456            // 4. Return result.
457            // `IteratorResult::from_value` does this for us.
458
459            // `IteratorStep(iteratorRecord)`
460            // https://tc39.es/ecma262/#sec-iteratorstep
461
462            // 1. Let result be ? IteratorNext(iteratorRecord).
463            let result = IteratorResult::from_value(result)?;
464            // 2. Let done be ? IteratorComplete(result).
465            // 3. If done is true, return false.
466            iter.done = result.complete(context)?;
467
468            iter.last_result = result;
469
470            Ok(())
471        })
472    }
473
474    /// `IteratorNext ( iteratorRecord [ , value ] )`
475    ///
476    /// The abstract operation `IteratorNext` takes argument `iteratorRecord` (an `Iterator`
477    /// Record) and optional argument `value` (an ECMAScript language value) and returns either a
478    /// normal completion containing an `Object` or a throw completion.
479    ///
480    /// More information:
481    ///  - [ECMA reference][spec]
482    ///
483    /// [spec]: https://tc39.es/ecma262/#sec-iteratornext
484    pub(crate) fn next(
485        &mut self,
486        value: Option<&JsValue>,
487        context: &mut Context,
488    ) -> JsResult<IteratorResult> {
489        // 1. If value is not present, then
490        //     a. Let result be Completion(Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]])).
491        // 2. Else,
492        //     a. Let result be Completion(Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »)).
493        // 3. If result is a throw completion, then
494        //     a. Set iteratorRecord.[[Done]] to true.
495        //     b. Return ? result.
496        // 4. Set result to ! result.
497        // 5. If result is not an Object, then
498        //     a. Set iteratorRecord.[[Done]] to true.
499        //     b. Throw a TypeError exception.
500        // 6. Return result.
501        // NOTE: In this case, `set_done_on_err` does all the heavylifting for us, which
502        // simplifies the instructions below.
503        self.set_done_on_err(|iter| {
504            iter.next_method
505                .call(
506                    &iter.iterator.clone().into(),
507                    value.map_or(&[], std::slice::from_ref),
508                    context,
509                )
510                .and_then(IteratorResult::from_value)
511        })
512    }
513
514    /// `IteratorStep ( iteratorRecord )`
515    ///
516    /// Updates the `IteratorRecord` and returns `true` if the next result record returned
517    /// `done: true`, otherwise returns `false`. This differs slightly from the spec, but also
518    /// simplifies some logic around iterators.
519    ///
520    /// More information:
521    ///  - [ECMA reference][spec]
522    ///
523    /// [spec]: https://tc39.es/ecma262/#sec-iteratorstep
524    pub(crate) fn step(&mut self, context: &mut Context) -> JsResult<bool> {
525        self.set_done_on_err(|iter| {
526            // 1. Let result be ? IteratorNext(iteratorRecord).
527            let result = iter.next(None, context)?;
528
529            // 2. Let done be Completion(IteratorComplete(result)).
530            // 3. If done is a throw completion, then
531            //     a. Set iteratorRecord.[[Done]] to true.
532            //     b. Return ? done.
533            // 4. Set done to ! done.
534            // 5. If done is true, then
535            //     a. Set iteratorRecord.[[Done]] to true.
536            //     b. Return done.
537            iter.done = result.complete(context)?;
538
539            iter.last_result = result;
540
541            // 6. Return result.
542            Ok(iter.done)
543        })
544    }
545
546    /// `IteratorStepValue ( iteratorRecord )`
547    ///
548    /// Updates the `IteratorRecord` and returns `Some(value)` if the next result record returned
549    /// `done: true`, otherwise returns `None`.
550    ///
551    /// More information:
552    ///  - [ECMA reference][spec]
553    ///
554    /// [spec]: https://tc39.es/ecma262/#sec-iteratorstepvalue
555    pub(crate) fn step_value(&mut self, context: &mut Context) -> JsResult<Option<JsValue>> {
556        // 1. Let result be ? IteratorStep(iteratorRecord).
557        if self.step(context)? {
558            // 2. If result is done, then
559            //     a. Return done.
560            Ok(None)
561        } else {
562            // 3. Let value be Completion(IteratorValue(result)).
563            // 4. If value is a throw completion, then
564            //     a. Set iteratorRecord.[[Done]] to true.
565            // 5. Return ? value.
566            self.value(context).map(Some)
567        }
568    }
569
570    /// [`IfAbruptCloseIterator( value, iteratorRecord )`][spec], but
571    /// adapted to be used inside `NativeCoroutine`.
572    ///
573    /// [spec]: https://tc39.es/ecma262/#sec-ifabruptcloseiterator
574    pub(crate) fn if_abrupt_close_iterator(
575        &self,
576        completion: CompletionRecord,
577        context: &mut Context,
578    ) -> CoroutineState {
579        // 1. Assert: value is a Completion Record.
580        // 2. If value is an abrupt completion, return ? IteratorClose(iteratorRecord, value).
581        // 3. Set value to ! value.
582        match completion {
583            CompletionRecord::Return(value) => {
584                self.close(Ok(value), context).branch()?;
585                CoroutineState::Break(Ok(()))
586            }
587            CompletionRecord::Throw(err) => self.close(Err(err), context).branch(),
588            CompletionRecord::Normal(value) => CoroutineState::Continue(value),
589        }
590    }
591
592    /// `IteratorClose ( iteratorRecord, completion )`
593    ///
594    /// The abstract operation `IteratorClose` takes arguments `iteratorRecord` (an
595    /// [Iterator Record][Self]) and `completion` (a `Completion` Record) and returns a
596    /// `Completion` Record. It is used to notify an iterator that it should perform any actions it
597    /// would normally perform when it has reached its completed state.
598    ///
599    /// More information:
600    ///  - [ECMA reference][spec]
601    ///
602    ///  [spec]: https://tc39.es/ecma262/#sec-iteratorclose
603    pub(crate) fn close(
604        &self,
605        completion: JsResult<JsValue>,
606        context: &mut Context,
607    ) -> JsResult<JsValue> {
608        // 1. Assert: Type(iteratorRecord.[[Iterator]]) is Object.
609
610        // 2. Let iterator be iteratorRecord.[[Iterator]].
611        let iterator = &self.iterator;
612
613        // 3. Let innerResult be Completion(GetMethod(iterator, "return")).
614        let inner_result = iterator.get_method(js_string!("return"), context);
615
616        // 4. If innerResult.[[Type]] is normal, then
617        let inner_result = match inner_result {
618            Ok(inner_result) => {
619                // a. Let return be innerResult.[[Value]].
620                let r#return = inner_result;
621
622                if let Some(r#return) = r#return {
623                    // c. Set innerResult to Completion(Call(return, iterator)).
624                    r#return.call(&iterator.clone().into(), &[], context)
625                } else {
626                    // b. If return is undefined, return ? completion.
627                    return completion;
628                }
629            }
630            Err(inner_result) => {
631                // 5. If completion.[[Type]] is throw, return ? completion.
632                completion?;
633
634                // 6. If innerResult.[[Type]] is throw, return ? innerResult.
635                return Err(inner_result);
636            }
637        };
638
639        // 5. If completion.[[Type]] is throw, return ? completion.
640        let completion = completion?;
641
642        // 6. If innerResult.[[Type]] is throw, return ? innerResult.
643        let inner_result = inner_result?;
644
645        if inner_result.is_object() {
646            // 8. Return ? completion.
647            Ok(completion)
648        } else {
649            // 7. If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception.
650            Err(JsNativeError::typ()
651                .with_message("inner result was not an object")
652                .into())
653        }
654    }
655
656    /// `IteratorToList ( iteratorRecord )`
657    ///
658    /// More information:
659    ///  - [ECMA reference][spec]
660    ///
661    ///  [spec]: https://tc39.es/ecma262/#sec-iteratortolist
662    pub(crate) fn into_list(mut self, context: &mut Context) -> JsResult<Vec<JsValue>> {
663        // 1. Let values be a new empty List.
664        let mut values = Vec::new();
665
666        // 2. Repeat,
667        //     a. Let next be ? IteratorStepValue(iteratorRecord).
668        while let Some(value) = self.step_value(context)? {
669            // c. Append next to values.
670            values.push(value);
671        }
672
673        //     b. If next is done, then
674        //         i. Return values.
675        Ok(values)
676    }
677}
678
679/// `GetIteratorDirect ( obj )`
680///
681/// The abstract operation `GetIteratorDirect` takes argument `obj` (an Object)
682/// and returns either a normal completion containing an Iterator Record or a
683/// throw completion.
684///
685/// More information:
686///  - [ECMAScript reference][spec]
687///
688/// [spec]: https://tc39.es/ecma262/#sec-getiteratordirect
689pub(crate) fn get_iterator_direct(
690    obj: &JsObject,
691    context: &mut Context,
692) -> JsResult<IteratorRecord> {
693    // 1. Let nextMethod be ? Get(obj, "next").
694    let next_method = obj.get(js_string!("next"), context)?;
695    // 2. Let iteratorRecord be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }.
696    // 3. Return iteratorRecord.
697    Ok(IteratorRecord::new(obj.clone(), next_method))
698}
699
700/// `GetIteratorFlattenable ( obj, stringHandling )`
701///
702/// The abstract operation `GetIteratorFlattenable` takes arguments `obj` (an ECMAScript
703/// language value) and `stringHandling` (iterate-strings or reject-strings) and returns
704/// either a normal completion containing an Iterator Record or a throw completion.
705///
706/// More information:
707///  - [ECMAScript reference][spec]
708///
709/// [spec]: https://tc39.es/ecma262/#sec-getiteratorflattenable
710pub(crate) fn get_iterator_flattenable(
711    obj: &JsValue,
712    iterate_strings: bool,
713    context: &mut Context,
714) -> JsResult<IteratorRecord> {
715    // 1. If obj is not an Object, then
716    if !obj.is_object() {
717        // a. If stringHandling is reject-strings or obj is not a String, throw a TypeError exception.
718        if !iterate_strings || !obj.is_string() {
719            return Err(JsNativeError::typ()
720                .with_message("GetIteratorFlattenable: value is not an object")
721                .into());
722        }
723    }
724
725    // 2. Let method be ? GetMethod(obj, @@iterator).
726    let method = obj.get_method(JsSymbol::iterator(), context)?;
727
728    match method {
729        // 3. If method is undefined, then
730        None => {
731            // a. Let iterator be obj.
732            let iterator = obj.as_object().ok_or_else(|| {
733                JsNativeError::typ()
734                    .with_message("GetIteratorFlattenable: value is not iterable and not an object")
735            })?;
736
737            // b. Return ? GetIteratorDirect(iterator).
738            get_iterator_direct(&iterator, context)
739        }
740        // 4. Else,
741        Some(method) => {
742            // a. Let iterator be ? Call(method, obj).
743            let iterator = method.call(obj, &[], context)?;
744
745            // b. If iterator is not an Object, throw a TypeError exception.
746            let iterator_obj = iterator.as_object().ok_or_else(|| {
747                JsNativeError::typ()
748                    .with_message("GetIteratorFlattenable: iterator result is not an object")
749            })?;
750
751            // c. Return ? GetIteratorDirect(iterator).
752            get_iterator_direct(&iterator_obj, context)
753        }
754    }
755}