use crate::{
object::JsObject,
property::{PropertyDescriptor, PropertyKey},
Context, JsResult,
};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
/// Definitions of the internal object methods for array exotic objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects
pub(crate) static ARRAY_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__define_own_property__: array_exotic_define_own_property,
..ORDINARY_INTERNAL_METHODS
};
/// Define an own property for an array exotic object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc
pub(crate) fn array_exotic_define_own_property(
obj: &JsObject,
key: PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
) -> JsResult<bool> {
// 1. Assert: IsPropertyKey(P) is true.
match key {
// 2. If P is "length", then
PropertyKey::String(ref s) if s == "length" => {
// a. Return ? ArraySetLength(A, Desc).
array_set_length(obj, desc, context)
}
// 3. Else if P is an array index, then
PropertyKey::Index(index) if index < u32::MAX => {
// a. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
let old_len_desc = super::ordinary_get_own_property(obj, &"length".into(), context)?
.expect("the property descriptor must exist");
// b. Assert: ! IsDataDescriptor(oldLenDesc) is true.
debug_assert!(old_len_desc.is_data_descriptor());
// c. Assert: oldLenDesc.[[Configurable]] is false.
debug_assert!(!old_len_desc.expect_configurable());
// d. Let oldLen be oldLenDesc.[[Value]].
// e. Assert: oldLen is a non-negative integral Number.
// f. Let index be ! ToUint32(P).
let old_len = old_len_desc
.expect_value()
.to_u32(context)
.expect("this ToUint32 call must not fail");
// g. If index ≥ oldLen and oldLenDesc.[[Writable]] is false, return false.
if index >= old_len && !old_len_desc.expect_writable() {
return Ok(false);
}
// h. Let succeeded be ! OrdinaryDefineOwnProperty(A, P, Desc).
if super::ordinary_define_own_property(obj, key, desc, context)? {
// j. If index ≥ oldLen, then
if index >= old_len {
// i. Set oldLenDesc.[[Value]] to index + 1𝔽.
let old_len_desc = PropertyDescriptor::builder()
.value(index + 1)
.maybe_writable(old_len_desc.writable())
.maybe_enumerable(old_len_desc.enumerable())
.maybe_configurable(old_len_desc.configurable());
// ii. Set succeeded to OrdinaryDefineOwnProperty(A, "length", oldLenDesc).
let succeeded = super::ordinary_define_own_property(
obj,
"length".into(),
old_len_desc.into(),
context,
)?;
// iii. Assert: succeeded is true.
debug_assert!(succeeded);
}
// k. Return true.
Ok(true)
} else {
// i. If succeeded is false, return false.
Ok(false)
}
}
// 4. Return OrdinaryDefineOwnProperty(A, P, Desc).
_ => super::ordinary_define_own_property(obj, key, desc, context),
}
}
/// Abstract operation `ArraySetLength ( A, Desc )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-arraysetlength
fn array_set_length(
obj: &JsObject,
desc: PropertyDescriptor,
context: &mut Context,
) -> JsResult<bool> {
// 1. If Desc.[[Value]] is absent, then
let new_len_val = match desc.value() {
Some(value) => value,
_ => {
// a. Return OrdinaryDefineOwnProperty(A, "length", Desc).
return super::ordinary_define_own_property(obj, "length".into(), desc, context);
}
};
// 3. Let newLen be ? ToUint32(Desc.[[Value]]).
let new_len = new_len_val.to_u32(context)?;
// 4. Let numberLen be ? ToNumber(Desc.[[Value]]).
let number_len = new_len_val.to_number(context)?;
// 5. If SameValueZero(newLen, numberLen) is false, throw a RangeError exception.
#[allow(clippy::float_cmp)]
if f64::from(new_len) != number_len {
return context.throw_range_error("bad length for array");
}
// 2. Let newLenDesc be a copy of Desc.
// 6. Set newLenDesc.[[Value]] to newLen.
let mut new_len_desc = PropertyDescriptor::builder()
.value(new_len)
.maybe_writable(desc.writable())
.maybe_enumerable(desc.enumerable())
.maybe_configurable(desc.configurable());
// 7. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
let old_len_desc = super::ordinary_get_own_property(obj, &"length".into(), context)?
.expect("the property descriptor must exist");
// 8. Assert: ! IsDataDescriptor(oldLenDesc) is true.
debug_assert!(old_len_desc.is_data_descriptor());
// 9. Assert: oldLenDesc.[[Configurable]] is false.
debug_assert!(!old_len_desc.expect_configurable());
// 10. Let oldLen be oldLenDesc.[[Value]].
let old_len = old_len_desc.expect_value();
// 11. If newLen ≥ oldLen, then
if new_len >= old_len.to_u32(context)? {
// a. Return OrdinaryDefineOwnProperty(A, "length", newLenDesc).
return super::ordinary_define_own_property(
obj,
"length".into(),
new_len_desc.build(),
context,
);
}
// 12. If oldLenDesc.[[Writable]] is false, return false.
if !old_len_desc.expect_writable() {
return Ok(false);
}
// 13. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true.
let new_writable = if new_len_desc.inner().writable().unwrap_or(true) {
true
}
// 14. Else,
else {
// a. NOTE: Setting the [[Writable]] attribute to false is deferred in case any
// elements cannot be deleted.
// c. Set newLenDesc.[[Writable]] to true.
new_len_desc = new_len_desc.writable(true);
// b. Let newWritable be false.
false
};
// 15. Let succeeded be ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
// 16. If succeeded is false, return false.
if !super::ordinary_define_own_property(
obj,
"length".into(),
new_len_desc.clone().build(),
context,
)
.expect("this OrdinaryDefineOwnProperty call must not fail")
{
return Ok(false);
}
// 17. For each own property key P of A that is an array index, whose numeric value is
// greater than or equal to newLen, in descending numeric index order, do
let ordered_keys = {
let mut keys: Vec<_> = obj
.borrow()
.properties
.index_property_keys()
.filter(|idx| new_len <= *idx && *idx < u32::MAX)
.collect();
keys.sort_unstable_by(|x, y| y.cmp(x));
keys
};
for index in ordered_keys {
// a. Let deleteSucceeded be ! A.[[Delete]](P).
// b. If deleteSucceeded is false, then
if !obj.__delete__(&index.into(), context)? {
// i. Set newLenDesc.[[Value]] to ! ToUint32(P) + 1𝔽.
new_len_desc = new_len_desc.value(index + 1);
// ii. If newWritable is false, set newLenDesc.[[Writable]] to false.
if !new_writable {
new_len_desc = new_len_desc.writable(false);
}
// iii. Perform ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
super::ordinary_define_own_property(
obj,
"length".into(),
new_len_desc.build(),
context,
)
.expect("this OrdinaryDefineOwnProperty call must not fail");
// iv. Return false.
return Ok(false);
}
}
// 18. If newWritable is false, then
if !new_writable {
// a. Set succeeded to ! OrdinaryDefineOwnProperty(A, "length",
// PropertyDescriptor { [[Writable]]: false }).
let succeeded = super::ordinary_define_own_property(
obj,
"length".into(),
PropertyDescriptor::builder().writable(false).build(),
context,
)
.expect("this OrdinaryDefineOwnProperty call must not fail");
// b. Assert: succeeded is true.
debug_assert!(succeeded);
}
// 19. Return true.
Ok(true)
}