Forráskód Böngészése

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 :^)
Luke 4 éve
szülő
commit
d72aeb2e1a

+ 56 - 24
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;
 }
 

+ 12 - 0
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("");

+ 15 - 0
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");
+});