LibJS: Implement Array.prototype.copyWithin generically

This commit is contained in:
davidot 2021-06-13 18:09:34 +02:00 committed by Linus Groh
parent 417f752306
commit 6c13cc67c6
Notes: sideshowbarker 2024-07-18 12:16:17 +09:00
5 changed files with 161 additions and 0 deletions

View file

@ -65,6 +65,7 @@ void ArrayPrototype::initialize(GlobalObject& global_object)
define_native_function(vm.names.at, at, 1, attr);
define_native_function(vm.names.keys, keys, 0, attr);
define_native_function(vm.names.entries, entries, 0, attr);
define_native_function(vm.names.copyWithin, copy_within, 2, attr);
// Use define_property here instead of define_native_function so that
// Object.is(Array.prototype[Symbol.iterator], Array.prototype.values)
@ -1378,6 +1379,90 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat_map)
return new_array;
}
// 23.1.3.3 Array.prototype.copyWithin ( target, start [ , end ] ), https://tc39.es/ecma262/#sec-array.prototype.copywithin
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::copy_within)
{
auto* this_object = vm.this_value(global_object).to_object(global_object);
if (!this_object)
return {};
auto length = length_of_array_like(global_object, *this_object);
if (vm.exception())
return {};
auto relative_target = vm.argument(0).to_integer_or_infinity(global_object);
if (vm.exception())
return {};
double to;
if (relative_target < 0)
to = max(length + relative_target, 0.0);
else
to = min(relative_target, (double)length);
auto relative_start = vm.argument(1).to_integer_or_infinity(global_object);
if (vm.exception())
return {};
double from;
if (relative_start < 0)
from = max(length + relative_start, 0.0);
else
from = min(relative_start, (double)length);
auto relative_end = vm.argument(2).is_undefined() ? length : vm.argument(2).to_integer_or_infinity(global_object);
if (vm.exception())
return {};
double final;
if (relative_end < 0)
final = max(length + relative_end, 0.0);
else
final = min(relative_end, (double)length);
double count = min(final - from, length - to);
i32 direction = 1;
if (from < to && to < from + count) {
direction = -1;
from = from + count - 1;
to = to + count - 1;
}
if (count < 0) {
return this_object;
}
size_t from_i = from;
size_t to_i = to;
size_t count_i = count;
while (count_i > 0) {
auto from_present = this_object->has_property(from_i);
if (vm.exception())
return {};
if (from_present) {
auto from_value = this_object->get(from_i).value_or(js_undefined());
if (vm.exception())
return {};
this_object->put(to_i, from_value);
if (vm.exception())
return {};
} else {
this_object->delete_property(to_i);
if (vm.exception())
return {};
}
from_i += direction;
to_i += direction;
--count_i;
}
return this_object;
}
// 1.1 Array.prototype.at ( index ), https://tc39.es/proposal-relative-indexing-method/#sec-array.prototype.at
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::at)
{

View file

@ -51,6 +51,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(at);
JS_DECLARE_NATIVE_FUNCTION(keys);
JS_DECLARE_NATIVE_FUNCTION(entries);
JS_DECLARE_NATIVE_FUNCTION(copy_within);
};
}

View file

@ -82,6 +82,7 @@ namespace JS {
P(console) \
P(construct) \
P(constructor) \
P(copyWithin) \
P(cos) \
P(cosh) \
P(count) \

View file

@ -130,6 +130,35 @@ describe("ability to work with generic non-array objects", () => {
}
});
test("copyWithin", () => {
const initial_o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
{
const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
// returns value and modifies
expect(Array.prototype.copyWithin.call(o, 0, 0)).toEqual(o);
expect(o).toEqual(initial_o);
}
{
const o = {};
expect(Array.prototype.copyWithin.call(o, 1, 16, 32)).toEqual(o);
expect(o).toEqual({});
}
{
const o = { length: 100 };
expect(Array.prototype.copyWithin.call(o, 1, 16, 32)).toEqual(o);
expect(o).toEqual({ length: 100 });
}
{
const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
// returns value and modifies
expect(Array.prototype.copyWithin.call(o, 2, 0)).toEqual(o);
expect(o).toEqual({ length: 5, 0: "foo", 1: "bar", 2: "foo", 3: "bar" });
}
});
const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
test("every", () => {

View file

@ -0,0 +1,45 @@
test("length is 2", () => {
expect(Array.prototype.copyWithin).toHaveLength(2);
});
describe("normal behavior", () => {
test("Noop", () => {
var array = [1, 2];
array.copyWithin(0, 0);
expect(array).toEqual([1, 2]);
});
test("basic behavior", () => {
var array = [1, 2, 3];
var b = array.copyWithin(1, 2);
expect(b).toEqual(array);
expect(array).toEqual([1, 3, 3]);
b = array.copyWithin(2, 0);
expect(b).toEqual(array);
expect(array).toEqual([1, 3, 1]);
});
test("start > target", () => {
var array = [1, 2, 3];
var b = array.copyWithin(0, 1);
expect(b).toEqual(array);
expect(array).toEqual([2, 3, 3]);
});
test("overwriting behavior", () => {
var array = [1, 2, 3];
var b = array.copyWithin(1, 0);
expect(b).toEqual(array);
expect(array).toEqual([1, 1, 2]);
});
test("specify end", () => {
var array = [1, 2, 3];
b = array.copyWithin(2, 0, 1);
expect(b).toEqual(array);
expect(array).toEqual([1, 2, 1]);
});
});