Browse Source

Refactor builtin `Map` intrinsics to follow more closely the spec (#1572)

* Refactor builtin `Map` object and document methods

* Replace some calls to `is_function` with `is_callable`

* Remove `expect_map` methods and cleanup some `expect`s
pull/1573/head
jedel1043 3 years ago committed by GitHub
parent
commit
8ba500a26a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      boa/src/builtins/array/array_iterator.rs
  2. 6
      boa/src/builtins/array/mod.rs
  3. 40
      boa/src/builtins/iterable/mod.rs
  4. 106
      boa/src/builtins/map/map_iterator.rs
  5. 443
      boa/src/builtins/map/mod.rs
  6. 9
      boa/src/builtins/map/ordered_map.rs
  7. 12
      boa/src/builtins/number/mod.rs
  8. 8
      boa/src/builtins/object/for_in_iterator.rs
  9. 10
      boa/src/builtins/regexp/regexp_string_iterator.rs
  10. 10
      boa/src/builtins/set/mod.rs
  11. 12
      boa/src/builtins/set/set_iterator.rs
  12. 2
      boa/src/builtins/string/mod.rs
  13. 10
      boa/src/builtins/string/string_iterator.rs
  14. 22
      boa/src/object/mod.rs
  15. 8
      boa/src/syntax/ast/node/array/mod.rs
  16. 8
      boa/src/syntax/ast/node/call/mod.rs
  17. 18
      boa/src/syntax/ast/node/declaration/mod.rs
  18. 10
      boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs
  19. 10
      boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs
  20. 8
      boa/src/syntax/ast/node/new/mod.rs
  21. 2
      boa/src/value/mod.rs

21
boa/src/builtins/array/array_iterator.rs

@ -40,9 +40,9 @@ impl ArrayIterator {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-createarrayiterator /// [spec]: https://tc39.es/ecma262/#sec-createarrayiterator
pub(crate) fn create_array_iterator( pub(crate) fn create_array_iterator(
context: &Context,
array: JsValue, array: JsValue,
kind: PropertyNameKind, kind: PropertyNameKind,
context: &Context,
) -> JsValue { ) -> JsValue {
let array_iterator = JsValue::new_object(context); let array_iterator = JsValue::new_object(context);
array_iterator.set_data(ObjectData::array_iterator(Self::new(array, kind))); array_iterator.set_data(ObjectData::array_iterator(Self::new(array, kind)));
@ -68,9 +68,9 @@ impl ArrayIterator {
let index = array_iterator.next_index; let index = array_iterator.next_index;
if array_iterator.array.is_undefined() { if array_iterator.array.is_undefined() {
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
context,
JsValue::undefined(), JsValue::undefined(),
true, true,
context,
)); ));
} }
let len = array_iterator let len = array_iterator
@ -82,34 +82,31 @@ impl ArrayIterator {
if array_iterator.next_index >= len { if array_iterator.next_index >= len {
array_iterator.array = JsValue::undefined(); array_iterator.array = JsValue::undefined();
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
context,
JsValue::undefined(), JsValue::undefined(),
true, true,
context,
)); ));
} }
array_iterator.next_index = index + 1; array_iterator.next_index = index + 1;
match array_iterator.kind { return match array_iterator.kind {
PropertyNameKind::Key => { PropertyNameKind::Key => {
Ok(create_iter_result_object(context, index.into(), false)) Ok(create_iter_result_object(index.into(), false, context))
} }
PropertyNameKind::Value => { PropertyNameKind::Value => {
let element_value = array_iterator.array.get_field(index, context)?; let element_value = array_iterator.array.get_field(index, context)?;
Ok(create_iter_result_object(context, element_value, false)) Ok(create_iter_result_object(element_value, false, context))
} }
PropertyNameKind::KeyAndValue => { PropertyNameKind::KeyAndValue => {
let element_value = array_iterator.array.get_field(index, context)?; let element_value = array_iterator.array.get_field(index, context)?;
let result = let result =
Array::create_array_from_list([index.into(), element_value], context); Array::create_array_from_list([index.into(), element_value], context);
Ok(create_iter_result_object(context, result.into(), false)) Ok(create_iter_result_object(result.into(), false, context))
} }
};
} }
} else {
context.throw_type_error("`this` is not an ArrayIterator")
} }
} else {
context.throw_type_error("`this` is not an ArrayIterator") context.throw_type_error("`this` is not an ArrayIterator")
} }
}
/// Create the %ArrayIteratorPrototype% object /// Create the %ArrayIteratorPrototype% object
/// ///
@ -117,7 +114,7 @@ impl ArrayIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: JsValue) -> JsObject { pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype // Create prototype

6
boa/src/builtins/array/mod.rs

@ -2528,9 +2528,9 @@ impl Array {
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
Ok(ArrayIterator::create_array_iterator( Ok(ArrayIterator::create_array_iterator(
context,
this.clone(), this.clone(),
PropertyNameKind::Value, PropertyNameKind::Value,
context,
)) ))
} }
@ -2546,9 +2546,9 @@ impl Array {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
Ok(ArrayIterator::create_array_iterator( Ok(ArrayIterator::create_array_iterator(
context,
this.clone(), this.clone(),
PropertyNameKind::Key, PropertyNameKind::Key,
context,
)) ))
} }
@ -2568,9 +2568,9 @@ impl Array {
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
Ok(ArrayIterator::create_array_iterator( Ok(ArrayIterator::create_array_iterator(
context,
this.clone(), this.clone(),
PropertyNameKind::KeyAndValue, PropertyNameKind::KeyAndValue,
context,
)) ))
} }

40
boa/src/builtins/iterable/mod.rs

