From 25705ef5a68b1e42f11311e47272c77722b3bb34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Sat, 24 Aug 2024 16:03:10 +0000 Subject: [PATCH] Refactor iterator APIs to be on parity with the latest spec (#3962) * The Great Refactoring Of Iterators * Update core/engine/src/builtins/promise/mod.rs Co-authored-by: raskad <32105367+raskad@users.noreply.github.com> * Update core/engine/src/builtins/promise/mod.rs Co-authored-by: raskad <32105367+raskad@users.noreply.github.com> --------- Co-authored-by: raskad <32105367+raskad@users.noreply.github.com> --- core/engine/src/builtins/array/mod.rs | 42 +-- core/engine/src/builtins/error/aggregate.rs | 15 +- .../src/builtins/intl/list_format/mod.rs | 27 +- .../iterable/async_from_sync_iterator.rs | 16 +- core/engine/src/builtins/iterable/mod.rs | 242 +++++++----- core/engine/src/builtins/map/mod.rs | 107 +++--- core/engine/src/builtins/object/mod.rs | 24 +- core/engine/src/builtins/promise/mod.rs | 349 ++++++++---------- core/engine/src/builtins/set/mod.rs | 25 +- core/engine/src/builtins/temporal/mod.rs | 28 +- .../src/builtins/typed_array/builtin.rs | 5 +- core/engine/src/builtins/typed_array/mod.rs | 12 +- core/engine/src/builtins/weak_map/mod.rs | 11 +- core/engine/src/builtins/weak_set/mod.rs | 19 +- core/engine/src/object/builtins/jsmap.rs | 20 +- core/engine/src/object/builtins/jsset.rs | 6 +- core/engine/src/vm/opcode/iteration/get.rs | 4 +- core/engine/src/vm/opcode/push/array.rs | 3 +- test262_config.toml | 2 +- 19 files changed, 470 insertions(+), 487 deletions(-) diff --git a/core/engine/src/builtins/array/mod.rs b/core/engine/src/builtins/array/mod.rs index 6fb6fa8118..45f4f0dab1 100644 --- a/core/engine/src/builtins/array/mod.rs +++ b/core/engine/src/builtins/array/mod.rs @@ -15,10 +15,7 @@ use boa_profiler::Profiler; use thin_vec::ThinVec; use crate::{ - builtins::{ - iterable::{if_abrupt_close_iterator, IteratorHint}, - BuiltInObject, Number, - }, + builtins::{iterable::if_abrupt_close_iterator, BuiltInObject, Number}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, @@ -610,44 +607,41 @@ impl Array { _ => Self::array_create(0, None, context)?, }; - // c. Let iteratorRecord be ? GetIterator(items, sync, usingIterator). - let mut iterator_record = - items.get_iterator(context, Some(IteratorHint::Sync), Some(using_iterator))?; + // c. Let iteratorRecord be ? GetIteratorFromMethod(items, usingIterator). + let mut iterator_record = items.get_iterator_from_method(&using_iterator, context)?; // d. Let k be 0. // e. Repeat, // i. If k ≥ 2^53 - 1 (MAX_SAFE_INTEGER), then // ... - // x. Set k to k + 1. + // ix. Set k to k + 1. for k in 0..9_007_199_254_740_991_u64 { - // iii. Let next be ? IteratorStep(iteratorRecord). - if iterator_record.step(context)? { - // 1. Perform ? Set(A, "length", 𝔽(k), true). + // iii. Let next be ? IteratorStepValue(iteratorRecord). + let Some(next) = iterator_record.step_value(context)? else { + // iv. If next is done, then + // 1. Perform ? Set(A, "length", 𝔽(k), true). a.set(StaticJsStrings::LENGTH, k, true, context)?; - // 2. Return A. + // 2. Return A. return Ok(a.into()); - } - - // iv. If next is false, then - // v. Let nextValue be ? IteratorValue(next). - let next_value = iterator_record.value(context)?; + }; - // vi. If mapping is true, then + // v. If mapping is true, then let mapped_value = if let Some(mapfn) = mapping { - // 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »). - let mapped_value = mapfn.call(this_arg, &[next_value, k.into()], context); + // 1. Let mappedValue be Completion(Call(mapper, thisArg, « next, 𝔽(k) »)). + let mapped_value = mapfn.call(this_arg, &[next, k.into()], context); // 2. IfAbruptCloseIterator(mappedValue, iteratorRecord). if_abrupt_close_iterator!(mapped_value, iterator_record, context) } else { - // vii. Else, let mappedValue be nextValue. - next_value + // vi. Else, + // 1. Let mappedValue be next. + next }; - // viii. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue). + // vii. Let defineStatus be Completion(CreateDataPropertyOrThrow(A, Pk, mappedValue)). let define_status = a.create_data_property_or_throw(k, mapped_value, context); - // ix. IfAbruptCloseIterator(defineStatus, iteratorRecord). + // viii. IfAbruptCloseIterator(defineStatus, iteratorRecord). if_abrupt_close_iterator!(define_status, iterator_record, context); } diff --git a/core/engine/src/builtins/error/aggregate.rs b/core/engine/src/builtins/error/aggregate.rs index 6a8042df31..3dacfbcaab 100644 --- a/core/engine/src/builtins/error/aggregate.rs +++ b/core/engine/src/builtins/error/aggregate.rs @@ -9,7 +9,7 @@ use crate::{ builtins::{ - iterable::iterable_to_list, Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, + iterable::IteratorHint, Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -56,7 +56,11 @@ impl BuiltInConstructor for AggregateError { const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::aggregate_error; - /// Create a new aggregate error object. + /// [`AggregateError ( errors, message [ , options ] )`][spec] + /// + /// Creates a new aggregate error object. + /// + /// [spec]: AggregateError ( errors, message [ , options ] ) fn constructor( new_target: &JsValue, args: &[JsValue], @@ -102,9 +106,12 @@ impl BuiltInConstructor for AggregateError { // 4. Perform ? InstallErrorCause(O, options). Error::install_error_cause(&o, args.get_or_undefined(2), context)?; - // 5. Let errorsList be ? IterableToList(errors). + // 5. Let errorsList be ? IteratorToList(? GetIterator(errors, sync)). let errors = args.get_or_undefined(0); - let errors_list = iterable_to_list(context, errors, None)?; + let errors_list = errors + .get_iterator(IteratorHint::Sync, context)? + .into_list(context)?; + // 6. Perform ! DefinePropertyOrThrow(O, "errors", // PropertyDescriptor { // [[Configurable]]: true, diff --git a/core/engine/src/builtins/intl/list_format/mod.rs b/core/engine/src/builtins/intl/list_format/mod.rs index 94c838326d..9da43f072d 100644 --- a/core/engine/src/builtins/intl/list_format/mod.rs +++ b/core/engine/src/builtins/intl/list_format/mod.rs @@ -9,6 +9,7 @@ use icu_provider::DataLocale; use crate::{ builtins::{ + iterable::IteratorHint, options::{get_option, get_options_object}, Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, }, @@ -486,23 +487,20 @@ fn string_list_from_iterable(iterable: &JsValue, context: &mut Context) -> JsRes return Ok(Vec::new()); } - // 2. Let iteratorRecord be ? GetIterator(iterable). - let mut iterator = iterable.get_iterator(context, None, None)?; + // 2. Let iteratorRecord be ? GetIterator(iterable, sync). + let mut iterator = iterable.get_iterator(IteratorHint::Sync, context)?; // 3. Let list be a new empty List. let mut list = Vec::new(); // 4. Let next be true. // 5. Repeat, while next is not false, - // a. Set next to ? IteratorStep(iteratorRecord). - // b. If next is not false, then - while !iterator.step(context)? { - let item = iterator.value(context)?; - // i. Let nextValue be ? IteratorValue(next). - // ii. If Type(nextValue) is not String, then - let Some(s) = item.as_string().cloned() else { - // 1. Let error be ThrowCompletion(a newly created TypeError object). - // 2. Return ? IteratorClose(iteratorRecord, error). + // a. Let next be ? IteratorStepValue(iteratorRecord). + while let Some(next) = iterator.step_value(context)? { + // c. If next is not a String, then + let Some(s) = next.as_string().cloned() else { + // i. Let error be ThrowCompletion(a newly created TypeError object). + // ii. Return ? IteratorClose(iteratorRecord, error). return Err(iterator .close( Err(JsNativeError::typ() @@ -510,13 +508,14 @@ fn string_list_from_iterable(iterable: &JsValue, context: &mut Context) -> JsRes .into()), context, ) - .expect_err("Should return the provided error")); + .expect_err("`close` should return the provided error")); }; - // iii. Append nextValue to the end of the List list. + // d. Append next to list. list.push(s); } - // 6. Return list. + // b. If next is done, then + // i. Return list. Ok(list) } diff --git a/core/engine/src/builtins/iterable/async_from_sync_iterator.rs b/core/engine/src/builtins/iterable/async_from_sync_iterator.rs index 8854bdf00e..032cc298e5 100644 --- a/core/engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/core/engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -98,7 +98,7 @@ impl AsyncFromSyncIterator { // 1. Let O be the this value. // 2. Assert: O is an Object that has a [[SyncIteratorRecord]] internal slot. // 4. Let syncIteratorRecord be O.[[SyncIteratorRecord]]. - let sync_iterator_record = this + let mut sync_iterator_record = this .as_object() .and_then(JsObject::downcast_ref::) .expect("async from sync iterator prototype must be object") @@ -113,18 +113,10 @@ impl AsyncFromSyncIterator { .expect("cannot fail with promise constructor"); // 5. If value is present, then - // a. Let result be Completion(IteratorNext(syncIteratorRecord, value)). + // a. Let result be Completion(IteratorNext(syncIteratorRecord, value)). // 6. Else, - // a. Let result be Completion(IteratorNext(syncIteratorRecord)). - let iterator = sync_iterator_record.iterator().clone(); - let next = sync_iterator_record.next_method(); - let result = next - .call( - &iterator.into(), - args.first().map_or(&[], std::slice::from_ref), - context, - ) - .and_then(IteratorResult::from_value); + // a. Let result be Completion(IteratorNext(syncIteratorRecord)). + let result = sync_iterator_record.next(args.first(), context); // 7. IfAbruptRejectPromise(result, promiseCapability). let result = if_abrupt_reject_promise!(result, promise_capability, context); diff --git a/core/engine/src/builtins/iterable/mod.rs b/core/engine/src/builtins/iterable/mod.rs index 63b21a87b4..ca3303ba59 100644 --- a/core/engine/src/builtins/iterable/mod.rs +++ b/core/engine/src/builtins/iterable/mod.rs @@ -225,7 +225,31 @@ pub enum IteratorHint { } impl JsValue { - /// `GetIterator ( obj [ , hint [ , method ] ] )` + /// `GetIteratorFromMethod ( obj, method )` + /// + /// More information: + /// - [ECMA reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-getiteratorfrommethod + pub fn get_iterator_from_method( + &self, + method: &JsObject, + context: &mut Context, + ) -> JsResult { + // 1. Let iterator be ? Call(method, obj). + let iterator = method.call(self, &[], context)?; + // 2. If iterator is not an Object, throw a TypeError exception. + let iterator_obj = iterator.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("returned iterator is not an object") + })?; + // 3. Let nextMethod be ? Get(iterator, "next"). + let next_method = iterator_obj.get(js_str!("next"), context)?; + // 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }. + // 5. Return iteratorRecord. + Ok(IteratorRecord::new(iterator_obj.clone(), next_method)) + } + + /// `GetIterator ( obj, kind )` /// /// More information: /// - [ECMA reference][spec] @@ -233,60 +257,51 @@ impl JsValue { /// [spec]: https://tc39.es/ecma262/#sec-getiterator pub fn get_iterator( &self, + hint: IteratorHint, context: &mut Context, - hint: Option, - method: Option, ) -> JsResult { - // 1. If hint is not present, set hint to sync. - let hint = hint.unwrap_or(IteratorHint::Sync); - - // 2. If method is not present, then - let method = if method.is_some() { - method - } else { - // a. If hint is async, then - if hint == IteratorHint::Async { - // i. Set method to ? GetMethod(obj, @@asyncIterator). - if let Some(method) = self.get_method(JsSymbol::async_iterator(), context)? { - Some(method) - } else { - // ii. If method is undefined, then - // 1. Let syncMethod be ? GetMethod(obj, @@iterator). - let sync_method = self.get_method(JsSymbol::iterator(), context)?; - - // 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod). + let method = match hint { + // 1. If kind is async, then + IteratorHint::Async => { + // a. Let method be ? GetMethod(obj, %Symbol.asyncIterator%). + let Some(method) = self.get_method(JsSymbol::async_iterator(), context)? else { + // b. If method is undefined, then + // i. Let syncMethod be ? GetMethod(obj, %Symbol.iterator%). + let sync_method = + self.get_method(JsSymbol::iterator(), context)? + .ok_or_else(|| { + // ii. If syncMethod is undefined, throw a TypeError exception. + JsNativeError::typ().with_message(format!( + "value with type `{}` is not iterable", + self.type_of() + )) + })?; + // iii. Let syncIteratorRecord be ? GetIteratorFromMethod(obj, syncMethod). let sync_iterator_record = - self.get_iterator(context, Some(IteratorHint::Sync), sync_method)?; - - // 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord). + self.get_iterator_from_method(&sync_method, context)?; + // iv. Return CreateAsyncFromSyncIterator(syncIteratorRecord). return Ok(AsyncFromSyncIterator::create(sync_iterator_record, context)); - } - } else { - // b. Otherwise, set method to ? GetMethod(obj, @@iterator). + }; + + Some(method) + } + // 2. Else, + IteratorHint::Sync => { + // a. Let method be ? GetMethod(obj, %Symbol.iterator%). self.get_method(JsSymbol::iterator(), context)? } - } - .ok_or_else(|| { + }; + + let method = method.ok_or_else(|| { + // 3. If method is undefined, throw a TypeError exception. JsNativeError::typ().with_message(format!( "value with type `{}` is not iterable", self.type_of() )) })?; - // 3. Let iterator be ? Call(method, obj). - let iterator = method.call(self, &[], context)?; - - // 4. If Type(iterator) is not Object, throw a TypeError exception. - let iterator_obj = iterator.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("returned iterator is not an object") - })?; - - // 5. Let nextMethod be ? GetV(iterator, "next"). - let next_method = iterator.get_v(js_str!("next"), context)?; - - // 6. Let iteratorRecord be the Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }. - // 7. Return iteratorRecord. - Ok(IteratorRecord::new(iterator_obj.clone(), next_method)) + // 4. Return ? GetIteratorFromMethod(obj, method). + self.get_iterator_from_method(&method, context) } } @@ -461,28 +476,35 @@ impl IteratorRecord { /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-iteratornext - pub(crate) fn step_with( + pub(crate) fn next( &mut self, value: Option<&JsValue>, context: &mut Context, - ) -> JsResult { + ) -> JsResult { let _timer = Profiler::global().start_event("IteratorRecord::step_with", "iterator"); + // 1. If value is not present, then + // a. Let result be Completion(Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]])). + // 2. Else, + // a. Let result be Completion(Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »)). + // 3. If result is a throw completion, then + // a. Set iteratorRecord.[[Done]] to true. + // b. Return ? result. + // 4. Set result to ! result. + // 5. If result is not an Object, then + // a. Set iteratorRecord.[[Done]] to true. + // b. Throw a TypeError exception. + // 6. Return result. + // NOTE: In this case, `set_done_on_err` does all the heavylifting for us, which + // simplifies the instructions below. self.set_done_on_err(|iter| { - // 1. If value is not present, then - // a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). - // 2. Else, - // a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »). - let result = iter.next_method.call( - &iter.iterator.clone().into(), - value.map_or(&[], std::slice::from_ref), - context, - )?; - - iter.update_result(result, context)?; - - // 4. Return result. - Ok(iter.done) + iter.next_method + .call( + &iter.iterator.clone().into(), + value.map_or(&[], std::slice::from_ref), + context, + ) + .and_then(IteratorResult::from_value) }) } @@ -497,7 +519,49 @@ impl IteratorRecord { /// /// [spec]: https://tc39.es/ecma262/#sec-iteratorstep pub(crate) fn step(&mut self, context: &mut Context) -> JsResult { - self.step_with(None, context) + self.set_done_on_err(|iter| { + // 1. Let result be ? IteratorNext(iteratorRecord). + let result = iter.next(None, context)?; + + // 2. Let done be Completion(IteratorComplete(result)). + // 3. If done is a throw completion, then + // a. Set iteratorRecord.[[Done]] to true. + // b. Return ? done. + // 4. Set done to ! done. + // 5. If done is true, then + // a. Set iteratorRecord.[[Done]] to true. + // b. Return done. + iter.done = result.complete(context)?; + + iter.last_result = result; + + // 6. Return result. + Ok(iter.done) + }) + } + + /// `IteratorStepValue ( iteratorRecord )` + /// + /// Updates the `IteratorRecord` and returns `Some(value)` if the next result record returned + /// `done: true`, otherwise returns `None`. + /// + /// More information: + /// - [ECMA reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-iteratorstepvalue + pub(crate) fn step_value(&mut self, context: &mut Context) -> JsResult> { + // 1. Let result be ? IteratorStep(iteratorRecord). + if self.step(context)? { + // 2. If result is done, then + // a. Return done. + Ok(None) + } else { + // 3. Let value be Completion(IteratorValue(result)). + // 4. If value is a throw completion, then + // a. Set iteratorRecord.[[Done]] to true. + // 5. Return ? value. + self.value(context).map(Some) + } } /// `IteratorClose ( iteratorRecord, completion )` @@ -565,40 +629,28 @@ impl IteratorRecord { .into()) } } -} -/// `IterableToList ( items [ , method ] )` -/// -/// More information: -/// - [ECMA reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-iterabletolist -pub(crate) fn iterable_to_list( - context: &mut Context, - items: &JsValue, - method: Option, -) -> JsResult> { - let _timer = Profiler::global().start_event("iterable_to_list", "iterator"); - - // 1. If method is present, then - // a. Let iteratorRecord be ? GetIterator(items, sync, method). - // 2. Else, - // a. Let iteratorRecord be ? GetIterator(items, sync). - let mut iterator_record = items.get_iterator(context, Some(IteratorHint::Sync), method)?; - - // 3. Let values be a new empty List. - let mut values = Vec::new(); - - // 4. Let next be true. - // 5. Repeat, while next is not false, - // a. Set next to ? IteratorStep(iteratorRecord). - // b. If next is not false, then - // i. Let nextValue be ? IteratorValue(next). - // ii. Append nextValue to the end of the List values. - while !iterator_record.step(context)? { - values.push(iterator_record.value(context)?); - } - - // 6. Return values. - Ok(values) + /// `IteratorToList ( iteratorRecord )` + /// + /// More information: + /// - [ECMA reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-iteratortolist + pub(crate) fn into_list(mut self, context: &mut Context) -> JsResult> { + let _timer = Profiler::global().start_event("IteratorRecord::to_list", "iterator"); + + // 1. Let values be a new empty List. + let mut values = Vec::new(); + + // 2. Repeat, + // a. Let next be ? IteratorStepValue(iteratorRecord). + while let Some(value) = self.step_value(context)? { + // c. Append next to values. + values.push(value); + } + + // b. If next is done, then + // i. Return values. + Ok(values) + } } diff --git a/core/engine/src/builtins/map/mod.rs b/core/engine/src/builtins/map/mod.rs index ef8eaad1e5..40223620e8 100644 --- a/core/engine/src/builtins/map/mod.rs +++ b/core/engine/src/builtins/map/mod.rs @@ -11,11 +11,11 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map use crate::{ - builtins::BuiltInObject, + builtins::{iterable::IteratorHint, BuiltInObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject}, + object::{internal_methods::get_prototype_from_constructor, JsFunction, JsObject}, property::{Attribute, PropertyNameKind}, realm::Realm, string::StaticJsStrings, @@ -26,7 +26,9 @@ use boa_macros::js_str; use boa_profiler::Profiler; use num_traits::Zero; -use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; +use super::{ + iterable::if_abrupt_close_iterator, BuiltInBuilder, BuiltInConstructor, IntrinsicObject, +}; mod map_iterator; pub(crate) use map_iterator::MapIterator; @@ -149,9 +151,16 @@ impl BuiltInConstructor for Map { }; // 5. Let adder be ? Get(map, "set"). - let adder = map.get(js_str!("set"), context)?; - - // 6. Return ? AddEntriesFromIterable(map, iterable, adder). + // 6. If IsCallable(adder) is false, throw a TypeError exception. + let adder = map + .get(js_str!("set"), context)? + .as_function() + .ok_or_else(|| { + JsNativeError::typ() + .with_message("Map: property `set` on new `Map` must be callable") + })?; + + // 7. Return ? AddEntriesFromIterable(map, iterable, adder). add_entries_from_iterable(&map, iterable, &adder, context) } } @@ -547,8 +556,8 @@ impl Map { let mut groups: IndexMap, BuildHasherDefault> = IndexMap::default(); - // 4. Let iteratorRecord be ? GetIterator(items). - let mut iterator = items.get_iterator(context, None, None)?; + // 4. Let iteratorRecord be ? GetIterator(items, sync). + let mut iterator = items.get_iterator(IteratorHint::Sync, context)?; // 5. Let k be 0. let mut k = 0u64; @@ -566,17 +575,15 @@ impl Map { return iterator.close(Err(error), context); } - // b. Let next be ? IteratorStep(iteratorRecord). - let done = iterator.step(context)?; - - // c. If next is false, then - if done { + // b. Let next be ? IteratorStepValue(iteratorRecord). + let Some(next) = iterator.step_value(context)? else { + // c. If next is false, then // i. Return groups. break; - } + }; - // d. Let value be ? IteratorValue(next). - let value = iterator.value(context)?; + // d. Let value be next. + let value = next; // e. Let key be Completion(Call(callbackfn, undefined, « value, 𝔽(k) »)). let key = callback.call(&JsValue::undefined(), &[value.clone(), k.into()], context); @@ -585,8 +592,8 @@ impl Map { let mut key = if_abrupt_close_iterator!(key, iterator, context); // h. Else, - // i. Assert: keyCoercion is zero. - // ii. If key is -0𝔽, set key to +0𝔽. + // i. Assert: keyCoercion is collection. + // ii. Set key to CanonicalizeKeyedCollectionKey(key). if key.as_number() == Some(-0.0) { key = 0.into(); } @@ -632,31 +639,20 @@ impl Map { pub(crate) fn add_entries_from_iterable( target: &JsObject, iterable: &JsValue, - adder: &JsValue, + adder: &JsFunction, context: &mut Context, ) -> JsResult { - // 1. If IsCallable(adder) is false, throw a TypeError exception. - let adder = adder.as_callable().ok_or_else(|| { - JsNativeError::typ().with_message("property `set` of `NewTarget` is not callable") - })?; - - // 2. Let iteratorRecord be ? GetIterator(iterable). - let mut iterator_record = iterable.get_iterator(context, None, None)?; - - // 3. Repeat, - loop { - // a. Let next be ? IteratorStep(iteratorRecord). - // b. If next is false, return target. - // c. Let nextItem be ? IteratorValue(next). - if iterator_record.step(context)? { - return Ok(target.clone().into()); - }; - - let next_item = iterator_record.value(context)?; - - let Some(next_item) = next_item.as_object() else { - // d. If Type(nextItem) is not Object, then - // i. Let error be ThrowCompletion(a newly created TypeError object). + // 1. Let iteratorRecord be ? GetIterator(iterable, sync). + let mut iterator_record = iterable.get_iterator(IteratorHint::Sync, context)?; + + // 2. Repeat, + // a. Let next be ? IteratorStepValue(iteratorRecord). + // b. If next is done, return target. + while let Some(next) = iterator_record.step_value(context)? { + let Some(next) = next.as_object() else { + // c. If next is not an Object, then + // i. Let error be ThrowCompletion(a newly created TypeError object). + // ii. Return ? IteratorClose(iteratorRecord, error). let err = Err(JsNativeError::typ() .with_message("cannot get key and value from primitive item of `iterable`") .into()); @@ -665,26 +661,19 @@ pub(crate) fn add_entries_from_iterable( return iterator_record.close(err, context); }; - // e. Let k be Get(nextItem, "0"). - // f. IfAbruptCloseIterator(k, iteratorRecord). - let key = match next_item.get(0, context) { - Ok(val) => val, - err => return iterator_record.close(err, context), - }; + // d. Let k be Completion(Get(next, "0")). + // e. IfAbruptCloseIterator(k, iteratorRecord). + let key = if_abrupt_close_iterator!(next.get(0, context), iterator_record, context); - // g. Let v be Get(nextItem, "1"). - // h. IfAbruptCloseIterator(v, iteratorRecord). - let value = match next_item.get(1, context) { - Ok(val) => val, - err => return iterator_record.close(err, context), - }; + // f. Let v be Completion(Get(next, "1")). + // g. IfAbruptCloseIterator(v, iteratorRecord). + let value = if_abrupt_close_iterator!(next.get(1, context), iterator_record, context); - // i. Let status be Call(adder, target, « k, v »). + // h. Let status be Completion(Call(adder, target, « k, v »)). + // i. IfAbruptCloseIterator(status, iteratorRecord). let status = adder.call(&target.clone().into(), &[key, value], context); - - // j. IfAbruptCloseIterator(status, iteratorRecord). - if status.is_err() { - return iterator_record.close(status, context); - } + if_abrupt_close_iterator!(status, iterator_record, context); } + + Ok(target.clone().into()) } diff --git a/core/engine/src/builtins/object/mod.rs b/core/engine/src/builtins/object/mod.rs index 92d2abe835..d90a5bd06a 100644 --- a/core/engine/src/builtins/object/mod.rs +++ b/core/engine/src/builtins/object/mod.rs @@ -17,7 +17,7 @@ use super::{ error::ErrorObject, Array, BuiltInBuilder, BuiltInConstructor, Date, IntrinsicObject, RegExp, }; use crate::{ - builtins::{map, BuiltInObject}, + builtins::{iterable::IteratorHint, map, BuiltInObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, @@ -1339,7 +1339,7 @@ impl OrdinaryObject { let adder = closure.length(2).name("").build(); // 6. Return ? AddEntriesFromIterable(obj, iterable, adder). - map::add_entries_from_iterable(&obj, iterable, &adder.into(), context) + map::add_entries_from_iterable(&obj, iterable, &adder, context) } /// [`Object.groupBy ( items, callbackfn )`][spec] @@ -1362,7 +1362,7 @@ impl OrdinaryObject { // 1. Let groups be ? GroupBy(items, callbackfn, property). // `GroupBy` - // https://tc39.es/proposal-array-grouping/#sec-group-by + // https://tc39.es/ecma262/#sec-groupby // inlined to change the key type. // 1. Perform ? RequireObjectCoercible(items). @@ -1377,8 +1377,8 @@ impl OrdinaryObject { let mut groups: IndexMap, BuildHasherDefault> = IndexMap::default(); - // 4. Let iteratorRecord be ? GetIterator(items). - let mut iterator = items.get_iterator(context, None, None)?; + // 4. Let iteratorRecord be ? GetIterator(items, sync). + let mut iterator = items.get_iterator(IteratorHint::Sync, context)?; // 5. Let k be 0. let mut k = 0u64; @@ -1396,17 +1396,15 @@ impl OrdinaryObject { return iterator.close(Err(error), context); } - // b. Let next be ? IteratorStep(iteratorRecord). - let done = iterator.step(context)?; - - // c. If next is false, then - if done { + // b. Let next be ? IteratorStepValue(iteratorRecord). + let Some(next) = iterator.step_value(context)? else { + // c. If next is false, then // i. Return groups. break; - } + }; - // d. Let value be ? IteratorValue(next). - let value = iterator.value(context)?; + // d. Let value be next. + let value = next; // e. Let key be Completion(Call(callbackfn, undefined, « value, 𝔽(k) »)). let key = callback.call(&JsValue::undefined(), &[value.clone(), k.into()], context); diff --git a/core/engine/src/builtins/promise/mod.rs b/core/engine/src/builtins/promise/mod.rs index e377938055..f9e0fe4e08 100644 --- a/core/engine/src/builtins/promise/mod.rs +++ b/core/engine/src/builtins/promise/mod.rs @@ -3,7 +3,10 @@ #[cfg(test)] mod tests; -use super::{iterable::IteratorRecord, BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; +use super::{ + iterable::{IteratorHint, IteratorRecord}, + BuiltInBuilder, BuiltInConstructor, IntrinsicObject, +}; use crate::{ builtins::{Array, BuiltInObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -581,8 +584,10 @@ impl Promise { let promise_resolve = if_abrupt_reject_promise!(promise_resolve, promise_capability, context); - // 5. Let iteratorRecord be Completion(GetIterator(iterable)). - let iterator_record = args.get_or_undefined(0).get_iterator(context, None, None); + // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)). + let iterator_record = args + .get_or_undefined(0) + .get_iterator(IteratorHint::Sync, context); // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). let mut iterator_record = @@ -649,56 +654,22 @@ impl Promise { let mut index = 0; // 4. Repeat, - loop { - // a. Let next be Completion(IteratorStep(iteratorRecord)). - // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. - // c. ReturnIfAbrupt(next). - let done = iterator_record.step(context)?; - - // d. If next is false, then - // i. Set iteratorRecord.[[Done]] to true. - if done { - // ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. - remaining_elements_count.set(remaining_elements_count.get() - 1); - - // iii. If remainingElementsCount.[[Value]] is 0, then - if remaining_elements_count.get() == 0 { - // 1. Let valuesArray be CreateArrayFromList(values). - let values_array = - Array::create_array_from_list(values.borrow().iter().cloned(), context); - - // 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »). - result_capability.functions.resolve.call( - &JsValue::undefined(), - &[values_array.into()], - context, - )?; - } - - // iv. Return resultCapability.[[Promise]]. - return Ok(result_capability.promise.clone()); - } - - // e. Let nextValue be Completion(IteratorValue(next)). - // f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. - // g. ReturnIfAbrupt(nextValue). - let next_value = iterator_record.value(context)?; - - // h. Append undefined to values. + while let Some(next) = iterator_record.step_value(context)? { + // c. Append undefined to values. values.borrow_mut().push(JsValue::Undefined); - // i. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). + // d. Let nextPromise be ? Call(promiseResolve, constructor, « next »). let next_promise = - promise_resolve.call(&constructor.clone().into(), &[next_value], context)?; - - // j. Let steps be the algorithm steps defined in Promise.all Resolve Element Functions. - // k. Let length be the number of non-optional parameters of the function definition in Promise.all Resolve Element Functions. - // l. Let onFulfilled be CreateBuiltinFunction(steps, length, "", « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »). - // m. Set onFulfilled.[[AlreadyCalled]] to false. - // n. Set onFulfilled.[[Index]] to index. - // o. Set onFulfilled.[[Values]] to values. - // p. Set onFulfilled.[[Capability]] to resultCapability. - // q. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. + promise_resolve.call(&constructor.clone().into(), &[next], context)?; + + // e. Let steps be the algorithm steps defined in Promise.all Resolve Element Functions. + // f. Let length be the number of non-optional parameters of the function definition in Promise.all Resolve Element Functions. + // g. Let onFulfilled be CreateBuiltinFunction(steps, length, "", « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »). + // h. Set onFulfilled.[[AlreadyCalled]] to false. + // i. Set onFulfilled.[[Index]] to index. + // j. Set onFulfilled.[[Values]] to values. + // k. Set onFulfilled.[[Capability]] to resultCapability. + // l. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. let on_fulfilled = FunctionObjectBuilder::new( context.realm(), NativeFunction::from_copy_closure_with_captures( @@ -761,10 +732,10 @@ impl Promise { .constructor(false) .build(); - // r. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1. + // m. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1. remaining_elements_count.set(remaining_elements_count.get() + 1); - // s. Perform ? Invoke(nextPromise, "then", « onFulfilled, resultCapability.[[Reject]] »). + // n. Perform ? Invoke(nextPromise, "then", « onFulfilled, resultCapability.[[Reject]] »). next_promise.invoke( js_str!("then"), &[ @@ -774,9 +745,30 @@ impl Promise { context, )?; - // t. Set index to index + 1. + // o. Set index to index + 1. index += 1; } + + // b. If next is done, then + // i. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + remaining_elements_count.set(remaining_elements_count.get() - 1); + + // ii. If remainingElementsCount.[[Value]] = 0, then + if remaining_elements_count.get() == 0 { + // 1. Let valuesArray be CreateArrayFromList(values). + let values_array = + Array::create_array_from_list(values.borrow().iter().cloned(), context); + + // 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »). + result_capability.functions.resolve.call( + &JsValue::undefined(), + &[values_array.into()], + context, + )?; + } + + // iii. Return resultCapability.[[Promise]]. + Ok(result_capability.promise.clone()) } /// `Promise.allSettled ( iterable )` @@ -807,8 +799,10 @@ impl Promise { let promise_resolve = if_abrupt_reject_promise!(promise_resolve, promise_capability, context); - // 5. Let iteratorRecord be Completion(GetIterator(iterable)). - let iterator_record = args.get_or_undefined(0).get_iterator(context, None, None); + // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)). + let iterator_record = args + .get_or_undefined(0) + .get_iterator(IteratorHint::Sync, context); // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). let mut iterator_record = @@ -875,59 +869,23 @@ impl Promise { let mut index = 0; // 4. Repeat, - loop { - // a. Let next be Completion(IteratorStep(iteratorRecord)). - // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. - // c. ReturnIfAbrupt(next). - let done = iterator_record.step(context)?; - - // d. If next is false, then - if done { - // i. Set iteratorRecord.[[Done]] to true. - // ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. - remaining_elements_count.set(remaining_elements_count.get() - 1); - - // iii. If remainingElementsCount.[[Value]] is 0, then - if remaining_elements_count.get() == 0 { - // 1. Let valuesArray be CreateArrayFromList(values). - let values_array = Array::create_array_from_list( - values.borrow().as_slice().iter().cloned(), - context, - ); - - // 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »). - result_capability.functions.resolve.call( - &JsValue::undefined(), - &[values_array.into()], - context, - )?; - } - - // iv. Return resultCapability.[[Promise]]. - return Ok(result_capability.promise.clone()); - } - - // e. Let nextValue be Completion(IteratorValue(next)). - // f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. - // g. ReturnIfAbrupt(nextValue). - let next_value = iterator_record.value(context)?; - - // h. Append undefined to values. + while let Some(next) = iterator_record.step_value(context)? { + // c. Append undefined to values. values.borrow_mut().push(JsValue::undefined()); - // i. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). + // d. Let nextPromise be ? Call(promiseResolve, constructor, « next »). let next_promise = - promise_resolve.call(&constructor.clone().into(), &[next_value], context)?; - - // j. Let stepsFulfilled be the algorithm steps defined in Promise.allSettled Resolve Element Functions. - // k. Let lengthFulfilled be the number of non-optional parameters of the function definition in Promise.allSettled Resolve Element Functions. - // l. Let onFulfilled be CreateBuiltinFunction(stepsFulfilled, lengthFulfilled, "", « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »). - // m. Let alreadyCalled be the Record { [[Value]]: false }. - // n. Set onFulfilled.[[AlreadyCalled]] to alreadyCalled. - // o. Set onFulfilled.[[Index]] to index. - // p. Set onFulfilled.[[Values]] to values. - // q. Set onFulfilled.[[Capability]] to resultCapability. - // r. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. + promise_resolve.call(&constructor.clone().into(), &[next], context)?; + + // e. Let stepsFulfilled be the algorithm steps defined in Promise.allSettled Resolve Element Functions. + // f. Let lengthFulfilled be the number of non-optional parameters of the function definition in Promise.allSettled Resolve Element Functions. + // g. Let onFulfilled be CreateBuiltinFunction(stepsFulfilled, lengthFulfilled, "", « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »). + // h. Let alreadyCalled be the Record { [[Value]]: false }. + // i. Set onFulfilled.[[AlreadyCalled]] to alreadyCalled. + // j. Set onFulfilled.[[Index]] to index. + // k. Set onFulfilled.[[Values]] to values. + // l. Set onFulfilled.[[Capability]] to resultCapability. + // m. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. let on_fulfilled = FunctionObjectBuilder::new( context.realm(), NativeFunction::from_copy_closure_with_captures( @@ -1010,14 +968,14 @@ impl Promise { .constructor(false) .build(); - // s. Let stepsRejected be the algorithm steps defined in Promise.allSettled Reject Element Functions. - // t. Let lengthRejected be the number of non-optional parameters of the function definition in Promise.allSettled Reject Element Functions. - // u. Let onRejected be CreateBuiltinFunction(stepsRejected, lengthRejected, "", « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »). - // v. Set onRejected.[[AlreadyCalled]] to alreadyCalled. - // w. Set onRejected.[[Index]] to index. - // x. Set onRejected.[[Values]] to values. - // y. Set onRejected.[[Capability]] to resultCapability. - // z. Set onRejected.[[RemainingElements]] to remainingElementsCount. + // n. Let stepsRejected be the algorithm steps defined in Promise.allSettled Reject Element Functions. + // o. Let lengthRejected be the number of non-optional parameters of the function definition in Promise.allSettled Reject Element Functions. + // p. Let onRejected be CreateBuiltinFunction(stepsRejected, lengthRejected, "", « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »). + // q. Set onRejected.[[AlreadyCalled]] to alreadyCalled. + // r. Set onRejected.[[Index]] to index. + // s. Set onRejected.[[Values]] to values. + // t. Set onRejected.[[Capability]] to resultCapability. + // u. Set onRejected.[[RemainingElements]] to remainingElementsCount. let on_rejected = FunctionObjectBuilder::new( context.realm(), NativeFunction::from_copy_closure_with_captures( @@ -1100,19 +1058,40 @@ impl Promise { .constructor(false) .build(); - // aa. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1. + // v. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1. remaining_elements_count.set(remaining_elements_count.get() + 1); - // ab. Perform ? Invoke(nextPromise, "then", « onFulfilled, onRejected »). + // w. Perform ? Invoke(nextPromise, "then", « onFulfilled, onRejected »). next_promise.invoke( js_str!("then"), &[on_fulfilled.into(), on_rejected.into()], context, )?; - // ac. Set index to index + 1. + // x. Set index to index + 1. index += 1; } + + // b. If next is done, then + // i. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + remaining_elements_count.set(remaining_elements_count.get() - 1); + + // ii. If remainingElementsCount.[[Value]] = 0, then + if remaining_elements_count.get() == 0 { + // 1. Let valuesArray be CreateArrayFromList(values). + let values_array = + Array::create_array_from_list(values.borrow().as_slice().iter().cloned(), context); + + // 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »). + result_capability.functions.resolve.call( + &JsValue::undefined(), + &[values_array.into()], + context, + )?; + } + + // iii. Return resultCapability.[[Promise]]. + Ok(result_capability.promise.clone()) } /// `Promise.any ( iterable )` @@ -1143,8 +1122,10 @@ impl Promise { let promise_resolve = if_abrupt_reject_promise!(promise_resolve, promise_capability, context); - // 5. Let iteratorRecord be Completion(GetIterator(iterable)). - let iterator_record = args.get_or_undefined(0).get_iterator(context, None, None); + // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)). + let iterator_record = args + .get_or_undefined(0) + .get_iterator(IteratorHint::Sync, context); // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). let mut iterator_record = @@ -1211,61 +1192,23 @@ impl Promise { let mut index = 0; // 4. Repeat, - loop { - // a. Let next be Completion(IteratorStep(iteratorRecord)). - // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. - // c. ReturnIfAbrupt(next). - let done = iterator_record.step(context)?; - - // d. If next is false, then - if done { - // i. Set iteratorRecord.[[Done]] to true. - - // ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. - remaining_elements_count.set(remaining_elements_count.get() - 1); - - // iii. If remainingElementsCount.[[Value]] is 0, then - if remaining_elements_count.get() == 0 { - // 1. Let error be a newly created AggregateError object. - // 2. Perform ! DefinePropertyOrThrow(error, "errors", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: CreateArrayFromList(errors) }). - let error = JsNativeError::aggregate( - errors - .borrow() - .iter() - .cloned() - .map(JsError::from_opaque) - .collect(), - ) - .with_message("no promise in Promise.any was fulfilled."); - - // 3. Return ThrowCompletion(error). - return Err(error.into()); - } - - // iv. Return resultCapability.[[Promise]]. - return Ok(result_capability.promise.clone()); - } - - // e. Let nextValue be Completion(IteratorValue(next)). - // f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. - // g. ReturnIfAbrupt(nextValue). - let next_value = iterator_record.value(context)?; - - // h. Append undefined to errors. + // a. Let next be ? IteratorStepValue(iteratorRecord). + while let Some(next) = iterator_record.step_value(context)? { + // c. Append undefined to errors. errors.borrow_mut().push(JsValue::undefined()); - // i. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). + // d. Let nextPromise be ? Call(promiseResolve, constructor, « next »). let next_promise = - promise_resolve.call(&constructor.clone().into(), &[next_value], context)?; - - // j. Let stepsRejected be the algorithm steps defined in Promise.any Reject Element Functions. - // k. Let lengthRejected be the number of non-optional parameters of the function definition in Promise.any Reject Element Functions. - // l. Let onRejected be CreateBuiltinFunction(stepsRejected, lengthRejected, "", « [[AlreadyCalled]], [[Index]], [[Errors]], [[Capability]], [[RemainingElements]] »). - // m. Set onRejected.[[AlreadyCalled]] to false. - // n. Set onRejected.[[Index]] to index. - // o. Set onRejected.[[Errors]] to errors. - // p. Set onRejected.[[Capability]] to resultCapability. - // q. Set onRejected.[[RemainingElements]] to remainingElementsCount. + promise_resolve.call(&constructor.clone().into(), &[next], context)?; + + // e. Let stepsRejected be the algorithm steps defined in Promise.any Reject Element Functions. + // f. Let lengthRejected be the number of non-optional parameters of the function definition in Promise.any Reject Element Functions. + // g. Let onRejected be CreateBuiltinFunction(stepsRejected, lengthRejected, "", « [[AlreadyCalled]], [[Index]], [[Errors]], [[Capability]], [[RemainingElements]] »). + // h. Set onRejected.[[AlreadyCalled]] to false. + // i. Set onRejected.[[Index]] to index. + // j. Set onRejected.[[Errors]] to errors. + // k. Set onRejected.[[Capability]] to resultCapability. + // l. Set onRejected.[[RemainingElements]] to remainingElementsCount. let on_rejected = FunctionObjectBuilder::new( context.realm(), NativeFunction::from_copy_closure_with_captures( @@ -1336,10 +1279,10 @@ impl Promise { .constructor(false) .build(); - // r. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1. + // m. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1. remaining_elements_count.set(remaining_elements_count.get() + 1); - // s. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], onRejected »). + // n. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], onRejected »). next_promise.invoke( js_str!("then"), &[ @@ -1349,9 +1292,33 @@ impl Promise { context, )?; - // t. Set index to index + 1. + // o. Set index to index + 1. index += 1; } + + // b. If next is done, then + // i. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + remaining_elements_count.set(remaining_elements_count.get() - 1); + // ii. If remainingElementsCount.[[Value]] = 0, then + if remaining_elements_count.get() == 0 { + // 1. Let error be a newly created AggregateError object. + let error = JsNativeError::aggregate( + errors + .borrow() + .iter() + .cloned() + .map(JsError::from_opaque) + .collect(), + ) + .with_message("no promise in Promise.any was fulfilled."); + + // 2. Perform ! DefinePropertyOrThrow(error, "errors", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: CreateArrayFromList(errors) }). + // 3. Return ThrowCompletion(error). + return Err(error.into()); + } + + // iii. Return resultCapability.[[Promise]]. + Ok(result_capability.promise.clone()) } /// `Promise.race ( iterable )` @@ -1387,8 +1354,8 @@ impl Promise { let promise_resolve = if_abrupt_reject_promise!(promise_resolve, promise_capability, context); - // 5. Let iteratorRecord be Completion(GetIterator(iterable)). - let iterator_record = iterable.get_iterator(context, None, None); + // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)). + let iterator_record = iterable.get_iterator(IteratorHint::Sync, context); // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). let mut iterator_record = @@ -1440,29 +1407,13 @@ impl Promise { context: &mut Context, ) -> JsResult { let constructor = constructor.clone().into(); - // 1. Repeat, - loop { - // a. Let next be Completion(IteratorStep(iteratorRecord)). - // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. - // c. ReturnIfAbrupt(next). - let done = iterator_record.step(context)?; - - if done { - // d. If next is false, then - // i. Set iteratorRecord.[[Done]] to true. - // ii. Return resultCapability.[[Promise]]. - return Ok(result_capability.promise.clone()); - } - - // e. Let nextValue be Completion(IteratorValue(next)). - // f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. - // g. ReturnIfAbrupt(nextValue). - let next_value = iterator_record.value(context)?; - // h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). - let next_promise = promise_resolve.call(&constructor, &[next_value], context)?; - - // i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »). + // 1. Repeat, + // a. Let next be ? IteratorStepValue(iteratorRecord). + while let Some(next) = iterator_record.step_value(context)? { + // c. Let nextPromise be ? Call(promiseResolve, constructor, « next »). + let next_promise = promise_resolve.call(&constructor, &[next], context)?; + // d. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »). next_promise.invoke( js_str!("then"), &[ @@ -1472,6 +1423,10 @@ impl Promise { context, )?; } + + // b. If next is done, then + // i. Return resultCapability.[[Promise]]. + Ok(result_capability.promise.clone()) } /// `Promise.reject ( r )` diff --git a/core/engine/src/builtins/set/mod.rs b/core/engine/src/builtins/set/mod.rs index 2927a9e846..c29fca4f4e 100644 --- a/core/engine/src/builtins/set/mod.rs +++ b/core/engine/src/builtins/set/mod.rs @@ -36,6 +36,8 @@ use num_traits::Zero; pub(crate) use set_iterator::SetIterator; +use super::iterable::IteratorHint; + #[derive(Debug, Clone)] pub(crate) struct Set; @@ -110,6 +112,9 @@ impl BuiltInConstructor for Set { const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::set; + /// [`Set ( [ iterable ] )`][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-set-iterable fn constructor( new_target: &JsValue, args: &[JsValue], @@ -146,26 +151,20 @@ impl BuiltInConstructor for Set { JsNativeError::typ().with_message("'add' of 'newTarget' is not a function") })?; - // 7. Let iteratorRecord be ? GetIterator(iterable). - let mut iterator_record = iterable.clone().get_iterator(context, None, None)?; + // 7. Let iteratorRecord be ? GetIterator(iterable, sync). + let mut iterator_record = iterable.clone().get_iterator(IteratorHint::Sync, context)?; // 8. Repeat, - // a. Let next be ? IteratorStep(iteratorRecord). - // b. If next is false, return set. - // c. Let nextValue be ? IteratorValue(next). - // d. Let status be Completion(Call(adder, set, « nextValue »)). - // e. IfAbruptCloseIterator(status, iteratorRecord). - while !iterator_record.step(context)? { - let next = iterator_record.value(context)?; - // c - - // d, e + // a. Let next be ? IteratorStepValue(iteratorRecord). + while let Some(next) = iterator_record.step_value(context)? { + // c. Let status be Completion(Call(adder, set, « next »)). if let Err(status) = adder.call(&set.clone().into(), &[next], context) { + // d. IfAbruptCloseIterator(status, iteratorRecord). return iterator_record.close(Err(status), context); } } - // 8.b + // b. If next is done, return set. Ok(set.into()) } } diff --git a/core/engine/src/builtins/temporal/mod.rs b/core/engine/src/builtins/temporal/mod.rs index 65bb87cf23..9f4fd983aa 100644 --- a/core/engine/src/builtins/temporal/mod.rs +++ b/core/engine/src/builtins/temporal/mod.rs @@ -189,28 +189,26 @@ pub(crate) fn _iterator_to_list_of_types( // 1. Let values be a new empty List. let mut values = Vec::new(); - // 2. Let next be true. - // 3. Repeat, while next is not false, - // a. Set next to ? IteratorStep(iteratorRecord). - // b. If next is not false, then - while iterator.step(context)? { - // i. Let nextValue be ? IteratorValue(next). - let next_value = iterator.value(context)?; - // ii. If Type(nextValue) is not an element of elementTypes, then - if element_types.contains(&next_value.get_type()) { - // 1. Let completion be ThrowCompletion(a newly created TypeError object). + // 2. Repeat, + // a. Let next be ? IteratorStepValue(iteratorRecord). + while let Some(next) = iterator.step_value(context)? { + // c. If Type(next) is not an element of elementTypes, then + + if element_types.contains(&next.get_type()) { + // i. Let completion be ThrowCompletion(a newly created TypeError object). let completion = JsNativeError::typ() .with_message("IteratorNext is not within allowed type values."); - // NOTE: The below should return as we are forcing a ThrowCompletion. - // 2. Return ? IteratorClose(iteratorRecord, completion). + // ii. Return ? IteratorClose(iteratorRecord, completion). let _never = iterator.close(Err(completion.into()), context)?; } - // iii. Append nextValue to the end of the List values. - values.push(next_value); + + // d. Append next to the end of the List values. + values.push(next); } - // 4. Return values. + // b. If next is done, then + // i. Return values. Ok(values) } diff --git a/core/engine/src/builtins/typed_array/builtin.rs b/core/engine/src/builtins/typed_array/builtin.rs index c419132b2e..cde663cd6e 100644 --- a/core/engine/src/builtins/typed_array/builtin.rs +++ b/core/engine/src/builtins/typed_array/builtin.rs @@ -16,7 +16,6 @@ use crate::{ utils::{memcpy, memmove, SliceRefMut}, ArrayBuffer, BufferObject, }, - iterable::iterable_to_list, Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -228,7 +227,9 @@ impl BuiltinTypedArray { // 6. If usingIterator is not undefined, then if let Some(using_iterator) = using_iterator { // a. Let values be ? IterableToList(source, usingIterator). - let values = iterable_to_list(context, source, Some(using_iterator))?; + let values = source + .get_iterator_from_method(&using_iterator, context)? + .into_list(context)?; // b. Let len be the number of elements in values. // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). diff --git a/core/engine/src/builtins/typed_array/mod.rs b/core/engine/src/builtins/typed_array/mod.rs index ee70b63a50..168464d25e 100644 --- a/core/engine/src/builtins/typed_array/mod.rs +++ b/core/engine/src/builtins/typed_array/mod.rs @@ -13,10 +13,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray use crate::{ - builtins::{ - iterable::iterable_to_list, BuiltInBuilder, BuiltInConstructor, BuiltInObject, - IntrinsicObject, - }, + builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, @@ -199,13 +196,14 @@ impl BuiltInConstructor for T { // either a [[TypedArrayName]] or an [[ArrayBufferData]] internal slot. // 2. Let usingIterator be ? GetMethod(firstArgument, @@iterator). - let using_iterator = first_argument.get_method(JsSymbol::iterator(), context)?; // 3. If usingIterator is not undefined, then if let Some(using_iterator) = using_iterator { - // a. Let values be ? IterableToList(firstArgument, usingIterator). - let values = iterable_to_list(context, &first_argument.into(), Some(using_iterator))?; + // a. Let values be ? IteratorToList(? GetIteratorFromMethod(firstArgument, usingIterator)). + let values = JsValue::from(first_argument.clone()) + .get_iterator_from_method(&using_iterator, context)? + .into_list(context)?; // b. Perform ? InitializeTypedArrayFromList(O, values). BuiltinTypedArray::initialize_from_list::(proto, values, context) diff --git a/core/engine/src/builtins/weak_map/mod.rs b/core/engine/src/builtins/weak_map/mod.rs index 57e2bd588e..dced501b8b 100644 --- a/core/engine/src/builtins/weak_map/mod.rs +++ b/core/engine/src/builtins/weak_map/mod.rs @@ -101,14 +101,11 @@ impl BuiltInConstructor for WeakMap { } // 5. Let adder be ? Get(map, "set"). - let adder = map.get(js_str!("set"), context)?; - // 6. If IsCallable(adder) is false, throw a TypeError exception. - if !adder.is_callable() { - return Err(JsNativeError::typ() - .with_message("WeakMap: 'add' is not a function") - .into()); - } + let adder = map + .get(js_str!("set"), context)? + .as_function() + .ok_or_else(|| JsNativeError::typ().with_message("WeakMap: 'add' is not a function"))?; // 7. Return ? AddEntriesFromIterable(map, iterable, adder). add_entries_from_iterable(&map, iterable, &adder, context) diff --git a/core/engine/src/builtins/weak_set/mod.rs b/core/engine/src/builtins/weak_set/mod.rs index 493051b873..4d19526bf0 100644 --- a/core/engine/src/builtins/weak_set/mod.rs +++ b/core/engine/src/builtins/weak_set/mod.rs @@ -22,6 +22,8 @@ use boa_gc::{Finalize, Trace}; use boa_macros::js_str; use boa_profiler::Profiler; +use super::iterable::IteratorHint; + type NativeWeakSet = boa_gc::WeakMap; #[derive(Debug, Trace, Finalize)] @@ -104,23 +106,20 @@ impl BuiltInConstructor for WeakSet { .as_callable() .ok_or_else(|| JsNativeError::typ().with_message("WeakSet: 'add' is not a function"))?; - // 7. Let iteratorRecord be ? GetIterator(iterable). - let mut iterator_record = iterable.clone().get_iterator(context, None, None)?; + // 7. Let iteratorRecord be ? GetIterator(iterable, sync). + let mut iterator_record = iterable.clone().get_iterator(IteratorHint::Sync, context)?; // 8. Repeat, - // a. Let next be ? IteratorStep(iteratorRecord). - while !iterator_record.step(context)? { - // c. Let nextValue be ? IteratorValue(next). - let next = iterator_record.value(context)?; - - // d. Let status be Completion(Call(adder, set, « nextValue »)). - // e. IfAbruptCloseIterator(status, iteratorRecord). + // a. Let next be ? IteratorStepValue(iteratorRecord). + while let Some(next) = iterator_record.step_value(context)? { + // c. Let status be Completion(Call(adder, set, « next »)). if let Err(status) = adder.call(&weak_set.clone().into(), &[next], context) { + // d. IfAbruptCloseIterator(status, iteratorRecord). return iterator_record.close(Err(status), context); } } - // b. If next is false, return set. + // b. If next is done, return set. Ok(weak_set.into()) } } diff --git a/core/engine/src/object/builtins/jsmap.rs b/core/engine/src/object/builtins/jsmap.rs index 196907d027..7027bbaa46 100644 --- a/core/engine/src/object/builtins/jsmap.rs +++ b/core/engine/src/object/builtins/jsmap.rs @@ -1,7 +1,10 @@ //! A Rust API wrapper for Boa's `Map` Builtin ECMAScript Object use crate::{ - builtins::map::{add_entries_from_iterable, ordered_map::OrderedMap}, - builtins::Map, + builtins::{ + iterable::IteratorHint, + map::{add_entries_from_iterable, ordered_map::OrderedMap}, + Map, + }, error::JsNativeError, object::{JsFunction, JsMapIterator, JsObject}, value::TryFromJs, @@ -126,8 +129,11 @@ impl JsMap { // Let adder be Get(map, "set") per spec. This action should not fail with default map. let adder = map - .get(js_str!("set"), context) - .expect("creating a map with the default prototype must not fail"); + .get(js_str!("set"), context)? + .as_function() + .ok_or_else(|| { + JsNativeError::typ().with_message("property `set` on new `Map` must be callable") + })?; let _completion_record = add_entries_from_iterable(&map, iterable, &adder, context)?; @@ -199,7 +205,7 @@ impl JsMap { #[inline] pub fn entries(&self, context: &mut Context) -> JsResult { let iterator_record = Map::entries(&self.inner.clone().into(), &[], context)? - .get_iterator(context, None, None)?; + .get_iterator(IteratorHint::Sync, context)?; let map_iterator_object = iterator_record.iterator(); JsMapIterator::from_object(map_iterator_object.clone()) } @@ -208,7 +214,7 @@ impl JsMap { #[inline] pub fn keys(&self, context: &mut Context) -> JsResult { let iterator_record = Map::keys(&self.inner.clone().into(), &[], context)? - .get_iterator(context, None, None)?; + .get_iterator(IteratorHint::Sync, context)?; let map_iterator_object = iterator_record.iterator(); JsMapIterator::from_object(map_iterator_object.clone()) } @@ -400,7 +406,7 @@ impl JsMap { #[inline] pub fn values(&self, context: &mut Context) -> JsResult { let iterator_record = Map::values(&self.inner.clone().into(), &[], context)? - .get_iterator(context, None, None)?; + .get_iterator(IteratorHint::Sync, context)?; let map_iterator_object = iterator_record.iterator(); JsMapIterator::from_object(map_iterator_object.clone()) } diff --git a/core/engine/src/object/builtins/jsset.rs b/core/engine/src/object/builtins/jsset.rs index d886c15c8b..6889fc9c25 100644 --- a/core/engine/src/object/builtins/jsset.rs +++ b/core/engine/src/object/builtins/jsset.rs @@ -4,7 +4,7 @@ use std::ops::Deref; use boa_gc::{Finalize, Trace}; use crate::{ - builtins::{set::ordered_set::OrderedSet, Set}, + builtins::{iterable::IteratorHint, set::ordered_set::OrderedSet, Set}, error::JsNativeError, object::{JsFunction, JsObject, JsSetIterator}, value::TryFromJs, @@ -104,7 +104,7 @@ impl JsSet { #[inline] pub fn values(&self, context: &mut Context) -> JsResult { let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? - .get_iterator(context, None, None)?; + .get_iterator(IteratorHint::Sync, context)?; JsSetIterator::from_object(iterator_object.iterator().clone()) } @@ -117,7 +117,7 @@ impl JsSet { #[inline] pub fn keys(&self, context: &mut Context) -> JsResult { let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? - .get_iterator(context, None, None)?; + .get_iterator(IteratorHint::Sync, context)?; JsSetIterator::from_object(iterator_object.iterator().clone()) } diff --git a/core/engine/src/vm/opcode/iteration/get.rs b/core/engine/src/vm/opcode/iteration/get.rs index a2bf610442..fc9aadef81 100644 --- a/core/engine/src/vm/opcode/iteration/get.rs +++ b/core/engine/src/vm/opcode/iteration/get.rs @@ -18,7 +18,7 @@ impl Operation for GetIterator { fn execute(context: &mut Context) -> JsResult { let object = context.vm.pop(); - let iterator = object.get_iterator(context, None, None)?; + let iterator = object.get_iterator(IteratorHint::Sync, context)?; context.vm.frame_mut().iterators.push(iterator); Ok(CompletionType::Normal) } @@ -38,7 +38,7 @@ impl Operation for GetAsyncIterator { fn execute(context: &mut Context) -> JsResult { let object = context.vm.pop(); - let iterator = object.get_iterator(context, Some(IteratorHint::Async), None)?; + let iterator = object.get_iterator(IteratorHint::Async, context)?; context.vm.frame_mut().iterators.push(iterator); Ok(CompletionType::Normal) } diff --git a/core/engine/src/vm/opcode/push/array.rs b/core/engine/src/vm/opcode/push/array.rs index 480e979ecf..d5650c4650 100644 --- a/core/engine/src/vm/opcode/push/array.rs +++ b/core/engine/src/vm/opcode/push/array.rs @@ -101,8 +101,7 @@ impl Operation for PushIteratorToArray { .expect("iterator stack should have at least an iterator"); let array = context.vm.pop(); - while !iterator.step(context)? { - let next = iterator.value(context)?; + while let Some(next) = iterator.step_value(context)? { Array::push(&array, &[next], context)?; } diff --git a/test262_config.toml b/test262_config.toml index 4a611dba04..a2d9285ffa 100644 --- a/test262_config.toml +++ b/test262_config.toml @@ -1,4 +1,4 @@ -commit = "12307f5c20a4c4211e69823939fd1872212894c5" +commit = "dde3050bdbfb8f425084077b6293563932d57ebc" [ignored] # Not implemented yet: