Boa 0.13.1

DEPRECATED. Use the boa_engine crate instead.
Documentation
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).

            // Abstract operation `ArraySetLength ( A, Desc )`
            //
            // https://tc39.es/ecma262/#sec-arraysetlength

            // 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 new_len as f64 != number_len {
                return Err(context.construct_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)?.unwrap();

            // 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,
            )
            .unwrap()
            {
                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)
                    .copied()
                    .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,
                    )
                    .unwrap();

                    // 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,
                )?;

                // b. Assert: succeeded is true.
                debug_assert!(succeeded);
            }

            // 19. Return true.
            Ok(true)
        }

        // 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)?.unwrap();

            // 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).unwrap();

            // 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,
                    )
                    .unwrap();

                    // 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),
    }
}