Skip to content

Commit 6c13cc6

Browse files
davidotlinusg
authored andcommitted
LibJS: Implement Array.prototype.copyWithin generically
1 parent 417f752 commit 6c13cc6

File tree

5 files changed

+161
-0
lines changed

5 files changed

+161
-0
lines changed

Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ void ArrayPrototype::initialize(GlobalObject& global_object)
6565
define_native_function(vm.names.at, at, 1, attr);
6666
define_native_function(vm.names.keys, keys, 0, attr);
6767
define_native_function(vm.names.entries, entries, 0, attr);
68+
define_native_function(vm.names.copyWithin, copy_within, 2, attr);
6869

6970
// Use define_property here instead of define_native_function so that
7071
// Object.is(Array.prototype[Symbol.iterator], Array.prototype.values)
@@ -1378,6 +1379,90 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat_map)
13781379
return new_array;
13791380
}
13801381

1382+
// 23.1.3.3 Array.prototype.copyWithin ( target, start [ , end ] ), https://tc39.es/ecma262/#sec-array.prototype.copywithin
1383+
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::copy_within)
1384+
{
1385+
auto* this_object = vm.this_value(global_object).to_object(global_object);
1386+
if (!this_object)
1387+
return {};
1388+
auto length = length_of_array_like(global_object, *this_object);
1389+
if (vm.exception())
1390+
return {};
1391+
1392+
auto relative_target = vm.argument(0).to_integer_or_infinity(global_object);
1393+
if (vm.exception())
1394+
return {};
1395+
1396+
double to;
1397+
if (relative_target < 0)
1398+
to = max(length + relative_target, 0.0);
1399+
else
1400+
to = min(relative_target, (double)length);
1401+
1402+
auto relative_start = vm.argument(1).to_integer_or_infinity(global_object);
1403+
if (vm.exception())
1404+
return {};
1405+
1406+
double from;
1407+
if (relative_start < 0)
1408+
from = max(length + relative_start, 0.0);
1409+
else
1410+
from = min(relative_start, (double)length);
1411+
1412+
auto relative_end = vm.argument(2).is_undefined() ? length : vm.argument(2).to_integer_or_infinity(global_object);
1413+
if (vm.exception())
1414+
return {};
1415+
1416+
double final;
1417+
if (relative_end < 0)
1418+
final = max(length + relative_end, 0.0);
1419+
else
1420+
final = min(relative_end, (double)length);
1421+
1422+
double count = min(final - from, length - to);
1423+
1424+
i32 direction = 1;
1425+
1426+
if (from < to && to < from + count) {
1427+
direction = -1;
1428+
from = from + count - 1;
1429+
to = to + count - 1;
1430+
}
1431+
1432+
if (count < 0) {
1433+
return this_object;
1434+
}
1435+
1436+
size_t from_i = from;
1437+
size_t to_i = to;
1438+
size_t count_i = count;
1439+
1440+
while (count_i > 0) {
1441+
auto from_present = this_object->has_property(from_i);
1442+
if (vm.exception())
1443+
return {};
1444+
1445+
if (from_present) {
1446+
auto from_value = this_object->get(from_i).value_or(js_undefined());
1447+
if (vm.exception())
1448+
return {};
1449+
this_object->put(to_i, from_value);
1450+
if (vm.exception())
1451+
return {};
1452+
} else {
1453+
this_object->delete_property(to_i);
1454+
if (vm.exception())
1455+
return {};
1456+
}
1457+
1458+
from_i += direction;
1459+
to_i += direction;
1460+
--count_i;
1461+
}
1462+
1463+
return this_object;
1464+
}
1465+
13811466
// 1.1 Array.prototype.at ( index ), https://tc39.es/proposal-relative-indexing-method/#sec-array.prototype.at
13821467
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::at)
13831468
{

Userland/Libraries/LibJS/Runtime/ArrayPrototype.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class ArrayPrototype final : public Array {
5151
JS_DECLARE_NATIVE_FUNCTION(at);
5252
JS_DECLARE_NATIVE_FUNCTION(keys);
5353
JS_DECLARE_NATIVE_FUNCTION(entries);
54+
JS_DECLARE_NATIVE_FUNCTION(copy_within);
5455
};
5556

5657
}

Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ namespace JS {
8282
P(console) \
8383
P(construct) \
8484
P(constructor) \
85+
P(copyWithin) \
8586
P(cos) \
8687
P(cosh) \
8788
P(count) \

Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,35 @@ describe("ability to work with generic non-array objects", () => {
130130
}
131131
});
132132

133+
test("copyWithin", () => {
134+
const initial_o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
135+
{
136+
const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
137+
// returns value and modifies
138+
expect(Array.prototype.copyWithin.call(o, 0, 0)).toEqual(o);
139+
expect(o).toEqual(initial_o);
140+
}
141+
142+
{
143+
const o = {};
144+
expect(Array.prototype.copyWithin.call(o, 1, 16, 32)).toEqual(o);
145+
expect(o).toEqual({});
146+
}
147+
148+
{
149+
const o = { length: 100 };
150+
expect(Array.prototype.copyWithin.call(o, 1, 16, 32)).toEqual(o);
151+
expect(o).toEqual({ length: 100 });
152+
}
153+
154+
{
155+
const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
156+
// returns value and modifies
157+
expect(Array.prototype.copyWithin.call(o, 2, 0)).toEqual(o);
158+
expect(o).toEqual({ length: 5, 0: "foo", 1: "bar", 2: "foo", 3: "bar" });
159+
}
160+
});
161+
133162
const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
134163

135164
test("every", () => {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
test("length is 2", () => {
2+
expect(Array.prototype.copyWithin).toHaveLength(2);
3+
});
4+
5+
describe("normal behavior", () => {
6+
test("Noop", () => {
7+
var array = [1, 2];
8+
array.copyWithin(0, 0);
9+
expect(array).toEqual([1, 2]);
10+
});
11+
12+
test("basic behavior", () => {
13+
var array = [1, 2, 3];
14+
15+
var b = array.copyWithin(1, 2);
16+
expect(b).toEqual(array);
17+
expect(array).toEqual([1, 3, 3]);
18+
19+
b = array.copyWithin(2, 0);
20+
expect(b).toEqual(array);
21+
expect(array).toEqual([1, 3, 1]);
22+
});
23+
24+
test("start > target", () => {
25+
var array = [1, 2, 3];
26+
var b = array.copyWithin(0, 1);
27+
expect(b).toEqual(array);
28+
expect(array).toEqual([2, 3, 3]);
29+
});
30+
31+
test("overwriting behavior", () => {
32+
var array = [1, 2, 3];
33+
var b = array.copyWithin(1, 0);
34+
expect(b).toEqual(array);
35+
expect(array).toEqual([1, 1, 2]);
36+
});
37+
38+
test("specify end", () => {
39+
var array = [1, 2, 3];
40+
41+
b = array.copyWithin(2, 0, 1);
42+
expect(b).toEqual(array);
43+
expect(array).toEqual([1, 2, 1]);
44+
});
45+
});

0 commit comments

Comments
 (0)