Repository: trynova/nova (language: rust)

Issue:
Array constuctor builtin functions



Discussion / hints:

Fix patch:
diff --git a/nova_vm/build.rs b/nova_vm/build.rs
index c36c7cc33..53492f69c 100644
--- a/nova_vm/build.rs
+++ b/nova_vm/build.rs
@@ -3,6 +3,15 @@ use std::fs::File;
 use std::io::{self, prelude::*, BufReader};
 
 fn replace_invalid_key_characters(string: &str) -> String {
+    let mut string = string.to_owned();
+
+    // If the first character is a number or a hyphen, prefix the string with an underscore.
+    if let Some(first_char) = string.chars().next() {
+        if first_char.is_numeric() || first_char == '-' {
+            string = format!("_{}", string);
+        }
+    }
+
     string.replace(['[', ']', ' ', '.', '-', '*'], "_")
 }
 
diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings
index 98b4678e1..aa33b8c52 100644
--- a/nova_vm/src/builtin_strings
+++ b/nova_vm/src/builtin_strings
@@ -1,3 +1,4 @@
+0
 -0
 [object Arguments]
 [object Array]
diff --git a/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs b/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs
index 124b97602..14464ef8e 100644
--- a/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs
+++ b/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs
@@ -204,7 +204,39 @@ pub(crate) fn iterator_step(
     Ok(Some(result))
 }
 
-/// ### [7.4.8 IteratorClose ( iteratorRecord, completion )](https://tc39.es/ecma262/#sec-iteratorclose)
+/// ### [7.4.8 IteratorStepValue ( iteratorRecord )](https://tc39.es/ecma262/#sec-iteratorstepvalue)
+/// The abstract operation IteratorStepValue takes argument iteratorRecord
+/// (an Iterator Record) and returns either a normal completion containing
+/// either an ECMAScript language value or done, or a throw completion. It
+/// requests the next value from iteratorRecord.[\[Iterator\]] by calling
+/// iteratorRecord.[\[NextMethod\]] and returns either done indicating that the
+/// iterator has reached its end or the value from the IteratorResult object if
+/// a next value is available.
+pub(crate) fn iterator_step_value(
+    _agent: &mut Agent,
+    _iterator_record: &IteratorRecord,
+) -> JsResult<Option<Value>> {
+    // 1. Let result be Completion(IteratorNext(iteratorRecord)).
+    // 2. If result is a throw completion, then
+    // a. Set iteratorRecord.[[Done]] to true.
+    // b. Return ? result.
+    // 3. Set result to ! result.
+    // 4. Let done be Completion(IteratorComplete(result)).
+    // 5. If done is a throw completion, then
+    // a. Set iteratorRecord.[[Done]] to true.
+    // b. Return ? done.
+    // 6. Set done to ! done.
+    // 7. If done is true, then
+    // a. Set iteratorRecord.[[Done]] to true.
+    // b. Return done.
+    // 8. Let value be Completion(Get(result, "value")).
+    // 9. If value is a throw completion, then
+    // a. Set iteratorRecord.[[Done]] to true.
+    // 10. Return ? value.
+    todo!()
+}
+
+/// ### [7.4.9 IteratorClose ( iteratorRecord, completion )](https://tc39.es/ecma262/#sec-iteratorclose)
 ///
 /// The abstract operation IteratorClose takes arguments iteratorRecord (an
 /// Iterator Record) and completion (a Completion Record) and returns a
@@ -230,7 +262,7 @@ pub(crate) fn iterator_close(
     todo!()
 }
 
-/// ### [7.4.9 IfAbruptCloseIterator ( value, iteratorRecord )](https://tc39.es/ecma262/#sec-ifabruptcloseiterator)
+/// ### [7.4.10 IfAbruptCloseIterator ( value, iteratorRecord )](https://tc39.es/ecma262/#sec-ifabruptcloseiterator)
 ///
 /// IfAbruptCloseIterator is a shorthand for a sequence of algorithm steps that
 /// use an Iterator Record.
@@ -245,7 +277,7 @@ pub(crate) fn if_abrupt_close_iterator(
     todo!()
 }
 
-/// ### [7.4.10 AsyncIteratorClose ( iteratorRecord, completion )](https://tc39.es/ecma262/#sec-asynciteratorclose)
+/// ### [7.4.11 AsyncIteratorClose ( iteratorRecord, completion )](https://tc39.es/ecma262/#sec-asynciteratorclose)
 ///
 /// The abstract operation AsyncIteratorClose takes arguments iteratorRecord
 /// (an Iterator Record) and completion (a Completion Record) and returns a
