diff options
author | Luke <luke.wilde@live.co.uk> | 2021-06-13 15:49:52 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-06-13 16:21:34 +0100 |
commit | d72aeb2e1a6970ed56ed28abd88ea8225027ff8c (patch) | |
tree | 61d7cd2c222dbbb53bbd9b6ba8784e8e41a5d793 | |
parent | 2e1a01a499d0fd0294922c4d77b19745ed0a6960 (diff) | |
download | serenity-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 :^)
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"); +}); |