diff --git a/boa/Cargo.toml b/boa/Cargo.toml index b35e34a496..60247300a8 100644 --- a/boa/Cargo.toml +++ b/boa/Cargo.toml @@ -11,7 +11,7 @@ exclude = ["../.vscode/*", "../Dockerfile", "../Makefile", "../.editorConfig"] edition = "2018" [features] -profiler = ["measureme", "once_cell"] +profiler = ["measureme"] deser = [] # Enable Bytecode generation & execution instead of tree walking @@ -38,10 +38,10 @@ chrono = "0.4.19" fast-float = "0.2.0" unicode-normalization = "0.1.19" dyn-clone = "1.0.4" +once_cell = "1.8.0" # Optional Dependencies measureme = { version = "9.1.2", optional = true } -once_cell = { version = "1.8.0", optional = true } [target.wasm32-unknown-unknown.dependencies] getrandom = { version = "0.2.3", features = ["js"] } diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index 695595fcae..92212c89de 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -15,19 +15,21 @@ use crate::{ /// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects #[derive(Debug, Clone, Finalize, Trace)] pub struct ArrayIterator { - array: JsValue, - next_index: u32, + array: JsObject, + next_index: usize, kind: PropertyNameKind, + done: bool, } impl ArrayIterator { pub(crate) const NAME: &'static str = "ArrayIterator"; - fn new(array: JsValue, kind: PropertyNameKind) -> Self { + fn new(array: JsObject, kind: PropertyNameKind) -> Self { ArrayIterator { array, kind, next_index: 0, + done: false, } } @@ -40,7 +42,7 @@ impl ArrayIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-createarrayiterator pub(crate) fn create_array_iterator( - array: JsValue, + array: JsObject, kind: PropertyNameKind, context: &Context, ) -> JsValue { @@ -66,21 +68,28 @@ impl ArrayIterator { let mut object = object.borrow_mut(); if let Some(array_iterator) = object.as_array_iterator_mut() { let index = array_iterator.next_index; - if array_iterator.array.is_undefined() { + if array_iterator.done { return Ok(create_iter_result_object( JsValue::undefined(), true, context, )); } - let len = array_iterator - .array - .get_field("length", context)? - .as_number() - .ok_or_else(|| context.construct_type_error("Not an array"))? - as u32; - if array_iterator.next_index >= len { - array_iterator.array = JsValue::undefined(); + + let len = if let Some(f) = array_iterator.array.borrow().as_typed_array() { + if f.is_detached() { + return context.throw_type_error( + "Cannot get value from typed array that has a detached array buffer", + ); + } + + f.array_length() + } else { + array_iterator.array.length_of_array_like(context)? + }; + + if index >= len { + array_iterator.done = true; return Ok(create_iter_result_object( JsValue::undefined(), true, @@ -93,11 +102,11 @@ impl ArrayIterator { Ok(create_iter_result_object(index.into(), false, context)) } PropertyNameKind::Value => { - let element_value = array_iterator.array.get_field(index, context)?; + let element_value = array_iterator.array.get(index, context)?; Ok(create_iter_result_object(element_value, false, context)) } PropertyNameKind::KeyAndValue => { - let element_value = array_iterator.array.get_field(index, context)?; + let element_value = array_iterator.array.get(index, context)?; let result = Array::create_array_from_list([index.into(), element_value], context); Ok(create_iter_result_object(result.into(), false, context)) diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 0b8f1a4a63..ef1783dce1 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -758,10 +758,11 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If separator is undefined, let sep be the single-element String ",". // 4. Else, let sep be ? ToString(separator). - let separator = if let Some(separator) = args.get(0) { - separator.to_string(context)? - } else { + let separator = args.get_or_undefined(0); + let separator = if separator.is_undefined() { JsString::new(",") + } else { + separator.to_string(context)? }; // 5. Let R be the empty String. @@ -2565,8 +2566,12 @@ impl Array { _: &[JsValue], context: &mut Context, ) -> JsResult { + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + + // 2. Return CreateArrayIterator(O, value). Ok(ArrayIterator::create_array_iterator( - this.clone(), + o, PropertyNameKind::Value, context, )) @@ -2580,11 +2585,15 @@ impl Array { /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// - /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.keys /// [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 { + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + + // 2. Return CreateArrayIterator(O, key). Ok(ArrayIterator::create_array_iterator( - this.clone(), + o, PropertyNameKind::Key, context, )) @@ -2598,15 +2607,19 @@ impl Array { /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// - /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.entries /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values pub(crate) fn entries( this: &JsValue, _: &[JsValue], context: &mut Context, ) -> JsResult { + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + + // 2. Return CreateArrayIterator(O, key+value). Ok(ArrayIterator::create_array_iterator( - this.clone(), + o, PropertyNameKind::KeyAndValue, context, )) diff --git a/boa/src/builtins/array/tests.rs b/boa/src/builtins/array/tests.rs index 82c7bc9242..e8bf8508c9 100644 --- a/boa/src/builtins/array/tests.rs +++ b/boa/src/builtins/array/tests.rs @@ -1412,7 +1412,7 @@ fn array_spread_non_iterable() { try { const array2 = [...5]; } catch (err) { - err.name === "TypeError" && err.message === "Not an iterable" + err.name === "TypeError" && err.message === "Value is not callable" } "#; assert_eq!(forward(&mut context, init), "true"); diff --git a/boa/src/builtins/array_buffer/mod.rs b/boa/src/builtins/array_buffer/mod.rs new file mode 100644 index 0000000000..21d27ee457 --- /dev/null +++ b/boa/src/builtins/array_buffer/mod.rs @@ -0,0 +1,789 @@ +use crate::{ + builtins::{typed_array::TypedArrayName, BuiltIn, JsArgs}, + context::StandardObjects, + gc::{Finalize, Trace}, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, + JsObject, ObjectData, + }, + profiler::BoaProfiler, + property::Attribute, + symbol::WellKnownSymbols, + value::{IntegerOrInfinity, Numeric}, + Context, JsResult, JsValue, +}; +use num_traits::{Signed, ToPrimitive}; +use std::convert::TryInto; + +#[derive(Debug, Clone, Trace, Finalize)] +pub struct ArrayBuffer { + pub array_buffer_data: Option>, + pub array_buffer_byte_length: usize, + pub array_buffer_detach_key: JsValue, +} + +impl ArrayBuffer { + pub(crate) fn array_buffer_byte_length(&self) -> usize { + self.array_buffer_byte_length + } +} + +impl BuiltIn for ArrayBuffer { + const NAME: &'static str = "ArrayBuffer"; + + const ATTRIBUTE: Attribute = Attribute::WRITABLE + .union(Attribute::NON_ENUMERABLE) + .union(Attribute::CONFIGURABLE); + + fn init(context: &mut Context) -> JsValue { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + let get_species = FunctionBuilder::native(context, Self::get_species) + .name("get [Symbol.species]") + .constructable(false) + .build(); + + ConstructorBuilder::with_standard_object( + context, + Self::constructor, + context.standard_objects().array_buffer_object().clone(), + ) + .name(Self::NAME) + .length(Self::LENGTH) + .static_accessor( + WellKnownSymbols::species(), + Some(get_species), + None, + Attribute::CONFIGURABLE, + ) + .static_method(Self::is_view, "isView", 1) + .method(Self::byte_length, "byteLength", 0) + .method(Self::slice, "slice", 2) + .property( + WellKnownSymbols::to_string_tag(), + Self::NAME, + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .build() + .into() + } +} + +impl ArrayBuffer { + const LENGTH: usize = 1; + + /// `25.1.3.1 ArrayBuffer ( length )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer-length + fn constructor( + new_target: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. If NewTarget is undefined, throw a TypeError exception. + if new_target.is_undefined() { + return context + .throw_type_error("ArrayBuffer.constructor called with undefined new target"); + } + + // 2. Let byteLength be ? ToIndex(length). + let byte_length = args.get_or_undefined(0).to_index(context)?; + + // 3. Return ? AllocateArrayBuffer(NewTarget, byteLength). + Ok(Self::allocate(new_target, byte_length, context)?.into()) + } + + /// `25.1.4.3 get ArrayBuffer [ @@species ]` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer-@@species + fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + // 1. Return the this value. + Ok(this.clone()) + } + + /// `25.1.4.1 ArrayBuffer.isView ( arg )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.isview + fn is_view(_: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult { + // 1. If Type(arg) is not Object, return false. + // 2. If arg has a [[ViewedArrayBuffer]] internal slot, return true. + // 3. Return false. + Ok(args + .get_or_undefined(0) + .as_object() + .map(|obj| obj.borrow().is_typed_array()) + .unwrap_or_default() + .into()) + } + + /// `25.1.5.1 get ArrayBuffer.prototype.byteLength` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength + fn byte_length(this: &JsValue, _args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + let obj = if let Some(obj) = this.as_object() { + obj + } else { + return context.throw_type_error("ArrayBuffer.byteLength called with non-object value"); + }; + let obj = obj.borrow(); + let o = if let Some(o) = obj.as_array_buffer() { + o + } else { + return context.throw_type_error("ArrayBuffer.byteLength called with invalid object"); + }; + + // TODO: Shared Array Buffer + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + + // 4. If IsDetachedBuffer(O) is true, return +0𝔽. + if Self::is_detached_buffer(o) { + return Ok(0.into()); + } + + // 5. Let length be O.[[ArrayBufferByteLength]]. + // 6. Return 𝔽(length). + Ok(o.array_buffer_byte_length.into()) + } + + /// `25.1.5.3 ArrayBuffer.prototype.slice ( start, end )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice + fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + let obj = if let Some(obj) = this.as_object() { + obj + } else { + return context.throw_type_error("ArrayBuffer.slice called with non-object value"); + }; + let obj_borrow = obj.borrow(); + let o = if let Some(o) = obj_borrow.as_array_buffer() { + o + } else { + return context.throw_type_error("ArrayBuffer.slice called with invalid object"); + }; + + // TODO: Shared Array Buffer + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + + // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception. + if Self::is_detached_buffer(o) { + return context.throw_type_error("ArrayBuffer.slice called with detached buffer"); + } + + // 5. Let len be O.[[ArrayBufferByteLength]]. + let len = o.array_buffer_byte_length as i64; + + // 6. Let relativeStart be ? ToIntegerOrInfinity(start). + let relative_start = args.get_or_undefined(0).to_integer_or_infinity(context)?; + + let first = match relative_start { + // 7. If relativeStart is -∞, let first be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 8. Else if relativeStart < 0, let first be max(len + relativeStart, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 9. Else, let first be min(relativeStart, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + let end = args.get_or_undefined(1); + let relative_end = if end.is_undefined() { + IntegerOrInfinity::Integer(len) + } else { + end.to_integer_or_infinity(context)? + }; + + let r#final = match relative_end { + // 11. If relativeEnd is -∞, let final be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 13. Else, let final be min(relativeEnd, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 14. Let newLen be max(final - first, 0). + let new_len = std::cmp::max(r#final - first, 0) as usize; + + // 15. Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%). + let ctor = obj.species_constructor(StandardObjects::array_buffer_object, context)?; + + // 16. Let new be ? Construct(ctor, « 𝔽(newLen) »). + let new = Self::constructor(&ctor.into(), &[new_len.into()], context)?; + + // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). + let new_obj = if let Some(obj) = new.as_object() { + obj + } else { + return context.throw_type_error("ArrayBuffer constructor returned non-object value"); + }; + let mut new_obj_borrow = new_obj.borrow_mut(); + let new_array_buffer = if let Some(b) = new_obj_borrow.as_array_buffer_mut() { + b + } else { + return context.throw_type_error("ArrayBuffer constructor returned invalid object"); + }; + + // TODO: Shared Array Buffer + // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. + + // 19. If IsDetachedBuffer(new) is true, throw a TypeError exception. + if Self::is_detached_buffer(new_array_buffer) { + return context + .throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer"); + } + + // 20. If SameValue(new, O) is true, throw a TypeError exception. + if JsValue::same_value(&new, this) { + return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer"); + } + + // 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception. + if new_array_buffer.array_buffer_byte_length < new_len { + return context.throw_type_error("New ArrayBuffer length too small"); + } + + // 22. NOTE: Side-effects of the above steps may have detached O. + // 23. If IsDetachedBuffer(O) is true, throw a TypeError exception. + if Self::is_detached_buffer(o) { + return context + .throw_type_error("ArrayBuffer detached while ArrayBuffer.slice was running"); + } + + // 24. Let fromBuf be O.[[ArrayBufferData]]. + let from_buf = o + .array_buffer_data + .as_ref() + .expect("ArrayBuffer cannot be detached here"); + + // 25. Let toBuf be new.[[ArrayBufferData]]. + let to_buf = new_array_buffer + .array_buffer_data + .as_mut() + .expect("ArrayBuffer cannot be detached here"); + + // 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). + copy_data_block_bytes(to_buf, 0, from_buf, first as usize, new_len); + + // 27. Return new. + Ok(new) + } + + /// `25.1.2.1 AllocateArrayBuffer ( constructor, byteLength )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-allocatearraybuffer + pub(crate) fn allocate( + constructor: &JsValue, + byte_length: usize, + context: &mut Context, + ) -> JsResult { + // 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »). + let prototype = get_prototype_from_constructor( + constructor, + StandardObjects::array_buffer_object, + context, + )?; + let obj = context.construct_object(); + obj.set_prototype_instance(prototype.into()); + + // 2. Let block be ? CreateByteDataBlock(byteLength). + // TODO: for now just a arbitrary limit to not OOM. + if byte_length > 8589934592 { + return Err(context.construct_range_error("ArrayBuffer allocation failed")); + } + let block = vec![0; byte_length]; + + // 3. Set obj.[[ArrayBufferData]] to block. + // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. + obj.borrow_mut().data = ObjectData::array_buffer(ArrayBuffer { + array_buffer_data: Some(block), + array_buffer_byte_length: byte_length, + array_buffer_detach_key: JsValue::Undefined, + }); + + // 5. Return obj. + Ok(obj) + } + + /// `25.1.2.2 IsDetachedBuffer ( arrayBuffer )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer + pub(crate) fn is_detached_buffer(&self) -> bool { + // 1. If arrayBuffer.[[ArrayBufferData]] is null, return true. + // 2. Return false. + self.array_buffer_data.is_none() + } + + /// `25.1.2.4 CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength, cloneConstructor )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-clonearraybuffer + pub(crate) fn clone_array_buffer( + &self, + src_byte_offset: usize, + src_length: usize, + clone_constructor: &JsValue, + context: &mut Context, + ) -> JsResult { + // 1. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength). + let target_buffer = Self::allocate(clone_constructor, src_length, context)?; + + // 2. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. + // 3. Let srcBlock be srcBuffer.[[ArrayBufferData]]. + let src_block = if let Some(b) = &self.array_buffer_data { + b + } else { + return Err(context.construct_syntax_error("Cannot clone detached array buffer")); + }; + + { + // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]]. + let mut target_buffer_mut = target_buffer.borrow_mut(); + let target_block = target_buffer_mut + .as_array_buffer_mut() + .expect("This must be an ArrayBuffer"); + + // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). + copy_data_block_bytes( + target_block + .array_buffer_data + .as_mut() + .expect("ArrayBuffer cannot me detached here"), + 0, + src_block, + src_byte_offset, + src_length, + ); + } + + // 6. Return targetBuffer. + Ok(target_buffer) + } + + /// `25.1.2.6 IsUnclampedIntegerElementType ( type )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-isunclampedintegerelementtype + fn is_unclamped_integer_element_type(t: TypedArrayName) -> bool { + // 1. If type is Int8, Uint8, Int16, Uint16, Int32, or Uint32, return true. + // 2. Return false. + matches!( + t, + TypedArrayName::Int8Array + | TypedArrayName::Uint8Array + | TypedArrayName::Int16Array + | TypedArrayName::Uint16Array + | TypedArrayName::Int32Array + | TypedArrayName::Uint32Array + ) + } + + /// `25.1.2.7 IsBigIntElementType ( type )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-isbigintelementtype + fn is_big_int_element_type(t: TypedArrayName) -> bool { + // 1. If type is BigUint64 or BigInt64, return true. + // 2. Return false. + matches!( + t, + TypedArrayName::BigUint64Array | TypedArrayName::BigInt64Array + ) + } + + /// `25.1.2.8 IsNoTearConfiguration ( type, order )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-isnotearconfiguration + // TODO: Allow unused function until shared array buffers are implemented. + #[allow(dead_code)] + fn is_no_tear_configuration(t: TypedArrayName, order: SharedMemoryOrder) -> bool { + // 1. If ! IsUnclampedIntegerElementType(type) is true, return true. + if Self::is_unclamped_integer_element_type(t) { + return true; + } + + // 2. If ! IsBigIntElementType(type) is true and order is not Init or Unordered, return true. + if Self::is_big_int_element_type(t) + && !matches!( + order, + SharedMemoryOrder::Init | SharedMemoryOrder::Unordered + ) + { + return true; + } + + // 3. Return false. + false + } + + /// `25.1.2.9 RawBytesToNumeric ( type, rawBytes, isLittleEndian )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-rawbytestonumeric + fn raw_bytes_to_numeric(t: TypedArrayName, bytes: &[u8], is_little_endian: bool) -> JsValue { + let n: Numeric = match t { + TypedArrayName::Int8Array => { + if is_little_endian { + i8::from_le_bytes(bytes.try_into().expect("slice with incorrect length")).into() + } else { + i8::from_be_bytes(bytes.try_into().expect("slice with incorrect length")).into() + } + } + TypedArrayName::Uint8Array | TypedArrayName::Uint8ClampedArray => { + if is_little_endian { + u8::from_le_bytes(bytes.try_into().expect("slice with incorrect length")).into() + } else { + u8::from_be_bytes(bytes.try_into().expect("slice with incorrect length")).into() + } + } + TypedArrayName::Int16Array => { + if is_little_endian { + i16::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } else { + i16::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } + } + TypedArrayName::Uint16Array => { + if is_little_endian { + u16::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } else { + u16::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } + } + TypedArrayName::Int32Array => { + if is_little_endian { + i32::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } else { + i32::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } + } + TypedArrayName::Uint32Array => { + if is_little_endian { + u32::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } else { + u32::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } + } + TypedArrayName::BigInt64Array => { + if is_little_endian { + i64::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } else { + i64::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } + } + TypedArrayName::BigUint64Array => { + if is_little_endian { + u64::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } else { + u64::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } + } + TypedArrayName::Float32Array => { + if is_little_endian { + f32::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } else { + f32::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } + } + TypedArrayName::Float64Array => { + if is_little_endian { + f64::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } else { + f64::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) + .into() + } + } + }; + + n.into() + } + + /// `25.1.2.10 GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-getvaluefrombuffer + pub(crate) fn get_value_from_buffer( + &self, + byte_index: usize, + t: TypedArrayName, + _is_typed_array: bool, + _order: SharedMemoryOrder, + is_little_endian: Option, + ) -> JsValue { + // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. + // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. + // 3. Let block be arrayBuffer.[[ArrayBufferData]]. + let block = self + .array_buffer_data + .as_ref() + .expect("ArrayBuffer cannot be detached here"); + + // 4. Let elementSize be the Element Size value specified in Table 73 for Element Type type. + let element_size = t.element_size(); + + // TODO: Shared Array Buffer + // 5. If IsSharedArrayBuffer(arrayBuffer) is true, then + + // 6. Else, let rawValue be a List whose elements are bytes from block at indices byteIndex (inclusive) through byteIndex + elementSize (exclusive). + // 7. Assert: The number of elements in rawValue is elementSize. + let raw_value = &block[byte_index..byte_index + element_size]; + + // TODO: Agent Record [[LittleEndian]] filed + // 8. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. + let is_little_endian = is_little_endian.unwrap_or(true); + + // 9. Return RawBytesToNumeric(type, rawValue, isLittleEndian). + Self::raw_bytes_to_numeric(t, raw_value, is_little_endian) + } + + /// `25.1.2.11 NumericToRawBytes ( type, value, isLittleEndian )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-numerictorawbytes + fn numeric_to_raw_bytes( + t: TypedArrayName, + value: JsValue, + is_little_endian: bool, + context: &mut Context, + ) -> JsResult> { + Ok(match t { + TypedArrayName::Int8Array if is_little_endian => { + value.to_int8(context)?.to_le_bytes().to_vec() + } + TypedArrayName::Int8Array => value.to_int8(context)?.to_be_bytes().to_vec(), + TypedArrayName::Uint8Array if is_little_endian => { + value.to_uint8(context)?.to_le_bytes().to_vec() + } + TypedArrayName::Uint8Array => value.to_uint8(context)?.to_be_bytes().to_vec(), + TypedArrayName::Uint8ClampedArray if is_little_endian => { + value.to_uint8_clamp(context)?.to_le_bytes().to_vec() + } + TypedArrayName::Uint8ClampedArray => { + value.to_uint8_clamp(context)?.to_be_bytes().to_vec() + } + TypedArrayName::Int16Array if is_little_endian => { + value.to_int16(context)?.to_le_bytes().to_vec() + } + TypedArrayName::Int16Array => value.to_int16(context)?.to_be_bytes().to_vec(), + TypedArrayName::Uint16Array if is_little_endian => { + value.to_uint16(context)?.to_le_bytes().to_vec() + } + TypedArrayName::Uint16Array => value.to_uint16(context)?.to_be_bytes().to_vec(), + TypedArrayName::Int32Array if is_little_endian => { + value.to_i32(context)?.to_le_bytes().to_vec() + } + TypedArrayName::Int32Array => value.to_i32(context)?.to_be_bytes().to_vec(), + TypedArrayName::Uint32Array if is_little_endian => { + value.to_u32(context)?.to_le_bytes().to_vec() + } + TypedArrayName::Uint32Array => value.to_u32(context)?.to_be_bytes().to_vec(), + TypedArrayName::BigInt64Array if is_little_endian => { + let big_int = value.to_big_int64(context)?; + big_int + .to_i64() + .unwrap_or_else(|| { + if big_int.is_positive() { + i64::MAX + } else { + i64::MIN + } + }) + .to_le_bytes() + .to_vec() + } + TypedArrayName::BigInt64Array => { + let big_int = value.to_big_int64(context)?; + big_int + .to_i64() + .unwrap_or_else(|| { + if big_int.is_positive() { + i64::MAX + } else { + i64::MIN + } + }) + .to_be_bytes() + .to_vec() + } + TypedArrayName::BigUint64Array if is_little_endian => value + .to_big_uint64(context)? + .to_u64() + .unwrap_or(u64::MAX) + .to_le_bytes() + .to_vec(), + TypedArrayName::BigUint64Array => value + .to_big_uint64(context)? + .to_u64() + .unwrap_or(u64::MAX) + .to_be_bytes() + .to_vec(), + TypedArrayName::Float32Array => match value.to_number(context)? { + f if is_little_endian => (f as f32).to_le_bytes().to_vec(), + f => (f as f32).to_be_bytes().to_vec(), + }, + TypedArrayName::Float64Array => match value.to_number(context)? { + f if is_little_endian => f.to_le_bytes().to_vec(), + f => f.to_be_bytes().to_vec(), + }, + }) + } + + /// `25.1.2.12 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-setvalueinbuffer + pub(crate) fn set_value_in_buffer( + &mut self, + byte_index: usize, + t: TypedArrayName, + value: JsValue, + _order: SharedMemoryOrder, + is_little_endian: Option, + context: &mut Context, + ) -> JsResult { + // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. + // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. + // 3. Assert: Type(value) is BigInt if ! IsBigIntElementType(type) is true; otherwise, Type(value) is Number. + // 4. Let block be arrayBuffer.[[ArrayBufferData]]. + let block = self + .array_buffer_data + .as_mut() + .expect("ArrayBuffer cannot be detached here"); + + // 5. Let elementSize be the Element Size value specified in Table 73 for Element Type type. + + // TODO: Agent Record [[LittleEndian]] filed + // 6. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. + let is_little_endian = is_little_endian.unwrap_or(true); + + // 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian). + let raw_bytes = Self::numeric_to_raw_bytes(t, value, is_little_endian, context)?; + + // TODO: Shared Array Buffer + // 8. If IsSharedArrayBuffer(arrayBuffer) is true, then + + // 9. Else, store the individual bytes of rawBytes into block, starting at block[byteIndex]. + for (i, raw_byte) in raw_bytes.iter().enumerate() { + block[byte_index + i] = *raw_byte; + } + + // 10. Return NormalCompletion(undefined). + Ok(JsValue::undefined()) + } +} + +/// `6.2.8.3 CopyDataBlockBytes ( toBlock, toIndex, fromBlock, fromIndex, count )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-copydatablockbytes +fn copy_data_block_bytes( + to_block: &mut Vec, + mut to_index: usize, + from_block: &[u8], + mut from_index: usize, + mut count: usize, +) { + // 1. Assert: fromBlock and toBlock are distinct values. + // 2. Let fromSize be the number of bytes in fromBlock. + let from_size = from_block.len(); + + // 3. Assert: fromIndex + count ≤ fromSize. + assert!(from_index + count <= from_size); + + // 4. Let toSize be the number of bytes in toBlock. + let to_size = to_block.len(); + + // 5. Assert: toIndex + count ≤ toSize. + assert!(to_index + count <= to_size); + + // 6. Repeat, while count > 0, + while count > 0 { + // a. If fromBlock is a Shared Data Block, then + // TODO: Shared Data Block + + // b. Else, + // i. Assert: toBlock is not a Shared Data Block. + // ii. Set toBlock[toIndex] to fromBlock[fromIndex]. + to_block[to_index] = from_block[from_index]; + + // c. Set toIndex to toIndex + 1. + to_index += 1; + + // d. Set fromIndex to fromIndex + 1. + from_index += 1; + + // e. Set count to count - 1. + count -= 1; + } + + // 7. Return NormalCompletion(empty). +} + +// TODO: Allow unused variants until shared array buffers are implemented. +#[allow(dead_code)] +#[derive(Debug, PartialEq)] +pub(crate) enum SharedMemoryOrder { + Init, + SeqCst, + Unordered, +} diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index a6f183f10b..4d76268361 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -17,9 +17,11 @@ use crate::{ object::ConstructorBuilder, property::Attribute, symbol::WellKnownSymbols, - value::IntegerOrInfinity, + value::{IntegerOrInfinity, PreferredType}, BoaProfiler, Context, JsBigInt, JsResult, JsValue, }; +use num_bigint::ToBigInt; + #[cfg(test)] mod tests; @@ -78,11 +80,35 @@ impl BigInt { /// [spec]: https://tc39.es/ecma262/#sec-bigint-objects /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt fn constructor(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let data = match args.get(0) { - Some(value) => value.to_bigint(context)?, - None => JsBigInt::zero(), - }; - Ok(JsValue::new(data)) + let value = args.get_or_undefined(0); + + // 2. Let prim be ? ToPrimitive(value, number). + let prim = value.to_primitive(context, PreferredType::Number)?; + + // 3. If Type(prim) is Number, return ? NumberToBigInt(prim). + if let Some(number) = prim.as_number() { + return Self::number_to_bigint(number, context); + } + + // 4. Otherwise, return ? ToBigInt(value). + Ok(value.to_bigint(context)?.into()) + } + + /// `NumberToBigInt ( number )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-numbertobigint + #[inline] + fn number_to_bigint(number: f64, context: &mut Context) -> JsResult { + // 1. If IsIntegralNumber(number) is false, throw a RangeError exception. + if number.is_nan() || number.is_infinite() || number.fract() != 0.0 { + return context.throw_range_error(format!("Cannot convert {} to BigInt", number)); + } + + // 2. Return the BigInt value that represents ℝ(number). + Ok(JsBigInt::from(number.to_bigint().expect("This conversion must be safe")).into()) } /// The abstract operation thisBigIntValue takes argument value. diff --git a/boa/src/builtins/bigint/tests.rs b/boa/src/builtins/bigint/tests.rs index 0caf9b2ddf..15738818b3 100644 --- a/boa/src/builtins/bigint/tests.rs +++ b/boa/src/builtins/bigint/tests.rs @@ -90,7 +90,7 @@ fn bigint_function_conversion_from_rational_with_fractional_part() { "#; assert_eq!( forward(&mut context, scenario), - "\"TypeError: The number 0.1 cannot be converted to a BigInt because it is not an integer\"" + "\"RangeError: Cannot convert 0.1 to BigInt\"" ); } diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 8a23ee1f5c..254ee7cb37 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -1713,11 +1713,8 @@ impl Date { } // 4. Return ? Invoke(O, "toISOString"). - if let Some(to_iso_string) = o.get_method(context, "toISOString")? { - to_iso_string.call(this, &[], context) - } else { - context.throw_type_error("toISOString in undefined") - } + let func = o.get("toISOString", context)?; + context.call(&func, &o.into(), &[]) } /// `Date.prototype.toString()` diff --git a/boa/src/builtins/iterable/mod.rs b/boa/src/builtins/iterable/mod.rs index a6d852d3a1..43d8aeb23c 100644 --- a/boa/src/builtins/iterable/mod.rs +++ b/boa/src/builtins/iterable/mod.rs @@ -82,7 +82,7 @@ impl IteratorPrototypes { } } -/// CreateIterResultObject( value, done ) +/// `CreateIterResultObject( value, done )` /// /// Generates an object supporting the IteratorResult interface. pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Context) -> JsValue { @@ -100,18 +100,70 @@ pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Conte obj.into() } -/// Get an iterator record -pub fn get_iterator(iterable: &JsValue, context: &mut Context) -> JsResult { - let iterator_function = iterable.get_field(WellKnownSymbols::iterator(), context)?; - if iterator_function.is_null_or_undefined() { - return Err(context.construct_type_error("Not an iterable")); - } - let iterator_object = context.call(&iterator_function, iterable, &[])?; - let next_function = iterator_object.get_field("next", context)?; - if next_function.is_null_or_undefined() { - return Err(context.construct_type_error("Could not find property `next`")); +/// Iterator hint for `GetIterator`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IteratorHint { + Sync, + Async, +} + +impl JsValue { + /// `GetIterator ( obj [ , hint [ , method ] ] )` + /// + /// More information: + /// - [ECMA reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-getiterator + pub fn get_iterator( + &self, + context: &mut Context, + hint: Option, + method: Option, + ) -> JsResult { + // 1. If hint is not present, set hint to sync. + let hint = hint.unwrap_or(IteratorHint::Sync); + + // 2. If method is not present, then + let method = if let Some(method) = method { + method + } else { + // a. If hint is async, then + if hint == IteratorHint::Async { + // i. Set method to ? GetMethod(obj, @@asyncIterator). + let method = self.get_method(WellKnownSymbols::async_iterator(), context)?; + // ii. If method is undefined, then + if method.is_undefined() { + // 1. Let syncMethod be ? GetMethod(obj, @@iterator). + let sync_method = self.get_method(WellKnownSymbols::iterator(), context)?; + // 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod). + let _sync_iterator_record = + self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method)); + // 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord). + todo!("CreateAsyncFromSyncIterator"); + } + + method + } else { + // b. Otherwise, set method to ? GetMethod(obj, @@iterator). + self.get_method(WellKnownSymbols::iterator(), context)? + } + }; + + // 3. Let iterator be ? Call(method, obj). + let iterator = context.call(&method, self, &[])?; + + // 4. If Type(iterator) is not Object, throw a TypeError exception. + if !iterator.is_object() { + return Err(context.construct_type_error("the iterator is not an object")); + } + + // 5. Let nextMethod be ? GetV(iterator, "next"). + let next_method = iterator.get_v("next", context)?; + + // 6. Let iteratorRecord be the Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }. + // 7. Return iteratorRecord. + Ok(IteratorRecord::new(iterator, next_method)) } - Ok(IteratorRecord::new(iterator_object, next_function)) } /// Create the %IteratorPrototype% object @@ -201,6 +253,49 @@ impl IteratorRecord { } } +/// `IterableToList ( items [ , method ] )` +/// +/// More information: +/// - [ECMA reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-iterabletolist +pub(crate) fn iterable_to_list( + context: &mut Context, + items: JsValue, + method: Option, +) -> JsResult> { + // 1. If method is present, then + let iterator_record = if let Some(method) = method { + // a. Let iteratorRecord be ? GetIterator(items, sync, method). + items.get_iterator(context, Some(IteratorHint::Sync), Some(method))? + } else { + // 2. Else, + + // a. Let iteratorRecord be ? GetIterator(items, sync). + items.get_iterator(context, Some(IteratorHint::Sync), None)? + }; + // 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. + loop { + let next = iterator_record.next(context)?; + if next.done { + break; + } + + values.push(next.value) + } + + // 6. Return values. + Ok(values) +} + #[derive(Debug)] pub struct IteratorResult { pub value: JsValue, diff --git a/boa/src/builtins/map/map_iterator.rs b/boa/src/builtins/map/map_iterator.rs index 8b005b24aa..ee4fd2df26 100644 --- a/boa/src/builtins/map/map_iterator.rs +++ b/boa/src/builtins/map/map_iterator.rs @@ -79,37 +79,34 @@ impl MapIterator { .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(); - while index < num_entries { - let e = entries.get_index(index); - index += 1; - map_iterator.map_next_index = index; - if let Some((key, value)) = e { - let item = match item_kind { - PropertyNameKind::Key => { - Ok(create_iter_result_object(key.clone(), false, context)) - } - PropertyNameKind::Value => { - Ok(create_iter_result_object(value.clone(), false, context)) - } - PropertyNameKind::KeyAndValue => { - let result = Array::create_array_from_list( - [key.clone(), value.clone()], - context, - ); - Ok(create_iter_result_object(result.into(), false, context)) - } - }; - drop(map); - map_iterator.iterated_map = Some(obj); - return item; + let e = { + let map = obj.borrow(); + let entries = map.as_map_ref().expect("iterator should only iterate maps"); + let len = entries.full_len(); + loop { + let element = entries + .get_index(map_iterator.map_next_index) + .map(|(v, k)| (v.clone(), k.clone())); + map_iterator.map_next_index += 1; + if element.is_some() || map_iterator.map_next_index >= len { + break element; + } } + }; + if let Some((key, value)) = e { + let item = match item_kind { + PropertyNameKind::Key => Ok(create_iter_result_object(key, false, context)), + PropertyNameKind::Value => Ok(create_iter_result_object(value, false, context)), + PropertyNameKind::KeyAndValue => { + let result = Array::create_array_from_list([key, value], context); + Ok(create_iter_result_object(result.into(), false, context)) + } + }; + map_iterator.iterated_map = Some(obj); + return item; } } diff --git a/boa/src/builtins/map/mod.rs b/boa/src/builtins/map/mod.rs index 227415f699..54f0be6288 100644 --- a/boa/src/builtins/map/mod.rs +++ b/boa/src/builtins/map/mod.rs @@ -13,10 +13,7 @@ #![allow(clippy::mutable_key_type)] use crate::{ - builtins::{ - iterable::{get_iterator, IteratorResult}, - BuiltIn, - }, + builtins::{iterable::IteratorResult, BuiltIn}, context::StandardObjects, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -555,7 +552,7 @@ pub(crate) fn add_entries_from_iterable( }; // 2. Let iteratorRecord be ? GetIterator(iterable). - let iterator_record = get_iterator(iterable, context)?; + let iterator_record = iterable.get_iterator(context, None, None)?; // 3. Repeat, loop { diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index f1544ef3d1..b6b05808f0 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -1,6 +1,7 @@ //! Builtins live here, such as Object, String, Math, etc. pub mod array; +pub mod array_buffer; pub mod bigint; pub mod boolean; #[cfg(feature = "console")] @@ -22,6 +23,7 @@ pub mod regexp; pub mod set; pub mod string; pub mod symbol; +pub mod typed_array; pub mod undefined; pub(crate) use self::{ @@ -47,9 +49,15 @@ pub(crate) use self::{ set::Set, string::String, symbol::Symbol, + typed_array::{ + BigInt64Array, BigUint64Array, Float32Array, Float64Array, Int16Array, Int32Array, + Int8Array, Uint16Array, Uint32Array, Uint8Array, Uint8ClampedArray, + }, undefined::Undefined, }; + use crate::{ + builtins::array_buffer::ArrayBuffer, property::{Attribute, PropertyDescriptor}, Context, JsValue, }; @@ -113,6 +121,7 @@ pub fn init(context: &mut Context) { Math, Json, Array, + ArrayBuffer, BigInt, Boolean, Date, @@ -121,6 +130,17 @@ pub fn init(context: &mut Context) { Set, String, RegExp, + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + BigInt64Array, + BigUint64Array, + Float32Array, + Float64Array, Symbol, Error, RangeError, diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 4713579852..b09ba6436e 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -610,7 +610,6 @@ impl Number { let integer_cursor = int_iter.next().unwrap().0 + 1; let fraction_cursor = fraction_cursor + BUF_SIZE / 2; - // dbg!("Number: {}, Radix: {}, Cursors: {}, {}", value, radix, integer_cursor, fraction_cursor); String::from_utf8_lossy(&buffer[integer_cursor..fraction_cursor]).into() } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 51388dcac3..aa8e07c125 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -136,7 +136,7 @@ impl BuiltIn for RegExp { .method(Self::to_string, "toString", 0) .method( Self::r#match, - (WellKnownSymbols::match_(), "[Symbol.match]"), + (WellKnownSymbols::r#match(), "[Symbol.match]"), 1, ) .method( @@ -983,7 +983,7 @@ impl RegExp { let named_groups = match_value.named_groups(); let groups = if named_groups.clone().count() > 0 { // a. Let groups be ! OrdinaryObjectCreate(null). - let groups = JsValue::new_object(context); + let groups = JsValue::from(JsObject::new(Object::create(JsValue::null()))); // Perform 27.f here // f. If the ith capture of R was defined with a GroupName, then @@ -1211,16 +1211,17 @@ impl RegExp { let c = this .as_object() .unwrap_or_default() - .species_constructor(context.global_object().get(RegExp::NAME, context)?, context)?; + .species_constructor(StandardObjects::regexp_object, context)?; // 5. Let flags be ? ToString(? Get(R, "flags")). let flags = this.get_field("flags", context)?.to_string(context)?; // 6. Let matcher be ? Construct(C, « R, flags »). - let matcher = c - .as_object() - .expect("SpeciesConstructor returned non Object") - .construct(&[this.clone(), flags.clone().into()], &c, context)?; + let matcher = c.construct( + &[this.clone(), flags.clone().into()], + &c.clone().into(), + context, + )?; // 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). let last_index = this.get_field("lastIndex", context)?.to_length(context)?; @@ -1582,8 +1583,7 @@ impl RegExp { .to_string(context)?; // 4. Let C be ? SpeciesConstructor(rx, %RegExp%). - let constructor = - rx.species_constructor(context.global_object().get(RegExp::NAME, context)?, context)?; + let constructor = rx.species_constructor(StandardObjects::regexp_object, context)?; // 5. Let flags be ? ToString(? Get(rx, "flags")). let flags = rx.get("flags", context)?.to_string(context)?; @@ -1601,14 +1601,11 @@ impl RegExp { }; // 10. Let splitter be ? Construct(C, « rx, newFlags »). - let splitter = constructor - .as_object() - .expect("SpeciesConstructor returned non Object") - .construct( - &[JsValue::from(rx), new_flags.into()], - &constructor, - context, - )?; + let splitter = constructor.construct( + &[rx.into(), new_flags.into()], + &constructor.clone().into(), + context, + )?; // 11. Let A be ! ArrayCreate(0). let a = Array::array_create(0, None, context).unwrap(); diff --git a/boa/src/builtins/set/mod.rs b/boa/src/builtins/set/mod.rs index f1021b7b47..89fa6bea5a 100644 --- a/boa/src/builtins/set/mod.rs +++ b/boa/src/builtins/set/mod.rs @@ -11,7 +11,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set use crate::{ - builtins::{iterable::get_iterator, BuiltIn}, + builtins::BuiltIn, context::StandardObjects, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -152,7 +152,7 @@ impl Set { } // 7 - let iterator_record = get_iterator(iterable, context)?; + let iterator_record = iterable.clone().get_iterator(context, None, None)?; // 8.a let mut next = iterator_record.next(context)?; diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 7ee8e0584d..4d9e0053f2 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -758,16 +758,13 @@ impl String { // 2. If searchValue is neither undefined nor null, then if !search_value.is_null_or_undefined() { // a. Let replacer be ? GetMethod(searchValue, @@replace). - let replacer = search_value - .as_object() - .unwrap_or_default() - .get_method(context, WellKnownSymbols::replace())?; + let replacer = search_value.get_method(WellKnownSymbols::replace(), context)?; // b. If replacer is not undefined, then - if let Some(replacer) = replacer { + if !replacer.is_undefined() { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). return context.call( - &replacer.into(), + &replacer, search_value, &[this.clone(), replace_value.clone()], ); @@ -892,15 +889,12 @@ impl String { } // c. Let replacer be ? GetMethod(searchValue, @@replace). - let replacer = search_value - .as_object() - .unwrap_or_default() - .get_method(context, WellKnownSymbols::replace())?; + let replacer = search_value.get_method(WellKnownSymbols::replace(), context)?; // d. If replacer is not undefined, then - if let Some(replacer) = replacer { + if !replacer.is_undefined() { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). - return replacer.call(search_value, &[o.into(), replace_value.clone()], context); + return context.call(&replacer, search_value, &[o.into(), replace_value.clone()]); } } @@ -1133,12 +1127,11 @@ impl String { let regexp = args.get_or_undefined(0); if !regexp.is_null_or_undefined() { // a. Let matcher be ? GetMethod(regexp, @@match). + let matcher = regexp.get_method(WellKnownSymbols::r#match(), context)?; // b. If matcher is not undefined, then - if let Some(obj) = regexp.as_object() { - if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_())? { - // i. Return ? Call(matcher, regexp, « O »). - return matcher.call(regexp, &[o.clone()], context); - } + if !matcher.is_undefined() { + // i. Return ? Call(matcher, regexp, « O »). + return context.call(&matcher, regexp, &[o.clone()]); } } @@ -1149,12 +1142,7 @@ impl String { let rx = RegExp::create(regexp.clone(), JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@match, « S »). - let obj = rx.as_object().expect("RegExpCreate must return Object"); - if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_())? { - matcher.call(&rx, &[JsValue::new(s)], context) - } else { - context.throw_type_error("RegExp[Symbol.match] is undefined") - } + rx.invoke(WellKnownSymbols::r#match(), &[JsValue::new(s)], context) } /// Abstract method `StringPad`. @@ -1504,14 +1492,11 @@ impl String { // 2. If separator is neither undefined nor null, then if !separator.is_null_or_undefined() { // a. Let splitter be ? GetMethod(separator, @@split). + let splitter = separator.get_method(WellKnownSymbols::split(), context)?; // b. If splitter is not undefined, then - if let Some(splitter) = separator - .as_object() - .unwrap_or_default() - .get_method(context, WellKnownSymbols::split())? - { + if !splitter.is_undefined() { // i. Return ? Call(splitter, separator, « O, limit »). - return splitter.call(separator, &[this.clone(), limit.clone()], context); + return context.call(&splitter, separator, &[this.clone(), limit.clone()]); } } @@ -1694,12 +1679,11 @@ impl String { } // c. Let matcher be ? GetMethod(regexp, @@matchAll). + let matcher = regexp.get_method(WellKnownSymbols::match_all(), context)?; // d. If matcher is not undefined, then - if let Some(obj) = regexp.as_object() { - if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_all())? { - // i. Return ? Call(matcher, regexp, « O »). - return matcher.call(regexp, &[o.clone()], context); - } + if !matcher.is_undefined() { + // i. Return ? Call(matcher, regexp, « O »). + return context.call(&matcher, regexp, &[o.clone()]); } } @@ -1710,12 +1694,7 @@ impl String { let rx = RegExp::create(regexp.clone(), JsValue::new("g"), context)?; // 5. Return ? Invoke(rx, @@matchAll, « S »). - let obj = rx.as_object().expect("RegExpCreate must return Object"); - if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_all())? { - matcher.call(&rx, &[JsValue::new(s)], context) - } else { - context.throw_type_error("RegExp[Symbol.matchAll] is undefined") - } + rx.invoke(WellKnownSymbols::match_all(), &[JsValue::new(s)], context) } /// `String.prototype.normalize( [ form ] )` @@ -1778,12 +1757,11 @@ impl String { let regexp = args.get_or_undefined(0); if !regexp.is_null_or_undefined() { // a. Let searcher be ? GetMethod(regexp, @@search). + let searcher = regexp.get_method(WellKnownSymbols::search(), context)?; // b. If searcher is not undefined, then - if let Some(obj) = regexp.as_object() { - if let Some(searcher) = obj.get_method(context, WellKnownSymbols::search())? { - // i. Return ? Call(searcher, regexp, « O »). - return searcher.call(regexp, &[o.clone()], context); - } + if !searcher.is_undefined() { + // i. Return ? Call(searcher, regexp, « O »). + return context.call(&searcher, regexp, &[o.clone()]); } } @@ -1794,12 +1772,7 @@ impl String { let rx = RegExp::create(regexp.clone(), JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@search, « string »). - let obj = rx.as_object().expect("RegExpCreate must return Object"); - if let Some(matcher) = obj.get_method(context, WellKnownSymbols::search())? { - matcher.call(&rx, &[JsValue::new(string)], context) - } else { - context.throw_type_error("RegExp[Symbol.search] is undefined") - } + rx.invoke(WellKnownSymbols::search(), &[JsValue::new(string)], context) } pub(crate) fn iterator( diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index 482a7e0eb7..02cf30aabb 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -87,7 +87,7 @@ impl BuiltIn for Symbol { let symbol_has_instance = WellKnownSymbols::has_instance(); let symbol_is_concat_spreadable = WellKnownSymbols::is_concat_spreadable(); let symbol_iterator = WellKnownSymbols::iterator(); - let symbol_match = WellKnownSymbols::match_(); + let symbol_match = WellKnownSymbols::r#match(); let symbol_match_all = WellKnownSymbols::match_all(); let symbol_replace = WellKnownSymbols::replace(); let symbol_search = WellKnownSymbols::search(); diff --git a/boa/src/builtins/typed_array/integer_indexed_object.rs b/boa/src/builtins/typed_array/integer_indexed_object.rs new file mode 100644 index 0000000000..4541581d60 --- /dev/null +++ b/boa/src/builtins/typed_array/integer_indexed_object.rs @@ -0,0 +1,184 @@ +//! This module implements the `Integer-Indexed` exotic object. +//! +//! An `Integer-Indexed` exotic object is an exotic object that performs +//! special handling of integer index property keys. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects + +use crate::{ + builtins::typed_array::TypedArrayName, + gc::{empty_trace, Finalize, Trace}, + object::{JsObject, ObjectData}, + Context, JsResult, +}; + +/// Type of the array content. +#[derive(Debug, Clone, Copy, Finalize, PartialEq)] +pub(crate) enum ContentType { + Number, + BigInt, +} + +unsafe impl Trace for ContentType { + // safe because `ContentType` is `Copy` + empty_trace!(); +} + +/// +#[derive(Debug, Clone, Trace, Finalize)] +pub struct IntegerIndexed { + viewed_array_buffer: Option, + typed_array_name: TypedArrayName, + byte_offset: usize, + byte_length: usize, + array_length: usize, +} + +impl IntegerIndexed { + pub(crate) fn new( + viewed_array_buffer: Option, + typed_array_name: TypedArrayName, + byte_offset: usize, + byte_length: usize, + array_length: usize, + ) -> Self { + Self { + viewed_array_buffer, + typed_array_name, + byte_offset, + byte_length, + array_length, + } + } + + /// `IntegerIndexedObjectCreate ( prototype )` + /// + /// Create a new `JsObject from a prototype and a `IntergetIndexedObject` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-integerindexedobjectcreate + pub(super) fn create(prototype: JsObject, data: Self, context: &Context) -> JsObject { + // 1. Let internalSlotsList be « [[Prototype]], [[Extensible]], [[ViewedArrayBuffer]], [[TypedArrayName]], [[ContentType]], [[ByteLength]], [[ByteOffset]], [[ArrayLength]] ». + // 2. Let A be ! MakeBasicObject(internalSlotsList). + let a = context.construct_object(); + + // 3. Set A.[[GetOwnProperty]] as specified in 10.4.5.1. + // 4. Set A.[[HasProperty]] as specified in 10.4.5.2. + // 5. Set A.[[DefineOwnProperty]] as specified in 10.4.5.3. + // 6. Set A.[[Get]] as specified in 10.4.5.4. + // 7. Set A.[[Set]] as specified in 10.4.5.5. + // 8. Set A.[[Delete]] as specified in 10.4.5.6. + // 9. Set A.[[OwnPropertyKeys]] as specified in 10.4.5.7. + a.borrow_mut().data = ObjectData::integer_indexed(data); + + // 10. Set A.[[Prototype]] to prototype. + a.set_prototype_instance(prototype.into()); + + // 11. Return A. + a + } + + /// Abstract operation `IsDetachedBuffer ( arrayBuffer )`. + /// + /// Check if `[[ArrayBufferData]]` is null. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer + pub(crate) fn is_detached(&self) -> bool { + if let Some(obj) = &self.viewed_array_buffer { + obj.borrow() + .as_array_buffer() + .expect("Typed array must have internal array buffer object") + .is_detached_buffer() + } else { + false + } + } + + /// Get the integer indexed object's byte offset. + pub(crate) fn byte_offset(&self) -> usize { + self.byte_offset + } + + /// Set the integer indexed object's byte offset. + pub(crate) fn set_byte_offset(&mut self, byte_offset: usize) { + self.byte_offset = byte_offset; + } + + /// Get the integer indexed object's typed array name. + pub(crate) fn typed_array_name(&self) -> TypedArrayName { + self.typed_array_name + } + + /// Get a reference to the integer indexed object's viewed array buffer. + pub fn viewed_array_buffer(&self) -> Option<&JsObject> { + self.viewed_array_buffer.as_ref() + } + + ///(crate) Set the integer indexed object's viewed array buffer. + pub fn set_viewed_array_buffer(&mut self, viewed_array_buffer: Option) { + self.viewed_array_buffer = viewed_array_buffer; + } + + /// Get the integer indexed object's byte length. + pub fn byte_length(&self) -> usize { + self.byte_length + } + + /// Set the integer indexed object's byte length. + pub(crate) fn set_byte_length(&mut self, byte_length: usize) { + self.byte_length = byte_length; + } + + /// Get the integer indexed object's array length. + pub fn array_length(&self) -> usize { + self.array_length + } + + /// Set the integer indexed object's array length. + pub(crate) fn set_array_length(&mut self, array_length: usize) { + self.array_length = array_length; + } +} + +/// A Data Block +/// +/// The Data Block specification type is used to describe a distinct and mutable sequence of +/// byte-sized (8 bit) numeric values. A byte value is an integer value in the range `0` through +/// `255`, inclusive. A Data Block value is created with a fixed number of bytes that each have +/// the initial value `0`. +/// +/// For more information, check the [spec][spec]. +/// +/// [spec]: https://tc39.es/ecma262/#sec-data-blocks +#[derive(Debug, Clone, Default, Trace, Finalize)] +pub struct DataBlock { + inner: Vec, +} + +impl DataBlock { + /// `CreateByteDataBlock ( size )` abstract operation. + /// + /// The abstract operation `CreateByteDataBlock` takes argument `size` (a non-negative + /// integer). For more information, check the [spec][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-createbytedatablock + pub fn create_byte_data_block(size: usize) -> JsResult { + // 1. Let db be a new Data Block value consisting of size bytes. If it is impossible to + // create such a Data Block, throw a RangeError exception. + // 2. Set all of the bytes of db to 0. + // 3. Return db. + // TODO: waiting on for having fallible + // allocation. + Ok(Self { + inner: vec![0u8; size], + }) + } +} diff --git a/boa/src/builtins/typed_array/mod.rs b/boa/src/builtins/typed_array/mod.rs new file mode 100644 index 0000000000..a3ea0cc57a --- /dev/null +++ b/boa/src/builtins/typed_array/mod.rs @@ -0,0 +1,3370 @@ +//! This module implements the global `TypedArray` objects. +//! +//! A `TypedArray` object describes an array-like view of an underlying binary data buffer. +//! There is no global property named `TypedArray`, nor is there a directly visible `TypedArray` constructor. +//! Instead, there are a number of different global properties, +//! whose values are typed array constructors for specific element types. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! - [MDN documentation][mdn] +//! +//! [spec]: https://tc39.es/ecma262/#sec-typedarray-objects +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray + +use crate::{ + builtins::{ + array_buffer::{ArrayBuffer, SharedMemoryOrder}, + iterable::iterable_to_list, + typed_array::integer_indexed_object::{ContentType, IntegerIndexed}, + Array, ArrayIterator, BuiltIn, JsArgs, + }, + context::{StandardConstructor, StandardObjects}, + gc::{empty_trace, Finalize, Trace}, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, + JsObject, ObjectData, + }, + property::{Attribute, PropertyNameKind}, + symbol::WellKnownSymbols, + value::{IntegerOrInfinity, JsValue}, + BoaProfiler, Context, JsResult, JsString, +}; +use num_traits::{Signed, Zero}; +use std::cmp::Ordering; + +pub mod integer_indexed_object; + +macro_rules! typed_array { + ($ty:ident, $name:literal, $global_object_name:ident) => { + #[doc = concat!("JavaScript `", $name, "` built-in implementation.")] + #[derive(Debug, Clone, Copy)] + pub struct $ty; + + impl BuiltIn for $ty { + const NAME: &'static str = $name; + + const ATTRIBUTE: Attribute = Attribute::WRITABLE + .union(Attribute::NON_ENUMERABLE) + .union(Attribute::CONFIGURABLE); + + fn init(context: &mut Context) -> JsValue { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + let typed_array_constructor = context.typed_array_constructor().constructor(); + let typed_array_constructor_proto = context.typed_array_constructor().prototype(); + + let get_species = FunctionBuilder::native(context, TypedArray::get_species) + .name("get [Symbol.species]") + .constructable(false) + .build(); + + ConstructorBuilder::with_standard_object( + context, + Self::constructor, + context.standard_objects().$global_object_name().clone(), + ) + .name(Self::NAME) + .length(Self::LENGTH) + .static_accessor( + WellKnownSymbols::species(), + Some(get_species), + None, + Attribute::CONFIGURABLE, + ) + .property( + "BYTES_PER_ELEMENT", + TypedArrayName::$ty.element_size(), + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ) + .static_property( + "BYTES_PER_ELEMENT", + TypedArrayName::$ty.element_size(), + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ) + .custom_prototype(typed_array_constructor.into()) + .inherit(typed_array_constructor_proto.into()) + .build() + .into() + } + } + + impl $ty { + const LENGTH: usize = 3; + + /// `23.2.5.1 TypedArray ( ...args )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-typedarray + fn constructor( + new_target: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. If NewTarget is undefined, throw a TypeError exception. + if new_target.is_undefined() { + return context.throw_type_error(concat!( + "new target was undefined when constructing an ", + $name + )); + } + + // 2. Let constructorName be the String value of the Constructor Name value specified in Table 72 for this TypedArray constructor. + let constructor_name = TypedArrayName::$ty; + + // 3. Let proto be "%TypedArray.prototype%". + let proto = StandardObjects::$global_object_name; + + // 4. Let numberOfArgs be the number of elements in args. + let number_of_args = args.len(); + + // 5. If numberOfArgs = 0, then + if number_of_args == 0 { + // a. Return ? AllocateTypedArray(constructorName, NewTarget, proto, 0). + return Ok(TypedArray::allocate( + constructor_name, + new_target, + proto, + Some(0), + context, + )? + .into()); + } + // 6. Else, + + // a. Let firstArgument be args[0]. + let first_argument = &args[0]; + + // b. If Type(firstArgument) is Object, then + if let Some(first_argument) = first_argument.as_object() { + // i. Let O be ? AllocateTypedArray(constructorName, NewTarget, proto). + let o = + TypedArray::allocate(constructor_name, new_target, proto, None, context)?; + + // ii. If firstArgument has a [[TypedArrayName]] internal slot, then + if first_argument.is_typed_array() { + // 1. Perform ? InitializeTypedArrayFromTypedArray(O, firstArgument). + TypedArray::initialize_from_typed_array(&o, first_argument, context)?; + } else if first_argument.is_array_buffer() { + // iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then + + // 1. If numberOfArgs > 1, let byteOffset be args[1]; else let byteOffset be undefined. + let byte_offset = args.get_or_undefined(1); + + // 2. If numberOfArgs > 2, let length be args[2]; else let length be undefined. + let length = args.get_or_undefined(2); + + // 3. Perform ? InitializeTypedArrayFromArrayBuffer(O, firstArgument, byteOffset, length). + TypedArray::initialize_from_array_buffer( + &o, + first_argument, + byte_offset, + length, + context, + )?; + } else { + // iv. Else, + + // 1. Assert: Type(firstArgument) is Object and firstArgument does not have + // either a [[TypedArrayName]] or an [[ArrayBufferData]] internal slot. + + // 2. Let usingIterator be ? GetMethod(firstArgument, @@iterator). + + let first_argument_v = JsValue::from(first_argument.clone()); + let using_iterator = + first_argument_v.get_method(WellKnownSymbols::replace(), context)?; + + // 3. If usingIterator is not undefined, then + if !using_iterator.is_undefined() { + // a. Let values be ? IterableToList(firstArgument, usingIterator). + let values = + iterable_to_list(context, first_argument_v, Some(using_iterator))?; + + // b. Perform ? InitializeTypedArrayFromList(O, values). + TypedArray::initialize_from_list(&o, values, context)?; + } else { + // 4. Else, + + // a. NOTE: firstArgument is not an Iterable so assume it is already an array-like object. + // b. Perform ? InitializeTypedArrayFromArrayLike(O, firstArgument). + TypedArray::initialize_from_array_like(&o, &first_argument, context)?; + } + } + + // v. Return O. + Ok(o.into()) + } else { + // c. Else, + + // i. Assert: firstArgument is not an Object. + assert!(!first_argument.is_object(), "firstArgument was an object"); + + // ii. Let elementLength be ? ToIndex(firstArgument). + let element_length = first_argument.to_index(context)?; + + // iii. Return ? AllocateTypedArray(constructorName, NewTarget, proto, elementLength). + Ok(TypedArray::allocate( + constructor_name, + new_target, + proto, + Some(element_length), + context, + )? + .into()) + } + } + } + }; +} + +/// The JavaScript `%TypedArray%` object. +/// +/// +#[derive(Debug, Clone, Copy)] +pub(crate) struct TypedArray; + +impl TypedArray { + const NAME: &'static str = "TypedArray"; + + const LENGTH: usize = 0; + + pub(crate) fn init(context: &mut Context) -> JsObject { + let get_species = FunctionBuilder::native(context, Self::get_species) + .name("get [Symbol.species]") + .constructable(false) + .build(); + + let get_buffer = FunctionBuilder::native(context, Self::buffer) + .name("get buffer") + .constructable(false) + .build(); + + let get_byte_length = FunctionBuilder::native(context, Self::byte_length) + .name("get byteLength") + .constructable(false) + .build(); + + let get_byte_offset = FunctionBuilder::native(context, Self::byte_offset) + .name("get byteOffset") + .constructable(false) + .build(); + + let get_length = FunctionBuilder::native(context, Self::length) + .name("get length") + .constructable(false) + .build(); + + let get_to_string_tag = FunctionBuilder::native(context, Self::to_string_tag) + .name("get [Symbol.toStringTag]") + .constructable(false) + .build(); + + let values_function = FunctionBuilder::native(context, Self::values) + .name("values") + .length(0) + .constructable(false) + .build(); + + let object = ConstructorBuilder::with_standard_object( + context, + Self::constructor, + context.standard_objects().typed_array_object().clone(), + ) + .name(Self::NAME) + .length(Self::LENGTH) + .static_accessor( + WellKnownSymbols::species(), + Some(get_species), + None, + Attribute::CONFIGURABLE, + ) + .property( + "length", + 0, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ) + .property( + WellKnownSymbols::iterator(), + values_function, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .accessor( + "buffer", + Some(get_buffer), + None, + Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ) + .accessor( + "byteLength", + Some(get_byte_length), + None, + Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ) + .accessor( + "byteOffset", + Some(get_byte_offset), + None, + Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ) + .accessor( + "length", + Some(get_length), + None, + Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ) + .accessor( + WellKnownSymbols::to_string_tag(), + Some(get_to_string_tag), + None, + Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ) + .static_method(Self::from, "from", 1) + .static_method(Self::of, "of", 0) + .method(Self::at, "at", 1) + .method(Self::copy_within, "copyWithin", 2) + .method(Self::entries, "entries", 0) + .method(Self::every, "every", 1) + .method(Self::fill, "fill", 1) + .method(Self::filter, "filter", 1) + .method(Self::find, "find", 1) + .method(Self::findindex, "findIndex", 1) + .method(Self::foreach, "forEach", 1) + .method(Self::includes, "includes", 1) + .method(Self::index_of, "indexOf", 1) + .method(Self::join, "join", 1) + .method(Self::keys, "keys", 0) + .method(Self::last_index_of, "lastIndexOf", 1) + .method(Self::map, "map", 1) + .method(Self::reduce, "reduce", 1) + .method(Self::reduceright, "reduceRight", 1) + .method(Self::reverse, "reverse", 0) + .method(Self::set, "set", 1) + .method(Self::slice, "slice", 2) + .method(Self::some, "some", 1) + .method(Self::sort, "sort", 1) + .method(Self::subarray, "subarray", 2) + .method(Self::values, "values", 0) + // 23.2.3.29 %TypedArray%.prototype.toString ( ) + // The initial value of the %TypedArray%.prototype.toString data property is the same + // built-in function object as the Array.prototype.toString method defined in 23.1.3.30. + .method(Array::to_string, "toString", 0) + .build(); + + object + } + + /// `23.2.1.1 %TypedArray% ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray% + fn constructor( + _new_target: &JsValue, + _args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Throw a TypeError exception. + context.throw_type_error("the TypedArray constructor should never be called directly") + } + + /// `23.2.2.1 %TypedArray%.from ( source [ , mapfn [ , thisArg ] ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.from + fn from(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let C be the this value. + // 2. If IsConstructor(C) is false, throw a TypeError exception. + let constructor = match this.as_object() { + Some(obj) if obj.is_constructable() => obj, + _ => { + return context + .throw_type_error("TypedArray.from called on non-constructable value") + } + }; + + let mapping = match args.get(1) { + // 3. If mapfn is undefined, let mapping be false. + None | Some(JsValue::Undefined) => None, + // 4. Else, + Some(v) => match v.as_object() { + // b. Let mapping be true. + Some(obj) if obj.is_callable() => Some(obj), + // a. If IsCallable(mapfn) is false, throw a TypeError exception. + _ => { + return context + .throw_type_error("TypedArray.from called with non-callable mapfn") + } + }, + }; + + // 5. Let usingIterator be ? GetMethod(source, @@iterator). + let source = args.get_or_undefined(0); + let using_iterator = source.get_method(WellKnownSymbols::iterator(), context)?; + + let this_arg = args.get_or_undefined(2); + + // 6. If usingIterator is not undefined, then + if !using_iterator.is_undefined() { + // a. Let values be ? IterableToList(source, usingIterator). + let values = iterable_to_list(context, source.clone(), Some(using_iterator))?; + + // b. Let len be the number of elements in values. + // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). + let target_obj = Self::create(&constructor, &[values.len().into()], context)?; + + // d. Let k be 0. + // e. Repeat, while k < len, + for (k, k_value) in values.iter().enumerate() { + // i. Let Pk be ! ToString(𝔽(k)). + // ii. Let kValue be the first element of values and remove that element from values. + // iii. If mapping is true, then + let mapped_value = if let Some(map_fn) = &mapping { + // 1. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). + map_fn.call(this_arg, &[k_value.clone(), k.into()], context)? + } + // iv. Else, let mappedValue be kValue. + else { + k_value.clone() + }; + + // v. Perform ? Set(targetObj, Pk, mappedValue, true). + target_obj.set(k, mapped_value, true, context)?; + } + + // f. Assert: values is now an empty List. + // g. Return targetObj. + return Ok(target_obj.into()); + } + + // 7. NOTE: source is not an Iterable so assume it is already an array-like object. + // 8. Let arrayLike be ! ToObject(source). + let array_like = source + .to_object(context) + .expect("ToObject cannot fail here"); + + // 9. Let len be ? LengthOfArrayLike(arrayLike). + let len = array_like.length_of_array_like(context)?; + + // 10. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). + let target_obj = Self::create(&constructor, &[len.into()], context)?; + + // 11. Let k be 0. + // 12. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ? Get(arrayLike, Pk). + let k_value = array_like.get(k, context)?; + + // c. If mapping is true, then + let mapped_value = if let Some(map_fn) = &mapping { + // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). + map_fn.call(this_arg, &[k_value, k.into()], context)? + } + // d. Else, let mappedValue be kValue. + else { + k_value + }; + + // e. Perform ? Set(targetObj, Pk, mappedValue, true). + target_obj.set(k, mapped_value, true, context)?; + } + + // 13. Return targetObj. + Ok(target_obj.into()) + } + + /// `23.2.2.2 %TypedArray%.of ( ...items )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.of + fn of(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let len be the number of elements in items. + + // 2. Let C be the this value. + // 3. If IsConstructor(C) is false, throw a TypeError exception. + let constructor = match this.as_object() { + Some(obj) if obj.is_constructable() => obj, + _ => { + return context.throw_type_error("TypedArray.of called on non-constructable value") + } + }; + + // 4. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »). + let new_obj = Self::create(&constructor, &[args.len().into()], context)?; + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for (k, k_value) in args.iter().enumerate() { + // a. Let kValue be items[k]. + // b. Let Pk be ! ToString(𝔽(k)). + // c. Perform ? Set(newObj, Pk, kValue, true). + new_obj.set(k, k_value, true, context)?; + } + + // 7. Return newObj. + Ok(new_obj.into()) + } + + /// `23.2.2.4 get %TypedArray% [ @@species ]` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%-@@species + fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + // 1. Return the this value. + Ok(this.clone()) + } + + /// `23.2.3.1 %TypedArray%.prototype.at ( index )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.at + fn at(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). + let relative_index = args.get_or_undefined(0).to_integer_or_infinity(context)?; + + let k = match relative_index { + // Note: Early undefined return on infinity. + IntegerOrInfinity::PositiveInfinity | IntegerOrInfinity::NegativeInfinity => { + return Ok(JsValue::undefined()) + } + // 5. If relativeIndex ≥ 0, then + // a. Let k be relativeIndex. + IntegerOrInfinity::Integer(i) if i >= 0 => i, + // 6. Else, + // a. Let k be len + relativeIndex. + IntegerOrInfinity::Integer(i) => len + i, + }; + + // 7. If k < 0 or k ≥ len, return undefined. + if k < 0 || k >= len { + return Ok(JsValue::undefined()); + } + + // 8. Return ! Get(O, ! ToString(𝔽(k))). + Ok(obj.get(k, context).expect("Get cannot fail here")) + } + + /// `23.2.3.2 get %TypedArray%.prototype.buffer` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.buffer + fn buffer(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let typed_array = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + // 5. Return buffer. + Ok(typed_array + .viewed_array_buffer() + .map(|buffer| buffer.clone().into()) + .unwrap_or_else(JsValue::undefined)) + } + + /// `23.2.3.3 get %TypedArray%.prototype.byteLength` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.bytelength + fn byte_length(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let typed_array = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. + // 6. Let size be O.[[ByteLength]]. + // 7. Return 𝔽(size). + if typed_array.is_detached() { + Ok(0.into()) + } else { + Ok(typed_array.byte_length().into()) + } + } + + /// `23.2.3.4 get %TypedArray%.prototype.byteOffset` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.byteoffset + fn byte_offset(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let typed_array = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. + // 6. Let offset be O.[[ByteOffset]]. + // 7. Return 𝔽(offset). + if typed_array.is_detached() { + Ok(0.into()) + } else { + Ok(typed_array.byte_offset().into()) + } + } + + /// `23.2.3.6 %TypedArray%.prototype.copyWithin ( target, start [ , end ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin + fn copy_within(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + + let len = { + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + + // 2. Perform ? ValidateTypedArray(O). + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + o.array_length() as i64 + }; + + // 4. Let relativeTarget be ? ToIntegerOrInfinity(target). + let relative_target = args.get_or_undefined(0).to_integer_or_infinity(context)?; + + let to = match relative_target { + // 5. If relativeTarget is -∞, let to be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 6. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 7. Else, let to be min(relativeTarget, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 8. Let relativeStart be ? ToIntegerOrInfinity(start). + let relative_start = args.get_or_undefined(1).to_integer_or_infinity(context)?; + + let from = match relative_start { + // 9. If relativeStart is -∞, let from be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 10. Else if relativeStart < 0, let from be max(len + relativeStart, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 11. Else, let from be min(relativeStart, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 12. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + let end = args.get_or_undefined(2); + let relative_end = if end.is_undefined() { + IntegerOrInfinity::Integer(len) + } else { + end.to_integer_or_infinity(context)? + }; + + let r#final = match relative_end { + // 13. If relativeEnd is -∞, let final be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 14. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 15. Else, let final be min(relativeEnd, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 16. Let count be min(final - from, len - to). + let count = std::cmp::min(r#final - from, len - to); + + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + + // 17. If count > 0, then + if count > 0 { + // a. NOTE: The copying must be performed in a manner that preserves the bit-level encoding of the source data. + // b. Let buffer be O.[[ViewedArrayBuffer]]. + // c. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // d. Let typedArrayName be the String value of O.[[TypedArrayName]]. + let typed_array_name = o.typed_array_name(); + + // e. Let elementSize be the Element Size value specified in Table 73 for typedArrayName. + let element_size = typed_array_name.element_size() as i64; + + // f. Let byteOffset be O.[[ByteOffset]]. + let byte_offset = o.byte_offset() as i64; + + // g. Let toByteIndex be to × elementSize + byteOffset. + let mut to_byte_index = to * element_size + byte_offset; + + // h. Let fromByteIndex be from × elementSize + byteOffset. + let mut from_byte_index = from * element_size + byte_offset; + + // i. Let countBytes be count × elementSize. + let mut count_bytes = count * element_size; + + // j. If fromByteIndex < toByteIndex and toByteIndex < fromByteIndex + countBytes, then + let direction = if from_byte_index < to_byte_index + && to_byte_index < from_byte_index + count_bytes + { + // ii. Set fromByteIndex to fromByteIndex + countBytes - 1. + from_byte_index = from_byte_index + count_bytes - 1; + + // iii. Set toByteIndex to toByteIndex + countBytes - 1. + to_byte_index = to_byte_index + count_bytes - 1; + + // i. Let direction be -1. + -1 + } + // k. Else, + else { + // i. Let direction be 1. + 1 + }; + + let buffer_obj = o + .viewed_array_buffer() + .expect("Already checked for detached buffer"); + let mut buffer_obj_borrow = buffer_obj.borrow_mut(); + let buffer = buffer_obj_borrow + .as_array_buffer_mut() + .expect("Already checked for detached buffer"); + + // l. Repeat, while countBytes > 0, + while count_bytes > 0 { + // i. Let value be GetValueFromBuffer(buffer, fromByteIndex, Uint8, true, Unordered). + let value = buffer.get_value_from_buffer( + from_byte_index as usize, + TypedArrayName::Uint8Array, + true, + SharedMemoryOrder::Unordered, + None, + ); + + // ii. Perform SetValueInBuffer(buffer, toByteIndex, Uint8, value, true, Unordered). + buffer.set_value_in_buffer( + to_byte_index as usize, + TypedArrayName::Uint8Array, + value, + SharedMemoryOrder::Unordered, + None, + context, + )?; + + // iii. Set fromByteIndex to fromByteIndex + direction. + from_byte_index += direction; + + // iv. Set toByteIndex to toByteIndex + direction. + to_byte_index += direction; + + // v. Set countBytes to countBytes - 1. + count_bytes -= 1; + } + } + + // 18. Return O. + Ok(this.clone()) + } + + /// `23.2.3.7 %TypedArray%.prototype.entries ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.entries + fn entries(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let o = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.borrow() + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))? + .is_detached() + { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Return CreateArrayIterator(O, key+value). + Ok(ArrayIterator::create_array_iterator( + o, + PropertyNameKind::KeyAndValue, + context, + )) + } + + /// `23.2.3.8 %TypedArray%.prototype.every ( callbackfn [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.every + fn every(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => { + return context.throw_type_error( + "TypedArray.prototype.every called with non-callable callback function", + ) + } + }; + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context)?; + + // c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + let test_result = callback_fn + .call( + args.get_or_undefined(1), + &[k_value, k.into(), this.clone()], + context, + )? + .to_boolean(); + + // d. If testResult is false, return false. + if !test_result { + return Ok(false.into()); + } + } + + // 7. Return true. + Ok(true.into()) + } + + /// `23.2.3.9 %TypedArray%.prototype.fill ( value [ , start [ , end ] ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.fill + fn fill(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + // 4. If O.[[ContentType]] is BigInt, set value to ? ToBigInt(value). + let value: JsValue = if o.typed_array_name().content_type() == ContentType::BigInt { + args.get_or_undefined(0).to_bigint(context)?.into() + // 5. Otherwise, set value to ? ToNumber(value). + } else { + args.get_or_undefined(0).to_number(context)?.into() + }; + + // 6. Let relativeStart be ? ToIntegerOrInfinity(start). + let mut k = match args.get_or_undefined(1).to_integer_or_infinity(context)? { + // 7. If relativeStart is -∞, let k be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 8. Else if relativeStart < 0, let k be max(len + relativeStart, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 9. Else, let k be min(relativeStart, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + let end = args.get_or_undefined(2); + let relative_end = if end.is_undefined() { + IntegerOrInfinity::Integer(len) + } else { + end.to_integer_or_infinity(context)? + }; + + let r#final = match relative_end { + // 11. If relativeEnd is -∞, let final be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 13. Else, let final be min(relativeEnd, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 14. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 15. Repeat, while k < final, + while k < r#final { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Perform ! Set(O, Pk, value, true). + obj.set(k, value.clone(), true, context) + .expect("Set cannot fail here"); + + // c. Set k to k + 1. + k += 1; + } + + // 16. Return O. + Ok(this.clone()) + } + + /// `23.2.3.10 %TypedArray%.prototype.filter ( callbackfn [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.filter + fn filter(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => { + return context.throw_type_error( + "TypedArray.prototype.filter called with non-callable callback function", + ) + } + }; + + // 5. Let kept be a new empty List. + let mut kept = Vec::new(); + + // 6. Let k be 0. + // 7. Let captured be 0. + let mut captured = 0; + + // 8. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).# + let selected = callback_fn + .call( + args.get_or_undefined(1), + &[k_value.clone(), k.into(), this.clone()], + context, + )? + .to_boolean(); + + // d. If selected is true, then + if selected { + // i. Append kValue to the end of kept. + kept.push(k_value); + + // ii. Set captured to captured + 1. + captured += 1; + } + } + + // 9. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(captured) »). + let a = Self::species_create(&obj, o.typed_array_name(), &[captured.into()], context)?; + + // 10. Let n be 0. + // 11. For each element e of kept, do + for (n, e) in kept.iter().enumerate() { + // a. Perform ! Set(A, ! ToString(𝔽(n)), e, true). + a.set(n, e.clone(), true, context) + .expect("Set cannot fail here"); + // b. Set n to n + 1. + } + + // 12. Return A. + Ok(a.into()) + } + + /// `23.2.3.11 %TypedArray%.prototype.find ( predicate [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.find + fn find(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + // 4. If IsCallable(predicate) is false, throw a TypeError exception. + let predicate = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => { + return context.throw_type_error( + "TypedArray.prototype.find called with non-callable predicate function", + ) + } + }; + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). + // d. If testResult is true, return kValue. + if predicate + .call( + args.get_or_undefined(1), + &[k_value.clone(), k.into(), this.clone()], + context, + )? + .to_boolean() + { + return Ok(k_value); + } + } + + // 7. Return undefined. + Ok(JsValue::undefined()) + } + + /// `23.2.3.12 %TypedArray%.prototype.findIndex ( predicate [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.findindex + fn findindex(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + // 4. If IsCallable(predicate) is false, throw a TypeError exception. + let predicate = + match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => return context.throw_type_error( + "TypedArray.prototype.findindex called with non-callable predicate function", + ), + }; + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). + // d. If testResult is true, return 𝔽(k). + if predicate + .call( + args.get_or_undefined(1), + &[k_value.clone(), k.into(), this.clone()], + context, + )? + .to_boolean() + { + return Ok(k.into()); + } + } + + // 7. Return -1𝔽. + Ok((-1).into()) + } + + /// `23.2.3.13 %TypedArray%.prototype.forEach ( callbackfn [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach + fn foreach(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => { + return context.throw_type_error( + "TypedArray.prototype.foreach called with non-callable callback function", + ) + } + }; + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). + callback_fn.call( + args.get_or_undefined(1), + &[k_value, k.into(), this.clone()], + context, + )?; + } + + // 7. Return undefined. + Ok(JsValue::undefined()) + } + + /// `23.2.3.14 %TypedArray%.prototype.includes ( searchElement [ , fromIndex ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.includes + fn includes(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + // 4. If len is 0, return false. + if len == 0 { + return Ok(false.into()); + } + + // 5. Let n be ? ToIntegerOrInfinity(fromIndex). + // 6. Assert: If fromIndex is undefined, then n is 0. + let n = args.get_or_undefined(1).to_integer_or_infinity(context)?; + + let n = match n { + // 7. If n is +∞, return false. + IntegerOrInfinity::PositiveInfinity => return Ok(false.into()), + // 8. Else if n is -∞, set n to 0. + IntegerOrInfinity::NegativeInfinity => 0, + IntegerOrInfinity::Integer(i) => i, + }; + + // 9. If n ≥ 0, then + let mut k = if n >= 0 { + // a. Let k be n. + n + // 10. Else, + } else { + // a. Let k be len + n. + // b. If k < 0, set k to 0. + if len + n < 0 { + 0 + } else { + len + n + } + }; + + // 11. Repeat, while k < len, + while k < len { + // a. Let elementK be ! Get(O, ! ToString(𝔽(k))). + let element_k = obj.get(k, context).expect("Get cannot fail here"); + + // b. If SameValueZero(searchElement, elementK) is true, return true. + if JsValue::same_value_zero(args.get_or_undefined(0), &element_k) { + return Ok(true.into()); + } + + // c. Set k to k + 1. + k += 1; + } + + // 12. Return false. + Ok(false.into()) + } + + /// `23.2.3.15 %TypedArray%.prototype.indexOf ( searchElement [ , fromIndex ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.indexof + fn index_of(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + // 4. If len is 0, return -1𝔽. + if len == 0 { + return Ok((-1).into()); + } + + // 5. Let n be ? ToIntegerOrInfinity(fromIndex). + // 6. Assert: If fromIndex is undefined, then n is 0. + let n = args.get_or_undefined(1).to_integer_or_infinity(context)?; + + let n = match n { + // 7. If n is +∞, return -1𝔽. + IntegerOrInfinity::PositiveInfinity => return Ok((-1).into()), + // 8. Else if n is -∞, set n to 0. + IntegerOrInfinity::NegativeInfinity => 0, + IntegerOrInfinity::Integer(i) => i, + }; + + // 9. If n ≥ 0, then + let mut k = if n >= 0 { + // a. Let k be n. + n + // 10. Else, + } else { + // a. Let k be len + n. + // b. If k < 0, set k to 0. + if len + n < 0 { + 0 + } else { + len + n + } + }; + + // 11. Repeat, while k < len, + while k < len { + // a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))). + let k_present = obj + .has_property(k, context) + .expect("HasProperty cannot fail here"); + + // b. If kPresent is true, then + if k_present { + // i. Let elementK be ! Get(O, ! ToString(𝔽(k))). + let element_k = obj.get(k, context).expect("Get cannot fail here"); + + // ii. Let same be IsStrictlyEqual(searchElement, elementK). + // iii. If same is true, return 𝔽(k). + if args.get_or_undefined(0).strict_equals(&element_k) { + return Ok(k.into()); + } + } + + // c. Set k to k + 1. + k += 1; + } + + // 12. Return -1𝔽. + Ok((-1).into()) + } + + /// `23.2.3.16 %TypedArray%.prototype.join ( separator )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.join + fn join(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + // 4. If separator is undefined, let sep be the single-element String ",". + let separator = args.get_or_undefined(0); + let sep = if separator.is_undefined() { + JsString::new(",") + // 5. Else, let sep be ? ToString(separator). + } else { + separator.to_string(context)? + }; + + // 6. Let R be the empty String. + let mut r = JsString::new(""); + + // 7. Let k be 0. + // 8. Repeat, while k < len, + for k in 0..len { + // a. If k > 0, set R to the string-concatenation of R and sep. + if k > 0 { + r = JsString::concat(r, sep.clone()); + } + + // b. Let element be ! Get(O, ! ToString(𝔽(k))). + let element = obj.get(k, context).expect("Get cannot fail here"); + + // c. If element is undefined, let next be the empty String; otherwise, let next be ! ToString(element). + // d. Set R to the string-concatenation of R and next. + if !element.is_undefined() { + r = JsString::concat(r, element.to_string(context)?); + } + } + + // 9. Return R. + Ok(r.into()) + } + + /// `23.2.3.17 %TypedArray%.prototype.keys ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys + fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let o = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.borrow() + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))? + .is_detached() + { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Return CreateArrayIterator(O, key). + Ok(ArrayIterator::create_array_iterator( + o, + PropertyNameKind::Key, + context, + )) + } + + /// `23.2.3.18 %TypedArray%.prototype.lastIndexOf ( searchElement [ , fromIndex ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.lastindexof + fn last_index_of(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + // 4. If len is 0, return -1𝔽. + if len == 0 { + return Ok((-1).into()); + } + + // 5. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1. + let n = if let Some(n) = args.get(1) { + n.to_integer_or_infinity(context)? + } else { + IntegerOrInfinity::Integer(len - 1) + }; + + let mut k = match n { + // 6. If n is -∞, return -1𝔽. + IntegerOrInfinity::NegativeInfinity => return Ok((-1).into()), + // 7. If n ≥ 0, then + // a. Let k be min(n, len - 1). + IntegerOrInfinity::Integer(i) if i >= 0 => std::cmp::min(i, len - 1), + IntegerOrInfinity::PositiveInfinity => len - 1, + // 8. Else, + // a. Let k be len + n. + IntegerOrInfinity::Integer(i) => len + i, + }; + + // 9. Repeat, while k ≥ 0, + while k >= 0 { + // a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))). + let k_present = obj + .has_property(k, context) + .expect("HasProperty cannot fail here"); + + // b. If kPresent is true, then + if k_present { + // i. Let elementK be ! Get(O, ! ToString(𝔽(k))). + let element_k = obj.get(k, context).expect("Get cannot fail here"); + + // ii. Let same be IsStrictlyEqual(searchElement, elementK). + // iii. If same is true, return 𝔽(k). + if args.get_or_undefined(0).strict_equals(&element_k) { + return Ok(k.into()); + } + } + + // c. Set k to k - 1. + k -= 1; + } + + // 10. Return -1𝔽. + Ok((-1).into()) + } + + /// `23.2.3.19 get %TypedArray%.prototype.length` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length + fn length(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has [[ViewedArrayBuffer]] and [[ArrayLength]] internal slots. + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let typed_array = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. + // 6. Let length be O.[[ArrayLength]]. + // 7. Return 𝔽(length). + if typed_array.is_detached() { + Ok(0.into()) + } else { + Ok(typed_array.array_length().into()) + } + } + + /// `23.2.3.20 %TypedArray%.prototype.map ( callbackfn [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.map + fn map(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => { + return context.throw_type_error( + "TypedArray.prototype.map called with non-callable callback function", + ) + } + }; + + // 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »). + let a = Self::species_create(&obj, o.typed_array_name(), &[len.into()], context)?; + + // 6. Let k be 0. + // 7. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). + let mapped_value = callback_fn.call( + args.get_or_undefined(1), + &[k_value, k.into(), this.clone()], + context, + )?; + + // d. Perform ? Set(A, Pk, mappedValue, true). + a.set(k, mapped_value, true, context)?; + } + + // 8. Return A. + Ok(a.into()) + } + + /// `23.2.3.21 %TypedArray%.prototype.reduce ( callbackfn [ , initialValue ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce + fn reduce(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => { + return context.throw_type_error( + "TypedArray.prototype.reduce called with non-callable callback function", + ) + } + }; + + // 5. If len = 0 and initialValue is not present, throw a TypeError exception. + if len == 0 && args.get(1).is_none() { + return context + .throw_type_error("Typed array length is 0 and initial value is not present"); + } + + // 6. Let k be 0. + let mut k = 0; + + // 7. Let accumulator be undefined. + // 8. If initialValue is present, then + let mut accumulator = if let Some(initial_value) = args.get(1) { + // a. Set accumulator to initialValue. + initial_value.clone() + // 9. Else, + } else { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Set accumulator to ! Get(O, Pk). + // c. Set k to k + 1. + k += 1; + obj.get(0, context).expect("Get cannot fail here") + }; + + // 10. Repeat, while k < len, + while k < len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). + accumulator = callback_fn.call( + &JsValue::undefined(), + &[accumulator, k_value, k.into(), this.clone()], + context, + )?; + + // d. Set k to k + 1. + k += 1; + } + + // 11. Return accumulator. + Ok(accumulator) + } + + /// `23.2.3.22 %TypedArray%.prototype.reduceRight ( callbackfn [ , initialValue ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduceright + fn reduceright(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = + match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => return context.throw_type_error( + "TypedArray.prototype.reduceright called with non-callable callback function", + ), + }; + + // 5. If len = 0 and initialValue is not present, throw a TypeError exception. + if len == 0 && args.get(1).is_none() { + return context + .throw_type_error("Typed array length is 0 and initial value is not present"); + } + + // 6. Let k be len - 1. + let mut k = len - 1; + + // 7. Let accumulator be undefined. + // 8. If initialValue is present, then + let mut accumulator = if let Some(initial_value) = args.get(1) { + // a. Set accumulator to initialValue. + initial_value.clone() + // 9. Else, + } else { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Set accumulator to ! Get(O, Pk). + let accumulator = obj.get(k, context).expect("Get cannot fail here"); + + // c. Set k to k - 1. + k -= 1; + + accumulator + }; + + // 10. Repeat, while k ≥ 0, + while k >= 0 { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). + accumulator = callback_fn.call( + &JsValue::undefined(), + &[accumulator, k_value, k.into(), this.clone()], + context, + )?; + + // d. Set k to k - 1. + k -= 1; + } + + // 11. Return accumulator. + Ok(accumulator) + } + + /// `23.2.3.23 %TypedArray%.prototype.reverse ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reverse + #[allow(clippy::float_cmp)] + fn reverse(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as f64; + + // 4. Let middle be floor(len / 2). + let middle = (len / 2.0).floor(); + + // 5. Let lower be 0. + let mut lower = 0.0; + // 6. Repeat, while lower ≠ middle, + while lower != middle { + // a. Let upper be len - lower - 1. + let upper = len - lower - 1.0; + + // b. Let upperP be ! ToString(𝔽(upper)). + // c. Let lowerP be ! ToString(𝔽(lower)). + // d. Let lowerValue be ! Get(O, lowerP). + let lower_value = obj.get(lower, context).expect("Get cannot fail here"); + // e. Let upperValue be ! Get(O, upperP). + let upper_value = obj.get(upper, context).expect("Get cannot fail here"); + + // f. Perform ! Set(O, lowerP, upperValue, true). + obj.set(lower, upper_value, true, context) + .expect("Set cannot fail here"); + // g. Perform ! Set(O, upperP, lowerValue, true). + obj.set(upper, lower_value, true, context) + .expect("Set cannot fail here"); + + // h. Set lower to lower + 1. + lower += 1.0; + } + + // 7. Return O. + Ok(this.clone()) + } + + /// `23.2.3.24 %TypedArray%.prototype.set ( source [ , offset ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.set + fn set(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let target be the this value. + // 2. Perform ? RequireInternalSlot(target, [[TypedArrayName]]). + // 3. Assert: target has a [[ViewedArrayBuffer]] internal slot. + let target = this.as_object().ok_or_else(|| { + context.construct_type_error("TypedArray.set must be called on typed array object") + })?; + if !target.is_typed_array() { + return context.throw_type_error("TypedArray.set must be called on typed array object"); + } + + // 4. Let targetOffset be ? ToIntegerOrInfinity(offset). + let target_offset = args.get_or_undefined(1).to_integer_or_infinity(context)?; + + // 5. If targetOffset < 0, throw a RangeError exception. + match target_offset { + IntegerOrInfinity::Integer(i) if i < 0 => { + return context.throw_range_error("TypedArray.set called with negative offset") + } + IntegerOrInfinity::NegativeInfinity => { + return context.throw_range_error("TypedArray.set called with negative offset") + } + _ => {} + } + + let source = args.get_or_undefined(0); + match source { + // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then + JsValue::Object(source) if source.is_typed_array() => { + // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). + Self::set_typed_array_from_typed_array(&target, target_offset, source, context)?; + } + // 7. Else, + _ => { + // a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source). + Self::set_typed_array_from_array_like(&target, target_offset, source, context)?; + } + } + + // 8. Return undefined. + Ok(JsValue::undefined()) + } + + /// `3.2.3.24.1 SetTypedArrayFromTypedArray ( target, targetOffset, source )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-settypedarrayfromtypedarray + fn set_typed_array_from_typed_array( + target: &JsObject, + target_offset: IntegerOrInfinity, + source: &JsObject, + context: &mut Context, + ) -> JsResult<()> { + let target_borrow = target.borrow(); + let target_array = target_borrow + .as_typed_array() + .expect("Target must be a typed array"); + + let source_borrow = source.borrow(); + let source_array = source_borrow + .as_typed_array() + .expect("Source must be a typed array"); + + // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. + // 2. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. + if target_array.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + let target_buffer_obj = target_array + .viewed_array_buffer() + .expect("Already checked for detached buffer"); + + // 3. Let targetLength be target.[[ArrayLength]]. + let target_length = target_array.array_length(); + + // 4. Let srcBuffer be source.[[ViewedArrayBuffer]]. + // 5. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. + if source_array.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + let mut src_buffer_obj = source_array + .viewed_array_buffer() + .expect("Already checked for detached buffer") + .clone(); + + // 6. Let targetName be the String value of target.[[TypedArrayName]]. + // 7. Let targetType be the Element Type value in Table 73 for targetName. + let target_name = target_array.typed_array_name(); + + // 8. Let targetElementSize be the Element Size value specified in Table 73 for targetName. + let target_element_size = target_name.element_size(); + + // 9. Let targetByteOffset be target.[[ByteOffset]]. + let target_byte_offset = target_array.byte_offset(); + + // 10. Let srcName be the String value of source.[[TypedArrayName]]. + // 11. Let srcType be the Element Type value in Table 73 for srcName. + let src_name = source_array.typed_array_name(); + + // 12. Let srcElementSize be the Element Size value specified in Table 73 for srcName. + let src_element_size = src_name.element_size(); + + // 13. Let srcLength be source.[[ArrayLength]]. + let src_length = source_array.array_length(); + + // 14. Let srcByteOffset be source.[[ByteOffset]]. + let src_byte_offset = source_array.byte_offset(); + + // 15. If targetOffset is +∞, throw a RangeError exception. + let target_offset = match target_offset { + IntegerOrInfinity::Integer(i) if i >= 0 => i as usize, + IntegerOrInfinity::PositiveInfinity => { + return Err(context.construct_range_error("Target offset cannot be Infinity")) + } + _ => unreachable!(), + }; + + // 16. If srcLength + targetOffset > targetLength, throw a RangeError exception. + if src_length + target_offset > target_length { + return Err(context.construct_range_error( + "Source typed array and target offset longer than target typed array", + )); + } + + // 17. If target.[[ContentType]] ≠ source.[[ContentType]], throw a TypeError exception. + if target_name.content_type() != src_name.content_type() { + return Err(context.construct_type_error( + "Source typed array and target typed array have different content types", + )); + } + + // TODO: Shared Array Buffer + // 18. If both IsSharedArrayBuffer(srcBuffer) and IsSharedArrayBuffer(targetBuffer) are true, then + + // a. If srcBuffer.[[ArrayBufferData]] and targetBuffer.[[ArrayBufferData]] are the same Shared Data Block values, let same be true; else let same be false. + + // 19. Else, let same be SameValue(srcBuffer, targetBuffer). + let same = JsObject::equals(&src_buffer_obj, target_buffer_obj); + + // 20. If same is true, then + let mut src_byte_index = if same { + // a. Let srcByteLength be source.[[ByteLength]]. + let src_byte_length = source_array.byte_length(); + + // b. Set srcBuffer to ? CloneArrayBuffer(srcBuffer, srcByteOffset, srcByteLength, %ArrayBuffer%). + // c. NOTE: %ArrayBuffer% is used to clone srcBuffer because is it known to not have any observable side-effects. + let array_buffer_constructor = context + .standard_objects() + .array_buffer_object() + .constructor() + .into(); + let s = src_buffer_obj + .borrow() + .as_array_buffer() + .expect("Already checked for detached buffer") + .clone_array_buffer( + src_byte_offset, + src_byte_length, + &array_buffer_constructor, + context, + )?; + src_buffer_obj = s; + + // d. Let srcByteIndex be 0. + 0 + } + // 21. Else, let srcByteIndex be srcByteOffset. + else { + src_byte_offset + }; + + // 22. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset. + let mut target_byte_index = target_offset * target_element_size + target_byte_offset; + + // 23. Let limit be targetByteIndex + targetElementSize × srcLength. + let limit = target_byte_index + target_element_size * src_length; + + let src_buffer_obj_borrow = src_buffer_obj.borrow(); + let src_buffer = src_buffer_obj_borrow + .as_array_buffer() + .expect("Must be an array buffer"); + + // 24. If srcType is the same as targetType, then + if src_name == target_name { + // a. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. + // b. Repeat, while targetByteIndex < limit, + while target_byte_index < limit { + // i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered). + let value = src_buffer.get_value_from_buffer( + src_byte_index, + TypedArrayName::Uint8Array, + true, + SharedMemoryOrder::Unordered, + None, + ); + + // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). + target_buffer_obj + .borrow_mut() + .as_array_buffer_mut() + .expect("Must be an array buffer") + .set_value_in_buffer( + target_byte_index, + TypedArrayName::Uint8Array, + value, + SharedMemoryOrder::Unordered, + None, + context, + )?; + + // iii. Set srcByteIndex to srcByteIndex + 1. + src_byte_index += 1; + + // iv. Set targetByteIndex to targetByteIndex + 1. + target_byte_index += 1; + } + } + // 25. Else, + else { + // a. Repeat, while targetByteIndex < limit, + while target_byte_index < limit { + // i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, srcType, true, Unordered). + let value = src_buffer.get_value_from_buffer( + src_byte_index, + src_name, + true, + SharedMemoryOrder::Unordered, + None, + ); + + // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, Unordered). + target_buffer_obj + .borrow_mut() + .as_array_buffer_mut() + .expect("Must be an array buffer") + .set_value_in_buffer( + target_byte_index, + target_name, + value, + SharedMemoryOrder::Unordered, + None, + context, + )?; + + // iii. Set srcByteIndex to srcByteIndex + srcElementSize. + src_byte_index += src_element_size; + + // iv. Set targetByteIndex to targetByteIndex + targetElementSize. + target_byte_index += target_element_size; + } + } + + Ok(()) + } + + /// `23.2.3.24.2 SetTypedArrayFromArrayLike ( target, targetOffset, source )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-settypedarrayfromarraylike + fn set_typed_array_from_array_like( + target: &JsObject, + target_offset: IntegerOrInfinity, + source: &JsValue, + context: &mut Context, + ) -> JsResult<()> { + let target_borrow = target.borrow(); + let target_array = target_borrow + .as_typed_array() + .expect("Target must be a typed array"); + + // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. + // 2. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. + if target_array.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let targetLength be target.[[ArrayLength]]. + let target_length = target_array.array_length(); + + // 4. Let targetName be the String value of target.[[TypedArrayName]]. + // 6. Let targetType be the Element Type value in Table 73 for targetName. + let target_name = target_array.typed_array_name(); + + // 5. Let targetElementSize be the Element Size value specified in Table 73 for targetName. + let target_element_size = target_name.element_size(); + + // 7. Let targetByteOffset be target.[[ByteOffset]]. + let target_byte_offset = target_array.byte_offset(); + + // 8. Let src be ? ToObject(source). + let src = source.to_object(context)?; + + // 9. Let srcLength be ? LengthOfArrayLike(src). + let src_length = src.length_of_array_like(context)?; + + let target_offset = match target_offset { + // 10. If targetOffset is +∞, throw a RangeError exception. + IntegerOrInfinity::PositiveInfinity => { + return Err(context.construct_range_error("Target offset cannot be Infinity")) + } + IntegerOrInfinity::Integer(i) if i >= 0 => i as usize, + _ => unreachable!(), + }; + + // 11. If srcLength + targetOffset > targetLength, throw a RangeError exception. + if src_length + target_offset > target_length { + return Err(context.construct_range_error( + "Source object and target offset longer than target typed array", + )); + } + + // 12. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset. + let mut target_byte_index = target_offset * target_element_size + target_byte_offset; + + // 13. Let k be 0. + let mut k = 0; + + // 14. Let limit be targetByteIndex + targetElementSize × srcLength. + let limit = target_byte_index + target_element_size * src_length; + + // 15. Repeat, while targetByteIndex < limit, + while target_byte_index < limit { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let value be ? Get(src, Pk). + let value = src.get(k, context)?; + + // c. If target.[[ContentType]] is BigInt, set value to ? ToBigInt(value). + // d. Otherwise, set value to ? ToNumber(value). + let value = if target_name.content_type() == ContentType::BigInt { + value.to_bigint(context)?.into() + } else { + value.to_number(context)?.into() + }; + + let target_buffer_obj = target_array + .viewed_array_buffer() + .expect("Already checked for detached buffer"); + let mut target_buffer_obj_borrow = target_buffer_obj.borrow_mut(); + let target_buffer = target_buffer_obj_borrow + .as_array_buffer_mut() + .expect("Already checked for detached buffer"); + + // e. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. + if target_buffer.is_detached_buffer() { + return Err( + context.construct_type_error("Cannot set value on detached array buffer") + ); + } + + // f. Perform SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, Unordered). + target_buffer.set_value_in_buffer( + target_byte_index, + target_name, + value, + SharedMemoryOrder::Unordered, + None, + context, + )?; + + // g. Set k to k + 1. + k += 1; + + // h. Set targetByteIndex to targetByteIndex + targetElementSize. + target_byte_index += target_element_size; + } + + Ok(()) + } + + /// `23.2.3.25 %TypedArray%.prototype.slice ( start, end )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.slice + fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + // 4. Let relativeStart be ? ToIntegerOrInfinity(start). + let mut k = match args.get_or_undefined(0).to_integer_or_infinity(context)? { + // 5. If relativeStart is -∞, let k be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 6. Else if relativeStart < 0, let k be max(len + relativeStart, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 7. Else, let k be min(relativeStart, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 8. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + let end = args.get_or_undefined(1); + let relative_end = if end.is_undefined() { + IntegerOrInfinity::Integer(len) + } else { + end.to_integer_or_infinity(context)? + }; + + let r#final = match relative_end { + // 9. If relativeEnd is -∞, let final be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 10. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 11. Else, let final be min(relativeEnd, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 12. Let count be max(final - k, 0). + let count = std::cmp::max(r#final - k, 0) as usize; + + // 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »). + let a = Self::species_create(&obj, o.typed_array_name(), &[count.into()], context)?; + let a_borrow = a.borrow(); + let a_array = a_borrow + .as_typed_array() + .expect("This must be a typed array"); + + // 14. If count > 0, then + if count > 0 { + // a. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // b. Let srcName be the String value of O.[[TypedArrayName]]. + // c. Let srcType be the Element Type value in Table 73 for srcName. + // d. Let targetName be the String value of A.[[TypedArrayName]]. + // e. Let targetType be the Element Type value in Table 73 for targetName. + // f. If srcType is different from targetType, then + if o.typed_array_name() != a_array.typed_array_name() { + // i. Let n be 0. + let mut n = 0; + // ii. Repeat, while k < final, + while k < r#final { + // 1. Let Pk be ! ToString(𝔽(k)). + // 2. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // 3. Perform ! Set(A, ! ToString(𝔽(n)), kValue, true). + a.set(n, k_value, true, context) + .expect("Set cannot fail here"); + + // 4. Set k to k + 1. + k += 1; + + // 5. Set n to n + 1. + n += 1; + } + // g. Else, + } else { + // i. Let srcBuffer be O.[[ViewedArrayBuffer]]. + let src_buffer_obj = o.viewed_array_buffer().expect("Cannot be detached here"); + let src_buffer_obj_borrow = src_buffer_obj.borrow(); + let src_buffer = src_buffer_obj_borrow + .as_array_buffer() + .expect("Cannot be detached here"); + + // ii. Let targetBuffer be A.[[ViewedArrayBuffer]]. + let target_buffer_obj = a_array + .viewed_array_buffer() + .expect("Cannot be detached here"); + let mut target_buffer_obj_borrow = target_buffer_obj.borrow_mut(); + let target_buffer = target_buffer_obj_borrow + .as_array_buffer_mut() + .expect("Cannot be detached here"); + + // iii. Let elementSize be the Element Size value specified in Table 73 for Element Type srcType. + let element_size = o.typed_array_name().element_size(); + + // iv. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. + + // v. Let srcByteOffset be O.[[ByteOffset]]. + let src_byte_offset = o.byte_offset(); + + // vi. Let targetByteIndex be A.[[ByteOffset]]. + let mut target_byte_index = a_array.byte_offset(); + + // vii. Let srcByteIndex be (k × elementSize) + srcByteOffset. + let mut src_byte_index = k as usize * element_size + src_byte_offset; + + // viii. Let limit be targetByteIndex + count × elementSize. + let limit = target_byte_index + count * element_size; + + // ix. Repeat, while targetByteIndex < limit, + while target_byte_index < limit { + // 1. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered). + let value = src_buffer.get_value_from_buffer( + src_byte_index, + TypedArrayName::Uint8Array, + true, + SharedMemoryOrder::Unordered, + None, + ); + + // 2. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). + target_buffer.set_value_in_buffer( + target_byte_index, + TypedArrayName::Uint8Array, + value, + SharedMemoryOrder::Unordered, + None, + context, + )?; + + // 3. Set srcByteIndex to srcByteIndex + 1. + src_byte_index += 1; + + // 4. Set targetByteIndex to targetByteIndex + 1. + target_byte_index += 1; + } + } + } + + drop(a_borrow); + + // 15. Return A. + Ok(a.into()) + } + + /// `23.2.3.26 %TypedArray%.prototype.some ( callbackfn [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.some + fn some(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => { + return context.throw_type_error( + "TypedArray.prototype.some called with non-callable callback function", + ) + } + }; + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + // d. If testResult is true, return true. + if callback_fn + .call( + args.get_or_undefined(1), + &[k_value, k.into(), this.clone()], + context, + )? + .to_boolean() + { + return Ok(true.into()); + } + } + + // 7. Return false. + Ok(false.into()) + } + + /// `23.2.3.27 %TypedArray%.prototype.sort ( comparefn )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort + fn sort(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. + let compare_fn = match args.get(0) { + None | Some(JsValue::Undefined) => None, + Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj), + _ => { + return context + .throw_type_error("TypedArray.sort called with non-callable comparefn") + } + }; + + // 2. Let obj be the this value. + let obj = this.as_object().ok_or_else(|| { + context.construct_type_error("TypedArray.sort must be called on typed array object") + })?; + + // 4. Let buffer be obj.[[ViewedArrayBuffer]]. + // 5. Let len be obj.[[ArrayLength]]. + let (buffer, len) = { + // 3. Perform ? ValidateTypedArray(obj). + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + context.construct_type_error("TypedArray.sort must be called on typed array object") + })?; + if o.is_detached() { + return context.throw_type_error( + "TypedArray.sort called on typed array object with detached array buffer", + ); + } + + ( + o.viewed_array_buffer() + .expect("Already checked for detached buffer") + .clone(), + o.array_length(), + ) + }; + + // 4. Let items be a new empty List. + let mut items = Vec::with_capacity(len); + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kPresent be ? HasProperty(obj, Pk). + // c. If kPresent is true, then + if obj.has_property(k, context)? { + // i. Let kValue be ? Get(obj, Pk). + let k_val = obj.get(k, context)?; + // ii. Append kValue to items. + items.push(k_val); + } + // d. Set k to k + 1. + } + + // 7. Let itemCount be the number of elements in items. + let item_count = items.len(); + + let sort_compare = |x: &JsValue, + y: &JsValue, + compare_fn: Option<&JsObject>, + context: &mut Context| + -> JsResult { + // 1. Assert: Both Type(x) and Type(y) are Number or both are BigInt. + // 2. If comparefn is not undefined, then + if let Some(obj) = compare_fn { + // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). + let v = obj + .call(&JsValue::undefined(), &[x.clone(), y.clone()], context)? + .to_number(context)?; + + // b. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if buffer + .borrow() + .as_array_buffer() + .expect("Must be array buffer") + .is_detached_buffer() + { + return Err(context + .construct_type_error("Cannot sort typed array with detached buffer")); + } + + // c. If v is NaN, return +0𝔽. + // d. Return v. + return Ok(v.partial_cmp(&0.0).unwrap_or(Ordering::Equal)); + } + + if let (JsValue::BigInt(x), JsValue::BigInt(y)) = (x, y) { + // 6. If x < y, return -1𝔽. + if x < y { + return Ok(Ordering::Less); + } + + // 7. If x > y, return 1𝔽. + if x > y { + return Ok(Ordering::Greater); + } + + // 8. If x is -0𝔽 and y is +0𝔽, return -1𝔽. + if x.is_zero() + && y.is_zero() + && x.as_inner().is_negative() + && y.as_inner().is_positive() + { + return Ok(Ordering::Less); + } + + // 9. If x is +0𝔽 and y is -0𝔽, return 1𝔽. + if x.is_zero() + && y.is_zero() + && x.as_inner().is_positive() + && y.as_inner().is_negative() + { + return Ok(Ordering::Greater); + } + + // 10. Return +0𝔽. + Ok(Ordering::Equal) + } else { + let x = x + .as_number() + .expect("Typed array can only contain number or bigint"); + let y = y + .as_number() + .expect("Typed array can only contain number or bigint"); + + // 3. If x and y are both NaN, return +0𝔽. + if x.is_nan() && y.is_nan() { + return Ok(Ordering::Equal); + } + + // 4. If x is NaN, return 1𝔽. + if x.is_nan() { + return Ok(Ordering::Greater); + } + + // 5. If y is NaN, return -1𝔽. + if y.is_nan() { + return Ok(Ordering::Less); + } + + // 6. If x < y, return -1𝔽. + if x < y { + return Ok(Ordering::Less); + } + + // 7. If x > y, return 1𝔽. + if x > y { + return Ok(Ordering::Greater); + } + + // 8. If x is -0𝔽 and y is +0𝔽, return -1𝔽. + if x.is_zero() && y.is_zero() && x.is_sign_negative() && y.is_sign_positive() { + return Ok(Ordering::Less); + } + + // 9. If x is +0𝔽 and y is -0𝔽, return 1𝔽. + if x.is_zero() && y.is_zero() && x.is_sign_positive() && y.is_sign_negative() { + return Ok(Ordering::Greater); + } + + // 10. Return +0𝔽. + Ok(Ordering::Equal) + } + }; + + // 8. Sort items using an implementation-defined sequence of calls to SortCompare. + // If any such call returns an abrupt completion, stop before performing any further + // calls to SortCompare or steps in this algorithm and return that completion. + let mut sort_err = Ok(()); + items.sort_by(|x, y| { + if sort_err.is_ok() { + sort_compare(x, y, compare_fn, context).unwrap_or_else(|err| { + sort_err = Err(err); + Ordering::Equal + }) + } else { + Ordering::Equal + } + }); + sort_err?; + + // 9. Let j be 0. + // 10. Repeat, while j < itemCount, + for (j, item) in items.into_iter().enumerate() { + // a. Perform ? Set(obj, ! ToString(𝔽(j)), items[j], true). + obj.set(j, item, true, context)?; + // b. Set j to j + 1. + } + + // 11. Repeat, while j < len, + for j in item_count..len { + // a. Perform ? DeletePropertyOrThrow(obj, ! ToString(𝔽(j))). + obj.delete_property_or_throw(j, context)?; + // b. Set j to j + 1. + } + + // 12. Return obj. + Ok(obj.into()) + } + + /// `23.2.3.28 %TypedArray%.prototype.subarray ( begin, end )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.subarray + fn subarray(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + let obj = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + let buffer = o + .viewed_array_buffer() + .expect("Buffer cannot be detached here"); + + // 5. Let srcLength be O.[[ArrayLength]]. + let src_length = o.array_length() as i64; + + // 6. Let relativeBegin be ? ToIntegerOrInfinity(begin). + let begin_index = match args.get_or_undefined(0).to_integer_or_infinity(context)? { + // 7. If relativeBegin is -∞, let beginIndex be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 8. Else if relativeBegin < 0, let beginIndex be max(srcLength + relativeBegin, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(src_length + i, 0), + // 9. Else, let beginIndex be min(relativeBegin, srcLength). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, src_length), + IntegerOrInfinity::PositiveInfinity => src_length, + }; + + // 10. If end is undefined, let relativeEnd be srcLength; else let relativeEnd be ? ToIntegerOrInfinity(end). + let end = args.get_or_undefined(1); + let relative_end = if end.is_undefined() { + IntegerOrInfinity::Integer(src_length) + } else { + end.to_integer_or_infinity(context)? + }; + + let end_index = match relative_end { + // 11. If relativeEnd is -∞, let endIndex be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 12. Else if relativeEnd < 0, let endIndex be max(srcLength + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(src_length + i, 0), + // 13. Else, let endIndex be min(relativeEnd, srcLength). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, src_length), + IntegerOrInfinity::PositiveInfinity => src_length, + }; + + // 14. Let newLength be max(endIndex - beginIndex, 0). + let new_length = std::cmp::max(end_index - begin_index, 0); + + // 15. Let constructorName be the String value of O.[[TypedArrayName]]. + // 16. Let elementSize be the Element Size value specified in Table 73 for constructorName. + let element_size = o.typed_array_name().element_size(); + + // 17. Let srcByteOffset be O.[[ByteOffset]]. + let src_byte_offset = o.byte_offset(); + + // 18. Let beginByteOffset be srcByteOffset + beginIndex × elementSize. + let begin_byte_offset = src_byte_offset + begin_index as usize * element_size; + + // 19. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ». + // 20. Return ? TypedArraySpeciesCreate(O, argumentsList). + Ok(Self::species_create( + &obj, + o.typed_array_name(), + &[ + buffer.clone().into(), + begin_byte_offset.into(), + new_length.into(), + ], + context, + )? + .into()) + } + + // TODO: 23.2.3.29 %TypedArray%.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] ) + + /// `23.2.3.31 %TypedArray%.prototype.values ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.values + fn values(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let o = this + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.borrow() + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))? + .is_detached() + { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. Return CreateArrayIterator(O, value). + Ok(ArrayIterator::create_array_iterator( + o, + PropertyNameKind::Value, + context, + )) + } + + /// `23.2.3.33 get %TypedArray%.prototype [ @@toStringTag ]` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag + fn to_string_tag(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + // 1. Let O be the this value. + // 2. If Type(O) is not Object, return undefined. + // 3. If O does not have a [[TypedArrayName]] internal slot, return undefined. + // 4. Let name be O.[[TypedArrayName]]. + // 5. Assert: Type(name) is String. + // 6. Return name. + Ok(this + .as_object() + .map(|obj| { + obj.borrow() + .as_typed_array() + .map(|o| o.typed_array_name().name().into()) + }) + .flatten() + .unwrap_or(JsValue::Undefined)) + } + + /// `23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#typedarray-species-create + fn species_create( + exemplar: &JsObject, + typed_array_name: TypedArrayName, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let defaultConstructor be the intrinsic object listed in column one of Table 73 for exemplar.[[TypedArrayName]]. + let default_constructor = match typed_array_name { + TypedArrayName::Int8Array => StandardObjects::typed_int8_array_object, + TypedArrayName::Uint8Array => StandardObjects::typed_uint8_array_object, + TypedArrayName::Uint8ClampedArray => StandardObjects::typed_uint8clamped_array_object, + TypedArrayName::Int16Array => StandardObjects::typed_int16_array_object, + TypedArrayName::Uint16Array => StandardObjects::typed_uint16_array_object, + TypedArrayName::Int32Array => StandardObjects::typed_int32_array_object, + TypedArrayName::Uint32Array => StandardObjects::typed_uint32_array_object, + TypedArrayName::BigInt64Array => StandardObjects::typed_bigint64_array_object, + TypedArrayName::BigUint64Array => StandardObjects::typed_biguint64_array_object, + TypedArrayName::Float32Array => StandardObjects::typed_float32_array_object, + TypedArrayName::Float64Array => StandardObjects::typed_float64_array_object, + }; + + // 2. Let constructor be ? SpeciesConstructor(exemplar, defaultConstructor). + let constructor = exemplar.species_constructor(default_constructor, context)?; + + // 3. Let result be ? TypedArrayCreate(constructor, argumentList). + let result = Self::create(&constructor, args, context)?; + + // 4. Assert: result has [[TypedArrayName]] and [[ContentType]] internal slots. + // 5. If result.[[ContentType]] ≠ exemplar.[[ContentType]], throw a TypeError exception. + if result + .borrow() + .as_typed_array() + .expect("This can only be a typed array object") + .typed_array_name() + .content_type() + != typed_array_name.content_type() + { + return Err(context + .construct_type_error("New typed array has different context type than exemplar")); + } + + // 6. Return result. + Ok(result) + } + + /// `23.2.4.2 TypedArrayCreate ( constructor, argumentList )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#typedarray-create + fn create( + constructor: &JsObject, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let newTypedArray be ? Construct(constructor, argumentList). + let new_typed_array = constructor.construct(args, &constructor.clone().into(), context)?; + + // 2. Perform ? ValidateTypedArray(newTypedArray). + let obj = new_typed_array + .as_object() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj_borrow = obj.borrow(); + let o = obj_borrow + .as_typed_array() + .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + if o.is_detached() { + return Err(context.construct_type_error("Buffer of the typed array is detached")); + } + + // 3. If argumentList is a List of a single Number, then + if args.len() == 1 { + if let Some(number) = args[0].as_number() { + // a. If newTypedArray.[[ArrayLength]] < ℝ(argumentList[0]), throw a TypeError exception. + if (o.array_length() as f64) < number { + return Err(context + .construct_type_error("New typed array length is smaller than expected")); + } + } + } + + drop(obj_borrow); + + // 4. Return newTypedArray. + Ok(obj) + } + + /// + fn allocate_buffer( + indexed: &mut IntegerIndexed, + length: usize, + context: &mut Context, + ) -> JsResult<()> { + // 1. Assert: O.[[ViewedArrayBuffer]] is undefined. + assert!(indexed.viewed_array_buffer().is_none()); + + // 2. Let constructorName be the String value of O.[[TypedArrayName]]. + // 3. Let elementSize be the Element Size value specified in Table 73 for constructorName. + let element_size = indexed.typed_array_name().element_size(); + + // 4. Let byteLength be elementSize × length. + let byte_length = element_size * length; + + // 5. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength). + let data = ArrayBuffer::allocate( + &context + .standard_objects() + .array_buffer_object() + .constructor() + .into(), + byte_length, + context, + )?; + + // 6. Set O.[[ViewedArrayBuffer]] to data. + indexed.set_viewed_array_buffer(Some(data)); + // 7. Set O.[[ByteLength]] to byteLength. + indexed.set_byte_length(byte_length); + // 8. Set O.[[ByteOffset]] to 0. + indexed.set_byte_offset(0); + // 9. Set O.[[ArrayLength]] to length. + indexed.set_array_length(length); + + // 10. Return O. + Ok(()) + } + + /// + fn initialize_from_list( + o: &JsObject, + values: Vec, + context: &mut Context, + ) -> JsResult<()> { + // 1. Let len be the number of elements in values. + let len = values.len(); + { + let mut o = o.borrow_mut(); + let mut o_inner = o.as_typed_array_mut().expect("expected a TypedArray"); + + // 2. Perform ? AllocateTypedArrayBuffer(O, len). + TypedArray::allocate_buffer(&mut o_inner, len, context)?; + } + + // 3. Let k be 0. + // 4. Repeat, while k < len, + for (k, k_value) in values.into_iter().enumerate() { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be the first element of values and remove that element from values. + // c. Perform ? Set(O, Pk, kValue, true). + o.set(k, k_value, true, context)?; + // d. Set k to k + 1. + } + + // 5. Assert: values is now an empty List. + // It no longer exists. + Ok(()) + } + + /// `AllocateTypedArray ( constructorName, newTarget, defaultProto [ , length ] )` + /// + /// It is used to validate and create an instance of a `TypedArray` constructor. If the `length` + /// argument is passed, an `ArrayBuffer` of that length is also allocated and associated with the + /// new `TypedArray` instance. `AllocateTypedArray` provides common semantics that is used by + /// `TypedArray`. + /// + /// For more information, check the [spec][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-allocatetypedarray + fn allocate