@ -25,22 +25,22 @@ impl IteratorPrototypes {
let iterator_prototype = create_iterator_prototype(context); let iterator_prototype = create_iterator_prototype(context);
Self { Self {
array_iterator: ArrayIterator::create_prototype( array_iterator: ArrayIterator::create_prototype(
context,
iterator_prototype.clone().into(), iterator_prototype.clone().into(),
context,
), ),
set_iterator: SetIterator::create_prototype(context, iterator_prototype.clone().into()), set_iterator: SetIterator::create_prototype(iterator_prototype.clone().into(), context),
string_iterator: StringIterator::create_prototype( string_iterator: StringIterator::create_prototype(
context,
iterator_prototype.clone().into(), iterator_prototype.clone().into(),
context,
), ),
regexp_string_iterator: RegExpStringIterator::create_prototype( regexp_string_iterator: RegExpStringIterator::create_prototype(
context,
iterator_prototype.clone().into(), iterator_prototype.clone().into(),
context,
), ),
map_iterator: MapIterator::create_prototype(context, iterator_prototype.clone().into()), map_iterator: MapIterator::create_prototype(iterator_prototype.clone().into(), context),
for_in_iterator: ForInIterator::create_prototype( for_in_iterator: ForInIterator::create_prototype(
context,
iterator_prototype.clone().into(), iterator_prototype.clone().into(),
context,
), ),
iterator_prototype, iterator_prototype,
} }
@ -85,7 +85,7 @@ impl IteratorPrototypes {
/// CreateIterResultObject( value, done ) /// CreateIterResultObject( value, done )
/// ///
/// Generates an object supporting the IteratorResult interface. /// Generates an object supporting the IteratorResult interface.
pub fn create_iter_result_object(context: &mut Context, value: JsValue, done: bool) -> JsValue { pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Context) -> JsValue {
// 1. Assert: Type(done) is Boolean. // 1. Assert: Type(done) is Boolean.
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
let obj = context.construct_object(); let obj = context.construct_object();
@ -101,12 +101,12 @@ pub fn create_iter_result_object(context: &mut Context, value: JsValue, done: bo
} }
/// Get an iterator record /// Get an iterator record
pub fn get_iterator(context: &mut Context, iterable: JsValue) -> JsResult<IteratorRecord> { pub fn get_iterator(iterable: &JsValue, context: &mut Context) -> JsResult<IteratorRecord> {
let iterator_function = iterable.get_field(WellKnownSymbols::iterator(), context)?; let iterator_function = iterable.get_field(WellKnownSymbols::iterator(), context)?;
if iterator_function.is_null_or_undefined() { if iterator_function.is_null_or_undefined() {
return Err(context.construct_type_error("Not an iterable")); return Err(context.construct_type_error("Not an iterable"));
} }
let iterator_object = context.call(&iterator_function, &iterable, &[])?; let iterator_object = context.call(&iterator_function, iterable, &[])?;
let next_function = iterator_object.get_field("next", context)?; let next_function = iterator_object.get_field("next", context)?;
if next_function.is_null_or_undefined() { if next_function.is_null_or_undefined() {
return Err(context.construct_type_error("Could not find property `next`")); return Err(context.construct_type_error("Could not find property `next`"));
@ -158,8 +158,8 @@ impl IteratorRecord {
let next = context.call(&self.next_function, &self.iterator_object, &[])?; let next = context.call(&self.next_function, &self.iterator_object, &[])?;
let done = next.get_field("done", context)?.to_boolean(); let done = next.get_field("done", context)?.to_boolean();
let next_result = next.get_field("value", context)?; let value = next.get_field("value", context)?;
Ok(IteratorResult::new(next_result, done)) Ok(IteratorResult { value, done })
} }
/// Cleanup the iterator /// Cleanup the iterator
@ -203,20 +203,6 @@ impl IteratorRecord {
#[derive(Debug)] #[derive(Debug)]
pub struct IteratorResult { pub struct IteratorResult {
value: JsValue, pub value: JsValue,
done: bool, pub done: bool,
}
impl IteratorResult {
fn new(value: JsValue, done: bool) -> Self {
Self { value, done }
}
pub fn is_done(&self) -> bool {
self.done
}
pub fn value(self) -> JsValue {
self.value
}
} }

106
boa/src/builtins/map/map_iterator.rs

@ -7,7 +7,7 @@ use crate::{
}; };
use gc::{Finalize, Trace}; use gc::{Finalize, Trace};
use super::{ordered_map::MapLock, Map}; use super::ordered_map::MapLock;
/// The Map Iterator object represents an iteration over a map. It implements the iterator protocol. /// The Map Iterator object represents an iteration over a map. It implements the iterator protocol.
/// ///
/// More information: /// More information:
@ -16,7 +16,7 @@ use super::{ordered_map::MapLock, Map};
/// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects /// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects
#[derive(Debug, Clone, Finalize, Trace)] #[derive(Debug, Clone, Finalize, Trace)]
pub struct MapIterator { pub struct MapIterator {
iterated_map: JsValue, iterated_map: Option<JsObject>,
map_next_index: usize, map_next_index: usize,
map_iteration_kind: PropertyNameKind, map_iteration_kind: PropertyNameKind,
lock: MapLock, lock: MapLock,
@ -25,17 +25,6 @@ pub struct MapIterator {
impl MapIterator { impl MapIterator {
pub(crate) const NAME: &'static str = "MapIterator"; pub(crate) const NAME: &'static str = "MapIterator";
/// Constructs a new `MapIterator`, that will iterate over `map`, starting at index 0
fn new(map: JsValue, kind: PropertyNameKind, context: &mut Context) -> JsResult<Self> {
let lock = Map::lock(&map, context)?;
Ok(MapIterator {
iterated_map: map,
map_next_index: 0,
map_iteration_kind: kind,
lock,
})
}
/// Abstract operation CreateMapIterator( map, kind ) /// Abstract operation CreateMapIterator( map, kind )
/// ///
/// Creates a new iterator over the given map. /// Creates a new iterator over the given map.
@ -45,17 +34,29 @@ impl MapIterator {
/// ///
/// [spec]: https://www.ecma-international.org/ecma-262/11.0/index.html#sec-createmapiterator /// [spec]: https://www.ecma-international.org/ecma-262/11.0/index.html#sec-createmapiterator
pub(crate) fn create_map_iterator( pub(crate) fn create_map_iterator(
context: &mut Context, map: &JsValue,
map: JsValue,
kind: PropertyNameKind, kind: PropertyNameKind,
context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
if let Some(map_obj) = map.as_object() {
if let Some(map) = map_obj.borrow_mut().as_map_mut() {
let lock = map.lock(map_obj.clone());
let iter = MapIterator {
iterated_map: Some(map_obj.clone()),
map_next_index: 0,
map_iteration_kind: kind,
lock,
};
let map_iterator = JsValue::new_object(context); let map_iterator = JsValue::new_object(context);
map_iterator.set_data(ObjectData::map_iterator(Self::new(map, kind, context)?)); map_iterator.set_data(ObjectData::map_iterator(iter));
map_iterator map_iterator
.as_object() .as_object()
.expect("map iterator object") .expect("map iterator object")
.set_prototype_instance(context.iterator_prototypes().map_iterator().into()); .set_prototype_instance(context.iterator_prototypes().map_iterator().into());
Ok(map_iterator) return Ok(map_iterator);
}
}
context.throw_type_error("`this` is not a Map")
} }
/// %MapIteratorPrototype%.next( ) /// %MapIteratorPrototype%.next( )
@ -67,77 +68,56 @@ impl MapIterator {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if let JsValue::Object(ref object) = this { let iterator_object = match this {
let mut object = object.borrow_mut(); JsValue::Object(obj) if obj.borrow().is_map_iterator() => obj,
if let Some(map_iterator) = object.as_map_iterator_mut() { _ => return context.throw_type_error("`this` is not a MapIterator"),
let m = &map_iterator.iterated_map; };
let mut index = map_iterator.map_next_index;
let item_kind = &map_iterator.map_iteration_kind;
if map_iterator.iterated_map.is_undefined() { let mut iterator_object = iterator_object.borrow_mut();
return Ok(create_iter_result_object(
context,
JsValue::undefined(),
true,
));
}
if let JsValue::Object(ref object) = m { let map_iterator = iterator_object
if let Some(entries) = object.borrow().as_map_ref() { .as_map_iterator_mut()
.expect("checked that obj was a map iterator");
let mut index = map_iterator.map_next_index;
let item_kind = map_iterator.map_iteration_kind;
if let Some(obj) = map_iterator.iterated_map.take() {
let map = obj.borrow();
let entries = map.as_map_ref().expect("iterator should only iterate maps");
let num_entries = entries.full_len(); let num_entries = entries.full_len();
while index < num_entries { while index < num_entries {
let e = entries.get_index(index); let e = entries.get_index(index);
index += 1; index += 1;
map_iterator.map_next_index = index; map_iterator.map_next_index = index;
if let Some((key, value)) = e { if let Some((key, value)) = e {
match item_kind { let item = match item_kind {
PropertyNameKind::Key => { PropertyNameKind::Key => {
return Ok(create_iter_result_object( Ok(create_iter_result_object(key.clone(), false, context))
context,
key.clone(),
false,
));
} }
PropertyNameKind::Value => { PropertyNameKind::Value => {
return Ok(create_iter_result_object( Ok(create_iter_result_object(value.clone(), false, context))
context,
value.clone(),
false,
));
} }
PropertyNameKind::KeyAndValue => { PropertyNameKind::KeyAndValue => {
let result = Array::create_array_from_list( let result = Array::create_array_from_list(
[key.clone(), value.clone()], [key.clone(), value.clone()],
context, context,
); );
return Ok(create_iter_result_object( Ok(create_iter_result_object(result.into(), false, context))
context,
result.into(),
false,
));
}
} }
};
drop(map);
map_iterator.iterated_map = Some(obj);
return item;
} }
} }
} else {
return Err(context.construct_type_error("'this' is not a Map"));
}
} else {
return Err(context.construct_type_error("'this' is not a Map"));
} }
map_iterator.iterated_map = JsValue::undefined();
Ok(create_iter_result_object( Ok(create_iter_result_object(
context,
JsValue::undefined(), JsValue::undefined(),
true, true,
context,
)) ))
} else {
context.throw_type_error("`this` is not an MapIterator")
}
} else {
context.throw_type_error("`this` is not an MapIterator")
}
} }
/// Create the %MapIteratorPrototype% object /// Create the %MapIteratorPrototype% object
@ -146,7 +126,7 @@ impl MapIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%-object /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%-object
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: JsValue) -> JsObject { pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype // Create prototype

443
boa/src/builtins/map/mod.rs

@ -13,13 +13,16 @@
#![allow(clippy::mutable_key_type)] #![allow(clippy::mutable_key_type)]
use crate::{ use crate::{
builtins::BuiltIn, builtins::{
iterable::{get_iterator, IteratorResult},
BuiltIn,
},
context::StandardObjects, context::StandardObjects,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
ObjectData, JsObject, ObjectData,
}, },
property::{Attribute, PropertyDescriptor, PropertyNameKind}, property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, JsValue, BoaProfiler, Context, JsResult, JsValue,
}; };
@ -28,8 +31,6 @@ use ordered_map::OrderedMap;
pub mod map_iterator; pub mod map_iterator;
use map_iterator::MapIterator; use map_iterator::MapIterator;
use self::ordered_map::MapLock;
use super::JsArgs; use super::JsArgs;
use num_traits::Zero; use num_traits::Zero;
@ -50,14 +51,17 @@ impl BuiltIn for Map {
fn init(context: &mut Context) -> (&'static str, JsValue, Attribute) { fn init(context: &mut Context) -> (&'static str, JsValue, Attribute) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
let to_string_tag = WellKnownSymbols::to_string_tag();
let iterator_symbol = WellKnownSymbols::iterator();
let get_species = FunctionBuilder::native(context, Self::get_species) let get_species = FunctionBuilder::native(context, Self::get_species)
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructable(false) .constructable(false)
.build(); .build();
let get_size = FunctionBuilder::native(context, Self::get_size)
.name("get size")
.length(0)
.constructable(false)
.build();
let entries_function = FunctionBuilder::native(context, Self::entries) let entries_function = FunctionBuilder::native(context, Self::entries)
.name("entries") .name("entries")
.length(0) .length(0)
@ -83,23 +87,24 @@ impl BuiltIn for Map {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )
.property( .property(
to_string_tag, WellKnownSymbols::iterator(),
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
iterator_symbol,
entries_function, entries_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
) )
.method(Self::keys, "keys", 0) .property(
.method(Self::set, "set", 2) WellKnownSymbols::to_string_tag(),
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.method(Self::clear, "clear", 0)
.method(Self::delete, "delete", 1) .method(Self::delete, "delete", 1)
.method(Self::for_each, "forEach", 1)
.method(Self::get, "get", 1) .method(Self::get, "get", 1)
.method(Self::clear, "clear", 0)
.method(Self::has, "has", 1) .method(Self::has, "has", 1)
.method(Self::for_each, "forEach", 1) .method(Self::keys, "keys", 0)
.method(Self::set, "set", 2)
.method(Self::values, "values", 0) .method(Self::values, "values", 0)
.accessor("size", Some(get_size), None, Attribute::CONFIGURABLE)
.build(); .build();
(Self::NAME, map_object.into(), Self::attribute()) (Self::NAME, map_object.into(), Self::attribute())
@ -109,66 +114,47 @@ impl BuiltIn for Map {
impl Map { impl Map {
pub(crate) const LENGTH: usize = 0; pub(crate) const LENGTH: usize = 0;
/// Create a new map /// `Map ( [ iterable ] )`
///
/// Constructor for `Map` objects.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-map-iterable
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map
pub(crate) fn constructor( pub(crate) fn constructor(
new_target: &JsValue, new_target: &JsValue,
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, throw a TypeError exception.
if new_target.is_undefined() { if new_target.is_undefined() {
return context return context
.throw_type_error("calling a builtin Map constructor without new is forbidden"); .throw_type_error("calling a builtin Map constructor without new is forbidden");
} }
// 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%Map.prototype%", « [[MapData]] »).
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::map_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::map_object, context)?;
let map = context.construct_object();
map.set_prototype_instance(prototype.into());
let obj = context.construct_object(); // 3. Set map.[[MapData]] to a new empty List.
obj.set_prototype_instance(prototype.into()); map.borrow_mut().data = ObjectData::map(OrderedMap::new());
let this = JsValue::new(obj);
// add our arguments in
let data = match args.len() {
0 => OrderedMap::new(),
_ => match &args[0] {
JsValue::Object(object) => {
let object = object.borrow();
if let Some(map) = object.as_map_ref().cloned() {
map
} else if object.is_array() {
let mut map = OrderedMap::new();
let len = args[0].get_field("length", context)?.to_integer(context)? as i32;
for i in 0..len {
let val = &args[0].get_field(i, context)?;
let (key, value) =
Self::get_key_value(val, context)?.ok_or_else(|| {
context.construct_type_error(
"iterable for Map should have array-like objects",
)
})?;
map.insert(key, value);
}
map
} else {
return Err(context.construct_type_error(
"iterable for Map should have array-like objects",
));
}
}
_ => {
return Err(context
.construct_type_error("iterable for Map should have array-like objects"))
}
},
};
// finally create size property // 4. If iterable is either undefined or null, return map.
Self::set_size(&this, data.len()); let iterable = match args.get_or_undefined(0) {
val if !val.is_null_or_undefined() => val,
_ => return Ok(map.into()),
};
// This value is used by console.log and other routines to match Object type // 5. Let adder be ? Get(map, "set").
// to its Javascript Identifier (global constructor method name) let adder = map.get("set", context)?;
this.set_data(ObjectData::map(data));
Ok(this) // 6. Return ? AddEntriesFromIterable(map, iterable, adder).
add_entries_from_iterable(&map, iterable, &adder, context)
} }
/// `get Map [ @@species ]` /// `get Map [ @@species ]`
@ -201,7 +187,9 @@ impl Map {
_: &[JsValue], _: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
MapIterator::create_map_iterator(context, this.clone(), PropertyNameKind::KeyAndValue) // 1. Let M be the this value.
// 2. Return ? CreateMapIterator(M, key+value).
MapIterator::create_map_iterator(this, PropertyNameKind::KeyAndValue, context)
} }
/// `Map.prototype.keys()` /// `Map.prototype.keys()`
@ -215,23 +203,14 @@ impl Map {
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.keys /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.keys
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys
pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
MapIterator::create_map_iterator(context, this.clone(), PropertyNameKind::Key) // 1. Let M be the this value.
} // 2. Return ? CreateMapIterator(M, key).
MapIterator::create_map_iterator(this, PropertyNameKind::Key, context)
/// Helper function to set the size property.
fn set_size(this: &JsValue, size: usize) {
let size = PropertyDescriptor::builder()
.value(size)
.writable(false)
.enumerable(false)
.configurable(false);
this.set_property("size", size);
} }
/// `Map.prototype.set( key, value )` /// `Map.prototype.set( key, value )`
/// ///
/// This method associates the value with the key. Returns the map object. /// Inserts a new entry in the Map object.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -247,10 +226,14 @@ impl Map {
let key = args.get_or_undefined(0); let key = args.get_or_undefined(0);
let value = args.get_or_undefined(1); let value = args.get_or_undefined(1);
let size = if let Some(object) = this.as_object() { // 1. Let M be the this value.
if let Some(object) = this.as_object() {
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
// 3. Let entries be the List that is M.[[MapData]].
if let Some(map) = object.borrow_mut().as_map_mut() { if let Some(map) = object.borrow_mut().as_map_mut() {
let key = match key { let key = match key {
JsValue::Rational(r) => { JsValue::Rational(r) => {
// 5. If key is -0𝔽, set key to +0𝔽.
if r.is_zero() { if r.is_zero() {
JsValue::Rational(0f64) JsValue::Rational(0f64)
} else { } else {
@ -259,22 +242,55 @@ impl Map {
} }
_ => key.clone(), _ => key.clone(),
}; };
// 4. For each Record { [[Key]], [[Value]] } p of entries, do
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then
// i. Set p.[[Value]] to value.
// 6. Let p be the Record { [[Key]]: key, [[Value]]: value }.
// 7. Append p as the last element of entries.
map.insert(key, value.clone()); map.insert(key, value.clone());
map.len() // ii. Return M.
} else { // 8. Return M.
return Err(context.construct_type_error("'this' is not a Map")); return Ok(this.clone());
}
}
context.throw_type_error("'this' is not a Map")
} }
} else {
return Err(context.construct_type_error("'this' is not a Map"));
};
Self::set_size(this, size); /// `get Map.prototype.size`
Ok(this.clone()) ///
/// Obtains the size of the map, filtering empty keys to ensure it updates
/// while iterating.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-map.prototype.size
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size
pub(crate) fn get_size(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let M be the this value.
if let Some(object) = this.as_object() {
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
// 3. Let entries be the List that is M.[[MapData]].
if let Some(map) = object.borrow_mut().as_map_mut() {
// 4. Let count be 0.
// 5. For each Record { [[Key]], [[Value]] } p of entries, do
// a. If p.[[Key]] is not empty, set count to count + 1.
// 6. Return 𝔽(count).
return Ok(map.len().into());
}
}
context.throw_type_error("'this' is not a Map")
} }
/// `Map.prototype.delete( key )` /// `Map.prototype.delete( key )`
/// ///
/// This method removes the element associated with the key, if it exists. Returns true if there was an element, false otherwise. /// Removes the element associated with the key, if it exists.
/// Returns true if there was an element, and false otherwise.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -289,23 +305,25 @@ impl Map {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let key = args.get_or_undefined(0); let key = args.get_or_undefined(0);
let (deleted, size) = if let Some(object) = this.as_object() { // 1. Let M be the this value.
if let Some(object) = this.as_object() {
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
// 3. Let entries be the List that is M.[[MapData]].
if let Some(map) = object.borrow_mut().as_map_mut() { if let Some(map) = object.borrow_mut().as_map_mut() {
let deleted = map.remove(key).is_some(); // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then
(deleted, map.len()) // i. Set p.[[Key]] to empty.
} else { // ii. Set p.[[Value]] to empty.
return Err(context.construct_type_error("'this' is not a Map")); // iii. Return true.
// 5. Return false.
return Ok(map.remove(key).is_some().into());
} }
} else { }
return Err(context.construct_type_error("'this' is not a Map")); context.throw_type_error("'this' is not a Map")
};
Self::set_size(this, size);
Ok(deleted.into())
} }
/// `Map.prototype.get( key )` /// `Map.prototype.get( key )`
/// ///
/// This method returns the value associated with the key, or undefined if there is none. /// Returns the value associated with the key, or undefined if there is none.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -333,23 +351,24 @@ impl Map {
_ => key, _ => key,
}; };
// 1. Let M be the this value.
if let JsValue::Object(ref object) = this { if let JsValue::Object(ref object) = this {
let object = object.borrow(); // 2. Perform ? RequireInternalSlot(M, [[MapData]]).
if let Some(map) = object.as_map_ref() { // 3. Let entries be the List that is M.[[MapData]].
return Ok(if let Some(result) = map.get(key) { if let Some(map) = object.borrow().as_map_ref() {
result.clone() // 4. For each Record { [[Key]], [[Value]] } p of entries, do
} else { // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return p.[[Value]].
JsValue::undefined() // 5. Return undefined.
}); return Ok(map.get(key).cloned().unwrap_or_default());
} }
} }
Err(context.construct_type_error("'this' is not a Map")) context.throw_type_error("'this' is not a Map")
} }
/// `Map.prototype.clear( )` /// `Map.prototype.clear( )`
/// ///
/// This method removes all entries from the map. /// Removes all entries from the map.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -360,26 +379,24 @@ impl Map {
pub(crate) fn clear(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn clear(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let M be the this value. // 1. Let M be the this value.
// 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 2. Perform ? RequireInternalSlot(M, [[MapData]]).
if let Some(object) = this.as_object() {
// 3. Let entries be the List that is M.[[MapData]]. // 3. Let entries be the List that is M.[[MapData]].
if let Some(map) = object.borrow_mut().as_map_mut() {
// 4. For each Record { [[Key]], [[Value]] } p of entries, do // 4. For each Record { [[Key]], [[Value]] } p of entries, do
// a. Set p.[[Key]] to empty. // a. Set p.[[Key]] to empty.
// b. Set p.[[Value]] to empty. // b. Set p.[[Value]] to empty.
Self::set_size(this, 0);
if let Some(object) = this.as_object() {
if let Some(map) = object.borrow_mut().as_map_mut() {
map.clear(); map.clear();
} else {
return context.throw_type_error("'this' is not a Map"); // 5. Return undefined.
return Ok(JsValue::undefined());
} }
} else {
return context.throw_type_error("'this' is not a Map");
} }
Ok(JsValue::undefined()) context.throw_type_error("'this' is not a Map")
} }
/// `Map.prototype.has( key )` /// `Map.prototype.has( key )`
/// ///
/// This method checks if the map contains an entry with the given key. /// Checks if the map contains an entry with the given key.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -394,19 +411,24 @@ impl Map {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let key = args.get_or_undefined(0); let key = args.get_or_undefined(0);
// 1. Let M be the this value.
if let JsValue::Object(ref object) = this { if let JsValue::Object(ref object) = this {
let object = object.borrow(); // 2. Perform ? RequireInternalSlot(M, [[MapData]]).
if let Some(map) = object.as_map_ref() { // 3. Let entries be the List that is M.[[MapData]].
if let Some(map) = object.borrow().as_map_ref() {
// 4. For each Record { [[Key]], [[Value]] } p of entries, do
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return true.
// 5. Return false.
return Ok(map.contains_key(key).into()); return Ok(map.contains_key(key).into());
} }
} }
Err(context.construct_type_error("'this' is not a Map")) context.throw_type_error("'this' is not a Map")
} }
/// `Map.prototype.forEach( callbackFn [ , thisArg ] )` /// `Map.prototype.forEach( callbackFn [ , thisArg ] )`
/// ///
/// This method executes the provided callback function for each key-value pair in the map. /// Executes the provided callback function for each key-value pair in the map.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -419,77 +441,63 @@ impl Map {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
if args.is_empty() { // 1. Let M be the this value.
return Err(JsValue::new("Missing argument for Map.prototype.forEach")); // 2. Perform ? RequireInternalSlot(M, [[MapData]]).
} let map = match this {
JsValue::Object(obj) if obj.is_map() => obj,
let callback_arg = &args[0]; _ => return context.throw_type_error("`this` is not a Map"),
};
if !callback_arg.is_function() { // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let name = callback_arg.to_string(context)?; let callback = match args.get_or_undefined(0) {
JsValue::Object(obj) if obj.is_callable() => obj,
val => {
let name = val.to_string(context)?;
return context.throw_type_error(format!("{} is not a function", name)); return context.throw_type_error(format!("{} is not a function", name));
} }
};
let this_arg = args.get_or_undefined(1); let this_arg = args.get_or_undefined(1);
// NOTE:
//
// forEach does not directly mutate the object on which it is called but
// the object may be mutated by the calls to callbackfn. Each entry of a
// map's [[MapData]] is only visited once. New keys added after the call
// to forEach begins are visited. A key will be revisited if it is deleted
// after it has been visited and then re-added before the forEach call completes.
// Keys that are deleted after the call to forEach begins and before being visited
// are not visited unless the key is added again before the forEach call completes.
let _lock = map
.borrow_mut()
.as_map_mut()
.expect("checked that `this` was a map")
.lock(map.clone());
// 4. Let entries be the List that is M.[[MapData]].
// 5. For each Record { [[Key]], [[Value]] } e of entries, do
let mut index = 0; let mut index = 0;
loop {
let lock = Map::lock(this, context)?; let arguments = {
let map = map.borrow();
while index < Map::get_full_len(this, context)? { let map = map.as_map_ref().expect("checked that `this` was a map");
let arguments = if let JsValue::Object(ref object) = this { if index < map.full_len() {
let object = object.borrow(); map.get_index(index)
if let Some(map) = object.as_map_ref() { .map(|(k, v)| [v.clone(), k.clone(), this.clone()])
if let Some((key, value)) = map.get_index(index) {
Some([value.clone(), key.clone(), this.clone()])
} else { } else {
None // 6. Return undefined.
return Ok(JsValue::undefined());
} }
} else {
return context.throw_type_error("'this' is not a Map");
}
} else {
return context.throw_type_error("'this' is not a Map");
}; };
// a. If e.[[Key]] is not empty, then
if let Some(arguments) = arguments { if let Some(arguments) = arguments {
context.call(callback_arg, this_arg, &arguments)?; // i. Perform ? Call(callbackfn, thisArg, « e.[[Value]], e.[[Key]], M »).
callback.call(this_arg, &arguments, context)?;
} }
index += 1; index += 1;
} }
drop(lock);
Ok(JsValue::undefined())
}
/// Helper function to get the full size of the map.
fn get_full_len(map: &JsValue, context: &mut Context) -> JsResult<usize> {
if let JsValue::Object(ref object) = map {
let object = object.borrow();
if let Some(map) = object.as_map_ref() {
Ok(map.full_len())
} else {
Err(context.construct_type_error("'this' is not a Map"))
}
} else {
Err(context.construct_type_error("'this' is not a Map"))
}
}
/// Helper function to lock the map.
fn lock(map: &JsValue, context: &mut Context) -> JsResult<MapLock> {
if let JsValue::Object(ref object) = map {
let mut map = object.borrow_mut();
if let Some(map) = map.as_map_mut() {
Ok(map.lock(object.clone()))
} else {
Err(context.construct_type_error("'this' is not a Map"))
}
} else {
Err(context.construct_type_error("'this' is not a Map"))
}
} }
/// `Map.prototype.values()` /// `Map.prototype.values()`
@ -507,28 +515,79 @@ impl Map {
_: &[JsValue], _: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
MapIterator::create_map_iterator(context, this.clone(), PropertyNameKind::Value) // 1. Let M be the this value.
// 2. Return ? CreateMapIterator(M, value).
MapIterator::create_map_iterator(this, PropertyNameKind::Value, context)
} }
}
/// Helper function to get a key-value pair from an array. /// `AddEntriesFromIterable`
fn get_key_value( ///
value: &JsValue, /// Allows adding entries to a map from any object that has a `@@Iterator` field.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-add-entries-from-iterable
pub(crate) fn add_entries_from_iterable(
target: &JsObject,
iterable: &JsValue,
adder: &JsValue,
context: &mut Context, context: &mut Context,
) -> JsResult<Option<(JsValue, JsValue)>> { ) -> JsResult<JsValue> {
if let JsValue::Object(object) = value { // 1. If IsCallable(adder) is false, throw a TypeError exception.
if object.is_array() { let adder = match adder {
let (key, value) = JsValue::Object(obj) if obj.is_callable() => obj,
match value.get_field("length", context)?.as_number().unwrap() as i32 { _ => return context.throw_type_error("property `set` of `NewTarget` is not callable"),
0 => (JsValue::undefined(), JsValue::undefined()),
1 => (value.get_field("0", context)?, JsValue::undefined()),
_ => (
value.get_field("0", context)?,
value.get_field("1", context)?,
),
}; };
return Ok(Some((key, value)));
// 2. Let iteratorRecord be ? GetIterator(iterable).
let iterator_record = get_iterator(iterable, context)?;
// 3. Repeat,
loop {
// a. Let next be ? IteratorStep(iteratorRecord).
// c. Let nextItem be ? IteratorValue(next).
let IteratorResult { value, done } = iterator_record.next(context)?;
// b. If next is false, return target.
if done {
return Ok(target.clone().into());
}
let next_item = if let Some(obj) = value.as_object() {
obj
} }
// d. If Type(nextItem) is not Object, then
else {
// i. Let error be ThrowCompletion(a newly created TypeError object).
let err = context
.throw_type_error("cannot get key and value from primitive item of `iterable`");
// ii. Return ? IteratorClose(iteratorRecord, error).
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),
};
// 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),
};
// i. Let status be Call(adder, target, « k, v »).
let status = adder.call(&target.clone().into(), &[key, value], context);
// j. IfAbruptCloseIterator(status, iteratorRecord).
if status.is_err() {
return iterator_record.close(status, context);
} }
Ok(None)
} }
} }

