Przeglądaj źródła

LibJS: Avoid double construction in Array.fromAsync

This is a normative change in the array from async proposal, see:

https://github.com/tc39/proposal-array-from-async/commit/49cfde2

It fixes a double construction when Array.fromAsync is given an array
like object.
Shannon Booth 1 rok temu
rodzic
commit
9b884a9605

+ 21 - 19
Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp

@@ -335,39 +335,39 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async)
             using_sync_iterator = TRY(async_items.get_method(vm, vm.well_known_symbol_iterator()));
         }
 
-        GCPtr<Object> array;
-
-        // e. If IsConstructor(C) is true, then
-        if (constructor.is_constructor()) {
-            // i. Let A be ? Construct(C).
-            array = TRY(JS::construct(vm, constructor.as_function()));
-        }
-        // f. Else,
-        else {
-            // i. Let A be ! ArrayCreate(0).
-            array = MUST(Array::create(realm, 0));
-        }
-
-        // g. Let iteratorRecord be undefined.
+        // e. Let iteratorRecord be undefined.
         Optional<IteratorRecord> iterator_record;
 
-        // h. If usingAsyncIterator is not undefined, then
+        // f. If usingAsyncIterator is not undefined, then
         if (using_async_iterator) {
             // i. Set iteratorRecord to ? GetIterator(asyncItems, async, usingAsyncIterator).
             // FIXME: The Array.from proposal is out of date - it should be using GetIteratorFromMethod.
             iterator_record = TRY(get_iterator_from_method(vm, async_items, *using_async_iterator));
         }
-        // i. Else if usingSyncIterator is not undefined, then
+        // g. Else if usingSyncIterator is not undefined, then
         else if (using_sync_iterator) {
             // i. Set iteratorRecord to ? CreateAsyncFromSyncIterator(GetIterator(asyncItems, sync, usingSyncIterator)).
             // FIXME: The Array.from proposal is out of date - it should be using GetIteratorFromMethod.
             iterator_record = create_async_from_sync_iterator(vm, TRY(get_iterator_from_method(vm, async_items, *using_sync_iterator)));
         }
 
-        // j. If iteratorRecord is not undefined, then
+        // h. If iteratorRecord is not undefined, then
         if (iterator_record.has_value()) {
-            // i. Let k be 0.
-            // ii. Repeat,
+            GCPtr<Object> array;
+
+            // i. If IsConstructor(C) is true, then
+            if (constructor.is_constructor()) {
+                // 1. Let A be ? Construct(C).
+                array = TRY(JS::construct(vm, constructor.as_function()));
+            }
+            // ii. Else,
+            else {
+                // i. Let A be ! ArrayCreate(0).
+                array = MUST(Array::create(realm, 0));
+            }
+
+            // iii. Let k be 0.
+            // iv. Repeat,
             for (size_t k = 0;; ++k) {
                 // 1. If k ≥ 2^53 - 1, then
                 if (k >= MAX_ARRAY_LIKE_INDEX) {
@@ -471,6 +471,8 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async)
             // iii. Let len be ? LengthOfArrayLike(arrayLike).
             auto length = TRY(length_of_array_like(vm, array_like));
 
+            GCPtr<Object> array;
+
             // iv. If IsConstructor(C) is true, then
             if (constructor.is_constructor()) {
                 // 1. Let A be ? Construct(C, « 𝔽(len) »).

+ 22 - 2
Userland/Libraries/LibJS/Tests/builtins/Array/Array.fromAsync.js

@@ -3,13 +3,13 @@ test("length is 1", () => {
 });
 
 describe("normal behavior", () => {
-    function checkResult(promise) {
+    function checkResult(promise, type = Array) {
         expect(promise).toBeInstanceOf(Promise);
         let error = null;
         let passed = false;
         promise
             .then(value => {
-                expect(value instanceof Array).toBeTrue();
+                expect(value instanceof type).toBeTrue();
                 expect(value[0]).toBe(0);
                 expect(value[1]).toBe(2);
                 expect(value[2]).toBe(4);
@@ -57,4 +57,24 @@ describe("normal behavior", () => {
         );
         checkResult(promise);
     });
+
+    test("does not double construct from array like object", () => {
+        let callCount = 0;
+
+        class TestArray {
+            constructor() {
+                callCount += 1;
+            }
+        }
+
+        let promise = Array.fromAsync.call(TestArray, {
+            length: 3,
+            0: Promise.resolve(0),
+            1: Promise.resolve(2),
+            2: Promise.resolve(4),
+        });
+
+        checkResult(promise, TestArray);
+        expect(callCount).toBe(1);
+    });
 });