Browse Source

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>
pull/3969/head
José Julián Espina 3 months ago committed by GitHub
parent
commit
25705ef5a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 42
      core/engine/src/builtins/array/mod.rs
  2. 15
      core/engine/src/builtins/error/aggregate.rs
  3. 27
      core/engine/src/builtins/intl/list_format/mod.rs
  4. 16
      core/engine/src/builtins/iterable/async_from_sync_iterator.rs
  5. 242
      core/engine/src/builtins/iterable/mod.rs
  6. 107
      core/engine/src/builtins/map/mod.rs
  7. 24
      core/engine/src/builtins/object/mod.rs
  8. 349
      core/engine/src/builtins/promise/mod.rs
  9. 25
      core/engine/src/builtins/set/mod.rs
  10. 28
      core/engine/src/builtins/temporal/mod.rs
  11. 5
      core/engine/src/builtins/typed_array/builtin.rs
  12. 12
      core/engine/src/builtins/typed_array/mod.rs
  13. 11
      core/engine/src/builtins/weak_map/mod.rs
  14. 19
      core/engine/src/builtins/weak_set/mod.rs
  15. 20
      core/engine/src/object/builtins/jsmap.rs
  16. 6
      core/engine/src/object/builtins/jsset.rs
  17. 4
      core/engine/src/vm/opcode/iteration/get.rs
  18. 3
      core/engine/src/vm/opcode/push/array.rs
  19. 2
      test262_config.toml

42
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);
}

15
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,

27
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)
}

16
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::<Self>)
.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);

242
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<IteratorRecord> {
// 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<IteratorHint>,
method: Option<JsObject>,
) -> JsResult<IteratorRecord> {
// 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<bool> {
) -> JsResult<IteratorResult> {
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<bool> {
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<Option<JsValue>> {
// 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<JsObject>,
) -> JsResult<Vec<JsValue>> {
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<Vec<JsValue>> {
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)
}
}

107
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<JsValue, Vec<JsValue>, BuildHasherDefault<FxHasher>> =
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<JsValue> {
// 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())
}

24
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<PropertyKey, Vec<JsValue>, BuildHasherDefault<FxHasher>> =
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);

349
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<JsObject> {
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 )`

25
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())
}
}

28
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)
}

5
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) »).

12
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<T: TypedArrayMarker> 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::<T>(proto, values, context)

11
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)

19
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<ErasedVTableObject, ()>;
#[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())
}
}

20
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<JsMapIterator> {
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<JsMapIterator> {
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<JsMapIterator> {
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())
}

6
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<JsSetIterator> {
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<JsSetIterator> {
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())
}

4
core/engine/src/vm/opcode/iteration/get.rs

@ -18,7 +18,7 @@ impl Operation for GetIterator {
fn execute(context: &mut Context) -> JsResult<CompletionType> {
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<CompletionType> {
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)
}

3
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)?;
}

2
test262_config.toml

@ -1,4 +1,4 @@
commit = "12307f5c20a4c4211e69823939fd1872212894c5"
commit = "dde3050bdbfb8f425084077b6293563932d57ebc"
[ignored]
# Not implemented yet:

Loading…
Cancel
Save