9
boa/src/builtins/map/ordered_map.rs

@ -142,9 +142,12 @@ impl<V> OrderedMap<V> {
} }
} }
/// Removes all elements from the map and resets the counter of
/// empty entries.
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.map.clear(); self.map.clear();
self.map.shrink_to_fit(); self.map.shrink_to_fit();
self.empty_count = 0
} }
/// Return a reference to the value stored for `key`, if it is present, /// Return a reference to the value stored for `key`, if it is present,
@ -155,8 +158,10 @@ impl<V> OrderedMap<V> {
self.map.get(key).map(Option::as_ref).flatten() self.map.get(key).map(Option::as_ref).flatten()
} }
/// Get a key-value pair by index /// Get a key-value pair by index.
/// Valid indices are 0 <= index < self.full_len() ///
/// Valid indices are 0 <= index < self.full_len().
///
/// Computes in O(1) time. /// Computes in O(1) time.
pub fn get_index(&self, index: usize) -> Option<(&JsValue, &V)> { pub fn get_index(&self, index: usize) -> Option<(&JsValue, &V)> {
if let (MapKey::Key(key), Some(value)) = self.map.get_index(index)? { if let (MapKey::Key(key), Some(value)) = self.map.get_index(index)? {

12
boa/src/builtins/number/mod.rs

@ -1074,17 +1074,7 @@ impl Number {
if a.is_nan() && b.is_nan() { if a.is_nan() && b.is_nan() {
return true; return true;
} }
a == b && a.signum() == b.signum()
if a == 0.0 && b == 0.0 {
if (a.is_sign_negative() && b.is_sign_positive())
|| (a.is_sign_positive() && b.is_sign_negative())
{
return false;
};
true
} else {
a == b
}
} }
/// The abstract operation Number::sameValueZero takes arguments /// The abstract operation Number::sameValueZero takes arguments

8
boa/src/builtins/object/for_in_iterator.rs

@ -45,7 +45,7 @@ impl ForInIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-createforiniterator /// [spec]: https://tc39.es/ecma262/#sec-createforiniterator
pub(crate) fn create_for_in_iterator(context: &Context, object: JsValue) -> JsValue { pub(crate) fn create_for_in_iterator(object: JsValue, context: &Context) -> JsValue {
let for_in_iterator = JsValue::new_object(context); let for_in_iterator = JsValue::new_object(context);
for_in_iterator.set_data(ObjectData::for_in_iterator(Self::new(object))); for_in_iterator.set_data(ObjectData::for_in_iterator(Self::new(object)));
for_in_iterator for_in_iterator
@ -92,9 +92,9 @@ impl ForInIterator {
iterator.visited_keys.insert(r.clone()); iterator.visited_keys.insert(r.clone());
if desc.expect_enumerable() { if desc.expect_enumerable() {
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
context,
JsValue::new(r.to_string()), JsValue::new(r.to_string()),
false, false,
context,
)); ));
} }
} }
@ -106,9 +106,9 @@ impl ForInIterator {
} }
_ => { _ => {
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
context,
JsValue::undefined(), JsValue::undefined(),
true, true,
context,
)) ))
} }
} }
@ -129,7 +129,7 @@ impl ForInIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%-object /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%-object
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: JsValue) -> JsObject { pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype // Create prototype

10
boa/src/builtins/regexp/regexp_string_iterator.rs

@ -92,9 +92,9 @@ impl RegExpStringIterator {
if let Some(iterator) = object.as_regexp_string_iterator_mut() { if let Some(iterator) = object.as_regexp_string_iterator_mut() {
if iterator.completed { if iterator.completed {
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
context,
JsValue::undefined(), JsValue::undefined(),
true, true,
context,
)); ));
} }
@ -109,7 +109,7 @@ impl RegExpStringIterator {
// 1. Perform ? Yield(match). // 1. Perform ? Yield(match).
// 2. Return undefined. // 2. Return undefined.
iterator.completed = true; iterator.completed = true;
return Ok(create_iter_result_object(context, m.into(), false)); return Ok(create_iter_result_object(m.into(), false, context));
} }
// iv. Let matchStr be ? ToString(? Get(match, "0")). // iv. Let matchStr be ? ToString(? Get(match, "0")).
@ -137,14 +137,14 @@ impl RegExpStringIterator {
} }
// vi. Perform ? Yield(match). // vi. Perform ? Yield(match).
Ok(create_iter_result_object(context, m.into(), false)) Ok(create_iter_result_object(m.into(), false, context))
} else { } else {
// ii. If match is null, return undefined. // ii. If match is null, return undefined.
iterator.completed = true; iterator.completed = true;
Ok(create_iter_result_object( Ok(create_iter_result_object(
context,
JsValue::undefined(), JsValue::undefined(),
true, true,
context,
)) ))
} }
} else { } else {
@ -161,7 +161,7 @@ impl RegExpStringIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: JsValue) -> JsObject { pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
let _timer = BoaProfiler::global().start_event("RegExp String Iterator", "init"); let _timer = BoaProfiler::global().start_event("RegExp String Iterator", "init");
// Create prototype // Create prototype

10
boa/src/builtins/set/mod.rs

@ -152,15 +152,15 @@ impl Set {
} }
// 7 // 7
let iterator_record = get_iterator(context, iterable.clone())?; let iterator_record = get_iterator(iterable, context)?;
// 8.a // 8.a
let mut next = iterator_record.next(context)?; let mut next = iterator_record.next(context)?;
// 8 // 8
while !next.is_done() { while !next.done {
// c // c
let next_value = next.value(); let next_value = next.value;
// d, e // d, e
if let Err(status) = context.call(&adder, &set, &[next_value]) { if let Err(status) = context.call(&adder, &set, &[next_value]) {
@ -305,9 +305,9 @@ impl Set {
} }
Ok(SetIterator::create_set_iterator( Ok(SetIterator::create_set_iterator(
context,
this.clone(), this.clone(),
PropertyNameKind::KeyAndValue, PropertyNameKind::KeyAndValue,
context,
)) ))
} }
@ -419,9 +419,9 @@ impl Set {
} }
Ok(SetIterator::create_set_iterator( Ok(SetIterator::create_set_iterator(
context,
this.clone(), this.clone(),
PropertyNameKind::Value, PropertyNameKind::Value,
context,
)) ))
} }

