summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke <luke.wilde@live.co.uk>2021-06-13 15:49:52 +0100
committerLinus Groh <mail@linusgroh.de>2021-06-13 16:21:34 +0100
commitd72aeb2e1a6970ed56ed28abd88ea8225027ff8c (patch)
tree61d7cd2c222dbbb53bbd9b6ba8784e8e41a5d793
parent2e1a01a499d0fd0294922c4d77b19745ed0a6960 (diff)
downloadserenity-d72aeb2e1a6970ed56ed28abd88ea8225027ff8c.zip
LibJS: Rewrite Array.prototype.slice to be spec compliant
This makes it generic in the process (which is required by jQuery) This fixes 19 test262 test cases :^)
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp80
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js12
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.slice.js15
3 files changed, 83 insertions, 24 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
index 630bf86d25..d7d264696b 100644
--- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
@@ -387,46 +387,78 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::concat)
// 23.1.3.25 Array.prototype.slice ( start, end ), https://tc39.es/ecma262/#sec-array.prototype.slice
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::slice)
{
- auto* array = Array::typed_this(vm, global_object);
- if (!array)
+ auto* this_object = vm.this_value(global_object).to_object(global_object);
+ if (!this_object)
return {};
- auto* new_array = Array::create(global_object);
- if (vm.argument_count() == 0) {
- new_array->indexed_properties().append_all(array, array->indexed_properties());
- if (vm.exception())
- return {};
- return new_array;
- }
+ auto initial_length = length_of_array_like(global_object, *this_object);
+ if (vm.exception())
+ return {};
- ssize_t array_size = static_cast<ssize_t>(array->indexed_properties().array_like_size());
- auto start_slice = vm.argument(0).to_i32(global_object);
+ auto relative_start = vm.argument(0).to_integer_or_infinity(global_object);
if (vm.exception())
return {};
- auto end_slice = array_size;
- if (start_slice > array_size)
- return new_array;
+ double actual_start;
- if (start_slice < 0)
- start_slice = end_slice + start_slice;
+ if (Value(relative_start).is_negative_infinity())
+ actual_start = 0.0;
+ else if (relative_start < 0.0)
+ actual_start = max((double)initial_length + relative_start, 0.0);
+ else
+ actual_start = min(relative_start, (double)initial_length);
- if (vm.argument_count() >= 2) {
- end_slice = vm.argument(1).to_i32(global_object);
+ double relative_end;
+
+ if (vm.argument(1).is_undefined() || vm.argument(1).is_empty()) {
+ relative_end = (double)initial_length;
+ } else {
+ relative_end = vm.argument(1).to_integer_or_infinity(global_object);
if (vm.exception())
return {};
- if (end_slice < 0)
- end_slice = array_size + end_slice;
- else if (end_slice > array_size)
- end_slice = array_size;
}
- for (ssize_t i = start_slice; i < end_slice; ++i) {
- new_array->indexed_properties().append(array->get(i));
+ double final;
+
+ if (Value(relative_end).is_negative_infinity())
+ final = 0.0;
+ else if (relative_end < 0.0)
+ final = max((double)initial_length + relative_end, 0.0);
+ else
+ final = min(relative_end, (double)initial_length);
+
+ auto count = max(final - actual_start, 0.0);
+
+ // FIXME: Use ArraySpeciesCreate.
+ auto* new_array = Array::create(global_object, (size_t)count);
+ if (vm.exception())
+ return {};
+
+ size_t index = 0;
+
+ while (actual_start < final) {
+ bool present = this_object->has_property(actual_start);
if (vm.exception())
return {};
+
+ if (present) {
+ auto value = this_object->get(actual_start).value_or(js_undefined());
+ if (vm.exception())
+ return {};
+
+ new_array->define_property(index, value);
+ if (vm.exception())
+ return {};
+ }
+
+ ++actual_start;
+ ++index;
}
+ new_array->put(vm.names.length, Value(index));
+ if (vm.exception())
+ return {};
+
return new_array;
}
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js
index 7121d6c18d..85ac3bf9e5 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype-generic-functions.js
@@ -39,6 +39,18 @@ describe("ability to work with generic non-array objects", () => {
expect(removed[1]).toBeUndefined();
});
+ test("slice", () => {
+ const o = { length: 3, 0: "hello", 2: "serenity" };
+ const slice = Array.prototype.slice.call(o, 0, 2);
+ expect(o).toHaveLength(3);
+ expect(o[0]).toBe("hello");
+ expect(o[1]).toBeUndefined();
+ expect(o[2]).toBe("serenity");
+ expect(slice).toHaveLength(2);
+ expect(slice[0]).toBe("hello");
+ expect(slice[1]).toBeUndefined();
+ });
+
test("join", () => {
expect(Array.prototype.join.call({})).toBe("");
expect(Array.prototype.join.call({ length: "foo" })).toBe("");
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.slice.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.slice.js
index c1c45be082..62f495b917 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.slice.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.slice.js
@@ -37,3 +37,18 @@ test("basic functionality", () => {
expect(array).toEqual(["hello", "friends", "serenity", 1]);
expect(slice).toEqual(["hello", "friends", "serenity", 1]);
});
+
+// FIXME: These tests are currently skipped because an invalid array length in this case is 2**32 or above.
+// The codebase currently uses size_t for lengths, which is currently the same as u32 when building for Serenity.
+// This means these lengths wrap around to 0, making the test not work correctly.
+test.skip("Invalid lengths", () => {
+ var length = Math.pow(2, 32);
+
+ var obj = {
+ length: length,
+ };
+
+ expect(() => {
+ Array.prototype.slice.call(obj, 0);
+ }).toThrowWithMessage(RangeError, "Invalid array length");
+});