@@ -272,7 +304,7 @@ pub(crate) fn async_iterator_close(
     todo!()
 }
 
-/// ### [7.4.11 CreateIterResultObject ( value, done )](https://tc39.es/ecma262/#sec-createiterresultobject)
+/// ### [7.4.12 CreateIterResultObject ( value, done )](https://tc39.es/ecma262/#sec-createiterresultobject)
 ///
 /// The abstract operation CreateIterResultObject takes arguments value (an
 /// ECMAScript language value) and done (a Boolean) and returns an Object that
@@ -286,7 +318,7 @@ pub(crate) fn create_iter_result_object(_agent: &mut Agent, _value: Value, _done
     todo!()
 }
 
-/// ### [7.4.12 CreateListIteratorRecord ( list )](https://tc39.es/ecma262/#sec-createlistiteratorRecord)
+/// ### [7.4.13 CreateListIteratorRecord ( list )](https://tc39.es/ecma262/#sec-createlistiteratorRecord)
 ///
 /// The abstract operation CreateListIteratorRecord takes argument list (a List
 /// of ECMAScript language values) and returns an Iterator Record. It creates
@@ -302,7 +334,7 @@ pub(crate) fn create_list_iterator_record(_agent: &mut Agent, _list: &[Value]) -
     todo!()
 }
 
-/// ### [7.4.13 IteratorToList ( iteratorRecord )](https://tc39.es/ecma262/#sec-iteratortolist)
+/// ### [7.4.14 IteratorToList ( iteratorRecord )](https://tc39.es/ecma262/#sec-iteratortolist)
 ///
 /// The abstract operation IteratorToList takes argument iteratorRecord (an
 /// Iterator Record) and returns either a normal completion containing a List
