Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

3232 lines
122 KiB

use std::{
cmp::{self, min},
sync::atomic::{self, Ordering},
};
use boa_macros::utf16;
use num_traits::Zero;
use super::{
object::typed_array_set_element, ContentType, TypedArray, TypedArrayKind, TypedArrayMarker,
};
use crate::{
builtins::{
array::{find_via_predicate, ArrayIterator, Direction},
array_buffer::{
utils::{memcpy, memmove, SliceRefMut},
ArrayBuffer, BufferObject,
},
iterable::iterable_to_list,
Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
object::internal_methods::get_prototype_from_constructor,
property::{Attribute, PropertyNameKind},
realm::Realm,
string::common::StaticJsStrings,
value::IntegerOrInfinity,
Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
/// The JavaScript `%TypedArray%` object.
///
/// <https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object>
#[derive(Debug, Clone, Copy)]
pub(crate) struct BuiltinTypedArray;
impl IntrinsicObject for BuiltinTypedArray {
fn init(realm: &Realm) {
let get_species = BuiltInBuilder::callable(realm, Self::get_species)
.name(js_string!("get [Symbol.species]"))
.build();
let get_buffer = BuiltInBuilder::callable(realm, Self::buffer)
.name(js_string!("get buffer"))
.build();
let get_byte_length = BuiltInBuilder::callable(realm, Self::byte_length)
.name(js_string!("get byteLength"))
.build();
let get_byte_offset = BuiltInBuilder::callable(realm, Self::byte_offset)
.name(js_string!("get byteOffset"))
.build();
let get_length = BuiltInBuilder::callable(realm, Self::length)
.name(js_string!("get length"))
.build();
let get_to_string_tag = BuiltInBuilder::callable(realm, Self::to_string_tag)
.name(js_string!("get [Symbol.toStringTag]"))
.build();
let values_function = BuiltInBuilder::callable(realm, Self::values)
.name(js_string!("values"))
.length(0)
.build();
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.static_accessor(
JsSymbol::species(),
Some(get_species),
None,
Attribute::CONFIGURABLE,
)
.property(
JsSymbol::iterator(),
values_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.accessor(
utf16!("buffer"),
Some(get_buffer),
None,
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.accessor(
utf16!("byteLength"),
Some(get_byte_length),
None,
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.accessor(
utf16!("byteOffset"),
Some(get_byte_offset),
None,
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.accessor(
StaticJsStrings::LENGTH,
Some(get_length),
None,
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.accessor(
JsSymbol::to_string_tag(),
Some(get_to_string_tag),
None,
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.static_method(Self::from, js_string!("from"), 1)
.static_method(Self::of, js_string!("of"), 0)
.method(Self::at, js_string!("at"), 1)
.method(Self::copy_within, js_string!("copyWithin"), 2)
.method(Self::entries, js_string!("entries"), 0)
.method(Self::every, js_string!("every"), 1)
.method(Self::fill, js_string!("fill"), 1)
.method(Self::filter, js_string!("filter"), 1)
.method(Self::find, js_string!("find"), 1)
.method(Self::find_index, js_string!("findIndex"), 1)
.method(Self::find_last, js_string!("findLast"), 1)
.method(Self::find_last_index, js_string!("findLastIndex"), 1)
.method(Self::for_each, js_string!("forEach"), 1)
.method(Self::includes, js_string!("includes"), 1)
.method(Self::index_of, js_string!("indexOf"), 1)
.method(Self::join, js_string!("join"), 1)
.method(Self::keys, js_string!("keys"), 0)
.method(Self::last_index_of, js_string!("lastIndexOf"), 1)
.method(Self::map, js_string!("map"), 1)
.method(Self::reduce, js_string!("reduce"), 1)
.method(Self::reduceright, js_string!("reduceRight"), 1)
.method(Self::reverse, js_string!("reverse"), 0)
.method(Self::set, js_string!("set"), 1)
.method(Self::slice, js_string!("slice"), 2)
.method(Self::some, js_string!("some"), 1)
.method(Self::sort, js_string!("sort"), 1)
.method(Self::subarray, js_string!("subarray"), 2)
.method(Self::to_locale_string, js_string!("toLocaleString"), 0)
.method(Self::to_reversed, js_string!("toReversed"), 0)
.method(Self::to_sorted, js_string!("toSorted"), 1)
.method(Self::with, js_string!("with"), 2)
// 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.
.property(
js_string!("toString"),
realm.intrinsics().objects().array_prototype_to_string(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
js_string!("values"),
values_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
}
impl BuiltInObject for BuiltinTypedArray {
const NAME: JsString = StaticJsStrings::TYPED_ARRAY;
}
impl BuiltInConstructor for BuiltinTypedArray {
const LENGTH: usize = 0;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::typed_array;
/// `%TypedArray% ( )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%
fn constructor(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
// 1. Throw a TypeError exception.
Err(JsNativeError::typ()
.with_message("the TypedArray constructor should never be called directly")
.into())
}
}
impl BuiltinTypedArray {
/// `%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<JsValue> {
// 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_constructor() => obj,
_ => {
return Err(JsNativeError::typ()
.with_message("TypedArray.from called on non-constructable value")
.into())
}
};
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 Err(JsNativeError::typ()
.with_message("TypedArray.from called with non-callable mapfn")
.into())
}
},
};
// 5. Let usingIterator be ? GetMethod(source, @@iterator).
let source = args.get_or_undefined(0);
let using_iterator = source.get_method(JsSymbol::iterator(), context)?;
let this_arg = args.get_or_undefined(2);
// 6. If usingIterator is not undefined, then
if let Some(using_iterator) = using_iterator {
// a. Let values be ? IterableToList(source, usingIterator).
let values = iterable_to_list(context, source, Some(using_iterator))?;
// 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)?.upcast();
// 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)?.upcast();
// 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())
}
/// [`TypedArrayCreateSameType ( exemplar, argumentList )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-typedarray-create-same-type
fn from_kind_and_length(
kind: TypedArrayKind,
length: u64,
context: &mut Context,
) -> JsResult<JsObject> {
let constructor =
kind.standard_constructor()(context.intrinsics().constructors()).constructor();
Self::create(&constructor, &[length.into()], context).map(JsObject::upcast)
}
/// `%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<JsValue> {
// 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_constructor() => obj,
_ => {
return Err(JsNativeError::typ()
.with_message("TypedArray.of called on non-constructable value")
.into())
}
};
// 4. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »).
let new_obj = Self::create(constructor, &[args.len().into()], context)?.upcast();
// 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.clone(), true, context)?;
}
// 7. Return newObj.
Ok(new_obj.into())
}
/// `get %TypedArray% [ @@species ]`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%-@@species
#[allow(clippy::unnecessary_wraps)]
pub(super) fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
// 1. Return the this value.
Ok(this.clone())
}
/// `%TypedArray%.prototype.at ( index )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.at
pub(crate) fn at(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (o, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = o.borrow().data.array_length(buf_len) 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(o.upcast().get(k, context).expect("Get cannot fail here"))
}
/// `get %TypedArray%.prototype.buffer`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.buffer
pub(crate) fn buffer(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
let ta = this
.as_object()
.and_then(JsObject::downcast_ref::<TypedArray>)
.ok_or_else(|| {
JsNativeError::typ().with_message("`this` is not a typed array object")
})?;
// 4. Let buffer be O.[[ViewedArrayBuffer]].
// 5. Return buffer.
Ok(ta.viewed_array_buffer().clone().into())
}
/// `get %TypedArray%.prototype.byteLength`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.bytelength
pub(crate) fn byte_length(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
let ta = this
.as_object()
.and_then(JsObject::downcast_ref::<TypedArray>)
.ok_or_else(|| {
JsNativeError::typ().with_message("`this` is not a typed array object")
})?;
// 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst).
let buf_len = ta
.viewed_array_buffer()
.as_buffer()
.bytes(Ordering::SeqCst)
.map(|s| s.len())
.unwrap_or_default();
// 5. Let size be TypedArrayByteLength(taRecord).
// 6. Return 𝔽(size).
Ok(ta.byte_length(buf_len).into())
}
/// `get %TypedArray%.prototype.byteOffset`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.byteoffset
pub(crate) fn byte_offset(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
let ta = this
.as_object()
.and_then(JsObject::downcast_ref::<TypedArray>)
.ok_or_else(|| {
JsNativeError::typ().with_message("Value is not a typed array object")
})?;
// 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst).
// 5. If IsTypedArrayOutOfBounds(taRecord) is true, return +0𝔽.
if ta
.viewed_array_buffer()
.as_buffer()
.bytes(Ordering::SeqCst)
.filter(|s| !ta.is_out_of_bounds(s.len()))
.is_none()
{
return Ok(0.into());
}
// 6. Let offset be O.[[ByteOffset]].
// 7. Return 𝔽(offset).
Ok(ta.byte_offset().into())
}
/// `%TypedArray%.prototype.copyWithin ( target, start [ , end ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin
pub(crate) fn copy_within(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 4. Let relativeTarget be ? ToIntegerOrInfinity(target).
// 5. If relativeTarget is -∞, let to be 0.
// 6. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0).
// 7. Else, let to be min(relativeTarget, len).
let to = Array::get_relative_start(context, args.get_or_undefined(0), len)?;
// 8. Let relativeStart be ? ToIntegerOrInfinity(start).
// 9. If relativeStart is -∞, let from be 0.
// 10. Else if relativeStart < 0, let from be max(len + relativeStart, 0).
// 11. Else, let from be min(relativeStart, len).
let from = Array::get_relative_start(context, args.get_or_undefined(1), len)?;
// 12. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
// 13. If relativeEnd is -∞, let final be 0.
// 14. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
// 15. Else, let final be min(relativeEnd, len).
let final_ = Array::get_relative_end(context, args.get_or_undefined(2), len)?;
// 16. Let count be min(final - from, len - to).
let count = match (final_.checked_sub(from), len.checked_sub(to)) {
(Some(lhs), Some(rhs)) => min(lhs, rhs),
_ => 0,
};
// 17. If count > 0, then
if count > 0 {
let ta = ta.borrow();
let ta = &ta.data;
// 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. Set taRecord to MakeTypedArrayWithBufferWitnessRecord(O, seq-cst).
// d. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
let buffer_obj = ta.viewed_array_buffer();
let mut buffer = buffer_obj.as_buffer_mut();
let Some(mut buf) = buffer
.bytes(Ordering::SeqCst)
.filter(|s| !ta.is_out_of_bounds(s.len()))
else {
return Err(JsNativeError::typ()
.with_message("typed array is outside the bounds of its inner buffer")
.into());
};
// e. Set len to TypedArrayLength(taRecord).
let len = ta.array_length(buf.len());
// f. Let elementSize be TypedArrayElementSize(O).
let element_size = ta.kind().element_size();
// g. Let byteOffset be O.[[ByteOffset]].
let byte_offset = ta.byte_offset();
// h. Let bufferByteLimit be (len × elementSize) + byteOffset.
let buffer_byte_limit = ((len * element_size) + byte_offset) as usize;
// i. Let toByteIndex be (targetIndex × elementSize) + byteOffset.
let to_byte_index = (to * element_size + byte_offset) as usize;
// j. Let fromByteIndex be (startIndex × elementSize) + byteOffset.
let from_byte_index = (from * element_size + byte_offset) as usize;
// k. Let countBytes be count × elementSize.
let mut count_bytes = (count * element_size) as usize;
// Readjust considering the buffer_byte_limit. A resize could
// have readjusted the buffer size, which could put `count_bytes`
// outside the allowed range.
if to_byte_index >= buffer_byte_limit || from_byte_index >= buffer_byte_limit {
return Ok(this.clone());
}
count_bytes = min(
count_bytes,
min(
buffer_byte_limit - to_byte_index,
buffer_byte_limit - from_byte_index,
),
);
// l. If fromByteIndex < toByteIndex and toByteIndex < fromByteIndex + countBytes, then
// i. Let direction be -1.
// ii. Set fromByteIndex to fromByteIndex + countBytes - 1.
// iii. Set toByteIndex to toByteIndex + countBytes - 1.
// m. Else,
// i. Let direction be 1.
// n. Repeat, while countBytes > 0,
// i. If fromByteIndex < bufferByteLimit and toByteIndex < bufferByteLimit, then
// 1. Let value be GetValueFromBuffer(buffer, fromByteIndex, uint8, true, unordered).
// 2. Perform SetValueInBuffer(buffer, toByteIndex, uint8, value, true, unordered).
// 3. Set fromByteIndex to fromByteIndex + direction.
// 4. Set toByteIndex to toByteIndex + direction.
// 5. Set countBytes to countBytes - 1.
// ii. Else,
// 1. Set countBytes to 0.
#[cfg(debug_assertions)]
{
assert!(buf.subslice_mut(from_byte_index..).len() >= count_bytes);
assert!(buf.subslice_mut(to_byte_index..).len() >= count_bytes);
}
// SAFETY: All previous checks are made to ensure this memmove is always in-bounds,
// making this operation safe.
unsafe {
memmove(buf.as_ptr(), from_byte_index, to_byte_index, count_bytes);
}
}
// 18. Return O.
Ok(this.clone())
}
/// `%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<JsValue> {
// 1. Let O be the this value.
// 2. Perform ? ValidateTypedArray(O, seq-cst).
let (ta, _) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Return CreateArrayIterator(O, key+value).
Ok(ArrayIterator::create_array_iterator(
ta.upcast(),
PropertyNameKind::KeyAndValue,
context,
))
}
/// `%TypedArray%.prototype.every ( callbackfn [ , thisArg ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.every
pub(crate) fn every(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 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 Err(JsNativeError::typ()
.with_message(
"TypedArray.prototype.every called with non-callable callback function",
)
.into())
}
};
// 5. Let k be 0.
// 6. Repeat, while k < len,
let ta = ta.upcast();
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kValue be ! Get(O, Pk).
let k_value = ta.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())
}
/// `%TypedArray%.prototype.fill ( value [ , start [ , end ] ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.fill
pub(crate) fn fill(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
let value: JsValue = if ta.borrow().data.kind().content_type() == ContentType::BigInt {
// 4. If O.[[ContentType]] is BigInt, set value to ? ToBigInt(value).
args.get_or_undefined(0).to_bigint(context)?.into()
} else {
// 5. Otherwise, set value to ? ToNumber(value).
args.get_or_undefined(0).to_number(context)?.into()
};
// 6. Let relativeStart be ? ToIntegerOrInfinity(start).
// 7. If relativeStart = -∞, let startIndex be 0.
// 8. Else if relativeStart < 0, let startIndex be max(len + relativeStart, 0).
// 9. Else, let startIndex be min(relativeStart, len).
let start_index = Array::get_relative_start(context, args.get_or_undefined(1), len)?;
// 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
// 11. If relativeEnd = -∞, let endIndex be 0.
// 12. Else if relativeEnd < 0, let endIndex be max(len + relativeEnd, 0).
// 13. Else, let endIndex be min(relativeEnd, len).
let end_index = Array::get_relative_end(context, args.get_or_undefined(2), len)?;
// 14. Set taRecord to MakeTypedArrayWithBufferWitnessRecord(O, seq-cst).
// 15. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
let len = {
let ta = ta.borrow();
let Some(buf_len) = ta
.data
.viewed_array_buffer()
.as_buffer()
.bytes(Ordering::SeqCst)
.filter(|b| !ta.data.is_out_of_bounds(b.len()))
.map(|b| b.len())
else {
return Err(JsNativeError::typ()
.with_message("typed array is outside the bounds of its inner buffer")
.into());
};
// 16. Set len to TypedArrayLength(taRecord).
ta.data.array_length(buf_len)
};
// 17. Set endIndex to min(endIndex, len).
let end_index = min(end_index, len);
// 18. Let k be startIndex.
// 19. Repeat, while k < endIndex,
let ta = ta.upcast();
for k in start_index..end_index {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Perform ! Set(O, Pk, value, true).
ta.set(k, value.clone(), true, context)
.expect("Set cannot fail here");
// c. Set k to k + 1.
}
// 20. Return O.
Ok(this.clone())
}
/// `%TypedArray%.prototype.filter ( callbackfn [ , thisArg ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.filter
pub(crate) fn filter(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
let typed_array_kind = ta.borrow().data.kind();
// 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 Err(JsNativeError::typ()
.with_message(
"TypedArray.prototype.filter called with non-callable callback function",
)
.into()),
};
// 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,
let ta = ta.upcast();
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kValue be ! Get(O, Pk).
let k_value = ta.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(&ta, typed_array_kind, &[captured.into()], context)?.upcast();
// 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())
}
/// `%TypedArray%.prototype.find ( predicate [ , thisArg ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.find
pub(crate) fn find(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
let predicate = args.get_or_undefined(0);
let this_arg = args.get_or_undefined(1);
// 4. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg).
let (_, value) = find_via_predicate(
&ta.upcast(),
len,
Direction::Ascending,
predicate,
this_arg,
context,
"TypedArray.prototype.find",
)?;
// 5. Return findRec.[[Value]].
Ok(value)
}
/// `%TypedArray%.prototype.findIndex ( predicate [ , thisArg ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.findindex
pub(crate) fn find_index(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
let predicate = args.get_or_undefined(0);
let this_arg = args.get_or_undefined(1);
// 4. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg).
let (index, _) = find_via_predicate(
&ta.upcast(),
len,
Direction::Ascending,
predicate,
this_arg,
context,
"TypedArray.prototype.findIndex",
)?;
// 5. Return findRec.[[Index]].
Ok(index)
}
/// `%TypedArray%.prototype.findLast ( predicate [ , thisArg ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlast
pub(crate) fn find_last(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
let predicate = args.get_or_undefined(0);
let this_arg = args.get_or_undefined(1);
// 4. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg).
let (_, value) = find_via_predicate(
&ta.upcast(),
len,
Direction::Descending,
predicate,
this_arg,
context,
"TypedArray.prototype.findLast",
)?;
// 5. Return findRec.[[Value]].
Ok(value)
}
/// `%TypedArray%.prototype.findLastIndex ( predicate [ , thisArg ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlastindex
pub(crate) fn find_last_index(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
let predicate = args.get_or_undefined(0);
let this_arg = args.get_or_undefined(1);
// 4. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg).
let (index, _) = find_via_predicate(
&ta.upcast(),
len,
Direction::Descending,
predicate,
this_arg,
context,
"TypedArray.prototype.findLastIndex",
)?;
// 5. Return findRec.[[Index]].
Ok(index)
}
/// `%TypedArray%.prototype.forEach ( callbackfn [ , thisArg ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach
pub(crate) fn for_each(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 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 Err(JsNativeError::typ()
.with_message(
"TypedArray.prototype.foreach called with non-callable callback function",
)
.into()),
};
// 5. Let k be 0.
// 6. Repeat, while k < len,
let ta = ta.upcast();
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kValue be ! Get(O, Pk).
let k_value = ta.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())
}
/// `%TypedArray%.prototype.includes ( searchElement [ , fromIndex ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.includes
pub(crate) fn includes(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 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 k = if n >= 0 {
// a. Let k be n.
n as u64
} else {
// 10. Else,
// a. Let k be len + n.
// b. If k < 0, set k to 0.
len.saturating_add_signed(n)
};
// 11. Repeat, while k < len,
let ta = ta.upcast();
for k in k..len {
// a. Let elementK be ! Get(O, ! ToString(𝔽(k))).
let element_k = ta.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.
}
// 12. Return false.
Ok(false.into())
}
/// `%TypedArray%.prototype.indexOf ( searchElement [ , fromIndex ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.indexof
pub(crate) fn index_of(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 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 k = if n >= 0 {
// a. Let k be n.
n as u64
// 10. Else,
} else {
// a. Let k be len + n.
// b. If k < 0, set k to 0.
len.saturating_add_signed(n)
};
// 11. Repeat, while k < len,
let ta = ta.upcast();
for k in k..len {
// a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))).
let k_present = ta
.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 = ta.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.
}
// 12. Return -1𝔽.
Ok((-1).into())
}
/// `%TypedArray%.prototype.join ( separator )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.join
pub(crate) fn join(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 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() {
js_string!(",")
// 5. Else, let sep be ? ToString(separator).
} else {
separator.to_string(context)?
};
// 6. Let R be the empty String.
let mut r = Vec::new();
// 7. Let k be 0.
// 8. Repeat, while k < len,
let ta = ta.upcast();
for k in 0..len {
// a. If k > 0, set R to the string-concatenation of R and sep.
if k > 0 {
r.extend_from_slice(&sep);
}
// b. Let element be ! Get(O, ! ToString(𝔽(k))).
let element = ta.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.extend_from_slice(&element.to_string(context)?);
}
}
// 9. Return R.
Ok(js_string!(r).into())
}
/// `%TypedArray%.prototype.keys ( )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys
pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, _) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Return CreateArrayIterator(O, key).
Ok(ArrayIterator::create_array_iterator(
ta.upcast(),
PropertyNameKind::Key,
context,
))
}
/// `%TypedArray%.prototype.lastIndexOf ( searchElement [ , fromIndex ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.lastindexof
pub(crate) fn last_index_of(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 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 k = match args.get(1) {
None => len,
Some(n) => {
let n = n.to_integer_or_infinity(context)?;
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 => min(i as u64 + 1, len),
IntegerOrInfinity::PositiveInfinity => len,
// 8. Else,
// a. Let k be len + n.
IntegerOrInfinity::Integer(i) => len.saturating_add_signed(i + 1),
}
}
};
// 9. Repeat, while k ≥ 0,
let ta = ta.upcast();
for k in (0..k).rev() {
// a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))).
let k_present = ta
.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 = ta.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.
}
// 10. Return -1𝔽.
Ok((-1).into())
}
/// `get %TypedArray%.prototype.length`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length
pub(crate) fn length(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
// 3. Assert: O has [[ViewedArrayBuffer]] and [[ArrayLength]] internal slots.
let ta = this
.as_object()
.and_then(JsObject::downcast_ref::<TypedArray>)
.ok_or_else(|| {
JsNativeError::typ().with_message("`this` is not a typed array object")
})?;
// 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst).
// 5. If IsTypedArrayOutOfBounds(taRecord) is true, return +0𝔽.
let buf = ta.viewed_array_buffer().as_buffer();
let Some(buf) = buf
.bytes(Ordering::SeqCst)
.filter(|s| !ta.is_out_of_bounds(s.len()))
else {
return Ok(0.into());
};
// 6. Let length be TypedArrayLength(taRecord).
// 7. Return 𝔽(length).
Ok(ta.array_length(buf.len()).into())
}
/// `%TypedArray%.prototype.map ( callbackfn [ , thisArg ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.map
pub(crate) fn map(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
let typed_array_kind = ta.borrow().data.kind();
// 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 Err(JsNativeError::typ()
.with_message(
"TypedArray.prototype.map called with non-callable callback function",
)
.into())
}
};
let ta = ta.upcast();
// 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »).
let a = Self::species_create(&ta, typed_array_kind, &[len.into()], context)?.upcast();
// 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 = ta.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())
}
/// `%TypedArray%.prototype.reduce ( callbackfn [ , initialValue ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce
pub(crate) fn reduce(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 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 Err(JsNativeError::typ()
.with_message(
"TypedArray.prototype.reduce called with non-callable callback function",
)
.into()),
};
// 5. If len = 0 and initialValue is not present, throw a TypeError exception.
if len == 0 && args.get(1).is_none() {
return Err(JsNativeError::typ()
.with_message("Typed array length is 0 and initial value is not present")
.into());
}
let ta = ta.upcast();
// 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;
ta.get(0, context).expect("Get cannot fail here")
};
// 10. Repeat, while k < len,
for k in k..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kValue be ! Get(O, Pk).
let k_value = ta.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.
}
// 11. Return accumulator.
Ok(accumulator)
}
/// `%TypedArray%.prototype.reduceRight ( callbackfn [ , initialValue ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduceright
pub(crate) fn reduceright(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 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 Err(JsNativeError::typ()
.with_message(
"TypedArray.prototype.reduceright called with non-callable callback function",
)
.into()),
};
// 5. If len = 0 and initialValue is not present, throw a TypeError exception.
if len == 0 && args.get(1).is_none() {
return Err(JsNativeError::typ()
.with_message("Typed array length is 0 and initial value is not present")
.into());
}
let ta = ta.upcast();
// 6. Let k be len - 1.
// 7. Let accumulator be undefined.
// 8. If initialValue is present, then
let (mut accumulator, k) = if let Some(initial_value) = args.get(1) {
// a. Set accumulator to initialValue.
(initial_value.clone(), len)
// 9. Else,
} else {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Set accumulator to ! Get(O, Pk).
let accumulator = ta.get(len - 1, context).expect("Get cannot fail here");
// c. Set k to k - 1.
(accumulator, len - 1)
};
// 10. Repeat, while k ≥ 0,
for k in (0..k).rev() {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kValue be ! Get(O, Pk).
let k_value = ta.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.
}
// 11. Return accumulator.
Ok(accumulator)
}
/// `%TypedArray%.prototype.reverse ( )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reverse
#[allow(clippy::float_cmp)]
pub(crate) fn reverse(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
let ta = ta.upcast();
// 4. Let middle be floor(len / 2).
let middle = len / 2;
// 5. Let lower be 0.
let mut lower = 0;
// 6. Repeat, while lower ≠ middle,
while lower != middle {
// a. Let upper be len - lower - 1.
let upper = len - lower - 1;
// b. Let upperP be ! ToString(𝔽(upper)).
// c. Let lowerP be ! ToString(𝔽(lower)).
// d. Let lowerValue be ! Get(O, lowerP).
let lower_value = ta.get(lower, context).expect("Get cannot fail here");
// e. Let upperValue be ! Get(O, upperP).
let upper_value = ta.get(upper, context).expect("Get cannot fail here");
// f. Perform ! Set(O, lowerP, upperValue, true).
ta.set(lower, upper_value, true, context)
.expect("Set cannot fail here");
// g. Perform ! Set(O, upperP, lowerValue, true).
ta.set(upper, lower_value, true, context)
.expect("Set cannot fail here");
// h. Set lower to lower + 1.
lower += 1;
}
// 7. Return O.
Ok(this.clone())
}
/// [`%TypedArray%.prototype.toReversed ( )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.toreversed
pub(crate) fn to_reversed(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
let kind = ta.borrow().data.kind();
// 4. Let A be ? TypedArrayCreateSameType(O, « 𝔽(length) »).
let new_array = Self::from_kind_and_length(kind, len, context)?;
// 5. Let k be 0.
// 6. Repeat, while k < length,
let ta = ta.upcast();
for k in 0..len {
// a. Let from be ! ToString(𝔽(length - k - 1)).
// b. Let Pk be ! ToString(𝔽(k)).
// c. Let fromValue be ! Get(O, from).
let value = ta
.get(len - k - 1, context)
.expect("cannot fail per the spec");
// d. Perform ! Set(A, Pk, fromValue, true).
new_array
.set(k, value, true, context)
.expect("cannot fail per the spec");
// e. Set k to k + 1.
}
// 7. Return A.
Ok(new_array.into())
}
/// `%TypedArray%.prototype.set ( source [ , offset ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.set
pub(crate) fn set(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 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()
.and_then(|o| o.clone().downcast::<TypedArray>().ok())
.ok_or_else(|| {
JsNativeError::typ()
.with_message("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.
let target_offset = match target_offset {
IntegerOrInfinity::Integer(i) if i < 0 => {
return Err(JsNativeError::range()
.with_message("TypedArray.set called with negative offset")
.into())
}
IntegerOrInfinity::NegativeInfinity => {
return Err(JsNativeError::range()
.with_message("TypedArray.set called with negative offset")
.into())
}
IntegerOrInfinity::PositiveInfinity => U64OrPositiveInfinity::PositiveInfinity,
IntegerOrInfinity::Integer(i) => U64OrPositiveInfinity::U64(i as u64),
};
// 6. If source is an Object that has a [[TypedArrayName]] internal slot, then
let source = args.get_or_undefined(0);
if let Some(source) = source
.as_object()
.and_then(|o| o.clone().downcast::<TypedArray>().ok())
{
// a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source).
Self::set_typed_array_from_typed_array(&target, &target_offset, &source, context)?;
}
// 7. Else,
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<TypedArray>,
target_offset: &U64OrPositiveInfinity,
source: &JsObject<TypedArray>,
context: &mut Context,
) -> JsResult<()> {
// 1. Let targetBuffer be target.[[ViewedArrayBuffer]].
// 2. Let targetRecord be MakeTypedArrayWithBufferWitnessRecord(target, seq-cst).
// 3. If IsTypedArrayOutOfBounds(targetRecord) is true, throw a TypeError exception.
let target_array = target.borrow();
let target_buf_obj = target_array.data.viewed_array_buffer().clone();
let Some(target_buf_len) = target_buf_obj
.as_buffer()
.bytes(Ordering::SeqCst)
.filter(|s| !target_array.data.is_out_of_bounds(s.len()))
.map(|s| s.len())
else {
return Err(JsNativeError::typ()
.with_message("typed array is outside the bounds of its inner buffer")
.into());
};
// 4. Let targetLength be TypedArrayLength(targetRecord).
let target_length = target_array.data.array_length(target_buf_len);
// 5. Let srcBuffer be source.[[ViewedArrayBuffer]].
// 6. Let srcRecord be MakeTypedArrayWithBufferWitnessRecord(source, seq-cst).
// 7. If IsTypedArrayOutOfBounds(srcRecord) is true, throw a TypeError exception.
let src_array = source.borrow();
let mut src_buf_obj = src_array.data.viewed_array_buffer().clone();
let Some(mut src_buf_len) = src_buf_obj
.as_buffer()
.bytes(Ordering::SeqCst)
.filter(|s| !src_array.data.is_out_of_bounds(s.len()))
.map(|s| s.len())
else {
return Err(JsNativeError::typ()
.with_message("typed array is outside the bounds of its inner buffer")
.into());
};
// 8. Let srcLength be TypedArrayLength(srcRecord).
let src_length = src_array.data.array_length(src_buf_len);
// 9. Let targetType be TypedArrayElementType(target).
let target_type = target_array.data.kind();
// 10. Let targetElementSize be TypedArrayElementSize(target).
let target_element_size = target_type.element_size();
// 11. Let targetByteOffset be target.[[ByteOffset]].
let target_byte_offset = target_array.data.byte_offset();
// 12. Let srcType be TypedArrayElementType(source).
let src_type = src_array.data.kind();
// 13. Let srcElementSize be TypedArrayElementSize(source).
let src_element_size = src_type.element_size();
// 14. Let srcByteOffset be source.[[ByteOffset]].
let src_byte_offset = src_array.data.byte_offset();
// a. Let srcByteLength be source.[[ByteLength]].
let src_byte_length = src_array.data.byte_length(src_buf_len);
drop(target_array);
drop(src_array);
// 15. If targetOffset = +∞, throw a RangeError exception.
let U64OrPositiveInfinity::U64(target_offset) = target_offset else {
return Err(JsNativeError::range()
.with_message("Target offset cannot be Infinity")
.into());
};
// 16. If srcLength + targetOffset > targetLength, throw a RangeError exception.
if src_length + target_offset > target_length {
return Err(JsNativeError::range()
.with_message("Source typed array and target offset longer than target typed array")
.into());
}
// 17. If target.[[ContentType]] is not source.[[ContentType]], throw a TypeError exception.
if target_type.content_type() != src_type.content_type() {
return Err(JsNativeError::typ()
.with_message(
"Source typed array and target typed array have different content types",
)
.into());
}
// 18. If IsSharedArrayBuffer(srcBuffer) is true, IsSharedArrayBuffer(targetBuffer) is true,
// and srcBuffer.[[ArrayBufferData]] is targetBuffer.[[ArrayBufferData]], let
// sameSharedArrayBuffer be true; otherwise, let sameSharedArrayBuffer be false.
// 19. If SameValue(srcBuffer, targetBuffer) is true or sameSharedArrayBuffer is true, then
let src_byte_index = if BufferObject::equals(&src_buf_obj, &target_buf_obj) {
// a. Let srcByteLength be source.[[ByteLength]].
let src_byte_offset = src_byte_offset as usize;
let src_byte_length = src_byte_length as usize;
let s = {
let slice = src_buf_obj.as_buffer();
let slice = slice
.bytes_with_len(src_buf_len)
.expect("Already checked for detached buffer");
// 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 subslice = slice.subslice(src_byte_offset..src_byte_offset + src_byte_length);
src_buf_len = subslice.len();
subslice.clone(context)?
};
src_buf_obj = BufferObject::Buffer(s);
// d. Let srcByteIndex be 0.
0
}
// 20. Else,
else {
// a. Let srcByteIndex be srcByteOffset.
src_byte_offset
};
// 22. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset.
let target_byte_index = target_offset * target_element_size + target_byte_offset;
let src_buffer = src_buf_obj.as_buffer();
let src_buffer = src_buffer
.bytes_with_len(src_buf_len)
.expect("Already checked for detached buffer");
let mut target_buffer = target_buf_obj.as_buffer_mut();
let mut target_buffer = target_buffer
.bytes_with_len(target_buf_len)
.expect("Already checked for detached buffer");
// 24. If srcType is the same as targetType, then
if src_type == target_type {
let src_byte_index = src_byte_index as usize;
let target_byte_index = target_byte_index as usize;
let byte_count = (target_element_size * src_length) as usize;
// 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,
// i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered).
// ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered).
// iii. Set srcByteIndex to srcByteIndex + 1.
// iv. Set targetByteIndex to targetByteIndex + 1.
let src = src_buffer.subslice(src_byte_index..);
let mut target = target_buffer.subslice_mut(target_byte_index..);
#[cfg(debug_assertions)]
{
assert!(src.len() >= byte_count);
assert!(target.len() >= byte_count);
}
// SAFETY: We already asserted that the indices are in bounds.
unsafe {
memcpy(src.as_ptr(), target.as_ptr(), byte_count);
}
}
// 25. Else,
else {
// 23. Let limit be targetByteIndex + targetElementSize × srcLength.
let limit = (target_byte_index + target_element_size * src_length) as usize;
let mut src_byte_index = src_byte_index as usize;
let mut target_byte_index = target_byte_index as usize;
// a. Repeat, while targetByteIndex < limit,
while target_byte_index < limit {
// i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, srcType, true, Unordered).
let value = unsafe {
src_buffer
.subslice(src_byte_index..)
.get_value(src_type, atomic::Ordering::Relaxed)
};
let value = JsValue::from(value);
let value = target_type
.get_element(&value, context)
.expect("value can only be f64 or BigInt");
// ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, Unordered).
// SAFETY: previous checks preserve the validity of the indices.
unsafe {
target_buffer
.subslice_mut(target_byte_index..)
.set_value(value, atomic::Ordering::Relaxed);
}
// iii. Set srcByteIndex to srcByteIndex + srcElementSize.
src_byte_index += src_element_size as usize;
// iv. Set targetByteIndex to targetByteIndex + targetElementSize.
target_byte_index += target_element_size as usize;
}
}
Ok(())
}
/// `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<TypedArray>,
target_offset: &U64OrPositiveInfinity,
source: &JsValue,
context: &mut Context,
) -> JsResult<()> {
// 3. Let targetLength be TypedArrayLength(targetRecord).
let target_length = {
let target = target.borrow();
let target = &target.data;
// 1. Let targetRecord be MakeTypedArrayWithBufferWitnessRecord(target, seq-cst).
// 2. If IsTypedArrayOutOfBounds(targetRecord) is true, throw a TypeError exception.
let Some(buf_len) = target
.viewed_array_buffer()
.as_buffer()
.bytes(Ordering::SeqCst)
.filter(|s| !target.is_out_of_bounds(s.len()))
.map(|s| s.len())
else {
return Err(JsNativeError::typ()
.with_message("typed array is outside the bounds of its inner buffer")
.into());
};
// 3. Let targetLength be target.[[ArrayLength]].
target.array_length(buf_len)
};
// 4. Let src be ? ToObject(source).
let src = source.to_object(context)?;
// 5. Let srcLength be ? LengthOfArrayLike(src).
let src_length = src.length_of_array_like(context)?;
// 6. If targetOffset = +∞, throw a RangeError exception.
let target_offset = match target_offset {
U64OrPositiveInfinity::U64(target_offset) => target_offset,
U64OrPositiveInfinity::PositiveInfinity => {
return Err(JsNativeError::range()
.with_message("Target offset cannot be positive infinity")
.into())
}
};
// 7. If srcLength + targetOffset > targetLength, throw a RangeError exception.
if src_length + target_offset > target_length {
return Err(JsNativeError::range()
.with_message("Source object and target offset longer than target typed array")
.into());
}
// 8. Let k be 0.
// 9. Repeat, while k < srcLength,
let target = target.clone().upcast();
for k in 0..src_length {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let value be ? Get(src, Pk).
let value = src.get(k, context)?;
// c. Let targetIndex be 𝔽(targetOffset + k).
let target_index = target_offset + k;
// d. Perform ? IntegerIndexedElementSet(target, targetIndex, value).
typed_array_set_element(&target, target_index as f64, &value, &mut context.into())?;
// e. Set k to k + 1.
}
// 10. Return unused.
Ok(())
}
/// `%TypedArray%.prototype.slice ( start, end )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.slice
pub(crate) fn slice(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (src, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
let src_borrow = src.borrow();
// 3. Let len be TypedArrayLength(taRecord).
let src_len = src_borrow.data.array_length(buf_len);
// e. Let srcType be TypedArrayElementType(O).
let src_type = src_borrow.data.kind();
drop(src_borrow);
// 4. Let relativeStart be ? ToIntegerOrInfinity(start).
// 5. If relativeStart = -∞, let startIndex be 0.
// 6. Else if relativeStart < 0, let startIndex be max(srcArrayLength + relativeStart, 0).
// 7. Else, let startIndex be min(relativeStart, srcArrayLength).
let start_index = Array::get_relative_start(context, args.get_or_undefined(0), src_len)?;
// 8. If end is undefined, let relativeEnd be srcArrayLength; else let relativeEnd be ? ToIntegerOrInfinity(end).
// 9. If relativeEnd = -∞, let endIndex be 0.
// 10. Else if relativeEnd < 0, let endIndex be max(srcArrayLength + relativeEnd, 0).
// 11. Else, let endIndex be min(relativeEnd, srcArrayLength).
let end_index = Array::get_relative_end(context, args.get_or_undefined(1), src_len)?;
// 12. Let countBytes be max(endIndex - startIndex, 0).
let count = end_index.saturating_sub(start_index);
// 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(countBytes) »).
let target =
Self::species_create(&src.clone().upcast(), src_type, &[count.into()], context)?;
// 14. If countBytes > 0, then
if count == 0 {
// 15. Return A.
return Ok(target.upcast().into());
}
let src_borrow = src.borrow();
let target_borrow = target.borrow();
// a. Set taRecord to MakeTypedArrayWithBufferWitnessRecord(O, seq-cst).
// b. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
let src_buf_borrow = src_borrow.data.viewed_array_buffer().as_buffer();
let Some(src_buf) = src_buf_borrow
.bytes(Ordering::SeqCst)
.filter(|s| !src_borrow.data.is_out_of_bounds(s.len()))
else {
return Err(JsNativeError::typ()
.with_message("typed array is outside the bounds of its inner buffer")
.into());
};
// c. Set endIndex to min(endIndex, TypedArrayLength(taRecord)).
let end_index = min(end_index, src_borrow.data.array_length(src_buf.len()));
// d. Set countBytes to max(endIndex - startIndex, 0).
let count = end_index.saturating_sub(start_index) as usize;
// f. Let targetType be TypedArrayElementType(A).
let target_type = target_borrow.data.kind();
// g. If srcType is targetType, then
if src_type == target_type {
{
let byte_count = count * src_type.element_size() as usize;
// i. NOTE: The transfer must be performed in a manner that preserves the bit-level encoding of the source data.
// ii. Let srcBuffer be O.[[ViewedArrayBuffer]].
// iii. Let targetBuffer be A.[[ViewedArrayBuffer]].
let target_borrow = target_borrow;
let mut target_buf = target_borrow.data.viewed_array_buffer().as_buffer_mut();
let mut target_buf = target_buf
.bytes_with_len(byte_count)
.expect("newly created array cannot be detached");
// iv. Let elementSize be TypedArrayElementSize(O).
let element_size = src_type.element_size();
// v. Let srcByteOffset be O.[[ByteOffset]].
let src_byte_offset = src_borrow.data.byte_offset();
// vi. Let srcByteIndex be (startIndex × elementSize) + srcByteOffset.
let src_byte_index = (start_index * element_size + src_byte_offset) as usize;
// vii. Let targetByteIndex be A.[[ByteOffset]].
let target_byte_index = target_borrow.data.byte_offset() as usize;
// viii. Let endByteIndex be targetByteIndex + (countBytes × elementSize).
// Not needed by the impl.
// ix. Repeat, while targetByteIndex < endByteIndex,
// 1. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, uint8, true, unordered).
// 2. Perform SetValueInBuffer(targetBuffer, targetByteIndex, uint8, value, true, unordered).
// 3. Set srcByteIndex to srcByteIndex + 1.
// 4. Set targetByteIndex to targetByteIndex + 1.
let src = src_buf.subslice(src_byte_index..);
let mut target = target_buf.subslice_mut(target_byte_index..);
#[cfg(debug_assertions)]
{
assert!(src.len() >= byte_count);
assert!(target.len() >= byte_count);
}
// SAFETY: All previous checks put the indices at least within the bounds of `src_buffer`.
// Also, `target_buffer` is precisely allocated to fit all sliced elements from
// `src_buffer`, making this operation safe.
unsafe {
memcpy(src.as_ptr(), target.as_ptr(), byte_count);
}
}
// 15. Return A.
Ok(target.upcast().into())
} else {
// h. Else,
drop(src_buf_borrow);
drop((src_borrow, target_borrow));
// i. Let n be 0.
// ii. Let k be startIndex.
// iii. Repeat, while k < endIndex,
let src = src.upcast();
let target = target.upcast();
for (n, k) in (start_index..end_index).enumerate() {
// 1. Let Pk be ! ToString(𝔽(k)).
// 2. Let kValue be ! Get(O, Pk).
let k_value = src.get(k, context).expect("Get cannot fail here");
// 3. Perform ! Set(A, ! ToString(𝔽(n)), kValue, true).
target
.set(n, k_value, true, context)
.expect("Set cannot fail here");
// 4. Set k to k + 1.
// 5. Set n to n + 1.
}
// 15. Return A.
Ok(target.into())
}
}
/// `%TypedArray%.prototype.some ( callbackfn [ , thisArg ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.some
pub(crate) fn some(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 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 Err(JsNativeError::typ()
.with_message(
"TypedArray.prototype.some called with non-callable callback function",
)
.into())
}
};
// 5. Let k be 0.
// 6. Repeat, while k < len,
let ta = ta.upcast();
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kValue be ! Get(O, Pk).
let k_value = ta.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())
}
/// `%TypedArray%.prototype.sort ( comparefn )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort
pub(crate) fn sort(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception.
let compare_fn = match args.first() {
None | Some(JsValue::Undefined) => None,
Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj),
_ => {
return Err(JsNativeError::typ()
.with_message("TypedArray.sort called with non-callable comparefn")
.into())
}
};
// 2. Let obj be the this value.
// 3. Let taRecord be ? ValidateTypedArray(obj, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 4. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 5. NOTE: The following closure performs a numeric comparison rather than the string comparison used in 23.1.3.30.
// 6. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called:
let sort_compare =
|x: &JsValue, y: &JsValue, context: &mut Context| -> JsResult<cmp::Ordering> {
// a. Return ? CompareTypedArrayElements(x, y, comparefn).
compare_typed_array_elements(x, y, compare_fn, context)
};
let ta = ta.upcast();
// 7. Let sortedList be ? SortIndexedProperties(obj, len, SortCompare, read-through-holes).
let sorted = Array::sort_indexed_properties(&ta, len, sort_compare, false, context)?;
// 8. Let j be 0.
// 9. Repeat, while j < len,
for (j, item) in sorted.into_iter().enumerate() {
// a. Perform ! Set(obj, ! ToString(𝔽(j)), sortedList[j], true).
ta.set(j, item, true, context)
.expect("cannot fail per spec");
// b. Set j to j + 1.
}
// 10. Return obj.
Ok(ta.into())
}
/// [`%TypedArray%.prototype.toSorted ( comparefn )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.tosorted
pub(crate) fn to_sorted(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception.
let compare_fn = match args.first() {
None | Some(JsValue::Undefined) => None,
Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj),
_ => {
return Err(JsNativeError::typ()
.with_message("TypedArray.sort called with non-callable comparefn")
.into())
}
};
// 2. Let O be the this value.
// 3. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 4. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
// 5. Let A be ? TypedArrayCreateSameType(O, « 𝔽(len) »).
let new_array = Self::from_kind_and_length(ta.borrow().data.kind(), len, context)?;
// 6. NOTE: The following closure performs a numeric comparison rather than the string comparison used in 23.1.3.34.
// 7. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called:
let sort_compare =
|x: &JsValue, y: &JsValue, context: &mut Context| -> JsResult<cmp::Ordering> {
// a. Return ? CompareTypedArrayElements(x, y, comparefn).
compare_typed_array_elements(x, y, compare_fn, context)
};
let ta = ta.upcast();
// 8. Let sortedList be ? SortIndexedProperties(O, len, SortCompare, read-through-holes).
let sorted = Array::sort_indexed_properties(&ta, len, sort_compare, false, context)?;
// 9. Let j be 0.
// 10. Repeat, while j < len;
for (j, item) in sorted.into_iter().enumerate() {
// a. Perform ! Set(A, ! ToString(𝔽(j)), sortedList[j], true).
new_array
.set(j, item, true, context)
.expect("cannot fail per spec");
// b. Set j to j + 1.
}
// 11. Return A.
Ok(new_array.into())
}
/// `%TypedArray%.prototype.subarray ( begin, end )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.subarray
pub(crate) fn subarray(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
let src = this
.as_object()
.and_then(|o| o.clone().downcast::<TypedArray>().ok())
.ok_or_else(|| {
JsNativeError::typ().with_message("Value is not a typed array object")
})?;
let src_borrow = src.borrow();
// 4. Let buffer be O.[[ViewedArrayBuffer]].
let buffer = src_borrow.data.viewed_array_buffer().clone();
// 5. Let srcRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst).
// 6. If IsTypedArrayOutOfBounds(srcRecord) is true, then
// a. Let srcLength be 0.
// 7. Else,
// a. Let srcLength be TypedArrayLength(srcRecord).
let src_len = if let Some(buf) = buffer
.as_buffer()
.bytes(Ordering::SeqCst)
.filter(|s| !src_borrow.data.is_out_of_bounds(s.len()))
{
src_borrow.data.array_length(buf.len())
} else {
0
};
let kind = src_borrow.data.kind();
// 12. Let elementSize be TypedArrayElementSize(O).
let element_size = kind.element_size();
// 13. Let srcByteOffset be O.[[ByteOffset]].
let src_byte_offset = src_borrow.data.byte_offset();
let is_auto_length = src_borrow.data.is_auto_length();
drop(src_borrow);
// 8. Let relativeStart be ? ToIntegerOrInfinity(start).
// 9. If relativeStart = -∞, let startIndex be 0.
// 10. Else if relativeStart < 0, let startIndex be max(srcLength + relativeStart, 0).
// 11. Else, let startIndex be min(relativeStart, srcLength).
let start_index = Array::get_relative_start(context, args.get_or_undefined(0), src_len)?;
// 14. Let beginByteOffset be srcByteOffset + (startIndex × elementSize).
let begin_byte_offset = src_byte_offset + (start_index * element_size);
let end = args.get_or_undefined(1);
// 15. If O.[[ArrayLength]] is auto and end is undefined, then
if is_auto_length && end.is_undefined() {
// a. Let argumentsList be « buffer, 𝔽(beginByteOffset) ».
// 17. Return ? TypedArraySpeciesCreate(O, argumentsList).
Ok(Self::species_create(
&src.upcast(),
kind,
&[buffer.into(), begin_byte_offset.into()],
context,
)?
.upcast()
.into())
} else {
// 16. Else,
// a. If end is undefined, let relativeEnd be srcLength; else let relativeEnd be ? ToIntegerOrInfinity(end).
// b. If relativeEnd = -∞, let endIndex be 0.
// c. Else if relativeEnd < 0, let endIndex be max(srcLength + relativeEnd, 0).
// d. Else, let endIndex be min(relativeEnd, srcLength).
let end_index = Array::get_relative_end(context, end, src_len)?;
// e. Let newLength be max(endIndex - startIndex, 0).
let new_len = end_index.saturating_sub(start_index);
// f. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ».
Ok(Self::species_create(
&src.upcast(),
kind,
&[buffer.into(), begin_byte_offset.into(), new_len.into()],
context,
)?
.upcast()
.into())
}
}
/// `%TypedArray%.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] )`
/// `Array.prototype.toLocaleString ( [ locales [ , options ] ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [ECMA-402 reference][spec-402]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.tolocalestring
/// [spec-402]: https://402.ecma-international.org/10.0/#sup-array.prototype.tolocalestring
pub(crate) fn to_locale_string(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// This is a distinct method that implements the same algorithm as Array.prototype.toLocaleString as defined in
// 23.1.3.32 except that TypedArrayLength is called in place of performing a [[Get]] of "length".
let array = this.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("Value is not a typed array object")
})?;
let (len, is_fixed_len) = {
let o = array.downcast_ref::<TypedArray>().ok_or_else(|| {
JsNativeError::typ().with_message("Value is not a typed array object")
})?;
let buf = o.viewed_array_buffer().as_buffer();
let Some((buf_len, is_fixed_len)) = buf
.bytes(Ordering::SeqCst)
.filter(|s| !o.is_out_of_bounds(s.len()))
.map(|s| (s.len(), buf.is_fixed_len()))
else {
return Err(JsNativeError::typ()
.with_message("typed array is outside the bounds of its inner buffer")
.into());
};
(o.array_length(buf_len), is_fixed_len)
};
let separator = {
#[cfg(feature = "intl")]
{
// TODO: this should eventually return a locale-sensitive separator.
utf16!(", ")
}
#[cfg(not(feature = "intl"))]
{
utf16!(", ")
}
};
let mut r = Vec::new();
for k in 0..len {
if k > 0 {
r.extend_from_slice(separator);
}
let next_element = array.get(k, context)?;
// Mirrors the behaviour of `join`, but the compiler
// could unswitch the loop using `is_fixed_len`.
if is_fixed_len || !next_element.is_undefined() {
let s = next_element
.invoke(
utf16!("toLocaleString"),
&[
args.get_or_undefined(0).clone(),
args.get_or_undefined(1).clone(),
],
context,
)?
.to_string(context)?;
r.extend_from_slice(&s);
};
}
Ok(js_string!(r).into())
}
/// `%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<JsValue> {
// 1. Let O be the this value.
// 2. Perform ? ValidateTypedArray(O, seq-cst).
let (ta, _) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Return CreateArrayIterator(O, value).
Ok(ArrayIterator::create_array_iterator(
ta.upcast(),
PropertyNameKind::Value,
context,
))
}
/// [`%TypedArray%.prototype.with ( index, value )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.with
pub(crate) fn with(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let taRecord be ? ValidateTypedArray(O, seq-cst).
let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?;
// 3. Let len be TypedArrayLength(taRecord).
let len = ta.borrow().data.array_length(buf_len);
let kind = ta.borrow().data.kind();
// 4. Let relativeIndex be ? ToIntegerOrInfinity(index).
// triggers any conversion errors before throwing range errors.
let relative_index = args.get_or_undefined(0).to_integer_or_infinity(context)?;
let value = args.get_or_undefined(1);
// 7. If O.[[ContentType]] is bigint, let numericValue be ? ToBigInt(value).
let numeric_value: JsValue = if kind.content_type() == ContentType::BigInt {
value.to_bigint(context)?.into()
} else {
// 8. Else, let numericValue be ? ToNumber(value).
value.to_number(context)?.into()
};
// 9. If IsValidIntegerIndex(O, 𝔽(actualIndex)) is false, throw a RangeError exception.
let IntegerOrInfinity::Integer(relative_index) = relative_index else {
return Err(JsNativeError::range()
.with_message("invalid integer index for TypedArray operation")
.into());
};
let actual_index = u64::try_from(relative_index) // should succeed if `relative_index >= 0`
.ok()
.or_else(|| len.checked_add_signed(relative_index))
// TODO: Replace with `is_valid_integer_index_u64` or equivalent.
.filter(|&rel| is_valid_integer_index(&ta.clone().upcast(), rel as f64))
.ok_or_else(|| {
JsNativeError::range()
.with_message("invalid integer index for TypedArray operation")
})?;
// 10. Let A be ? TypedArrayCreateSameType(O, « 𝔽(len) »).
let new_array = Self::from_kind_and_length(kind, len, context)?;
// 11. Let k be 0.
// 12. Repeat, while k < len,
let ta = ta.upcast();
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
let value = if k == actual_index {
// b. If k is actualIndex, let fromValue be numericValue.
numeric_value.clone()
} else {
// c. Else, let fromValue be ! Get(O, Pk).
ta.get(k, context).expect("cannot fail per the spec")
};
// d. Perform ! Set(A, Pk, fromValue, true).
new_array
.set(k, value, true, context)
.expect("cannot fail per the spec");
// e. Set k to k + 1.
}
// 13. Return A.
Ok(new_array.into())
}
/// `get %TypedArray%.prototype [ @@toStringTag ]`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn to_string_tag(
this: &JsValue,
_: &[JsValue],
_: &mut Context,
) -> JsResult<JsValue> {
// 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()
.and_then(|obj| {
obj.downcast_ref::<TypedArray>()
.map(|o| o.kind().js_name().into())
})
.unwrap_or(JsValue::Undefined))
}
/// `TypedArraySpeciesCreate ( exemplar, argumentList )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#typedarray-species-create
fn species_create(
exemplar: &JsObject,
kind: TypedArrayKind,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsObject<TypedArray>> {
// 1. Let defaultConstructor be the intrinsic object listed in column one of Table 73 for exemplar.[[TypedArrayName]].
let default_constructor = kind.standard_constructor();
// 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().data.kind().content_type() != kind.content_type() {
return Err(JsNativeError::typ()
.with_message("New typed array has different context type than exemplar")
.into());
}
// 6. Return result.
Ok(result)
}
/// [`TypedArrayCreateFromConstructor ( constructor, argumentList )`][spec].
///
/// [spec]: https://tc39.es/ecma262/#sec-typedarraycreatefromconstructor
fn create(
constructor: &JsObject,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsObject<TypedArray>> {
// 1. Let newTypedArray be ? Construct(constructor, argumentList).
let new_typed_array = constructor.construct(args, Some(constructor), context)?;
// 2. Let taRecord be ? ValidateTypedArray(newTypedArray, seq-cst).
let (new_ta, buf_len) =
TypedArray::validate(&JsValue::Object(new_typed_array), Ordering::SeqCst)?;
// 3. If the number of elements in argumentList is 1 and argumentList[0] is a Number, then
if args.len() == 1 {
if let Some(number) = args[0].as_number() {
let new_ta = new_ta.borrow();
// a. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
if new_ta.data.is_out_of_bounds(buf_len) {
return Err(JsNativeError::typ()
.with_message("new typed array outside of the bounds of its inner buffer")
.into());
}
// b. Let length be TypedArrayLength(taRecord).
// c. If length < ℝ(argumentList[0]), throw a TypeError exception.
if (new_ta.data.array_length(buf_len) as f64) < number {
return Err(JsNativeError::typ()
.with_message("new typed array length is smaller than expected")
.into());
}
}
}
// 4. Return newTypedArray.
Ok(new_ta)
}
/// [`AllocateTypedArrayBuffer ( O, length )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-allocatetypedarraybuffer
fn allocate_buffer<T: TypedArrayMarker>(
length: u64,
context: &mut Context,
) -> JsResult<TypedArray> {
// 1. Assert: O.[[ViewedArrayBuffer]] is undefined.
// 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 = T::ERASED.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
.intrinsics()
.constructors()
.array_buffer()
.constructor()
.into(),
byte_length,
None,
context,
)?;
// 10. Return O.
Ok(TypedArray::new(
// 6. Set O.[[ViewedArrayBuffer]] to data.
BufferObject::Buffer(data),
T::ERASED,
// 8. Set O.[[ByteOffset]] to 0.
0,
// 7. Set O.[[ByteLength]] to byteLength.
Some(byte_length),
// 9. Set O.[[ArrayLength]] to length.
Some(length),
))
}
/// <https://tc39.es/ecma262/#sec-initializetypedarrayfromlist>
pub(crate) fn initialize_from_list<T: TypedArrayMarker>(
proto: JsObject,
values: Vec<JsValue>,
context: &mut Context,
) -> JsResult<JsObject> {
// 1. Let len be the number of elements in values.
let len = values.len() as u64;
// 2. Perform ? AllocateTypedArrayBuffer(O, len).
let buf = Self::allocate_buffer::<T>(len, context)?;
let obj = JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, buf);
// 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).
obj.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(obj)
}
/// `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
pub(super) fn allocate<T: TypedArrayMarker>(
new_target: &JsValue,
length: u64,
context: &mut Context,
) -> JsResult<JsObject> {
// 1. Let proto be ? GetPrototypeFromConstructor(newTarget, defaultProto).
let proto = get_prototype_from_constructor(new_target, T::STANDARD_CONSTRUCTOR, 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.
// 8. Else,
// a. Perform ? AllocateTypedArrayBuffer(obj, length).
let indexed = Self::allocate_buffer::<T>(length, context)?;
// 2. Let obj be ! IntegerIndexedObjectCreate(proto).
let obj =
JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, indexed);
// 9. Return obj.
Ok(obj)
}
/// `InitializeTypedArrayFromTypedArray ( O, srcArray )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray
pub(super) fn initialize_from_typed_array<T: TypedArrayMarker>(
proto: JsObject,
src_array: &JsObject<TypedArray>,
context: &mut Context,
) -> JsResult<JsObject> {
let src_array = src_array.borrow();
let src_array = &src_array.data;
// 1. Let srcData be srcArray.[[ViewedArrayBuffer]].
let src_data = src_array.viewed_array_buffer();
let src_data = src_data.as_buffer();
// 2. Let elementType be TypedArrayElementType(O).
let element_type = T::ERASED;
// 3. Let elementSize be TypedArrayElementSize(O).
let element_size = element_type.element_size();
// 4. Let srcType be TypedArrayElementType(srcArray).
let src_type = src_array.kind();
// 5. Let srcElementSize be TypedArrayElementSize(srcArray).
let src_element_size = src_type.element_size();
// 6. Let srcByteOffset be srcArray.[[ByteOffset]].
let src_byte_offset = src_array.byte_offset();
// 7. Let srcRecord be MakeTypedArrayWithBufferWitnessRecord(srcArray, seq-cst).
// 8. If IsTypedArrayOutOfBounds(srcRecord) is true, throw a TypeError exception.
let Some(src_data) = src_data
.bytes(Ordering::SeqCst)
.filter(|buf| !src_array.is_out_of_bounds(buf.len()))
else {
return Err(JsNativeError::typ()
.with_message("Cannot initialize typed array from invalid buffer")
.into());
};
// 9. Let elementLength be TypedArrayLength(srcRecord).
let element_length = src_array.array_length(src_data.len());
// 10. Let byteLength be elementSize × elementLength.
let byte_length = element_size * element_length;
// 11. If elementType is srcType, then
let new_buffer = if element_type == src_type {
let start = src_byte_offset as usize;
let count = byte_length as usize;
// a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength).
src_data.subslice(start..start + count).clone(context)?
} else {
// 12. Else,
// a. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength).
let data_obj = ArrayBuffer::allocate(
&context
.realm()
.intrinsics()
.constructors()
.array_buffer()
.constructor()
.into(),
byte_length,
None,
context,
)?;
{
let mut data = data_obj.borrow_mut();
let mut data = SliceRefMut::Slice(
data.data
.bytes_mut()
.expect("a new buffer cannot be detached"),
);
// b. If srcArray.[[ContentType]] is not O.[[ContentType]], throw a TypeError exception.
if src_type.content_type() != element_type.content_type() {
return Err(JsNativeError::typ()
.with_message("Cannot initialize typed array from different content type")
.into());
}
let src_element_size = src_element_size as usize;
let target_element_size = element_size as usize;
// c. Let srcByteIndex be srcByteOffset.
let mut src_byte_index = src_byte_offset as usize;
// d. Let targetByteIndex be 0.
let mut target_byte_index = 0;
// e. Let count be elementLength.
let mut count = element_length;
// f. Repeat, while count > 0,
while count > 0 {
// i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, unordered).
// SAFETY: All integer indexed objects are always in-bounds and properly
// aligned to their underlying buffer.
let value = unsafe {
src_data
.subslice(src_byte_index..)
.get_value(src_type, atomic::Ordering::Relaxed)
};
let value = JsValue::from(value);
// TODO: cast between types instead of converting to `JsValue`.
let value = element_type
.get_element(&value, context)
.expect("value must be bigint or float");
// ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, unordered).
// SAFETY: The newly created buffer has at least `element_size * element_length`
// bytes available, which makes `target_byte_index` always in-bounds.
unsafe {
data.subslice_mut(target_byte_index..)
.set_value(value, atomic::Ordering::Relaxed);
}
// iii. Set srcByteIndex to srcByteIndex + srcElementSize.
src_byte_index += src_element_size;
// iv. Set targetByteIndex to targetByteIndex + elementSize.
target_byte_index += target_element_size;
// v. Set count to count - 1.
count -= 1;
}
}
data_obj
};
// 13. Set O.[[ViewedArrayBuffer]] to data.
// 14. Set O.[[ByteLength]] to byteLength.
// 15. Set O.[[ByteOffset]] to 0.
// 16. Set O.[[ArrayLength]] to elementLength.
let obj = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
proto,
TypedArray::new(
BufferObject::Buffer(new_buffer),
element_type,
0,
Some(byte_length),
Some(element_length),
),
);
// 17. Return unused.
Ok(obj)
}
/// [`InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset, length )`][spec].
///
/// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer
pub(super) fn initialize_from_array_buffer<T: TypedArrayMarker>(
proto: JsObject,
buffer: BufferObject,
byte_offset: &JsValue,
length: &JsValue,
context: &mut Context,
) -> JsResult<JsObject> {
// 1. Let elementSize be TypedArrayElementSize(O).
let element_size = T::ERASED.element_size();
// 2. Let offset be ? ToIndex(byteOffset).
let offset = byte_offset.to_index(context)?;
// 3. If offset modulo elementSize ≠ 0, throw a RangeError exception.
if offset % element_size != 0 {
return Err(JsNativeError::range()
.with_message("byte offset of typed array must be aligned")
.into());
}
// 4. Let bufferIsFixedLength be IsFixedLengthArrayBuffer(buffer).
let is_fixed_length = buffer.as_buffer().is_fixed_len();
// 5. If length is not undefined, then
let new_length = if length.is_undefined() {
None
} else {
// a. Let newLength be ? ToIndex(length).
Some(length.to_index(context)?)
};
let buffer_byte_length = {
let buffer = buffer.as_buffer();
// 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
let Some(data) = buffer.bytes(Ordering::SeqCst) else {
return Err(JsNativeError::typ()
.with_message("cannot construct typed array from detached buffer")
.into());
};
// 7. Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst).
data.len() as u64
};
let (byte_length, array_length) = if let Some(new_length) = new_length {
// 9. Else,
// b. Else,
// i. Let newByteLength be newLength × elementSize.
let new_byte_length = new_length * element_size;
// ii. If offset + newByteLength > bufferByteLength, throw a RangeError exception.
if offset + new_byte_length > buffer_byte_length {
return Err(JsNativeError::range()
.with_message(
"cannot create a typed array spanning a byte range outside of its buffer",
)
.into());
}
// c. Set O.[[ByteLength]] to newByteLength.
// d. Set O.[[ArrayLength]] to newByteLength / elementSize.
(Some(new_byte_length), Some(new_length))
} else if !is_fixed_length {
// 8. If length is undefined and bufferIsFixedLength is false, then
// a. If offset > bufferByteLength, throw a RangeError exception.
if offset > buffer_byte_length {
return Err(JsNativeError::range()
.with_message("TypedArray offset outside of buffer length")
.into());
}
// b. Set O.[[ByteLength]] to auto.
// c. Set O.[[ArrayLength]] to auto.
(None, None)
} else {
// a. If length is undefined, then
// i. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError exception.
if buffer_byte_length % element_size != 0 {
return Err(JsNativeError::range()
.with_message("cannot construct a typed array with an unaligned buffer")
.into());
}
// ii. Let newByteLength be bufferByteLength - offset.
// iii. If newByteLength < 0, throw a RangeError exception.
let Some(new_byte_length) = buffer_byte_length.checked_sub(offset) else {
return Err(JsNativeError::range()
.with_message("offset of typed array exceeds buffer size")
.into());
};
// c. Set O.[[ByteLength]] to newByteLength.
// d. Set O.[[ArrayLength]] to newByteLength / elementSize.
(Some(new_byte_length), Some(new_byte_length / element_size))
};
// 10. Set O.[[ViewedArrayBuffer]] to buffer.
// 11. Set O.[[ByteOffset]] to offset.
// 12. Return unused.
Ok(JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
proto,
TypedArray::new(buffer, T::ERASED, offset, byte_length, array_length),
))
}
/// `InitializeTypedArrayFromArrayLike ( O, arrayLike )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraylike
pub(super) fn initialize_from_array_like<T: TypedArrayMarker>(
proto: JsObject,
array_like: &JsObject,
context: &mut Context,
) -> JsResult<JsObject> {
// 1. Let len be ? LengthOfArrayLike(arrayLike).
let len = array_like.length_of_array_like(context)?;
// 2. Perform ? AllocateTypedArrayBuffer(O, len).
let buf = Self::allocate_buffer::<T>(len, context)?;
let obj = JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, buf);
// 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).
obj.set(k, k_value, true, context)?;
}
Ok(obj)
}
}
#[derive(Debug)]
enum U64OrPositiveInfinity {
U64(u64),
PositiveInfinity,
}
/// `CompareTypedArrayElements ( x, y, comparefn )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-comparetypedarrayelements
fn compare_typed_array_elements(
x: &JsValue,
y: &JsValue,
compare_fn: Option<&JsObject>,
context: &mut Context,
) -> JsResult<cmp::Ordering> {
// 1. Assert: x is a Number and y is a Number, or x is a BigInt and y is a BigInt.
// 2. If comparefn is not undefined, then
if let Some(compare_fn) = compare_fn {
// a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)).
let v = compare_fn
.call(&JsValue::undefined(), &[x.clone(), y.clone()], context)?
.to_number(context)?;
// b. If v is NaN, return +0𝔽.
if v.is_nan() {
return Ok(cmp::Ordering::Equal);
}
// c. Return v.
if v.is_sign_positive() {
return Ok(cmp::Ordering::Greater);
}
return Ok(cmp::Ordering::Less);
}
match (x, y) {
(JsValue::BigInt(x), JsValue::BigInt(y)) => {
// Note: Other steps are not relevant for BigInts.
// 6. If x < y, return -1𝔽.
// 7. If x > y, return 1𝔽.
// 10. Return +0𝔽.
Ok(x.cmp(y))
}
(JsValue::Integer(x), JsValue::Integer(y)) => {
// Note: Other steps are not relevant for integers.
// 6. If x < y, return -1𝔽.
// 7. If x > y, return 1𝔽.
// 10. Return +0𝔽.
Ok(x.cmp(y))
}
(JsValue::Rational(x), JsValue::Rational(y)) => {
// 3. If x and y are both NaN, return +0𝔽.
if x.is_nan() && y.is_nan() {
return Ok(cmp::Ordering::Equal);
}
// 4. If x is NaN, return 1𝔽.
if x.is_nan() {
return Ok(cmp::Ordering::Greater);
}
// 5. If y is NaN, return -1𝔽.
if y.is_nan() {
return Ok(cmp::Ordering::Less);
}
// 6. If x < y, return -1𝔽.
if x < y {
return Ok(cmp::Ordering::Less);
}
// 7. If x > y, return 1𝔽.
if x > y {
return Ok(cmp::Ordering::Greater);
}
// 8. If x is -0𝔽 and y is +0𝔽, return -1𝔽.
if x.is_sign_negative() && x.is_zero() && y.is_sign_positive() && y.is_zero() {
return Ok(cmp::Ordering::Less);
}
// 9. If x is +0𝔽 and y is -0𝔽, return 1𝔽.
if x.is_sign_positive() && x.is_zero() && y.is_sign_negative() && y.is_zero() {
return Ok(cmp::Ordering::Greater);
}
// 10. Return +0𝔽.
Ok(cmp::Ordering::Equal)
}
_ => unreachable!("x and y must be both Numbers or BigInts"),
}
}
/// Abstract operation `IsValidIntegerIndex ( O, index )`.
///
/// Returns `true` if the index is valid, or `false` otherwise.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isvalidintegerindex
pub(crate) fn is_valid_integer_index(obj: &JsObject, index: f64) -> bool {
let inner = obj.downcast_ref::<TypedArray>().expect(
"integer indexed exotic method should only be callable from integer indexed objects",
);
let buf = inner.viewed_array_buffer();
let buf = buf.as_buffer();
// 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false.
// 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, unordered).
// 5. NOTE: Bounds checking is not a synchronizing operation when O's backing buffer is a growable SharedArrayBuffer.
let Some(buf_len) = buf.bytes(Ordering::Relaxed).map(|s| s.len()) else {
return false;
};
inner.validate_index(index, buf_len).is_some()
}