12
boa/src/builtins/set/set_iterator.rs

@ -44,9 +44,9 @@ impl SetIterator {
/// ///
/// [spec]: https://www.ecma-international.org/ecma-262/11.0/index.html#sec-createsetiterator /// [spec]: https://www.ecma-international.org/ecma-262/11.0/index.html#sec-createsetiterator
pub(crate) fn create_set_iterator( pub(crate) fn create_set_iterator(
context: &Context,
set: JsValue, set: JsValue,
kind: PropertyNameKind, kind: PropertyNameKind,
context: &Context,
) -> JsValue { ) -> JsValue {
let set_iterator = JsValue::new_object(context); let set_iterator = JsValue::new_object(context);
set_iterator.set_data(ObjectData::set_iterator(Self::new(set, kind))); set_iterator.set_data(ObjectData::set_iterator(Self::new(set, kind)));
@ -75,9 +75,9 @@ impl SetIterator {
if set_iterator.iterated_set.is_undefined() { if set_iterator.iterated_set.is_undefined() {
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
context,
JsValue::undefined(), JsValue::undefined(),
true, true,
context,
)); ));
} }
@ -92,9 +92,9 @@ impl SetIterator {
match item_kind { match item_kind {
PropertyNameKind::Value => { PropertyNameKind::Value => {
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
context,
value.clone(), value.clone(),
false, false,
context,
)); ));
} }
PropertyNameKind::KeyAndValue => { PropertyNameKind::KeyAndValue => {
@ -103,9 +103,9 @@ impl SetIterator {
context, context,
); );
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
context,
result.into(), result.into(),
false, false,
context,
)); ));
} }
PropertyNameKind::Key => { PropertyNameKind::Key => {
@ -123,9 +123,9 @@ impl SetIterator {
set_iterator.iterated_set = JsValue::undefined(); set_iterator.iterated_set = JsValue::undefined();
Ok(create_iter_result_object( Ok(create_iter_result_object(
context,
JsValue::undefined(), JsValue::undefined(),
true, true,
context,
)) ))
} else { } else {
context.throw_type_error("`this` is not an SetIterator") context.throw_type_error("`this` is not an SetIterator")
@ -141,7 +141,7 @@ impl SetIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%-object /// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%-object
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: JsValue) -> JsObject { pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype // Create prototype

2
boa/src/builtins/string/mod.rs

@ -1777,7 +1777,7 @@ impl String {
_: &[JsValue], _: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
StringIterator::create_string_iterator(context, this.clone()) StringIterator::create_string_iterator(this.clone(), context)
} }
} }