( + constructor_name: TypedArrayName, + new_target: &JsValue, + default_proto: P, + length: Option, + context: &mut Context, + ) -> JsResult + where + P: FnOnce(&StandardObjects) -> &StandardConstructor, + { + // 1. Let proto be ? GetPrototypeFromConstructor(newTarget, defaultProto). + let proto = get_prototype_from_constructor(new_target, default_proto, context)?; + + // 3. Assert: obj.[[ViewedArrayBuffer]] is undefined. + // 4. Set obj.[[TypedArrayName]] to constructorName. + // 5. If constructorName is "BigInt64Array" or "BigUint64Array", set obj.[[ContentType]] to BigInt. + // 6. Otherwise, set obj.[[ContentType]] to Number. + // 7. If length is not present, then + // a. Set obj.[[ByteLength]] to 0. + // b. Set obj.[[ByteOffset]] to 0. + // c. Set obj.[[ArrayLength]] to 0. + let mut indexed = IntegerIndexed::new(None, constructor_name, 0, 0, 0); + + // 8. Else, + if let Some(length) = length { + // a. Perform ? AllocateTypedArrayBuffer(obj, length). + TypedArray::allocate_buffer(&mut indexed, length, context)?; + } + + // 2. Let obj be ! IntegerIndexedObjectCreate(proto). + let obj = IntegerIndexed::create(proto, indexed, context); + + // 9. Return obj. + Ok(obj) + } + + /// `23.2.5.1.2 InitializeTypedArrayFromTypedArray ( O, srcArray )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray + fn initialize_from_typed_array( + o: &JsObject, + src_array: JsObject, + context: &mut Context, + ) -> JsResult<()> { + let o_obj = o.borrow(); + let src_array_obj = src_array.borrow(); + let o_array = o_obj.as_typed_array().expect("this must be a typed array"); + let src_array = src_array_obj + .as_typed_array() + .expect("this must be a typed array"); + + // 1. Let srcData be srcArray.[[ViewedArrayBuffer]]. + // 2. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. + if src_array.is_detached() { + return Err( + context.construct_type_error("Cannot initialize typed array from detached buffer") + ); + } + let src_data_obj = src_array + .viewed_array_buffer() + .expect("Already checked for detached buffer"); + let src_data_obj_b = src_data_obj.borrow(); + let src_data = src_data_obj_b + .as_array_buffer() + .expect("Already checked for detached buffer"); + + // 3. Let constructorName be the String value of O.[[TypedArrayName]]. + // 4. Let elementType be the Element Type value in Table 73 for constructorName. + // 10. Let elementSize be the Element Size value specified in Table 73 for constructorName. + let constructor_name = o_array.typed_array_name(); + + // 5. Let elementLength be srcArray.[[ArrayLength]]. + let element_length = src_array.array_length(); + + // 6. Let srcName be the String value of srcArray.[[TypedArrayName]]. + // 7. Let srcType be the Element Type value in Table 73 for srcName. + // 8. Let srcElementSize be the Element Size value specified in Table 73 for srcName. + let src_name = src_array.typed_array_name(); + + // 9. Let srcByteOffset be srcArray.[[ByteOffset]]. + let src_byte_offset = src_array.byte_offset(); + + // 11. Let byteLength be elementSize × elementLength. + let byte_length = constructor_name.element_size() * element_length; + + // 12. If IsSharedArrayBuffer(srcData) is false, then + // a. Let bufferConstructor be ? SpeciesConstructor(srcData, %ArrayBuffer%). + // TODO: Shared Array Buffer + // 13. Else, + // a. Let bufferConstructor be %ArrayBuffer%. + let buffer_constructor = + src_data_obj.species_constructor(StandardObjects::array_buffer_object, context)?; + + // 14. If elementType is the same as srcType, then + let data = if constructor_name == src_name { + // a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength, bufferConstructor). + src_data.clone_array_buffer( + src_byte_offset, + byte_length, + &buffer_constructor.into(), + context, + )? + // 15. Else, + } else { + // a. Let data be ? AllocateArrayBuffer(bufferConstructor, byteLength). + let data_obj = ArrayBuffer::allocate(&buffer_constructor.into(), byte_length, context)?; + let mut data_obj_b = data_obj.borrow_mut(); + let data = data_obj_b + .as_array_buffer_mut() + .expect("Must be ArrayBuffer"); + + // b. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. + if src_data.is_detached_buffer() { + return Err(context + .construct_type_error("Cannot initialize typed array from detached buffer")); + } + + // c. If srcArray.[[ContentType]] ≠ O.[[ContentType]], throw a TypeError exception. + if src_name.content_type() != constructor_name.content_type() { + return Err(context.construct_type_error( + "Cannot initialize typed array from different content type", + )); + } + + // d. Let srcByteIndex be srcByteOffset. + let mut src_byte_index = src_byte_offset; + // e. Let targetByteIndex be 0. + let mut target_byte_index = 0; + // f. Let count be elementLength. + let mut count = element_length; + // g. Repeat, while count > 0, + while count > 0 { + // i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, Unordered). + let value = src_data.get_value_from_buffer( + src_byte_index, + src_name, + true, + SharedMemoryOrder::Unordered, + None, + ); + + // ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, Unordered). + data.set_value_in_buffer( + target_byte_index, + constructor_name, + value, + SharedMemoryOrder::Unordered, + None, + context, + )?; + + // iii. Set srcByteIndex to srcByteIndex + srcElementSize. + src_byte_index += src_name.element_size(); + + // iv. Set targetByteIndex to targetByteIndex + elementSize. + target_byte_index += constructor_name.element_size(); + + // v. Set count to count - 1. + count -= 1; + } + drop(data_obj_b); + data_obj + }; + + // 16. Set O.[[ViewedArrayBuffer]] to data. + // 17. Set O.[[ByteLength]] to byteLength. + // 18. Set O.[[ByteOffset]] to 0. + // 19. Set O.[[ArrayLength]] to elementLength. + drop(o_obj); + o.borrow_mut().data = ObjectData::integer_indexed(IntegerIndexed::new( + Some(data), + constructor_name, + 0, + byte_length, + element_length, + )); + + Ok(()) + } + + /// `23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset, length )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer + fn initialize_from_array_buffer( + o: &JsObject, + buffer: JsObject, + byte_offset: &JsValue, + length: &JsValue, + context: &mut Context, + ) -> JsResult<()> { + // 1. Let constructorName be the String value of O.[[TypedArrayName]]. + // 2. Let elementSize be the Element Size value specified in Table 73 for constructorName. + let constructor_name = o + .borrow() + .as_typed_array() + .expect("This must be a typed array") + .typed_array_name(); + + // 3. Let offset be ? ToIndex(byteOffset). + let offset = byte_offset.to_index(context)?; + + // 4. If offset modulo elementSize ≠ 0, throw a RangeError exception. + if offset % constructor_name.element_size() != 0 { + return Err(context.construct_range_error("Invalid length for typed array")); + } + + let buffer_byte_length = { + let buffer_obj_b = buffer.borrow(); + let buffer_array = buffer_obj_b + .as_array_buffer() + .expect("This must be an ArrayBuffer"); + + // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if buffer_array.is_detached_buffer() { + return Err(context + .construct_type_error("Cannot construct typed array from detached buffer")); + } + + // 7. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. + buffer_array.array_buffer_byte_length() + }; + + // 8. If length is undefined, then + let new_byte_length = if length.is_undefined() { + // a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError exception. + if buffer_byte_length % constructor_name.element_size() != 0 { + return Err(context.construct_range_error("Invalid length for typed array")); + } + + // b. Let newByteLength be bufferByteLength - offset. + let new_byte_length = buffer_byte_length as isize - offset as isize; + + // c. If newByteLength < 0, throw a RangeError exception. + if new_byte_length < 0 { + return Err(context.construct_range_error("Invalid length for typed array")); + } + + new_byte_length as usize + // 9. Else, + } else { + // 5. If length is not undefined, then + // a. Let newLength be ? ToIndex(length). + + // a. Let newByteLength be newLength × elementSize. + let new_byte_length = length.to_index(context)? * constructor_name.element_size(); + + // b. If offset + newByteLength > bufferByteLength, throw a RangeError exception. + if offset + new_byte_length > buffer_byte_length { + return Err(context.construct_range_error("Invalid length for typed array")); + } + + new_byte_length + }; + + let mut o_obj_borrow = o.borrow_mut(); + let o = o_obj_borrow + .as_typed_array_mut() + .expect("This must be an ArrayBuffer"); + + // 10. Set O.[[ViewedArrayBuffer]] to buffer. + o.set_viewed_array_buffer(Some(buffer)); + // 11. Set O.[[ByteLength]] to newByteLength. + o.set_byte_length(new_byte_length); + // 12. Set O.[[ByteOffset]] to offset. + o.set_byte_offset(offset); + // 13. Set O.[[ArrayLength]] to newByteLength / elementSize. + o.set_array_length(new_byte_length / constructor_name.element_size()); + + Ok(()) + } + + /// `23.2.5.1.5 InitializeTypedArrayFromArrayLike ( O, arrayLike )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraylike + fn initialize_from_array_like( + o: &JsObject, + array_like: &JsObject, + context: &mut Context, + ) -> JsResult<()> { + // 1. Let len be ? LengthOfArrayLike(arrayLike). + let len = array_like.length_of_array_like(context)?; + + // 2. Perform ? AllocateTypedArrayBuffer(O, len). + { + let mut o_borrow = o.borrow_mut(); + let o = o_borrow.as_typed_array_mut().expect("Must be typed array"); + TypedArray::allocate_buffer(o, len, context)?; + } + + // 3. Let k be 0. + // 4. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ? Get(arrayLike, Pk). + let k_value = array_like.get(k, context)?; + + // c. Perform ? Set(O, Pk, kValue, true). + o.set(k, k_value, true, context)?; + } + + Ok(()) + } +} + +/// Names of all the typed arrays. +#[derive(Debug, Clone, Copy, Finalize, PartialEq)] +pub(crate) enum TypedArrayName { + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + BigInt64Array, + BigUint64Array, + Float32Array, + Float64Array, +} + +unsafe impl Trace for TypedArrayName { + // Safe because `TypedArrayName` is `Copy` + empty_trace!(); +} + +impl TypedArrayName { + /// Gets the element size of the given typed array name, as per the [spec]. + /// + /// [spec]: https://tc39.es/ecma262/#table-the-typedarray-constructors + #[inline] + pub(crate) const fn element_size(self) -> usize { + match self { + Self::Int8Array | Self::Uint8Array | Self::Uint8ClampedArray => 1, + Self::Int16Array | Self::Uint16Array => 2, + Self::Int32Array | Self::Uint32Array | Self::Float32Array => 4, + Self::BigInt64Array | Self::BigUint64Array | Self::Float64Array => 8, + } + } + + /// Gets the content type of this typed array name. + #[inline] + pub(crate) const fn content_type(self) -> ContentType { + match self { + Self::BigInt64Array | Self::BigUint64Array => ContentType::BigInt, + _ => ContentType::Number, + } + } + + /// Gets the name of this typed array name. + #[inline] + pub(crate) const fn name(&self) -> &str { + match self { + TypedArrayName::Int8Array => "Int8Array", + TypedArrayName::Uint8Array => "Uint8Array", + TypedArrayName::Uint8ClampedArray => "Uint8ClampedArray", + TypedArrayName::Int16Array => "Int16Array", + TypedArrayName::Uint16Array => "Uint16Array", + TypedArrayName::Int32Array => "Int32Array", + TypedArrayName::Uint32Array => "Uint32Array", + TypedArrayName::BigInt64Array => "BigInt64Array", + TypedArrayName::BigUint64Array => "BigUint64Array", + TypedArrayName::Float32Array => "Float32Array", + TypedArrayName::Float64Array => "Float64Array", + } + } +} + +typed_array!(Int8Array, "Int8Array", typed_int8_array_object); +typed_array!(Uint8Array, "Uint8Array", typed_uint8_array_object); +typed_array!( + Uint8ClampedArray, + "Uint8ClampedArray", + typed_uint8clamped_array_object +); +typed_array!(Int16Array, "Int16Array", typed_int16_array_object); +typed_array!(Uint16Array, "Uint16Array", typed_uint16_array_object); +typed_array!(Int32Array, "Int32Array", typed_int32_array_object); +typed_array!(Uint32Array, "Uint32Array", typed_uint32_array_object); +typed_array!(BigInt64Array, "BigInt64Array", typed_bigint64_array_object); +typed_array!( + BigUint64Array, + "BigUint64Array", + typed_biguint64_array_object +); +typed_array!(Float32Array, "Float32Array", typed_float32_array_object); +typed_array!(Float64Array, "Float64Array", typed_float64_array_object); diff --git a/boa/src/context.rs b/boa/src/context.rs index 4cbc59c920..1205e6b655 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -1,7 +1,7 @@ //! Javascript context. use crate::{ - builtins::{self, iterable::IteratorPrototypes}, + builtins::{self, iterable::IteratorPrototypes, typed_array::TypedArray}, class::{Class, ClassBuilder}, exec::Interpreter, object::{ @@ -92,6 +92,19 @@ pub struct StandardObjects { uri_error: StandardConstructor, map: StandardConstructor, set: StandardConstructor, + typed_array: StandardConstructor, + typed_int8_array: StandardConstructor, + typed_uint8_array: StandardConstructor, + typed_uint8clamped_array: StandardConstructor, + typed_int16_array: StandardConstructor, + typed_uint16_array: StandardConstructor, + typed_int32_array: StandardConstructor, + typed_uint32_array: StandardConstructor, + typed_bigint64_array: StandardConstructor, + typed_biguint64_array: StandardConstructor, + typed_float32_array: StandardConstructor, + typed_float64_array: StandardConstructor, + array_buffer: StandardConstructor, } impl Default for StandardObjects { @@ -115,6 +128,19 @@ impl Default for StandardObjects { uri_error: StandardConstructor::default(), map: StandardConstructor::default(), set: StandardConstructor::default(), + typed_array: StandardConstructor::default(), + typed_int8_array: StandardConstructor::default(), + typed_uint8_array: StandardConstructor::default(), + typed_uint8clamped_array: StandardConstructor::default(), + typed_int16_array: StandardConstructor::default(), + typed_uint16_array: StandardConstructor::default(), + typed_int32_array: StandardConstructor::default(), + typed_uint32_array: StandardConstructor::default(), + typed_bigint64_array: StandardConstructor::default(), + typed_biguint64_array: StandardConstructor::default(), + typed_float32_array: StandardConstructor::default(), + typed_float64_array: StandardConstructor::default(), + array_buffer: StandardConstructor::default(), } } } @@ -209,6 +235,71 @@ impl StandardObjects { pub fn set_object(&self) -> &StandardConstructor { &self.set } + + #[inline] + pub fn typed_array_object(&self) -> &StandardConstructor { + &self.typed_array + } + + #[inline] + pub fn typed_int8_array_object(&self) -> &StandardConstructor { + &self.typed_int8_array + } + + #[inline] + pub fn typed_uint8_array_object(&self) -> &StandardConstructor { + &self.typed_uint8_array + } + + #[inline] + pub fn typed_uint8clamped_array_object(&self) -> &StandardConstructor { + &self.typed_uint8clamped_array + } + + #[inline] + pub fn typed_int16_array_object(&self) -> &StandardConstructor { + &self.typed_int16_array + } + + #[inline] + pub fn typed_uint16_array_object(&self) -> &StandardConstructor { + &self.typed_uint16_array + } + + #[inline] + pub fn typed_uint32_array_object(&self) -> &StandardConstructor { + &self.typed_uint32_array + } + + #[inline] + pub fn typed_int32_array_object(&self) -> &StandardConstructor { + &self.typed_int32_array + } + + #[inline] + pub fn typed_bigint64_array_object(&self) -> &StandardConstructor { + &self.typed_bigint64_array + } + + #[inline] + pub fn typed_biguint64_array_object(&self) -> &StandardConstructor { + &self.typed_biguint64_array + } + + #[inline] + pub fn typed_float32_array_object(&self) -> &StandardConstructor { + &self.typed_float32_array + } + + #[inline] + pub fn typed_float64_array_object(&self) -> &StandardConstructor { + &self.typed_float64_array + } + + #[inline] + pub fn array_buffer_object(&self) -> &StandardConstructor { + &self.array_buffer + } } /// Internal representation of the strict mode types. @@ -275,6 +366,9 @@ pub struct Context { /// Cached iterator prototypes. iterator_prototypes: IteratorPrototypes, + /// Cached TypedArray constructor. + typed_array_constructor: StandardConstructor, + /// Cached standard objects and their prototypes. standard_objects: StandardObjects, @@ -295,6 +389,7 @@ impl Default for Context { #[cfg(feature = "console")] console: Console::default(), iterator_prototypes: IteratorPrototypes::default(), + typed_array_constructor: StandardConstructor::default(), standard_objects: Default::default(), strict: StrictType::Off, #[cfg(feature = "vm")] @@ -309,6 +404,14 @@ impl Default for Context { // Add new builtIns to Context Realm // At a later date this can be removed from here and called explicitly, // but for now we almost always want these default builtins + let typed_array_constructor_constructor = TypedArray::init(&mut context); + let typed_array_constructor_prototype = typed_array_constructor_constructor + .get("prototype", &mut context) + .expect("prototype must exist") + .as_object() + .expect("prototype must be object"); + context.typed_array_constructor.constructor = typed_array_constructor_constructor; + context.typed_array_constructor.prototype = typed_array_constructor_prototype; context.create_intrinsics(); context.iterator_prototypes = IteratorPrototypes::init(&mut context); context @@ -378,7 +481,7 @@ impl Context { builtins::init(self); } - /// Construct an empty object. + /// Constructs an object with the `%Object.prototype%` prototype. #[inline] pub fn construct_object(&self) -> JsObject { let object_prototype: JsValue = self.standard_objects().object_object().prototype().into(); @@ -394,8 +497,8 @@ impl Context { args: &[JsValue], ) -> JsResult { match *f { - JsValue::Object(ref object) => object.call(this, args, self), - _ => self.throw_type_error("not a function"), + JsValue::Object(ref object) if object.is_callable() => object.call(this, args, self), + _ => self.throw_type_error("Value is not callable"), } } @@ -931,6 +1034,12 @@ impl Context { &self.iterator_prototypes } + /// Return the cached TypedArray constructor. + #[inline] + pub(crate) fn typed_array_constructor(&self) -> &StandardConstructor { + &self.typed_array_constructor + } + /// Return the core standard objects. #[inline] pub fn standard_objects(&self) -> &StandardObjects { diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 816b78f6bd..217fc214e0 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -912,8 +912,8 @@ fn to_bigint() { assert!(JsValue::null().to_bigint(&mut context).is_err()); assert!(JsValue::undefined().to_bigint(&mut context).is_err()); - assert!(JsValue::new(55).to_bigint(&mut context).is_ok()); - assert!(JsValue::new(10.0).to_bigint(&mut context).is_ok()); + assert!(JsValue::new(55).to_bigint(&mut context).is_err()); + assert!(JsValue::new(10.0).to_bigint(&mut context).is_err()); assert!(JsValue::new("100").to_bigint(&mut context).is_ok()); } @@ -1262,9 +1262,9 @@ fn not_a_function() { check_output(&[ TestAction::Execute(init), - TestAction::TestEq(scenario1, "\"TypeError: not a function\""), - TestAction::TestEq(scenario2, "\"TypeError: not a function\""), - TestAction::TestEq(scenario3, "\"TypeError: not a function\""), + TestAction::TestEq(scenario1, "\"TypeError: Value is not callable\""), + TestAction::TestEq(scenario2, "\"TypeError: Value is not callable\""), + TestAction::TestEq(scenario3, "\"TypeError: Value is not callable\""), ]); } diff --git a/boa/src/object/internal_methods/integer_indexed.rs b/boa/src/object/internal_methods/integer_indexed.rs new file mode 100644 index 0000000000..a86f86da02 --- /dev/null +++ b/boa/src/object/internal_methods/integer_indexed.rs @@ -0,0 +1,388 @@ +use crate::{ + builtins::{array_buffer::SharedMemoryOrder, typed_array::integer_indexed_object::ContentType}, + object::JsObject, + property::{PropertyDescriptor, PropertyKey}, + Context, JsResult, JsValue, +}; + +use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; + +/// Definitions of the internal object methods for integer-indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects +pub(crate) static INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = + InternalObjectMethods { + __get_own_property__: integer_indexed_exotic_get_own_property, + __has_property__: integer_indexed_exotic_has_property, + __define_own_property__: integer_indexed_exotic_define_own_property, + __get__: integer_indexed_exotic_get, + __set__: integer_indexed_exotic_set, + __delete__: integer_indexed_exotic_delete, + __own_property_keys__: integer_indexed_exotic_own_property_keys, + ..ORDINARY_INTERNAL_METHODS + }; + +/// InternalMethod `[[GetOwnProperty]]` for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p +#[inline] +pub(crate) fn integer_indexed_exotic_get_own_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut Context, +) -> JsResult> { + // 1. If Type(P) is String, then + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + // b. If numericIndex is not undefined, then + if let PropertyKey::Index(index) = key { + // i. Let value be ! IntegerIndexedElementGet(O, numericIndex). + // ii. If value is undefined, return undefined. + // iii. Return the PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }. + Ok(integer_indexed_element_get(obj, *index as usize).map(|v| { + PropertyDescriptor::builder() + .value(v) + .writable(true) + .enumerable(true) + .configurable(true) + .build() + })) + } else { + // 2. Return OrdinaryGetOwnProperty(O, P). + super::ordinary_get_own_property(obj, key, context) + } +} + +/// InternalMethod `[[HasProperty]]` for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p +#[inline] +pub(crate) fn integer_indexed_exotic_has_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut Context, +) -> JsResult { + // 1. If Type(P) is String, then + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + if let PropertyKey::Index(index) = key { + // b. If numericIndex is not undefined, return ! IsValidIntegerIndex(O, numericIndex). + Ok(is_valid_integer_index(obj, *index as usize)) + } else { + // 2. Return ? OrdinaryHasProperty(O, P). + super::ordinary_has_property(obj, key, context) + } +} + +/// InternalMethod `[[DefineOwnProperty]]` for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc +#[inline] +pub(crate) fn integer_indexed_exotic_define_own_property( + obj: &JsObject, + key: PropertyKey, + desc: PropertyDescriptor, + context: &mut Context, +) -> JsResult { + // 1. If Type(P) is String, then + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + // b. If numericIndex is not undefined, then + if let PropertyKey::Index(index) = key { + // i. If ! IsValidIntegerIndex(O, numericIndex) is false, return false. + // ii. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, return false. + // iii. If Desc has an [[Enumerable]] field and if Desc.[[Enumerable]] is false, return false. + // v. If Desc has a [[Writable]] field and if Desc.[[Writable]] is false, return false. + // iv. If ! IsAccessorDescriptor(Desc) is true, return false. + if !is_valid_integer_index(obj, index as usize) + || !desc + .configurable() + .or_else(|| desc.enumerable()) + .or_else(|| desc.writable()) + .unwrap_or(true) + || desc.is_accessor_descriptor() + { + return Ok(false); + } + + // vi. If Desc has a [[Value]] field, perform ? IntegerIndexedElementSet(O, numericIndex, Desc.[[Value]]). + if let Some(value) = desc.value() { + integer_indexed_element_set(obj, index as usize, value, context)? + } + + // vii. Return true. + Ok(true) + } else { + // 2. Return ! OrdinaryDefineOwnProperty(O, P, Desc). + super::ordinary_define_own_property(obj, key, desc, context) + } +} + +/// Internal method `[[Get]]` for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-get-p-receiver +#[inline] +pub(crate) fn integer_indexed_exotic_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut Context, +) -> JsResult { + // 1. If Type(P) is String, then + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + // b. If numericIndex is not undefined, then + if let PropertyKey::Index(index) = key { + // i. Return ! IntegerIndexedElementGet(O, numericIndex). + Ok(integer_indexed_element_get(obj, *index as usize).unwrap_or_default()) + } else { + // 2. Return ? OrdinaryGet(O, P, Receiver). + super::ordinary_get(obj, key, receiver, context) + } +} + +/// Internal method `[[Set]]` for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver +#[inline] +pub(crate) fn integer_indexed_exotic_set( + obj: &JsObject, + key: PropertyKey, + value: JsValue, + receiver: JsValue, + context: &mut Context, +) -> JsResult { + // 1. If Type(P) is String, then + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + // b. If numericIndex is not undefined, then + if let PropertyKey::Index(index) = key { + // i. Perform ? IntegerIndexedElementSet(O, numericIndex, V). + integer_indexed_element_set(obj, index as usize, &value, context)?; + + // ii. Return true. + Ok(true) + } else { + // 2. Return ? OrdinarySet(O, P, V, Receiver). + super::ordinary_set(obj, key, value, receiver, context) + } +} + +/// Internal method `[[Delete]]` for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p +#[inline] +pub(crate) fn integer_indexed_exotic_delete( + obj: &JsObject, + key: &PropertyKey, + context: &mut Context, +) -> JsResult { + // 1. If Type(P) is String, then + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + // b. If numericIndex is not undefined, then + if let PropertyKey::Index(index) = key { + // i. If ! IsValidIntegerIndex(O, numericIndex) is false, return true; else return false. + Ok(!is_valid_integer_index(obj, *index as usize)) + } else { + // 2. Return ? OrdinaryDelete(O, P). + super::ordinary_delete(obj, key, context) + } +} + +/// Internal method `[[OwnPropertyKeys]]` for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys +#[inline] +pub(crate) fn integer_indexed_exotic_own_property_keys( + obj: &JsObject, + _context: &mut Context, +) -> JsResult> { + let obj = obj.borrow(); + let inner = obj.as_typed_array().expect( + "integer indexed exotic method should only be callable from integer indexed objects", + ); + + // 1. Let keys be a new empty List. + let mut keys = if inner.is_detached() { + vec![] + } else { + // 2. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false, then + // a. For each integer i starting with 0 such that i < O.[[ArrayLength]], in ascending order, do + // i. Add ! ToString(𝔽(i)) as the last element of keys. + (0..inner.array_length()) + .into_iter() + .map(|index| PropertyKey::Index(index as u32)) + .collect() + }; + + // 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do + // a. Add P as the last element of keys. + keys.extend( + obj.properties + .string_property_keys() + .cloned() + .map(|s| s.into()), + ); + + // 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do + // a. Add P as the last element of keys. + keys.extend( + obj.properties + .symbol_property_keys() + .cloned() + .map(|sym| sym.into()), + ); + + // 5. Return keys. + Ok(keys) +} + +/// Abstract operation `IsValidIntegerIndex ( O, index )`. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-isvalidintegerindex +pub(crate) fn is_valid_integer_index(obj: &JsObject, index: usize) -> bool { + let obj = obj.borrow(); + let inner = obj.as_typed_array().expect( + "integer indexed exotic method should only be callable from integer indexed objects", + ); + // 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false. + // 2. If ! IsIntegralNumber(index) is false, return false. + // 3. If index is -0𝔽, return false. + // 4. If ℝ(index) < 0 or ℝ(index) ≥ O.[[ArrayLength]], return false. + // 5. Return true. + !inner.is_detached() && index < inner.array_length() +} + +/// Abstract operation `IntegerIndexedElementGet ( O, index )`. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementget +fn integer_indexed_element_get(obj: &JsObject, index: usize) -> Option { + // 1. If ! IsValidIntegerIndex(O, index) is false, return undefined. + if !is_valid_integer_index(obj, index) { + return None; + } + + let obj = obj.borrow(); + let inner = obj + .as_typed_array() + .expect("Already checked for detached buffer"); + let buffer_obj = inner + .viewed_array_buffer() + .expect("Already checked for detached buffer"); + let buffer_obj_borrow = buffer_obj.borrow(); + let buffer = buffer_obj_borrow + .as_array_buffer() + .expect("Already checked for detached buffer"); + + // 2. Let offset be O.[[ByteOffset]]. + let offset = inner.byte_offset(); + + // 3. Let arrayTypeName be the String value of O.[[TypedArrayName]]. + // 6. Let elementType be the Element Type value in Table 73 for arrayTypeName. + let elem_type = inner.typed_array_name(); + + // 4. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName. + let size = elem_type.element_size(); + + // 5. Let indexedPosition be (ℝ(index) × elementSize) + offset. + let indexed_position = (index * size) + offset; + + // 7. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered). + Some(buffer.get_value_from_buffer( + indexed_position, + elem_type, + true, + SharedMemoryOrder::Unordered, + None, + )) +} + +/// Abstract operation `IntegerIndexedElementSet ( O, index, value )`. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementset +fn integer_indexed_element_set( + obj: &JsObject, + index: usize, + value: &JsValue, + context: &mut Context, +) -> JsResult<()> { + let obj_borrow = obj.borrow(); + let inner = obj_borrow.as_typed_array().expect( + "integer indexed exotic method should only be callable from integer indexed objects", + ); + + let num_value = if inner.typed_array_name().content_type() == ContentType::BigInt { + // 1. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value). + value.to_bigint(context)?.into() + } else { + // 2. Otherwise, let numValue be ? ToNumber(value). + value.to_number(context)?.into() + }; + + // 3. If ! IsValidIntegerIndex(O, index) is true, then + if is_valid_integer_index(obj, index) { + // a. Let offset be O.[[ByteOffset]]. + let offset = inner.byte_offset(); + + // b. Let arrayTypeName be the String value of O.[[TypedArrayName]]. + // e. Let elementType be the Element Type value in Table 73 for arrayTypeName. + let elem_type = inner.typed_array_name(); + + // c. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName. + let size = elem_type.element_size(); + + // d. Let indexedPosition be (ℝ(index) × elementSize) + offset. + let indexed_position = (index * size) + offset; + + let buffer_obj = inner + .viewed_array_buffer() + .expect("Already checked for detached buffer"); + let mut buffer_obj_borrow = buffer_obj.borrow_mut(); + let buffer = buffer_obj_borrow + .as_array_buffer_mut() + .expect("Already checked for detached buffer"); + + // f. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, numValue, true, Unordered). + buffer + .set_value_in_buffer( + indexed_position, + elem_type, + num_value, + SharedMemoryOrder::Unordered, + None, + context, + ) + .expect("SetValueInBuffer cannot fail here"); + } + + // 4. Return NormalCompletion(undefined). + Ok(()) +} diff --git a/boa/src/object/internal_methods/mod.rs b/boa/src/object/internal_methods/mod.rs index 154008411b..8a7c99e8c0 100644 --- a/boa/src/object/internal_methods/mod.rs +++ b/boa/src/object/internal_methods/mod.rs @@ -16,6 +16,7 @@ use crate::{ use super::PROTOTYPE; pub(super) mod array; +pub(super) mod integer_indexed; pub(super) mod string; impl JsObject { diff --git a/boa/src/object/gcobject.rs b/boa/src/object/jsobject.rs similarity index 97% rename from boa/src/object/gcobject.rs rename to boa/src/object/jsobject.rs index 9624a1709a..1ef53cf798 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/jsobject.rs @@ -507,7 +507,7 @@ impl JsObject { self.borrow_mut().set_prototype_instance(prototype) } - /// Checks if it an `Array` object. + /// Checks if it's an `Array` object. /// /// # Panics /// @@ -529,6 +529,17 @@ impl JsObject { self.borrow().is_array_iterator() } + /// Checks if it's an `ArrayBuffer` object. + /// + /// # Panics + /// + /// Panics if the object is currently mutably borrowed. + #[inline] + #[track_caller] + pub fn is_array_buffer(&self) -> bool { + self.borrow().is_array_buffer() + } + /// Checks if it is a `Map` object.pub /// /// # Panics @@ -540,7 +551,7 @@ impl JsObject { self.borrow().is_map() } - /// Checks if it a `String` object. + /// Checks if it's a `String` object. /// /// # Panics /// @@ -551,7 +562,7 @@ impl JsObject { self.borrow().is_string() } - /// Checks if it a `Function` object. + /// Checks if it's a `Function` object. /// /// # Panics /// @@ -562,7 +573,7 @@ impl JsObject { self.borrow().is_function() } - /// Checks if it a Symbol object. + /// Checks if it's a `Symbol` object. /// /// # Panics /// @@ -573,7 +584,7 @@ impl JsObject { self.borrow().is_symbol() } - /// Checks if it an Error object. + /// Checks if it's an `Error` object. /// /// # Panics /// @@ -584,7 +595,7 @@ impl JsObject { self.borrow().is_error() } - /// Checks if it a Boolean object. + /// Checks if it's a `Boolean` object. /// /// # Panics /// @@ -595,7 +606,7 @@ impl JsObject { self.borrow().is_boolean() } - /// Checks if it a `Number` object. + /// Checks if it's a `Number` object. /// /// # Panics /// @@ -606,7 +617,7 @@ impl JsObject { self.borrow().is_number() } - /// Checks if it a `BigInt` object. + /// Checks if it's a `BigInt` object. /// /// # Panics /// @@ -617,7 +628,7 @@ impl JsObject { self.borrow().is_bigint() } - /// Checks if it a `RegExp` object. + /// Checks if it's a `RegExp` object. /// /// # Panics /// @@ -628,7 +639,18 @@ impl JsObject { self.borrow().is_regexp() } - /// Checks if it an ordinary object. + /// Checks if it's a `TypedArray` object. + /// + /// # Panics + /// + /// Panics if the object is currently mutably borrowed. + #[inline] + #[track_caller] + pub fn is_typed_array(&self) -> bool { + self.borrow().is_typed_array() + } + + /// Checks if it's an ordinary object. /// /// # Panics /// diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 9f751675bd..36e47455ad 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -2,10 +2,11 @@ use crate::{ builtins::{ - array::array_iterator::ArrayIterator, map::map_iterator::MapIterator, - map::ordered_map::OrderedMap, regexp::regexp_string_iterator::RegExpStringIterator, - set::ordered_set::OrderedSet, set::set_iterator::SetIterator, - string::string_iterator::StringIterator, Date, RegExp, + array::array_iterator::ArrayIterator, array_buffer::ArrayBuffer, + map::map_iterator::MapIterator, map::ordered_map::OrderedMap, + regexp::regexp_string_iterator::RegExpStringIterator, set::ordered_set::OrderedSet, + set::set_iterator::SetIterator, string::string_iterator::StringIterator, + typed_array::integer_indexed_object::IntegerIndexed, Date, RegExp, }, context::StandardConstructor, gc::{Finalize, Trace}, @@ -23,20 +24,20 @@ use std::{ mod tests; pub mod function; -mod gcobject; pub(crate) mod internal_methods; +mod jsobject; mod operations; mod property_map; use crate::builtins::object::for_in_iterator::ForInIterator; -pub use gcobject::{JsObject, RecursionLimiter, Ref, RefMut}; use internal_methods::InternalObjectMethods; +pub use jsobject::{JsObject, RecursionLimiter, Ref, RefMut}; pub use operations::IntegrityLevel; pub use property_map::*; use self::internal_methods::{ - array::ARRAY_EXOTIC_INTERNAL_METHODS, string::STRING_EXOTIC_INTERNAL_METHODS, - ORDINARY_INTERNAL_METHODS, + array::ARRAY_EXOTIC_INTERNAL_METHODS, integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, + string::STRING_EXOTIC_INTERNAL_METHODS, ORDINARY_INTERNAL_METHODS, }; /// Static `prototype`, usually set on constructors as a key to point to their respective prototype object. @@ -89,6 +90,7 @@ pub struct ObjectData { pub enum ObjectKind { Array, ArrayIterator(ArrayIterator), + ArrayBuffer(ArrayBuffer), Map(OrderedMap), MapIterator(MapIterator), RegExp(Box), @@ -108,6 +110,7 @@ pub enum ObjectKind { Date(Date), Global, NativeObject(Box), + IntegerIndexed(IntegerIndexed), } impl ObjectData { @@ -127,6 +130,14 @@ impl ObjectData { } } + /// Create the `ArrayBuffer` object data + pub fn array_buffer(array_buffer: ArrayBuffer) -> Self { + Self { + kind: ObjectKind::ArrayBuffer(array_buffer), + internal_methods: &ORDINARY_INTERNAL_METHODS, + } + } + /// Create the `Map` object data pub fn map(map: OrderedMap) -> Self { Self { @@ -278,37 +289,43 @@ impl ObjectData { internal_methods: &ORDINARY_INTERNAL_METHODS, } } + + /// Creates the `IntegerIndexed` object data + pub fn integer_indexed(integer_indexed: IntegerIndexed) -> Self { + Self { + kind: ObjectKind::IntegerIndexed(integer_indexed), + internal_methods: &INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, + } + } } impl Display for ObjectKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Self::Array => "Array", - Self::ArrayIterator(_) => "ArrayIterator", - Self::ForInIterator(_) => "ForInIterator", - Self::Function(_) => "Function", - Self::RegExp(_) => "RegExp", - Self::RegExpStringIterator(_) => "RegExpStringIterator", - Self::Map(_) => "Map", - Self::MapIterator(_) => "MapIterator", - Self::Set(_) => "Set", - Self::SetIterator(_) => "SetIterator", - Self::String(_) => "String", - Self::StringIterator(_) => "StringIterator", - Self::Symbol(_) => "Symbol", - Self::Error => "Error", - Self::Ordinary => "Ordinary", - Self::Boolean(_) => "Boolean", - Self::Number(_) => "Number", - Self::BigInt(_) => "BigInt", - Self::Date(_) => "Date", - Self::Global => "Global", - Self::NativeObject(_) => "NativeObject", - } - ) + f.write_str(match self { + Self::Array => "Array", + Self::ArrayIterator(_) => "ArrayIterator", + Self::ArrayBuffer(_) => "ArrayBuffer", + Self::ForInIterator(_) => "ForInIterator", + Self::Function(_) => "Function", + Self::RegExp(_) => "RegExp", + Self::RegExpStringIterator(_) => "RegExpStringIterator", + Self::Map(_) => "Map", + Self::MapIterator(_) => "MapIterator", + Self::Set(_) => "Set", + Self::SetIterator(_) => "SetIterator", + Self::String(_) => "String", + Self::StringIterator(_) => "StringIterator", + Self::Symbol(_) => "Symbol", + Self::Error => "Error", + Self::Ordinary => "Ordinary", + Self::Boolean(_) => "Boolean", + Self::Number(_) => "Number", + Self::BigInt(_) => "BigInt", + Self::Date(_) => "Date", + Self::Global => "Global", + Self::NativeObject(_) => "NativeObject", + Self::IntegerIndexed(_) => "TypedArray", + }) } } @@ -353,13 +370,12 @@ impl Object { } } - /// ObjectCreate is used to specify the runtime creation of new ordinary objects. + /// `OrdinaryObjectCreate` is used to specify the runtime creation of new ordinary objects. /// /// More information: /// - [ECMAScript reference][spec] /// - /// [spec]: https://tc39.es/ecma262/#sec-objectcreate - // TODO: proto should be a &Value here + /// [spec]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate #[inline] pub fn create(proto: JsValue) -> Self { let mut obj = Self::new(); @@ -511,6 +527,40 @@ impl Object { } } + /// Checks if it an `ArrayBuffer` object. + #[inline] + pub fn is_array_buffer(&self) -> bool { + matches!( + self.data, + ObjectData { + kind: ObjectKind::ArrayBuffer(_), + .. + } + ) + } + + #[inline] + pub fn as_array_buffer(&self) -> Option<&ArrayBuffer> { + match &self.data { + ObjectData { + kind: ObjectKind::ArrayBuffer(buffer), + .. + } => Some(buffer), + _ => None, + } + } + + #[inline] + pub fn as_array_buffer_mut(&mut self) -> Option<&mut ArrayBuffer> { + match &mut self.data { + ObjectData { + kind: ObjectKind::ArrayBuffer(buffer), + .. + } => Some(buffer), + _ => None, + } + } + #[inline] pub fn as_array_iterator_mut(&mut self) -> Option<&mut ArrayIterator> { match &mut self.data { @@ -849,6 +899,7 @@ impl Object { ) } + #[inline] pub fn as_date(&self) -> Option<&Date> { match self.data { ObjectData { @@ -871,6 +922,7 @@ impl Object { ) } + /// Gets the regexp data if the object is a regexp. #[inline] pub fn as_regexp(&self) -> Option<&RegExp> { match self.data { @@ -882,6 +934,42 @@ impl Object { } } + /// Checks if it a `TypedArray` object. + #[inline] + pub fn is_typed_array(&self) -> bool { + matches!( + self.data, + ObjectData { + kind: ObjectKind::IntegerIndexed(_), + .. + } + ) + } + + /// Gets the typed array data (integer indexed object) if this is a typed array. + #[inline] + pub fn as_typed_array(&self) -> Option<&IntegerIndexed> { + match self.data { + ObjectData { + kind: ObjectKind::IntegerIndexed(ref integer_indexed_object), + .. + } => Some(integer_indexed_object), + _ => None, + } + } + + /// Gets the typed array data (integer indexed object) if this is a typed array. + #[inline] + pub fn as_typed_array_mut(&mut self) -> Option<&mut IntegerIndexed> { + match self.data { + ObjectData { + kind: ObjectKind::IntegerIndexed(ref mut integer_indexed_object), + .. + } => Some(integer_indexed_object), + _ => None, + } + } + /// Checks if it an ordinary object. #[inline] pub fn is_ordinary(&self) -> bool { @@ -894,6 +982,7 @@ impl Object { ) } + /// Gets the prototype instance of this object. #[inline] pub fn prototype_instance(&self) -> &JsValue { &self.prototype @@ -1357,6 +1446,7 @@ pub struct ConstructorBuilder<'context> { callable: bool, constructable: bool, inherit: Option, + custom_prototype: Option, } impl Debug for ConstructorBuilder<'_> { @@ -1387,6 +1477,7 @@ impl<'context> ConstructorBuilder<'context> { callable: true, constructable: true, inherit: None, + custom_prototype: None, } } @@ -1406,6 +1497,7 @@ impl<'context> ConstructorBuilder<'context> { callable: true, constructable: true, inherit: None, + custom_prototype: None, } } @@ -1614,6 +1706,15 @@ impl<'context> ConstructorBuilder<'context> { self } + /// Specify the __proto__ for this constructor. + /// + /// Default is `Function.prototype` + #[inline] + pub fn custom_prototype(&mut self, prototype: JsValue) -> &mut Self { + self.custom_prototype = Some(prototype); + self + } + /// Return the current context. #[inline] pub fn context(&mut self) -> &'_ mut Context { @@ -1645,14 +1746,17 @@ impl<'context> ConstructorBuilder<'context> { constructor.insert("length", length); constructor.insert("name", name); - constructor.set_prototype_instance( - self.context - .standard_objects() - .function_object() - .prototype() - .into(), - ); - + if let Some(proto) = &self.custom_prototype { + constructor.set_prototype_instance(proto.clone()); + } else { + constructor.set_prototype_instance( + self.context + .standard_objects() + .function_object() + .prototype() + .into(), + ); + } constructor.insert_property( PROTOTYPE, PropertyDescriptor::builder() diff --git a/boa/src/object/operations.rs b/boa/src/object/operations.rs index 8c7498bcbf..7037093fc1 100644 --- a/boa/src/object/operations.rs +++ b/boa/src/object/operations.rs @@ -1,5 +1,6 @@ use crate::{ builtins::Array, + context::{StandardConstructor, StandardObjects}, object::JsObject, property::{PropertyDescriptor, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, @@ -211,35 +212,6 @@ impl JsObject { Ok(success) } - /// Retrieves value of specific property, when the value of the property is expected to be a function. - /// - /// More information: - /// - [EcmaScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-getmethod - #[inline] - pub(crate) fn get_method(&self, context: &mut Context, key: K) -> JsResult> - where - K: Into, - { - // 1. Assert: IsPropertyKey(P) is true. - // 2. Let func be ? GetV(V, P). - let value = self.get(key, context)?; - - // 3. If func is either undefined or null, return undefined. - if value.is_null_or_undefined() { - return Ok(None); - } - - // 4. If IsCallable(func) is false, throw a TypeError exception. - // 5. Return func. - match value.as_object() { - Some(object) if object.is_callable() => Ok(Some(object)), - _ => Err(context - .construct_type_error("value returned for property of object is not a function")), - } - } - /// Check if object has property. /// /// More information: @@ -452,11 +424,14 @@ impl JsObject { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-speciesconstructor - pub(crate) fn species_constructor( + pub(crate) fn species_constructor( &self, - default_constructor: JsValue, + default_constructor: F, context: &mut Context, - ) -> JsResult { + ) -> JsResult + where + F: FnOnce(&StandardObjects) -> &StandardConstructor, + { // 1. Assert: Type(O) is Object. // 2. Let C be ? Get(O, "constructor"). @@ -464,12 +439,12 @@ impl JsObject { // 3. If C is undefined, return defaultConstructor. if c.is_undefined() { - return Ok(default_constructor); + return Ok(default_constructor(context.standard_objects()).constructor()); } // 4. If Type(C) is not Object, throw a TypeError exception. if !c.is_object() { - return context.throw_type_error("property 'constructor' is not an object"); + return Err(context.construct_type_error("property 'constructor' is not an object")); } // 5. Let S be ? Get(C, @@species). @@ -477,19 +452,19 @@ impl JsObject { // 6. If S is either undefined or null, return defaultConstructor. if s.is_null_or_undefined() { - return Ok(default_constructor); + return Ok(default_constructor(context.standard_objects()).constructor()); } // 7. If IsConstructor(S) is true, return S. // 8. Throw a TypeError exception. if let Some(obj) = s.as_object() { if obj.is_constructable() { - Ok(s) + Ok(obj) } else { - context.throw_type_error("property 'constructor' is not a constructor") + Err(context.construct_type_error("property 'constructor' is not a constructor")) } } else { - context.throw_type_error("property 'constructor' is not an object") + Err(context.construct_type_error("property 'constructor' is not an object")) } } @@ -575,9 +550,58 @@ impl JsObject { } impl JsValue { - // todo: GetV + /// Abstract operation `GetV ( V, P )`. + /// + /// Retrieves the value of a specific property of an ECMAScript language value. If the value is + /// not an object, the property lookup is performed using a wrapper object appropriate for the + /// type of the value. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-getmethod + #[inline] + pub(crate) fn get_v(&self, key: K, context: &mut Context) -> JsResult + where + K: Into, + { + // 1. Let O be ? ToObject(V). + let o = self.to_object(context)?; - // todo: GetMethod + // 2. Return ? O.[[Get]](P, V). + o.__get__(&key.into(), self.clone(), context) + } + + /// Abstract operation `GetMethod ( V, P )` + /// + /// Retrieves the value of a specific property, when the value of the property is expected to be a function. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-getmethod + pub(crate) fn get_method(&self, key: K, context: &mut Context) -> JsResult + where + K: Into, + { + // 1. Assert: IsPropertyKey(P) is true. + // 2. Let func be ? GetV(V, P). + let func = self.get_v(key, context)?; + + // 3. If func is either undefined or null, return undefined. + if func.is_null_or_undefined() { + return Ok(JsValue::undefined()); + } + + // 4. If IsCallable(func) is false, throw a TypeError exception. + if !func.is_callable() { + Err(context + .construct_type_error("value returned for property of object is not a function")) + } else { + // 5. Return func. + Ok(func) + } + } /// It is used to create List value whose elements are provided by the indexed properties of /// self. @@ -636,4 +660,29 @@ impl JsValue { // 7. Return list. Ok(list) } + + /// Abstract operation `( V, P [ , argumentsList ] ) + /// + /// Calls a method property of an ECMAScript language value. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-invoke + pub(crate) fn invoke( + &self, + key: K, + args: &[JsValue], + context: &mut Context, + ) -> JsResult + where + K: Into, + { + // 1. If argumentsList is not present, set argumentsList to a new empty List. + // 2. Let func be ? GetV(V, P). + let func = self.get_v(key, context)?; + + // 3. Return ? Call(func, V, argumentsList) + context.call(&func, self, args) + } } diff --git a/boa/src/string.rs b/boa/src/string.rs index b5179737b1..2eaf9be8c5 100644 --- a/boa/src/string.rs +++ b/boa/src/string.rs @@ -305,6 +305,7 @@ impl Inner { /// on the stack and a pointer to the data (this is also known as fat pointers). /// The `JsString` length and data is stored on the heap. and just an non-null /// pointer is kept, so its size is the size of a pointer. +#[derive(Finalize)] pub struct JsString { inner: NonNull, _marker: PhantomData>, @@ -487,8 +488,6 @@ impl JsString { } } -impl Finalize for JsString {} - // Safety: [`JsString`] does not contain any objects which recquire trace, // so this is safe. unsafe impl Trace for JsString { diff --git a/boa/src/symbol.rs b/boa/src/symbol.rs index e35f154dce..37fbe9fda2 100644 --- a/boa/src/symbol.rs +++ b/boa/src/symbol.rs @@ -42,7 +42,7 @@ pub struct WellKnownSymbols { has_instance: JsSymbol, is_concat_spreadable: JsSymbol, iterator: JsSymbol, - match_: JsSymbol, + r#match: JsSymbol, match_all: JsSymbol, replace: JsSymbol, search: JsSymbol, @@ -106,7 +106,7 @@ impl WellKnownSymbols { has_instance, is_concat_spreadable, iterator, - match_, + r#match: match_, match_all, replace, search, @@ -161,8 +161,8 @@ impl WellKnownSymbols { /// A regular expression method that matches the regular expression /// against a string. Called by the `String.prototype.match` method. #[inline] - pub fn match_() -> JsSymbol { - WELL_KNOW_SYMBOLS.with(|symbols| symbols.match_.clone()) + pub fn r#match() -> JsSymbol { + WELL_KNOW_SYMBOLS.with(|symbols| symbols.r#match.clone()) } /// The `Symbol.matchAll` well known symbol. diff --git a/boa/src/syntax/ast/node/array/mod.rs b/boa/src/syntax/ast/node/array/mod.rs index b7ce44d05d..a387ef011e 100644 --- a/boa/src/syntax/ast/node/array/mod.rs +++ b/boa/src/syntax/ast/node/array/mod.rs @@ -2,7 +2,7 @@ use super::{join_nodes, Node}; use crate::{ - builtins::{iterable, Array}, + builtins::Array, exec::Executable, gc::{Finalize, Trace}, BoaProfiler, Context, JsResult, JsValue, @@ -46,7 +46,7 @@ impl Executable for ArrayDecl { for elem in self.as_ref() { if let Node::Spread(ref x) = elem { let val = x.run(context)?; - let iterator_record = iterable::get_iterator(&val, context)?; + let iterator_record = val.get_iterator(context, None, None)?; // 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 // let mut next_index = 0; diff --git a/boa/src/syntax/ast/node/call/mod.rs b/boa/src/syntax/ast/node/call/mod.rs index 5a01310be9..30314a9332 100644 --- a/boa/src/syntax/ast/node/call/mod.rs +++ b/boa/src/syntax/ast/node/call/mod.rs @@ -1,5 +1,4 @@ use crate::{ - builtins::iterable, exec::Executable, exec::InterpreterState, gc::{Finalize, Trace}, @@ -66,7 +65,7 @@ impl Executable for Call { Node::GetConstField(ref get_const_field) => { let mut obj = get_const_field.obj().run(context)?; if !obj.is_object() { - obj = JsValue::Object(obj.to_object(context)?); + obj = JsValue::from(obj.to_object(context)?); } ( obj.clone(), @@ -76,7 +75,7 @@ impl Executable for Call { Node::GetField(ref get_field) => { let mut obj = get_field.obj().run(context)?; if !obj.is_object() { - obj = JsValue::Object(obj.to_object(context)?); + obj = JsValue::from(obj.to_object(context)?); } let field = get_field.field().run(context)?; ( @@ -94,7 +93,7 @@ impl Executable for Call { for arg in self.args() { if let Node::Spread(ref x) = arg { let val = x.run(context)?; - let iterator_record = iterable::get_iterator(&val, context)?; + let iterator_record = val.get_iterator(context, None, None)?; loop { let next = iterator_record.next(context)?; if next.done { diff --git a/boa/src/syntax/ast/node/declaration/mod.rs b/boa/src/syntax/ast/node/declaration/mod.rs index 84c59f83bd..ccb329fabd 100644 --- a/boa/src/syntax/ast/node/declaration/mod.rs +++ b/boa/src/syntax/ast/node/declaration/mod.rs @@ -1,6 +1,6 @@ //! Declaration nodes use crate::{ - builtins::{iterable::get_iterator, Array}, + builtins::Array, environment::lexical_environment::VariableScope, exec::Executable, gc::{Finalize, Trace}, @@ -673,7 +673,7 @@ impl DeclarationPatternArray { } // 1. Let iteratorRecord be ? GetIterator(value). - let iterator = get_iterator(&value, context)?; + let iterator = value.get_iterator(context, None, None)?; let mut result = Vec::new(); // 2. Let result be IteratorBindingInitialization of ArrayBindingPattern with arguments iteratorRecord and environment. diff --git a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs index 5ecd4c6dbf..c6f2318121 100644 --- a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs @@ -1,5 +1,4 @@ use crate::{ - builtins::iterable::get_iterator, environment::{ declarative_environment_record::DeclarativeEnvironmentRecord, lexical_environment::VariableScope, @@ -83,7 +82,7 @@ impl Executable for ForOfLoop { fn run(&self, context: &mut Context) -> JsResult { let _timer = BoaProfiler::global().start_event("ForOf", "exec"); let iterable = self.iterable().run(context)?; - let iterator = get_iterator(&iterable, context)?; + let iterator = iterable.get_iterator(context, None, None)?; let mut result = JsValue::undefined(); loop { diff --git a/boa/src/syntax/ast/node/new/mod.rs b/boa/src/syntax/ast/node/new/mod.rs index c9fafe9cc8..433911a653 100644 --- a/boa/src/syntax/ast/node/new/mod.rs +++ b/boa/src/syntax/ast/node/new/mod.rs @@ -1,5 +1,4 @@ use crate::{ - builtins::iterable, exec::Executable, gc::{Finalize, Trace}, syntax::ast::node::{Call, Node}, @@ -56,7 +55,7 @@ impl Executable for New { for arg in self.args() { if let Node::Spread(ref x) = arg { let val = x.run(context)?; - let iterator_record = iterable::get_iterator(&val, context)?; + let iterator_record = val.get_iterator(context, None, None)?; loop { let next = iterator_record.next(context)?; if next.done { diff --git a/boa/src/syntax/ast/node/operator/bin_op/mod.rs b/boa/src/syntax/ast/node/operator/bin_op/mod.rs index 7531017d0a..54182a2aee 100644 --- a/boa/src/syntax/ast/node/operator/bin_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/bin_op/mod.rs @@ -147,28 +147,33 @@ impl Executable for BinOp { context.has_property(&y, &key)? } CompOp::InstanceOf => { - if let Some(object) = y.as_object() { - let key = WellKnownSymbols::has_instance(); - - match object.get_method(context, key)? { - Some(instance_of_handler) => { - instance_of_handler.call(&y, &[x], context)?.to_boolean() - } - None if object.is_callable() => { - object.ordinary_has_instance(context, &x)? - } - None => { - return context.throw_type_error( - "right-hand side of 'instanceof' is not callable", - ); - } - } - } else { + // + // TODO: move to a separate instance_of_operator function + // 1. If Type(target) is not Object, throw a TypeError exception. + if !y.is_object() { return context.throw_type_error(format!( "right-hand side of 'instanceof' should be an object, got {}", y.type_of() )); } + + // 2. Let instOfHandler be ? GetMethod(target, @@hasInstance). + let inst_of_handler = + y.get_method(WellKnownSymbols::has_instance(), context)?; + + // 3. If instOfHandler is not undefined, then + if !inst_of_handler.is_undefined() { + // a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)). + context.call(&inst_of_handler, &y, &[x])?.to_boolean() + } else if !y.is_callable() { + // 4. If IsCallable(target) is false, throw a TypeError exception. + return Err(context.construct_type_error( + "right-hand side of 'instanceof' is not callable", + )); + } else { + // 5. Return ? OrdinaryHasInstance(target, V). + y.ordinary_has_instance(context, &x)? + } } })) } diff --git a/boa/src/syntax/ast/node/operator/unary_op/mod.rs b/boa/src/syntax/ast/node/operator/unary_op/mod.rs index 0153025f30..2ef824cf7b 100644 --- a/boa/src/syntax/ast/node/operator/unary_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/unary_op/mod.rs @@ -57,8 +57,11 @@ impl Executable for UnaryOp { op::UnaryOp::IncrementPost => { let x = self.target().run(context)?; let ret = x.clone(); - let result = x.to_number(context)? + 1.0; - context.set_value(self.target(), result.into())?; + let result = match x.to_numeric(context)? { + Numeric::Number(n) => (n + 1.0).into(), + Numeric::BigInt(b) => (JsBigInt::add(&b, &JsBigInt::from(1))).into(), + }; + context.set_value(self.target(), result)?; ret } op::UnaryOp::IncrementPre => { diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index 5e7ab67ec0..2a9802d76d 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -16,10 +16,15 @@ use crate::{ BoaProfiler, Context, JsBigInt, JsResult, JsString, }; use gc::{Finalize, Trace}; +use num_bigint::BigInt; +use num_integer::Integer; +use num_traits::Zero; +use once_cell::sync::Lazy; use std::{ collections::HashSet, convert::TryFrom, fmt::{self, Display}, + ops::{Deref, Sub}, str::FromStr, }; @@ -37,6 +42,16 @@ pub use hash::*; pub use operations::*; pub use r#type::Type; +static TWO_E_64: Lazy = Lazy::new(|| { + const TWO_E_64: u128 = 2u128.pow(64); + BigInt::from(TWO_E_64) +}); + +static TWO_E_63: Lazy = Lazy::new(|| { + const TWO_E_63: u128 = 2u128.pow(63); + BigInt::from(TWO_E_63) +}); + /// A Javascript value #[derive(Trace, Finalize, Debug, Clone)] pub enum JsValue { @@ -108,7 +123,7 @@ impl JsValue { Self::Rational(f64::NEG_INFINITY) } - /// Returns a new empty object + /// Returns a new empty object with the `%Object.prototype%` prototype. pub(crate) fn new_object(context: &Context) -> Self { let _timer = BoaProfiler::global().start_event("new_object", "value"); context.construct_object().into() @@ -378,17 +393,28 @@ impl JsValue { ) -> JsResult { // 1. Assert: input is an ECMAScript language value. (always a value not need to check) // 2. If Type(input) is Object, then - if let JsValue::Object(obj) = self { - if let Some(exotic_to_prim) = - obj.get_method(context, WellKnownSymbols::to_primitive())? - { + if self.is_object() { + // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive). + let exotic_to_prim = self.get_method(WellKnownSymbols::to_primitive(), context)?; + + // b. If exoticToPrim is not undefined, then + if !exotic_to_prim.is_undefined() { + // i. If preferredType is not present, let hint be "default". + // ii. Else if preferredType is string, let hint be "string". + // iii. Else, + // 1. Assert: preferredType is number. + // 2. Let hint be "number". let hint = match preferred_type { + PreferredType::Default => "default", PreferredType::String => "string", PreferredType::Number => "number", - PreferredType::Default => "default", } .into(); - let result = exotic_to_prim.call(self, &[hint], context)?; + + // iv. Let result be ? Call(exoticToPrim, input, « hint »). + let result = context.call(&exotic_to_prim, self, &[hint])?; + // v. If Type(result) is not Object, return result. + // vi. Throw a TypeError exception. return if result.is_object() { Err(context.construct_type_error("Symbol.toPrimitive cannot return an object")) } else { @@ -396,23 +422,28 @@ impl JsValue { }; } - let mut hint = preferred_type; - - if hint == PreferredType::Default { - hint = PreferredType::Number; + // c. If preferredType is not present, let preferredType be number. + let preferred_type = match preferred_type { + PreferredType::Default | PreferredType::Number => PreferredType::Number, + PreferredType::String => PreferredType::String, }; - // g. Return ? OrdinaryToPrimitive(input, hint). - obj.ordinary_to_primitive(context, hint) + // d. Return ? OrdinaryToPrimitive(input, preferredType). + self.as_object() + .expect("self was not an object") + .ordinary_to_primitive(context, preferred_type) } else { // 3. Return input. Ok(self.clone()) } } - /// Converts the value to a `BigInt`. + /// `7.1.13 ToBigInt ( argument )` /// - /// This function is equivelent to `BigInt(value)` in JavaScript. + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-tobigint pub fn to_bigint(&self, context: &mut Context) -> JsResult { match self { JsValue::Null => Err(context.construct_type_error("cannot convert null to a BigInt")), @@ -431,15 +462,8 @@ impl JsValue { } JsValue::Boolean(true) => Ok(JsBigInt::one()), JsValue::Boolean(false) => Ok(JsBigInt::zero()), - JsValue::Integer(num) => Ok(JsBigInt::new(*num)), - JsValue::Rational(num) => { - if let Ok(bigint) = JsBigInt::try_from(*num) { - return Ok(bigint); - } - Err(context.construct_type_error(format!( - "The number {} cannot be converted to a BigInt because it is not an integer", - num - ))) + JsValue::Integer(_) | JsValue::Rational(_) => { + Err(context.construct_type_error("cannot convert Number to a BigInt")) } JsValue::BigInt(b) => Ok(b.clone()), JsValue::Object(_) => { @@ -613,6 +637,200 @@ impl JsValue { Ok(f64_to_int32(number)) } + /// `7.1.10 ToInt8 ( argument )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-toint8 + pub fn to_int8(&self, context: &mut Context) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = self.to_number(context)?; + + // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽. + if number.is_nan() || number.is_zero() || number.is_infinite() { + return Ok(0); + } + + // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))). + let int = number.floor() as i64; + + // 4. Let int8bit be int modulo 2^8. + let int_8_bit = int % 2i64.pow(8); + + // 5. If int8bit ≥ 2^7, return 𝔽(int8bit - 2^8); otherwise return 𝔽(int8bit). + if int_8_bit >= 2i64.pow(7) { + Ok((int_8_bit - 2i64.pow(8)) as i8) + } else { + Ok(int_8_bit as i8) + } + } + + /// `7.1.11 ToUint8 ( argument )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-touint8 + pub fn to_uint8(&self, context: &mut Context) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = self.to_number(context)?; + + // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽. + if number.is_nan() || number.is_zero() || number.is_infinite() { + return Ok(0); + } + + // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))). + let int = number.floor() as i64; + + // 4. Let int8bit be int modulo 2^8. + let int_8_bit = int % 2i64.pow(8); + + // 5. Return 𝔽(int8bit). + Ok(int_8_bit as u8) + } + + /// `7.1.12 ToUint8Clamp ( argument )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-touint8clamp + pub fn to_uint8_clamp(&self, context: &mut Context) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = self.to_number(context)?; + + // 2. If number is NaN, return +0𝔽. + if number.is_nan() { + return Ok(0); + } + + // 3. If ℝ(number) ≤ 0, return +0𝔽. + if number <= 0.0 { + return Ok(0); + } + + // 4. If ℝ(number) ≥ 255, return 255𝔽. + if number >= 255.0 { + return Ok(255); + } + + // 5. Let f be floor(ℝ(number)). + let f = number.floor(); + + // 6. If f + 0.5 < ℝ(number), return 𝔽(f + 1). + if f + 0.5 < number { + return Ok(f as u8 + 1); + } + + // 7. If ℝ(number) < f + 0.5, return 𝔽(f). + if number < f + 0.5 { + return Ok(f as u8); + } + + // 8. If f is odd, return 𝔽(f + 1). + if f as u8 % 2 != 0 { + return Ok(f as u8 + 1); + } + + // 9. Return 𝔽(f). + Ok(f as u8) + } + + /// `7.1.8 ToInt16 ( argument )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-toint16 + pub fn to_int16(&self, context: &mut Context) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = self.to_number(context)?; + + // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽. + if number.is_nan() || number.is_zero() || number.is_infinite() { + return Ok(0); + } + + // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))). + let int = number.floor() as i64; + + // 4. Let int16bit be int modulo 2^16. + let int_16_bit = int % 2i64.pow(16); + + // 5. If int16bit ≥ 2^15, return 𝔽(int16bit - 2^16); otherwise return 𝔽(int16bit). + if int_16_bit >= 2i64.pow(15) { + Ok((int_16_bit - 2i64.pow(16)) as i16) + } else { + Ok(int_16_bit as i16) + } + } + + /// `7.1.9 ToUint16 ( argument )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-touint16 + pub fn to_uint16(&self, context: &mut Context) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = self.to_number(context)?; + + // 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽. + if number.is_nan() || number.is_zero() || number.is_infinite() { + return Ok(0); + } + + // 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))). + let int = number.floor() as i64; + + // 4. Let int16bit be int modulo 2^16. + let int_16_bit = int % 2i64.pow(16); + + // 5. Return 𝔽(int16bit). + Ok(int_16_bit as u16) + } + + /// `7.1.15 ToBigInt64 ( argument )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-tobigint64 + pub fn to_big_int64(&self, context: &mut Context) -> JsResult { + // 1. Let n be ? ToBigInt(argument). + let n = self.to_bigint(context)?; + + // 2. Let int64bit be ℝ(n) modulo 2^64. + let int64_bit = n.as_inner().mod_floor(&TWO_E_64); + + // 3. If int64bit ≥ 2^63, return ℤ(int64bit - 2^64); otherwise return ℤ(int64bit). + if &int64_bit >= TWO_E_63.deref() { + Ok(int64_bit.sub(TWO_E_64.deref())) + } else { + Ok(int64_bit) + } + } + + /// `7.1.16 ToBigUint64 ( argument )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-tobiguint64 + pub fn to_big_uint64(&self, context: &mut Context) -> JsResult { + let two_e_64: u128 = 0x1_0000_0000_0000_0000; + let two_e_64 = BigInt::from(two_e_64); + + // 1. Let n be ? ToBigInt(argument). + let n = self.to_bigint(context)?; + + // 2. Let int64bit be ℝ(n) modulo 2^64. + // 3. Return ℤ(int64bit). + Ok(n.as_inner().mod_floor(&two_e_64)) + } + /// Converts a value to a non-negative integer if it is a valid integer index value. /// /// See: @@ -821,6 +1039,39 @@ impl JsValue { Ok(false) } } + + /// It determines if the value is a callable function with a `[[Call]]` internal method. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-iscallable + #[track_caller] + pub(crate) fn is_callable(&self) -> bool { + if let Self::Object(obj) = self { + obj.is_callable() + } else { + false + } + } + + /// Determines if `value` inherits from the instance object inheritance path. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance + pub(crate) fn ordinary_has_instance( + &self, + context: &mut Context, + value: &JsValue, + ) -> JsResult { + if let Self::Object(obj) = self { + obj.ordinary_has_instance(context, value) + } else { + Ok(false) + } + } } impl Default for JsValue { @@ -853,6 +1104,20 @@ impl From for Numeric { } } +impl From for Numeric { + #[inline] + fn from(value: f32) -> Self { + Self::Number(value.into()) + } +} + +impl From for Numeric { + #[inline] + fn from(value: i64) -> Self { + Self::BigInt(value.into()) + } +} + impl From for Numeric { #[inline] fn from(value: i32) -> Self { @@ -874,6 +1139,13 @@ impl From for Numeric { } } +impl From for Numeric { + #[inline] + fn from(value: u64) -> Self { + Self::BigInt(value.into()) + } +} + impl From for Numeric { #[inline] fn from(value: u32) -> Self { diff --git a/boa/src/vm/mod.rs b/boa/src/vm/mod.rs index 4251b28b26..12a7801cfa 100644 --- a/boa/src/vm/mod.rs +++ b/boa/src/vm/mod.rs @@ -226,29 +226,24 @@ impl Context { self.vm.push(value); } Opcode::InstanceOf => { - let y = self.vm.pop(); - let x = self.vm.pop(); - let value = if let Some(object) = y.as_object() { - let key = WellKnownSymbols::has_instance(); - - match object.get_method(self, key)? { - Some(instance_of_handler) => { - instance_of_handler.call(&y, &[x], self)?.to_boolean() - } - None if object.is_callable() => object.ordinary_has_instance(self, &x)?, - None => { - return Err(self.construct_type_error( - "right-hand side of 'instanceof' is not callable", - )); - } - } - } else { + let target = self.vm.pop(); + let v = self.vm.pop(); + if !target.is_object() { return Err(self.construct_type_error(format!( "right-hand side of 'instanceof' should be an object, got {}", - y.type_of() + target.type_of() ))); }; - + let handler = target.get_method(WellKnownSymbols::has_instance(), self)?; + if !handler.is_undefined() { + let value = self.call(&handler, &target, &[v.clone()])?.to_boolean(); + self.vm.push(value); + } + if !target.is_callable() { + return Err(self + .construct_type_error("right-hand side of 'instanceof' is not callable")); + } + let value = target.ordinary_has_instance(self, &v)?; self.vm.push(value); } Opcode::Void => { diff --git a/boa_tester/src/exec/js262.rs b/boa_tester/src/exec/js262.rs index d7d90e050c..a42ec0b33b 100644 --- a/boa_tester/src/exec/js262.rs +++ b/boa_tester/src/exec/js262.rs @@ -1,4 +1,5 @@ use boa::{ + builtins::JsArgs, exec::Executable, object::{JsObject, ObjectInitializer}, property::Attribute, @@ -11,6 +12,7 @@ pub(super) fn init(context: &mut Context) -> JsObject { let obj = ObjectInitializer::new(context) .function(create_realm, "createRealm", 0) + .function(detach_array_buffer, "detachArrayBuffer", 2) .function(eval_script, "evalScript", 1) .property("global", global_obj, Attribute::default()) // .property("agent", agent, Attribute::default()) @@ -39,13 +41,43 @@ fn create_realm(_this: &JsValue, _: &[JsValue], _context: &mut Context) -> JsRes /// The `$262.detachArrayBuffer()` function. /// /// Implements the `DetachArrayBuffer` abstract operation. -#[allow(dead_code)] fn detach_array_buffer( _this: &JsValue, - _: &[JsValue], - _context: &mut Context, + args: &[JsValue], + context: &mut Context, ) -> JsResult { - todo!() + #[inline] + fn type_err(context: &mut Context) -> JsValue { + context.construct_type_error("The provided object was not an ArrayBuffer") + } + + let array_buffer = args + .get(0) + .map(JsValue::as_object) + .flatten() + .ok_or_else(|| type_err(context))?; + let mut array_buffer = array_buffer.borrow_mut(); + let array_buffer = array_buffer + .as_array_buffer_mut() + .ok_or_else(|| type_err(context))?; + + // 1. Assert: IsSharedArrayBuffer(arrayBuffer) is false. TODO + // 2. If key is not present, set key to undefined. + let key = args.get_or_undefined(1); + + // 3. If SameValue(arrayBuffer.[[ArrayBufferDetachKey]], key) is false, throw a TypeError exception. + if !JsValue::same_value(&array_buffer.array_buffer_detach_key, key) { + return Err(context.construct_type_error("Cannot detach array buffer with different key")); + } + + // 4. Set arrayBuffer.[[ArrayBufferData]] to null. + array_buffer.array_buffer_data = None; + + // 5. Set arrayBuffer.[[ArrayBufferByteLength]] to 0. + array_buffer.array_buffer_byte_length = 0; + + // 6. Return NormalCompletion(null). + Ok(JsValue::null()) } /// The `$262.evalScript()` function. diff --git a/boa_tester/src/results.rs b/boa_tester/src/results.rs index d42cfd29c1..86c6055cdc 100644 --- a/boa_tester/src/results.rs +++ b/boa_tester/src/results.rs @@ -441,6 +441,7 @@ fn compute_result_diff( match (base_test.result, new_test.result) { (a, b) if a == b => {} + (TestOutcomeResult::Ignored, TestOutcomeResult::Failed) => {} (_, TestOutcomeResult::Passed) => final_diff.fixed.push(test_name), (TestOutcomeResult::Panic, _) => final_diff.panic_fixes.push(test_name), diff --git a/test_ignore.txt b/test_ignore.txt index b3a465445a..db62fde73d 100644 --- a/test_ignore.txt +++ b/test_ignore.txt @@ -3,8 +3,10 @@ flag:module flag:async // Non-implemented features: -feature:TypedArray feature:json-modules +feature:DataView +feature:SharedArrayBuffer +feature:resizable-arraybuffer //feature:generators //feature:async-iteration //feature:class