Skip to content

Commit d381852

Browse files
committed
LibJS: Apply normative change to Array.fromAsync to fix double construct
Apply a normative fix to Array.fromAsync fixed in: tc39/proposal-array-from-async#41 This avoids a double construction when Array.fromAsync is given an array like object.
1 parent b0eea51 commit d381852

File tree

2 files changed

+43
-21
lines changed

2 files changed

+43
-21
lines changed

Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp

+21-19
Original file line numberDiff line numberDiff line change
@@ -335,39 +335,39 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async)
335335
using_sync_iterator = TRY(async_items.get_method(vm, vm.well_known_symbol_iterator()));
336336
}
337337

338-
GCPtr<Object> array;
339-
340-
// e. If IsConstructor(C) is true, then
341-
if (constructor.is_constructor()) {
342-
// i. Let A be ? Construct(C).
343-
array = TRY(JS::construct(vm, constructor.as_function()));
344-
}
345-
// f. Else,
346-
else {
347-
// i. Let A be ! ArrayCreate(0).
348-
array = MUST(Array::create(realm, 0));
349-
}
350-
351-
// g. Let iteratorRecord be undefined.
338+
// e. Let iteratorRecord be undefined.
352339
Optional<IteratorRecord> iterator_record;
353340

354-
// h. If usingAsyncIterator is not undefined, then
341+
// f. If usingAsyncIterator is not undefined, then
355342
if (using_async_iterator) {
356343
// i. Set iteratorRecord to ? GetIterator(asyncItems, async, usingAsyncIterator).
357344
// FIXME: The Array.from proposal is out of date - it should be using GetIteratorFromMethod.
358345
iterator_record = TRY(get_iterator_from_method(vm, async_items, *using_async_iterator));
359346
}
360-
// i. Else if usingSyncIterator is not undefined, then
347+
// g. Else if usingSyncIterator is not undefined, then
361348
else if (using_sync_iterator) {
362349
// i. Set iteratorRecord to ? CreateAsyncFromSyncIterator(GetIterator(asyncItems, sync, usingSyncIterator)).
363350
// FIXME: The Array.from proposal is out of date - it should be using GetIteratorFromMethod.
364351
iterator_record = create_async_from_sync_iterator(vm, TRY(get_iterator_from_method(vm, async_items, *using_sync_iterator)));
365352
}
366353

367-
// j. If iteratorRecord is not undefined, then
354+
// h. If iteratorRecord is not undefined, then
368355
if (iterator_record.has_value()) {
369-
// i. Let k be 0.
370-
// ii. Repeat,
356+
GCPtr<Object> array;
357+
358+
// i. If IsConstructor(C) is true, then
359+
if (constructor.is_constructor()) {
360+
// 1. Let A be ? Construct(C).
361+
array = TRY(JS::construct(vm, constructor.as_function()));
362+
}
363+
// ii. Else,
364+
else {
365+
// i. Let A be ! ArrayCreate(0).
366+
array = MUST(Array::create(realm, 0));
367+
}
368+
369+
// iii. Let k be 0.
370+
// iv. Repeat,
371371
for (size_t k = 0;; ++k) {
372372
// 1. If k ≥ 2^53 - 1, then
373373
if (k >= MAX_ARRAY_LIKE_INDEX) {
@@ -471,6 +471,8 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async)
471471
// iii. Let len be ? LengthOfArrayLike(arrayLike).
472472
auto length = TRY(length_of_array_like(vm, array_like));
473473

474+
GCPtr<Object> array;
475+
474476
// iv. If IsConstructor(C) is true, then
475477
if (constructor.is_constructor()) {
476478
// 1. Let A be ? Construct(C, « 𝔽(len) »).

Userland/Libraries/LibJS/Tests/builtins/Array/Array.fromAsync.js

+22-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ test("length is 1", () => {
33
});
44

55
describe("normal behavior", () => {
6-
function checkResult(promise) {
6+
function checkResult(promise, type = Array) {
77
expect(promise).toBeInstanceOf(Promise);
88
let error = null;
99
let passed = false;
1010
promise
1111
.then(value => {
12-
expect(value instanceof Array).toBeTrue();
12+
expect(value instanceof type).toBeTrue();
1313
expect(value[0]).toBe(0);
1414
expect(value[1]).toBe(2);
1515
expect(value[2]).toBe(4);
@@ -57,4 +57,24 @@ describe("normal behavior", () => {
5757
);
5858
checkResult(promise);
5959
});
60+
61+
test("does not double construct from array like object", () => {
62+
let callCount = 0;
63+
64+
class TestArray {
65+
constructor() {
66+
callCount += 1;
67+
}
68+
}
69+
70+
let promise = Array.fromAsync.call(TestArray, {
71+
length: 3,
72+
0: Promise.resolve(0),
73+
1: Promise.resolve(2),
74+
2: Promise.resolve(4),
75+
});
76+
77+
checkResult(promise, TestArray);
78+
expect(callCount).toBe(1);
79+
});
6080
});

0 commit comments

Comments
 (0)