10
boa/src/builtins/string/string_iterator.rs

@ -23,7 +23,7 @@ impl StringIterator {
} }
} }
pub fn create_string_iterator(context: &mut Context, string: JsValue) -> JsResult<JsValue> { pub fn create_string_iterator(string: JsValue, context: &mut Context) -> JsResult<JsValue> {
let string_iterator = JsValue::new_object(context); let string_iterator = JsValue::new_object(context);
string_iterator.set_data(ObjectData::string_iterator(Self::new(string))); string_iterator.set_data(ObjectData::string_iterator(Self::new(string)));
string_iterator string_iterator
@ -39,9 +39,9 @@ impl StringIterator {
if let Some(string_iterator) = object.as_string_iterator_mut() { if let Some(string_iterator) = object.as_string_iterator_mut() {
if string_iterator.string.is_undefined() { if string_iterator.string.is_undefined() {
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
context,
JsValue::undefined(), JsValue::undefined(),
true, true,
context,
)); ));
} }
let native_string = string_iterator.string.to_string(context)?; let native_string = string_iterator.string.to_string(context)?;
@ -50,9 +50,9 @@ impl StringIterator {
if position >= len { if position >= len {
string_iterator.string = JsValue::undefined(); string_iterator.string = JsValue::undefined();
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
context,
JsValue::undefined(), JsValue::undefined(),
true, true,
context,
)); ));
} }
let (_, code_unit_count, _) = let (_, code_unit_count, _) =
@ -63,7 +63,7 @@ impl StringIterator {
&[position.into(), string_iterator.next_index.into()], &[position.into(), string_iterator.next_index.into()],
context, context,
)?; )?;
Ok(create_iter_result_object(context, result_string, false)) Ok(create_iter_result_object(result_string, false, context))
} else { } else {
context.throw_type_error("`this` is not an ArrayIterator") context.throw_type_error("`this` is not an ArrayIterator")
} }
@ -78,7 +78,7 @@ impl StringIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: JsValue) -> JsObject { pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
let _timer = BoaProfiler::global().start_event("String Iterator", "init"); let _timer = BoaProfiler::global().start_event("String Iterator", "init");
// Create prototype // Create prototype

22
boa/src/object/mod.rs

@ -603,6 +603,28 @@ impl Object {
} }
} }
#[inline]
pub fn is_map_iterator(&self) -> bool {
matches!(
self.data,
ObjectData {
kind: ObjectKind::MapIterator(_),
..
}
)
}
#[inline]
pub fn as_map_iterator_ref(&self) -> Option<&MapIterator> {
match &self.data {
ObjectData {
kind: ObjectKind::MapIterator(iter),
..
} => Some(iter),
_ => None,
}
}
#[inline] #[inline]
pub fn as_map_iterator_mut(&mut self) -> Option<&mut MapIterator> { pub fn as_map_iterator_mut(&mut self) -> Option<&mut MapIterator> {
match &mut self.data { match &mut self.data {

8
boa/src/syntax/ast/node/array/mod.rs

@ -46,18 +46,18 @@ impl Executable for ArrayDecl {
for elem in self.as_ref() { for elem in self.as_ref() {
if let Node::Spread(ref x) = elem { if let Node::Spread(ref x) = elem {
let val = x.run(context)?; let val = x.run(context)?;
let iterator_record = iterable::get_iterator(context, val)?; let iterator_record = iterable::get_iterator(&val, context)?;
// TODO after proper internal Array representation as per https://github.com/boa-dev/boa/pull/811#discussion_r502460858 // TODO after proper internal Array representation as per https://github.com/boa-dev/boa/pull/811#discussion_r502460858
// next_index variable should be utilized here as per https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation // next_index variable should be utilized here as per https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation
// let mut next_index = 0; // let mut next_index = 0;
loop { loop {
let next = iterator_record.next(context)?; let next = iterator_record.next(context)?;
if next.is_done() { if next.done {
break; break;
} }
let next_value = next.value(); let next_value = next.value;
//next_index += 1; //next_index += 1;
elements.push(next_value.clone()); elements.push(next_value);
} }
} else { } else {
elements.push(elem.run(context)?); elements.push(elem.run(context)?);

8
boa/src/syntax/ast/node/call/mod.rs

@ -94,14 +94,14 @@ impl Executable for Call {
for arg in self.args() { for arg in self.args() {
if let Node::Spread(ref x) = arg { if let Node::Spread(ref x) = arg {
let val = x.run(context)?; let val = x.run(context)?;
let iterator_record = iterable::get_iterator(context, val)?; let iterator_record = iterable::get_iterator(&val, context)?;
loop { loop {
let next = iterator_record.next(context)?; let next = iterator_record.next(context)?;
if next.is_done() { if next.done {
break; break;
} }
let next_value = next.value(); let next_value = next.value;
v_args.push(next_value.clone()); v_args.push(next_value);
} }
break; // after spread we don't accept any new arguments break; // after spread we don't accept any new arguments
} else { } else {

18
boa/src/syntax/ast/node/declaration/mod.rs

@ -669,7 +669,7 @@ impl DeclarationPatternArray {
} }
// 1. Let iteratorRecord be ? GetIterator(value). // 1. Let iteratorRecord be ? GetIterator(value).
let iterator = get_iterator(context, value)?; let iterator = get_iterator(&value, context)?;
let mut result = Vec::new(); let mut result = Vec::new();
// 2. Let result be IteratorBindingInitialization of ArrayBindingPattern with arguments iteratorRecord and environment. // 2. Let result be IteratorBindingInitialization of ArrayBindingPattern with arguments iteratorRecord and environment.
@ -705,7 +705,7 @@ impl DeclarationPatternArray {
// 3. If iteratorRecord.[[Done]] is false, then // 3. If iteratorRecord.[[Done]] is false, then
// 4. If iteratorRecord.[[Done]] is true, let v be undefined. // 4. If iteratorRecord.[[Done]] is true, let v be undefined.
let mut v = if !next.is_done() { let mut v = if !next.done {
// a. Let next be IteratorStep(iteratorRecord). // a. Let next be IteratorStep(iteratorRecord).
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// c. ReturnIfAbrupt(next). // c. ReturnIfAbrupt(next).
@ -714,7 +714,7 @@ impl DeclarationPatternArray {
// i. Let v be IteratorValue(next). // i. Let v be IteratorValue(next).
// ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true. // ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true.
// iii. ReturnIfAbrupt(v). // iii. ReturnIfAbrupt(v).
next.value() next.value
} else { } else {
JsValue::undefined() JsValue::undefined()
}; };
@ -743,7 +743,7 @@ impl DeclarationPatternArray {
// 1. If iteratorRecord.[[Done]] is false, then // 1. If iteratorRecord.[[Done]] is false, then
// 2. If iteratorRecord.[[Done]] is true, let v be undefined. // 2. If iteratorRecord.[[Done]] is true, let v be undefined.
let v = if !next.is_done() { let v = if !next.done {
// a. Let next be IteratorStep(iteratorRecord). // a. Let next be IteratorStep(iteratorRecord).
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true. // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// c. ReturnIfAbrupt(next). // c. ReturnIfAbrupt(next).
@ -752,7 +752,7 @@ impl DeclarationPatternArray {
// i. Let v be IteratorValue(next). // i. Let v be IteratorValue(next).
// ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true. // ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true.
// iii. ReturnIfAbrupt(v). // iii. ReturnIfAbrupt(v).
Some(next.value()) Some(next.value)
} else { } else {
None None
}; };
@ -782,7 +782,7 @@ impl DeclarationPatternArray {
// iv. If next is false, set iteratorRecord.[[Done]] to true. // iv. If next is false, set iteratorRecord.[[Done]] to true.
// b. If iteratorRecord.[[Done]] is true, then // b. If iteratorRecord.[[Done]] is true, then
if next.is_done() { if next.done {
// i. If environment is undefined, return ? PutValue(lhs, A). // i. If environment is undefined, return ? PutValue(lhs, A).
// ii. Return InitializeReferencedBinding(lhs, A). // ii. Return InitializeReferencedBinding(lhs, A).
break result.push((ident.clone(), a.clone().into())); break result.push((ident.clone(), a.clone().into()));
@ -794,7 +794,7 @@ impl DeclarationPatternArray {
// f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), nextValue). // f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), nextValue).
// g. Set n to n + 1. // g. Set n to n + 1.
Array::add_to_array_object(&a.clone().into(), &[next.value()], context)?; Array::add_to_array_object(&a.clone().into(), &[next.value], context)?;
} }
} }
// BindingRestElement : ... BindingPattern // BindingRestElement : ... BindingPattern
@ -814,7 +814,7 @@ impl DeclarationPatternArray {
let next = iterator.next(context)?; let next = iterator.next(context)?;
// b. If iteratorRecord.[[Done]] is true, then // b. If iteratorRecord.[[Done]] is true, then
if next.is_done() { if next.done {
// i. Return the result of performing BindingInitialization of BindingPattern with A and environment as the arguments. // i. Return the result of performing BindingInitialization of BindingPattern with A and environment as the arguments.
break result break result
.append(&mut pattern.run(Some(a.clone().into()), context)?); .append(&mut pattern.run(Some(a.clone().into()), context)?);
@ -825,7 +825,7 @@ impl DeclarationPatternArray {
// e. ReturnIfAbrupt(nextValue). // e. ReturnIfAbrupt(nextValue).
// f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), nextValue). // f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), nextValue).
// g. Set n to n + 1. // g. Set n to n + 1.
Array::add_to_array_object(&a.clone().into(), &[next.value()], context)?; Array::add_to_array_object(&a.clone().into(), &[next.value], context)?;
} }
} }
} }

10
boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs

@ -89,7 +89,7 @@ impl Executable for ForInLoop {
return Ok(result); return Ok(result);
} }
let object = object.to_object(context)?; let object = object.to_object(context)?;
let for_in_iterator = ForInIterator::create_for_in_iterator(context, JsValue::new(object)); let for_in_iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), context);
let next_function = for_in_iterator let next_function = for_in_iterator
.get_property("next") .get_property("next")
.as_ref() .as_ref()
@ -104,24 +104,24 @@ impl Executable for ForInLoop {
context.push_environment(DeclarativeEnvironmentRecord::new(Some(env))); context.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
} }
let iterator_result = iterator.next(context)?; let iterator_result = iterator.next(context)?;
if iterator_result.is_done() { if iterator_result.done {
context.pop_environment(); context.pop_environment();
break; break;
} }
let next_result = iterator_result.value(); let next_result = iterator_result.value;
match self.variable() { match self.variable() {
Node::Identifier(ref name) => { Node::Identifier(ref name) => {
if context.has_binding(name.as_ref()) { if context.has_binding(name.as_ref()) {
// Binding already exists // Binding already exists
context.set_mutable_binding(name.as_ref(), next_result.clone(), true)?; context.set_mutable_binding(name.as_ref(), next_result, true)?;
} else { } else {
context.create_mutable_binding( context.create_mutable_binding(
name.as_ref().to_owned(), name.as_ref().to_owned(),
true, true,
VariableScope::Function, VariableScope::Function,
)?; )?;
context.initialize_binding(name.as_ref(), next_result.clone())?; context.initialize_binding(name.as_ref(), next_result)?;
} }
} }
Node::VarDeclList(ref list) => match list.as_ref() { Node::VarDeclList(ref list) => match list.as_ref() {

10
boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs

@ -83,7 +83,7 @@ impl Executable for ForOfLoop {
fn run(&self, context: &mut Context) -> JsResult<JsValue> { fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("ForOf", "exec"); let _timer = BoaProfiler::global().start_event("ForOf", "exec");
let iterable = self.iterable().run(context)?; let iterable = self.iterable().run(context)?;
let iterator = get_iterator(context, iterable)?; let iterator = get_iterator(&iterable, context)?;
let mut result = JsValue::undefined(); let mut result = JsValue::undefined();
loop { loop {
@ -92,24 +92,24 @@ impl Executable for ForOfLoop {
context.push_environment(DeclarativeEnvironmentRecord::new(Some(env))); context.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
} }
let iterator_result = iterator.next(context)?; let iterator_result = iterator.next(context)?;
if iterator_result.is_done() { if iterator_result.done {
context.pop_environment(); context.pop_environment();
break; break;
} }
let next_result = iterator_result.value(); let next_result = iterator_result.value;
match self.variable() { match self.variable() {
Node::Identifier(ref name) => { Node::Identifier(ref name) => {
if context.has_binding(name.as_ref()) { if context.has_binding(name.as_ref()) {
// Binding already exists // Binding already exists
context.set_mutable_binding(name.as_ref(), next_result.clone(), true)?; context.set_mutable_binding(name.as_ref(), next_result, true)?;
} else { } else {
context.create_mutable_binding( context.create_mutable_binding(
name.as_ref().to_owned(), name.as_ref().to_owned(),
true, true,
VariableScope::Function, VariableScope::Function,
)?; )?;
context.initialize_binding(name.as_ref(), next_result.clone())?; context.initialize_binding(name.as_ref(), next_result)?;
} }
} }
Node::VarDeclList(ref list) => match list.as_ref() { Node::VarDeclList(ref list) => match list.as_ref() {

8
boa/src/syntax/ast/node/new/mod.rs

@ -56,14 +56,14 @@ impl Executable for New {
for arg in self.args() { for arg in self.args() {
if let Node::Spread(ref x) = arg { if let Node::Spread(ref x) = arg {
let val = x.run(context)?; let val = x.run(context)?;
let iterator_record = iterable::get_iterator(context, val)?; let iterator_record = iterable::get_iterator(&val, context)?;
loop { loop {
let next = iterator_record.next(context)?; let next = iterator_record.next(context)?;
if next.is_done() { if next.done {
break; break;
} }
let next_value = next.value(); let next_value = next.value;
v_args.push(next_value.clone()); v_args.push(next_value);
} }
break; // after spread we don't accept any new arguments break; // after spread we don't accept any new arguments
} else { } else {

2
boa/src/value/mod.rs

@ -867,7 +867,7 @@ impl JsValue {
Self::Undefined => "undefined", Self::Undefined => "undefined",
Self::BigInt(_) => "bigint", Self::BigInt(_) => "bigint",
Self::Object(ref object) => { Self::Object(ref object) => {
if object.is_function() { if object.is_callable() {
"function" "function"
} else { } else {
"object" "object"

Loading…
Cancel
Save