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}