diff --git a/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs b/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs
index 6cf0ccaad..e13fc857c 100644
--- a/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs
+++ b/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs
@@ -2,10 +2,10 @@
 
 use super::{
     testing_and_comparison::{is_callable, same_value},
-    type_conversion::to_object,
+    type_conversion::{to_length, to_object},
 };
 use crate::ecmascript::{
-    builtins::{ArgumentsList, BuiltinFunction, ECMAScriptFunction},
+    builtins::{ArgumentsList, Array, BuiltinFunction, ECMAScriptFunction},
     execution::{agent::ExceptionType, Agent, JsResult, RealmIdentifier},
     types::{
         Function, InternalMethods, IntoObject, Object, PropertyDescriptor, PropertyKey, Value,
@@ -267,6 +267,23 @@ pub(crate) fn call(
     }
 }
 
+/// ### [7.3.18 LengthOfArrayLike ( obj )](https://tc39.es/ecma262/#sec-lengthofarraylike)
+///
+/// The abstract operation LengthOfArrayLike takes argument obj (an Object) and
+/// returns either a normal completion containing a non-negative integer or a
+/// throw completion. It returns the value of the "length" property of an
+/// array-like object.
+pub(crate) fn length_of_array_like(agent: &mut Agent, obj: Object) -> JsResult<i64> {
+    // NOTE: Fast path for Array objects.
+    if let Ok(array) = Array::try_from(obj) {
+        return Ok(array.len(agent) as i64);
+    }
+
+    // 1. Return ℝ(? ToLength(? Get(obj, "length"))).
+    let property = get(agent, obj, PropertyKey::from(BUILTIN_STRING_MEMORY.length))?;
+    to_length(agent, property)
+}
+
 /// Abstract operation Call specialized for a Function.
 pub(crate) fn call_function(
     agent: &mut Agent,
diff --git a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs
index 2a2bc9fda..aa611b154 100644
--- a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs
+++ b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs
@@ -618,7 +618,7 @@ pub(crate) fn canonical_numeric_index_string(
     argument: String,
 ) -> Option<Number> {
     // 1. If argument is "-0", return -0𝔽.
-    if argument == BUILTIN_STRING_MEMORY._0 {
+    if argument == BUILTIN_STRING_MEMORY.__0 {
         return Some((-0.0).into());
     }
 
diff --git a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_constructor.rs b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_constructor.rs
index 0e45cbf93..22aa8b629 100644
--- a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_constructor.rs
+++ b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_constructor.rs
@@ -1,18 +1,40 @@
+use crate::ecmascript::abstract_operations::operations_on_iterator_objects::get_iterator_from_method;
+use crate::ecmascript::abstract_operations::operations_on_iterator_objects::if_abrupt_close_iterator;
+use crate::ecmascript::abstract_operations::operations_on_iterator_objects::iterator_close;
+use crate::ecmascript::abstract_operations::operations_on_iterator_objects::iterator_step_value;
+use crate::ecmascript::abstract_operations::operations_on_objects::call;
+use crate::ecmascript::abstract_operations::operations_on_objects::construct;
+use crate::ecmascript::abstract_operations::operations_on_objects::create_data_property_or_throw;
+use crate::ecmascript::abstract_operations::operations_on_objects::get;
+use crate::ecmascript::abstract_operations::operations_on_objects::get_method;
+use crate::ecmascript::abstract_operations::operations_on_objects::length_of_array_like;
+use crate::ecmascript::abstract_operations::operations_on_objects::set;
 use crate::ecmascript::abstract_operations::testing_and_comparison::is_array;
 
+use crate::ecmascript::abstract_operations::testing_and_comparison::is_callable;
+use crate::ecmascript::abstract_operations::testing_and_comparison::is_constructor;
+use crate::ecmascript::abstract_operations::testing_and_comparison::same_value_zero;
+use crate::ecmascript::abstract_operations::type_conversion::to_object;
 use crate::ecmascript::builders::builtin_function_builder::BuiltinFunctionBuilder;
 
+use crate::ecmascript::builtins::array_create;
+use crate::ecmascript::builtins::ordinary::get_prototype_from_constructor;
 use crate::ecmascript::builtins::ArgumentsList;
 use crate::ecmascript::builtins::Behaviour;
 use crate::ecmascript::builtins::Builtin;
 use crate::ecmascript::builtins::BuiltinGetter;
 use crate::ecmascript::builtins::BuiltinIntrinsicConstructor;
+use crate::ecmascript::execution::agent::ExceptionType;
 use crate::ecmascript::execution::Agent;
 use crate::ecmascript::execution::JsResult;
 
+use crate::ecmascript::execution::ProtoIntrinsics;
 use crate::ecmascript::execution::RealmIdentifier;
 
+use crate::ecmascript::types::Function;
 use crate::ecmascript::types::IntoObject;
+use crate::ecmascript::types::IntoValue;
+use crate::ecmascript::types::Number;
 use crate::ecmascript::types::Object;
 use crate::ecmascript::types::PropertyKey;
 use crate::ecmascript::types::String;
@@ -20,6 +42,7 @@ use crate::ecmascript::types::Value;
 use crate::ecmascript::types::BUILTIN_STRING_MEMORY;
 use crate::heap::IntrinsicConstructorIndexes;
 use crate::heap::WellKnownSymbolIndexes;
+use crate::SmallInteger;
 
 pub struct ArrayConstructor;
 
@@ -60,47 +83,297 @@ impl BuiltinGetter for ArrayGetSpecies {
     const KEY: PropertyKey = WellKnownSymbolIndexes::Species.to_property_key();
 }
 
+/// ### [23.1.1 The Array Constructor](https://tc39.es/ecma262/#sec-array-constructor)
 impl ArrayConstructor {
+    /// ### [23.1.1.1 Array ( ...values )](https://tc39.es/ecma262/#sec-array)
     fn behaviour(
-        _agent: &mut Agent,
+        agent: &mut Agent,
         _this_value: Value,
-        _arguments: ArgumentsList,
-        _new_target: Option<Object>,
+        arguments: ArgumentsList,
+        new_target: Option<Object>,
     ) -> JsResult<Value> {
         // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
+        let new_target = new_target.map_or_else(
+            || agent.running_execution_context().function.unwrap(),
+            |new_target| Function::try_from(new_target).unwrap(),
+        );
+
         // 2. Let proto be ? GetPrototypeFromConstructor(newTarget, "%Array.prototype%").
+        let proto = get_prototype_from_constructor(agent, new_target, ProtoIntrinsics::Array)?;
+
         // 3. Let numberOfArgs be the number of elements in values.
+        let number_of_args = arguments.len();
+
         // 4. If numberOfArgs = 0, then
-        // a. Return ! ArrayCreate(0, proto).
+        if number_of_args == 0 {
+            // a. Return ! ArrayCreate(0, proto).
+            return Ok(array_create(agent, 0, 0, Some(proto)).unwrap().into_value());
+        }
+
         // 5. Else if numberOfArgs = 1, then
-        // a. Let len be values[0].
-        // b. Let array be ! ArrayCreate(0, proto).
-        // c. If len is not a Number, then
-        // i. Perform ! CreateDataPropertyOrThrow(array, "0", len).
-        // ii. Let intLen be 1𝔽.
-        // d. Else,
-        // i. Let intLen be ! ToUint32(len).
-        // ii. If SameValueZero(intLen, len) is false, throw a RangeError exception.
-        // e. Perform ! Set(array, "length", intLen, true).
-        // f. Return array.
+        if number_of_args == 1 {
+            // a. Let len be values[0].
+            let len = arguments.get(0);
+
+            // b. Let array be ! ArrayCreate(0, proto).
+            let array = array_create(agent, 0, 0, Some(proto)).unwrap();
+
+            // c. If len is not a Number, then
+            let int_len = if !len.is_number() {
+                // i. Perform ! CreateDataPropertyOrThrow(array, "0", len).
+                create_data_property_or_throw(
+                    agent,
+                    array.into(),
+                    PropertyKey::from(SmallInteger::zero()),
+                    len,
+                )
+                .unwrap();
+                // ii. Let intLen be 1𝔽.
+                1
+            } else {
+                // d. Else,
+                // i. Let intLen be ! ToUint32(len).
+                let int_len = len.to_uint32(agent).unwrap();
+                // ii. If SameValueZero(intLen, len) is false, throw a RangeError exception.
+                if !same_value_zero(agent, int_len, len) {
+                    return Err(
+                        agent.throw_exception(ExceptionType::RangeError, "Invalid array length")
+                    );
+                }
+                int_len
+            };
+
+            // e. Perform ! Set(array, "length", intLen, true).
+            set(
+                agent,
+                array.into(),
+                PropertyKey::from(BUILTIN_STRING_MEMORY.length),
+                int_len.into(),
+                true,
+            )
+            .unwrap();
+
+            // f. Return array.
+            return Ok(array.into_value());
+        }
+
         // 6. Else,
         // a. Assert: numberOfArgs ≥ 2.
+        debug_assert!(number_of_args >= 2);
+
         // b. Let array be ? ArrayCreate(numberOfArgs, proto).
+        let array = array_create(agent, number_of_args, 0, Some(proto))?;
+        // NOTE: `array_create` guarantees that it is less than `u32::MAX`
+        let number_of_args = number_of_args as u32;
+
         // c. Let k be 0.
+        let mut k: u32 = 0;
+
         // d. Repeat, while k < numberOfArgs,
-        // i. Let Pk be ! ToString(𝔽(k)).
-        // ii. Let itemK be values[k].
-        // iii. Perform ! CreateDataPropertyOrThrow(array, Pk, itemK).
-        // iv. Set k to k + 1.
+        while k < number_of_args {
+            // NOTE: We slightly deviate from the exact spec wording here, see [@aapoalas comment on #180](https://github.com/trynova/nova/pull/180#discussion_r1600382492)
+            // i. Let Pk be ! ToString(𝔽(k)).
+            let pk = PropertyKey::from(SmallInteger::from(k));
+
+            // ii. Let itemK be values[k].
+            let item_k = arguments.get(k as usize);
+
+            // iii. Perform ! CreateDataPropertyOrThrow(array, Pk, itemK).
+            create_data_property_or_throw(agent, array.into(), pk, item_k).unwrap();
+
+            // iv. Set k to k + 1.
+            k += 1;
+        }
+
         // e. Assert: The mathematical value of array's "length" property is numberOfArgs.
+        debug_assert_eq!(array.len(agent), number_of_args);
+
         // f. Return array.
-        todo!()
+        Ok(array.into_value())
     }
 
-    fn from(_agent: &mut Agent, _this_value: Value, _arguments: ArgumentsList) -> JsResult<Value> {
-        todo!();
+    /// ### [23.1.2.1 Array.from ( items \[ , mapfn \[ , thisArg \] \] )](https://tc39.es/ecma262/#sec-array.from)
+    fn from(agent: &mut Agent, this_value: Value, arguments: ArgumentsList) -> JsResult<Value> {
+        let items = arguments.get(0);
+        let mapfn = arguments.get(1);
+        let this_arg = arguments.get(2);
+
+        // 1. Let C be the this value.
+        // 2. If mapfn is undefined, then
+        let mapping = if mapfn.is_undefined() {
+            // a. Let mapping be false.
+            false
+        } else {
+            // 3. Else,
+            // a. If IsCallable(mapfn) is false, throw a TypeError exception.
+            if !is_callable(mapfn) {
+                return Err(agent.throw_exception(
+                    ExceptionType::TypeError,
+                    "The map function of Array.from is not callable",
+                ));
+            }
+
+            // b. Let mapping be true.
+            true
+        };
+
+        // 4. Let usingIterator be ? GetMethod(items, @@iterator).
+        let using_iterator = get_method(agent, items, WellKnownSymbolIndexes::Iterator.into())?;
+
+        // 5. If usingIterator is not undefined, then
+        if let Some(using_iterator) = using_iterator {
+            // a. If IsConstructor(C) is true, then
+            let a = if is_constructor(agent, this_value) {
+                let constructor = Function::try_from(this_value).unwrap();
+
+                // i. Let A be ? Construct(C).
+                construct(agent, constructor, None, None)?
+            } else {
+                // b. Else,
+                // i. Let A be ! ArrayCreate(0).
+                array_create(agent, 0, 0, None).unwrap().into_object()
+            };
+
+            // c. Let iteratorRecord be ? GetIteratorFromMethod(items, usingIterator).
+            let iterator_record = get_iterator_from_method(agent, items, using_iterator)?;
+
+            // d. Let k be 0.
+            let mut k = 0;
+
+            // e. Repeat,
+            loop {
+                // NOTE: The actual max size of an array is u32::MAX
+                // i. If k ≥ 2**53 - 1, then
+                if k >= u32::MAX as usize {
+                    // 1. Let error be ThrowCompletion(a newly created TypeError object).
+                    let error = agent.throw_exception(
+                        ExceptionType::TypeError,
+                        "Maximum array size of 2**53-1 exceeded",
+                    );
+                    // 2. Return ? IteratorClose(iteratorRecord, error).
+                    return iterator_close(agent, &iterator_record, Err(error));
+                }
+
+                let sk = SmallInteger::from(k as u32);
+                // 𝔽(k)
+                let fk = Number::from(sk).into_value();
+
+                // ii. Let Pk be ! ToString(𝔽(k)).
+                let pk = PropertyKey::from(sk);
+
+                // iii. Let next be ? IteratorStepValue(iteratorRecord).
+                let Some(next) = iterator_step_value(agent, &iterator_record)? else {
+                    // iv. If next is done, then
+                    // 1. Perform ? Set(A, "length", 𝔽(k), true).
+                    set(
+                        agent,
+                        a,
+                        PropertyKey::from(BUILTIN_STRING_MEMORY.length),
+                        fk,
+                        true,
+                    )?;
+
+                    // 2. Return A.
+                    return Ok(a.into_value());
+                };
+
+                // v. If mapping is true, then
+                let mapped_value = if mapping {
+                    // 1. Let mappedValue be Completion(Call(mapfn, thisArg, « next, 𝔽(k) »)).
+                    let mapped_value =
+                        call(agent, mapfn, this_arg, Some(ArgumentsList(&[next, fk])));
+
+                    // 2. IfAbruptCloseIterator(mappedValue, iteratorRecord).
+                    let _ = if_abrupt_close_iterator(agent, mapped_value, &iterator_record);
+
+                    mapped_value.unwrap()
+                } else {
+                    // vi. Else,
+                    // 1. Let mappedValue be next.
+                    next
+                };
+
+                // vii. Let defineStatus be Completion(CreateDataPropertyOrThrow(A, Pk, mappedValue)).
+                let define_status = create_data_property_or_throw(agent, a, pk, mapped_value);
+
+                // viii. IfAbruptCloseIterator(defineStatus, iteratorRecord).
+                let _ = if_abrupt_close_iterator(
+                    agent,
+                    define_status.map(|_| Value::Undefined),
+                    &iterator_record,
+                );
+
+                // ix. Set k to k + 1.
+                k += 1;
+            }
+        }
+
+        // 6. NOTE: items is not an Iterable so assume it is an array-like object.
+        // 7. Let arrayLike be ! ToObject(items).
+        let array_like = to_object(agent, items).unwrap();
+
+        // 8. Let len be ? LengthOfArrayLike(arrayLike).
+        let len = length_of_array_like(agent, array_like)?;
+        let len_value = Value::try_from(len).unwrap();
+
+        // 9. If IsConstructor(C) is true, then
+        let a = if is_constructor(agent, this_value) {
+            let constructor = Function::try_from(this_value).unwrap();
+
+            // a. Let A be ? Construct(C, « 𝔽(len) »).
+            construct(agent, constructor, Some(ArgumentsList(&[len_value])), None)?
+        } else {
+            // 10. Else,
+            // a. Let A be ? ArrayCreate(len).
+            array_create(agent, len as usize, len as usize, None)?.into_object()
+        };
+
+        // 11. Let k be 0.
+        let mut k = 0;
+
+        // 12. Repeat, while k < len,
+        while k < len {
+            let sk = SmallInteger::from(k as u32);
+            // 𝔽(k)
+            let fk = Number::from(sk).into_value();
+
+            // a. Let Pk be ! ToString(𝔽(k)).
+            let pk = PropertyKey::from(sk);
+
+            // b. Let kValue be ? Get(arrayLike, Pk).
+            let k_value = get(agent, array_like, pk)?;
+
+            // c. If mapping is true, then
+            let mapped_value = if mapping {
+                // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »).
+                call(agent, mapfn, this_arg, Some(ArgumentsList(&[k_value, fk])))?
+            } else {
+                // d. Else,
+                // i. Let mappedValue be kValue.
+                k_value
+            };
+
+            // e. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
+            create_data_property_or_throw(agent, a, pk, mapped_value)?;
+
+            // f. Set k to k + 1.
+            k += 1;
+        }
+
+        // 13. Perform ? Set(A, "length", 𝔽(len), true).
+        set(
+            agent,
+            a,
+            PropertyKey::from(BUILTIN_STRING_MEMORY.length),
+            Value::try_from(len).unwrap(),
+            true,
+        )?;
+
+        // 14. Return A.
+        Ok(a.into_value())
     }
 
+    /// ### [23.1.2.2 Array.isArray ( arg )](https://tc39.es/ecma262/#sec-array.isarray)
     fn is_array(
         agent: &mut Agent,
         _this_value: Value,
@@ -109,16 +382,65 @@ impl ArrayConstructor {
         is_array(agent, arguments.get(0)).map(Value::Boolean)
     }
 
-    fn of(_agent: &mut Agent, _this_value: Value, _arguments: ArgumentsList) -> JsResult<Value> {
-        todo!();
+    /// ### [23.1.2.3 Array.of ( ...items )](https://tc39.es/ecma262/#sec-array.of)
+    fn of(agent: &mut Agent, this_value: Value, arguments: ArgumentsList) -> JsResult<Value> {
+        // 1. Let len be the number of elements in items.
+        let len = arguments.len();
+
+        // 2. Let lenNumber be 𝔽(len).
+        let len_number = Value::try_from(len as i64).unwrap();
+
+        // 3. Let C be the this value.
+        // 4. If IsConstructor(C) is true, then
+        let a = if is_constructor(agent, this_value) {
+            let constructor = Function::try_from(this_value).unwrap();
+
+            // a. Let A be ? Construct(C, « lenNumber »).
+            construct(agent, constructor, Some(ArgumentsList(&[len_number])), None)?
+        } else {
+            // 5. Else,
+            // a. Let A be ? ArrayCreate(len).
+            array_create(agent, len, len, None)?.into_object()
+        };
+
+        // 6. Let k be 0.
+        let mut k = 0;
+
+        // 7. Repeat, while k < len,
+        while k < len {
+            // a. Let kValue be items[k].
+            let k_value = arguments.get(k);
+
+            // NOTE: `array_create` guarantees that `len` and by extension `k` is less than `u32::MAX`
+            // b. Let Pk be ! ToString(𝔽(k)).
+            let pk = PropertyKey::from(SmallInteger::from(k as u32));
+
+            // c. Perform ? CreateDataPropertyOrThrow(A, Pk, kValue).
+            create_data_property_or_throw(agent, a, pk, k_value)?;
+
+            // d. Set k to k + 1.
+            k += 1;
+        }
+
+        // 8. Perform ? Set(A, "length", lenNumber, true).
+        set(
+            agent,
+            a,
+            PropertyKey::from(BUILTIN_STRING_MEMORY.length),
+            len_number,
+            true,
+        )?;
+
+        // 9. Return A.
+        Ok(a.into_value())
     }
 
     fn get_species(
         _agent: &mut Agent,
-        _this_value: Value,
+        this_value: Value,
         _arguments: ArgumentsList,
     ) -> JsResult<Value> {
-        todo!();
+        Ok(this_value)
     }
 
     pub(crate) fn create_intrinsic(agent: &mut Agent, realm: RealmIdentifier) {


Test patch: