ladybird/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp
Idan Horowitz 5f09d78b9d LibJS: Bring the Array constructor slightly closer to the specification
Specifically, we now cast to a u32 instead of an i32, as well as use
the validity check required by the specification. The current
constructor is still quite far from the specification, as we directly
set the indexed properties' length instead of going through the Array's
overriden DefineOwnProperty. (and as a result the checks imposed by the
ArraySetLength abstract operation)
2021-06-30 12:35:24 +01:00

188 lines
6 KiB
C++

/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Function.h>
#include <LibJS/Heap/Heap.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayConstructor.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/Shape.h>
namespace JS {
ArrayConstructor::ArrayConstructor(GlobalObject& global_object)
: NativeFunction(vm().names.Array.as_string(), *global_object.function_prototype())
{
}
ArrayConstructor::~ArrayConstructor()
{
}
void ArrayConstructor::initialize(GlobalObject& global_object)
{
auto& vm = this->vm();
NativeFunction::initialize(global_object);
// 23.1.2.4 Array.prototype, https://tc39.es/ecma262/#sec-array.prototype
define_property(vm.names.prototype, global_object.array_prototype(), 0);
define_property(vm.names.length, Value(1), Attribute::Configurable);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(vm.names.from, from, 1, attr);
define_native_function(vm.names.isArray, is_array, 1, attr);
define_native_function(vm.names.of, of, 0, attr);
// 23.1.2.5 get Array [ @@species ], https://tc39.es/ecma262/#sec-get-array-@@species
define_native_accessor(*vm.well_known_symbol_species(), symbol_species_getter, {}, Attribute::Configurable);
}
// 23.1.1.1 Array ( ...values ), https://tc39.es/ecma262/#sec-array
Value ArrayConstructor::call()
{
if (vm().argument_count() <= 0)
return Array::create(global_object());
if (vm().argument_count() == 1 && vm().argument(0).is_number()) {
auto length = vm().argument(0);
auto int_length = length.to_u32(global_object());
if (int_length != length.as_double()) {
vm().throw_exception<RangeError>(global_object(), ErrorType::InvalidLength, "array");
return {};
}
auto* array = Array::create(global_object());
array->indexed_properties().set_array_like_size(int_length);
return array;
}
auto* array = Array::create(global_object());
for (size_t i = 0; i < vm().argument_count(); ++i)
array->indexed_properties().append(vm().argument(i));
return array;
}
// 23.1.1.1 Array ( ...values ), https://tc39.es/ecma262/#sec-array
Value ArrayConstructor::construct(FunctionObject&)
{
return call();
}
// 23.1.2.1 Array.from ( items [ , mapfn [ , thisArg ] ] ), https://tc39.es/ecma262/#sec-array.from
JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
{
auto value = vm.argument(0);
auto object = value.to_object(global_object);
if (!object)
return {};
auto* array = Array::create(global_object);
FunctionObject* map_fn = nullptr;
if (!vm.argument(1).is_undefined()) {
auto callback = vm.argument(1);
if (!callback.is_function()) {
vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback.to_string_without_side_effects());
return {};
}
map_fn = &callback.as_function();
}
auto this_arg = vm.argument(2);
// Array.from() lets you create Arrays from:
if (auto size = object->indexed_properties().array_like_size()) {
// * array-like objects (objects with a length property and indexed elements)
MarkedValueList elements(vm.heap());
elements.ensure_capacity(size);
for (size_t i = 0; i < size; ++i) {
if (map_fn) {
auto element = object->get(i);
if (vm.exception())
return {};
auto map_fn_result = vm.call(*map_fn, this_arg, element, Value((i32)i));
if (vm.exception())
return {};
elements.append(map_fn_result);
} else {
elements.append(object->get(i));
if (vm.exception())
return {};
}
}
array->set_indexed_property_elements(move(elements));
} else {
// * iterable objects
i32 i = 0;
get_iterator_values(global_object, value, [&](Value element) {
if (vm.exception())
return IterationDecision::Break;
if (map_fn) {
auto map_fn_result = vm.call(*map_fn, this_arg, element, Value(i));
i++;
if (vm.exception())
return IterationDecision::Break;
array->indexed_properties().append(map_fn_result);
} else {
array->indexed_properties().append(element);
}
return IterationDecision::Continue;
});
if (vm.exception())
return {};
}
return array;
}
// 23.1.2.2 Array.isArray ( arg ), https://tc39.es/ecma262/#sec-array.isarray
JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::is_array)
{
auto value = vm.argument(0);
return Value(value.is_array(global_object));
}
// 23.1.2.3 Array.of ( ...items ), https://tc39.es/ecma262/#sec-array.of
JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::of)
{
auto this_value = vm.this_value(global_object);
Value array;
if (this_value.is_constructor()) {
MarkedValueList arguments(vm.heap());
arguments.empend(vm.argument_count());
array = vm.construct(this_value.as_function(), this_value.as_function(), move(arguments));
if (vm.exception())
return {};
} else {
array = Array::create(global_object);
}
auto& array_object = array.as_object();
for (size_t k = 0; k < vm.argument_count(); ++k) {
array_object.define_property(k, vm.argument(k));
if (vm.exception())
return {};
}
array_object.put(vm.names.length, Value(vm.argument_count()));
if (vm.exception())
return {};
return array;
}
// 23.1.2.5 get Array [ @@species ], https://tc39.es/ecma262/#sec-get-array-@@species
JS_DEFINE_NATIVE_GETTER(ArrayConstructor::symbol_species_getter)
{
return vm.this_value(global_object);
}
}