LibJS: Replace Array.fromAsync with a native JavaScript implementation

This allows us to use the bytecode implementation of await, which
correctly suspends execution contexts and handles completion
injections.

This gains us 4 test262 tests around mutating Array.fromAsync's
iterable whilst it's suspended as well.

This is also one step towards removing spin_until, which the
non-bytecode implementation of await uses.

```
Duration:
     -5.98s

Summary:
    Diff Tests:
        +4     -4 

Diff Tests:
    [...]/Array/fromAsync/asyncitems-array-add-to-singleton.js  -> 
    [...]/Array/fromAsync/asyncitems-array-add.js               -> 
    [...]/Array/fromAsync/asyncitems-array-mutate.js            -> 
    [...]/Array/fromAsync/asyncitems-array-remove.js            -> 
```
This commit is contained in:
Luke Wilde 2025-11-06 19:20:29 +00:00 committed by Andreas Kling
parent a63b0cfaba
commit 0eceee0a05
Notes: github-actions[bot] 2025-11-30 10:56:04 +00:00
15 changed files with 942 additions and 233 deletions

View file

@ -153,6 +153,20 @@ op CreateArguments < Instruction
m_is_immutable: bool m_is_immutable: bool
endop endop
op CreateAsyncFromSyncIterator < Instruction
@nothrow
m_dst: Operand
m_iterator: Operand
m_next_method: Operand
m_done: Operand
endop
op CreateDataPropertyOrThrow < Instruction
m_object: Operand
m_property: Operand
m_value: Operand
endop
op CreateLexicalEnvironment < Instruction op CreateLexicalEnvironment < Instruction
@nothrow @nothrow
m_dst: Optional<Operand> m_dst: Optional<Operand>
@ -422,6 +436,18 @@ op InstanceOf < Instruction
m_rhs: Operand m_rhs: Operand
endop endop
op IsCallable < Instruction
@nothrow
m_dst: Operand
m_value: Operand
endop
op IsConstructor < Instruction
@nothrow
m_dst: Operand
m_value: Operand
endop
op IteratorClose < Instruction op IteratorClose < Instruction
m_iterator_object: Operand m_iterator_object: Operand
m_iterator_next: Operand m_iterator_next: Operand
@ -640,6 +666,11 @@ op NewArray < Instruction
m_elements: Operand[] m_elements: Operand[]
endop endop
op NewArrayWithLength < Instruction
m_dst: Operand
m_array_length: Operand
endop
op NewClass < Instruction op NewClass < Instruction
m_length: u32 m_length: u32
m_dst: Operand m_dst: Operand
@ -663,6 +694,11 @@ op NewObject < Instruction
m_dst: Operand m_dst: Operand
endop endop
op NewObjectWithNoPrototype < Instruction
@nothrow
m_dst: Operand
endop
op NewPrimitiveArray < Instruction op NewPrimitiveArray < Instruction
@nothrow @nothrow
m_length: u32 m_length: u32
@ -1041,6 +1077,22 @@ op ThrowIfTDZ < Instruction
m_src: Operand m_src: Operand
endop endop
op ToBoolean < Instruction
@nothrow
m_dst: Operand
m_value: Operand
endop
op ToLength < Instruction
m_dst: Operand
m_value: Operand
endop
op ToObject < Instruction
m_dst: Operand
m_value: Operand
endop
op Typeof < Instruction op Typeof < Instruction
m_dst: Operand m_dst: Operand
m_src: Operand m_src: Operand

View file

@ -1472,7 +1472,7 @@ ScopedOperand Generator::add_constant(Value value)
return append_new_constant(); return append_new_constant();
} }
CodeGenerationErrorOr<void> Generator::generate_builtin_abstract_operation(Identifier const& builtin_identifier, ReadonlySpan<CallExpression::Argument> arguments, ScopedOperand const&) CodeGenerationErrorOr<void> Generator::generate_builtin_abstract_operation(Identifier const& builtin_identifier, ReadonlySpan<CallExpression::Argument> arguments, ScopedOperand const& dst)
{ {
VERIFY(m_builtin_abstract_operations_enabled); VERIFY(m_builtin_abstract_operations_enabled);
for (auto const& argument : arguments) { for (auto const& argument : arguments) {
@ -1486,6 +1486,249 @@ CodeGenerationErrorOr<void> Generator::generate_builtin_abstract_operation(Ident
auto const& operation_name = builtin_identifier.string(); auto const& operation_name = builtin_identifier.string();
if (operation_name == "IsCallable"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"IsCallable only accepts one argument"sv,
};
}
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::IsCallable>(dst, source);
return {};
}
if (operation_name == "IsConstructor"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"IsConstructor only accepts one argument"sv,
};
}
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::IsConstructor>(dst, source);
return {};
}
if (operation_name == "ToBoolean"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"ToBoolean only accepts one argument"sv,
};
}
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::ToBoolean>(dst, source);
return {};
}
if (operation_name == "ToObject"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"ToObject only accepts one argument"sv,
};
}
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::ToObject>(dst, source);
return {};
}
if (operation_name == "ThrowTypeError"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"throw_type_error only accepts one argument"sv,
};
}
auto const* message = as_if<StringLiteral>(*arguments[0].value);
if (!message) {
return CodeGenerationError {
&builtin_identifier,
"ThrowTypeError's message must be a string literal"sv,
};
}
auto message_string = intern_string(message->value());
auto type_error_register = allocate_register();
emit<Op::NewTypeError>(type_error_register, message_string);
perform_needed_unwinds<Op::Throw>();
emit<Op::Throw>(type_error_register);
return {};
}
if (operation_name == "ThrowIfNotObject"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"ThrowIfNotObject only accepts one argument"sv,
};
}
auto source = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::ThrowIfNotObject>(source);
return {};
}
if (operation_name == "Call"sv) {
if (arguments.size() < 2) {
return CodeGenerationError {
&builtin_identifier,
"Call must have at least two arguments"sv,
};
}
auto const& callee_argument = arguments[0].value;
auto callee = TRY(callee_argument->generate_bytecode(*this)).value();
auto this_value = TRY(arguments[1].value->generate_bytecode(*this)).value();
auto arguments_to_call_with = arguments.slice(2);
Vector<ScopedOperand> argument_operands;
argument_operands.ensure_capacity(arguments_to_call_with.size());
for (auto const& argument : arguments_to_call_with) {
auto argument_value = TRY(argument.value->generate_bytecode(*this)).value();
argument_operands.unchecked_append(copy_if_needed_to_preserve_evaluation_order(argument_value));
}
auto expression_string = ([&callee_argument] -> Optional<Utf16String> {
if (auto const* identifier = as_if<Identifier>(*callee_argument))
return identifier->string().to_utf16_string();
if (auto const* member_expression = as_if<MemberExpression>(*callee_argument))
return member_expression->to_string_approximation();
return {};
})();
Optional<Bytecode::StringTableIndex> expression_string_index;
if (expression_string.has_value())
expression_string_index = intern_string(expression_string.release_value());
emit_with_extra_operand_slots<Bytecode::Op::Call>(
argument_operands.size(),
dst,
callee,
this_value,
expression_string_index,
argument_operands);
return {};
}
if (operation_name == "NewObjectWithNoPrototype"sv) {
if (!arguments.is_empty()) {
return CodeGenerationError {
&builtin_identifier,
"NewObjectWithNoPrototype does not take any arguments"sv,
};
}
emit<Op::NewObjectWithNoPrototype>(dst);
return {};
}
if (operation_name == "CreateAsyncFromSyncIterator"sv) {
if (arguments.size() != 3) {
return CodeGenerationError {
&builtin_identifier,
"CreateAsyncFromSyncIterator only accepts exactly three arguments"sv,
};
}
auto iterator = TRY(arguments[0].value->generate_bytecode(*this)).value();
auto next_method = TRY(arguments[1].value->generate_bytecode(*this)).value();
auto done = TRY(arguments[2].value->generate_bytecode(*this)).value();
emit<Op::CreateAsyncFromSyncIterator>(dst, iterator, next_method, done);
return {};
}
if (operation_name == "ToLength"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"ToLength only accepts exactly one argument"sv,
};
}
auto value = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::ToLength>(dst, value);
return {};
}
if (operation_name == "NewTypeError"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"NewTypeError only accepts one argument"sv,
};
}
auto const* message = as_if<StringLiteral>(*arguments[0].value);
if (!message) {
return CodeGenerationError {
&builtin_identifier,
"new_type_error's message must be a string literal"sv,
};
}
auto message_string = intern_string(message->value());
emit<Op::NewTypeError>(dst, message_string);
return {};
}
if (operation_name == "NewArrayWithLength"sv) {
if (arguments.size() != 1) {
return CodeGenerationError {
&builtin_identifier,
"NewArrayWithLength only accepts one argument"sv,
};
}
auto length = TRY(arguments[0].value->generate_bytecode(*this)).value();
emit<Op::NewArrayWithLength>(dst, length);
return {};
}
if (operation_name == "CreateDataPropertyOrThrow"sv) {
if (arguments.size() != 3) {
return CodeGenerationError {
&builtin_identifier,
"CreateDataPropertyOrThrow only accepts three arguments"sv,
};
}
auto object = TRY(arguments[0].value->generate_bytecode(*this)).value();
auto property = TRY(arguments[1].value->generate_bytecode(*this)).value();
auto value = TRY(arguments[2].value->generate_bytecode(*this)).value();
emit<Op::CreateDataPropertyOrThrow>(object, property, value);
return {};
}
#define __JS_ENUMERATE(snake_name, functionName, length) \
if (operation_name == #functionName##sv) { \
Vector<ScopedOperand> argument_operands; \
argument_operands.ensure_capacity(arguments.size()); \
for (auto const& argument : arguments) { \
auto argument_value = TRY(argument.value->generate_bytecode(*this)).value(); \
argument_operands.unchecked_append(copy_if_needed_to_preserve_evaluation_order(argument_value)); \
} \
emit_with_extra_operand_slots<Bytecode::Op::Call>( \
argument_operands.size(), \
dst, \
add_constant(m_vm.current_realm()->intrinsics().snake_name##_abstract_operation_function()), \
add_constant(js_undefined()), \
intern_string(builtin_identifier.string().to_utf16_string()), \
argument_operands); \
return {}; \
}
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS
#undef __JS_ENUMERATE
dbgln("Unknown builtin abstract operation: '{}'", operation_name); dbgln("Unknown builtin abstract operation: '{}'", operation_name);
return CodeGenerationError { return CodeGenerationError {
&builtin_identifier, &builtin_identifier,
@ -1504,6 +1747,18 @@ CodeGenerationErrorOr<Optional<ScopedOperand>> Generator::maybe_generate_builtin
if (!m_builtin_abstract_operations_enabled) if (!m_builtin_abstract_operations_enabled)
return OptionalNone {}; return OptionalNone {};
if (constant_name == "SYMBOL_ITERATOR"sv) {
return add_constant(vm().well_known_symbol_iterator());
}
if (constant_name == "SYMBOL_ASYNC_ITERATOR"sv) {
return add_constant(vm().well_known_symbol_async_iterator());
}
if (constant_name == "MAX_ARRAY_LIKE_INDEX"sv) {
return add_constant(Value(MAX_ARRAY_LIKE_INDEX));
}
dbgln("Unknown builtin constant: '{}'", constant_name); dbgln("Unknown builtin constant: '{}'", constant_name);
return CodeGenerationError { return CodeGenerationError {
&builtin_identifier, &builtin_identifier,

View file

@ -22,6 +22,8 @@
#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Accessor.h> #include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/AsyncFromSyncIterator.h>
#include <LibJS/Runtime/AsyncFromSyncIteratorPrototype.h>
#include <LibJS/Runtime/BigInt.h> #include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/CompletionCell.h> #include <LibJS/Runtime/CompletionCell.h>
#include <LibJS/Runtime/DeclarativeEnvironment.h> #include <LibJS/Runtime/DeclarativeEnvironment.h>
@ -450,6 +452,18 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
goto start; goto start;
} }
handle_IsCallable: {
auto& instruction = *reinterpret_cast<Op::IsCallable const*>(&bytecode[program_counter]);
set(instruction.dst(), Value(get(instruction.value()).is_function()));
DISPATCH_NEXT(IsCallable);
}
handle_IsConstructor: {
auto& instruction = *reinterpret_cast<Op::IsConstructor const*>(&bytecode[program_counter]);
set(instruction.dst(), Value(get(instruction.value()).is_constructor()));
DISPATCH_NEXT(IsConstructor);
}
#define HANDLE_INSTRUCTION(name) \ #define HANDLE_INSTRUCTION(name) \
handle_##name: \ handle_##name: \
{ \ { \
@ -491,6 +505,8 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(Catch); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(Catch);
HANDLE_INSTRUCTION(ConcatString); HANDLE_INSTRUCTION(ConcatString);
HANDLE_INSTRUCTION(CopyObjectExcludingProperties); HANDLE_INSTRUCTION(CopyObjectExcludingProperties);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(CreateAsyncFromSyncIterator);
HANDLE_INSTRUCTION(CreateDataPropertyOrThrow);
HANDLE_INSTRUCTION(CreateImmutableBinding); HANDLE_INSTRUCTION(CreateImmutableBinding);
HANDLE_INSTRUCTION(CreateMutableBinding); HANDLE_INSTRUCTION(CreateMutableBinding);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(CreateLexicalEnvironment); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(CreateLexicalEnvironment);
@ -550,9 +566,11 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
HANDLE_INSTRUCTION(Mod); HANDLE_INSTRUCTION(Mod);
HANDLE_INSTRUCTION(Mul); HANDLE_INSTRUCTION(Mul);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewArray); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewArray);
HANDLE_INSTRUCTION(NewArrayWithLength);
HANDLE_INSTRUCTION(NewClass); HANDLE_INSTRUCTION(NewClass);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewFunction); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewFunction);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewObject); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewObject);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewObjectWithNoPrototype);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewPrimitiveArray); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewPrimitiveArray);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewRegExp); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewRegExp);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewTypeError); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewTypeError);
@ -593,6 +611,9 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
HANDLE_INSTRUCTION(ThrowIfNotObject); HANDLE_INSTRUCTION(ThrowIfNotObject);
HANDLE_INSTRUCTION(ThrowIfNullish); HANDLE_INSTRUCTION(ThrowIfNullish);
HANDLE_INSTRUCTION(ThrowIfTDZ); HANDLE_INSTRUCTION(ThrowIfTDZ);
HANDLE_INSTRUCTION(ToLength);
HANDLE_INSTRUCTION(ToObject);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(ToBoolean);
HANDLE_INSTRUCTION(Typeof); HANDLE_INSTRUCTION(Typeof);
HANDLE_INSTRUCTION(TypeofBinding); HANDLE_INSTRUCTION(TypeofBinding);
HANDLE_INSTRUCTION(UnaryMinus); HANDLE_INSTRUCTION(UnaryMinus);
@ -2030,6 +2051,14 @@ void NewPrimitiveArray::execute_impl(Bytecode::Interpreter& interpreter) const
interpreter.set(dst(), array); interpreter.set(dst(), array);
} }
ThrowCompletionOr<void> NewArrayWithLength::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto length = static_cast<u64>(interpreter.get(m_array_length).as_double());
auto array = TRY(Array::create(interpreter.realm(), length));
interpreter.set(m_dst, array);
return {};
}
void AddPrivateName::execute_impl(Bytecode::Interpreter& interpreter) const void AddPrivateName::execute_impl(Bytecode::Interpreter& interpreter) const
{ {
auto const& name = interpreter.get_identifier(m_name); auto const& name = interpreter.get_identifier(m_name);
@ -2081,6 +2110,13 @@ void NewObject::execute_impl(Bytecode::Interpreter& interpreter) const
interpreter.set(dst(), Object::create(realm, realm.intrinsics().object_prototype())); interpreter.set(dst(), Object::create(realm, realm.intrinsics().object_prototype()));
} }
void NewObjectWithNoPrototype::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto& realm = *vm.current_realm();
interpreter.set(dst(), Object::create(realm, nullptr));
}
void NewRegExp::execute_impl(Bytecode::Interpreter& interpreter) const void NewRegExp::execute_impl(Bytecode::Interpreter& interpreter) const
{ {
interpreter.set(dst(), interpreter.set(dst(),
@ -3299,4 +3335,51 @@ ThrowCompletionOr<void> CreateMutableBinding::execute_impl(Bytecode::Interpreter
return environment.create_mutable_binding(interpreter.vm(), interpreter.get_identifier(m_identifier), m_can_be_deleted); return environment.create_mutable_binding(interpreter.vm(), interpreter.get_identifier(m_identifier), m_can_be_deleted);
} }
ThrowCompletionOr<void> ToObject::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.set(m_dst, TRY(interpreter.get(m_value).to_object(interpreter.vm())));
return {};
}
void ToBoolean::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.set(m_dst, Value(interpreter.get(m_value).to_boolean()));
}
ThrowCompletionOr<void> ToLength::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.set(m_dst, Value { TRY(interpreter.get(m_value).to_length(interpreter.vm())) });
return {};
}
void CreateAsyncFromSyncIterator::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto& realm = interpreter.realm();
auto& iterator = interpreter.get(m_iterator).as_object();
auto next_method = interpreter.get(m_next_method);
auto done = interpreter.get(m_done).as_bool();
auto iterator_record = realm.create<IteratorRecord>(iterator, next_method, done);
auto async_from_sync_iterator = create_async_from_sync_iterator(vm, iterator_record);
auto iterator_object = Object::create(realm, nullptr);
iterator_object->define_direct_property(vm.names.iterator, async_from_sync_iterator.iterator, default_attributes);
iterator_object->define_direct_property(vm.names.nextMethod, async_from_sync_iterator.next_method, default_attributes);
iterator_object->define_direct_property(vm.names.done, Value { async_from_sync_iterator.done }, default_attributes);
interpreter.set(m_dst, iterator_object);
}
ThrowCompletionOr<void> CreateDataPropertyOrThrow::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto& object = interpreter.get(m_object).as_object();
auto property = TRY(interpreter.get(m_property).to_property_key(vm));
auto value = interpreter.get(m_value);
TRY(object.create_data_property_or_throw(property, value));
return {};
}
} }

View file

@ -40,7 +40,7 @@ void ArrayConstructor::initialize(Realm& realm)
u8 attr = Attribute::Writable | Attribute::Configurable; u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.from, from, 1, attr); define_native_function(realm, vm.names.from, from, 1, attr);
define_native_function(realm, vm.names.fromAsync, from_async, 1, attr); define_native_javascript_backed_function(vm.names.fromAsync, realm.intrinsics().from_async_array_constructor_function(), 1, attr);
define_native_function(realm, vm.names.isArray, is_array, 1, attr); define_native_function(realm, vm.names.isArray, is_array, 1, attr);
define_native_function(realm, vm.names.of, of, 0, attr); define_native_function(realm, vm.names.of, of, 0, attr);
@ -284,236 +284,6 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
return array; return array;
} }
// 2.1.1.1 Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] ), https://tc39.es/proposal-array-from-async/#sec-array.fromAsync
JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async)
{
auto& realm = *vm.current_realm();
auto async_items = vm.argument(0);
auto mapfn = vm.argument(1);
auto this_arg = vm.argument(2);
// 1. Let C be the this value.
auto constructor = vm.this_value();
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor()));
// 3. Let fromAsyncClosure be a new Abstract Closure with no parameters that captures C, mapfn, and thisArg and performs the following steps when called:
auto from_async_closure = GC::create_function(realm.heap(), [constructor, mapfn, this_arg, &vm, &realm, async_items]() mutable -> Completion {
bool mapping;
// a. If mapfn is undefined, let mapping be false.
if (mapfn.is_undefined()) {
mapping = false;
}
// b. Else,
else {
// i. If IsCallable(mapfn) is false, throw a TypeError exception.
if (!mapfn.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, mapfn.to_string_without_side_effects());
// ii. Let mapping be true.
mapping = true;
}
// c. Let usingAsyncIterator be ? GetMethod(asyncItems, @@asyncIterator).
auto using_async_iterator = TRY(async_items.get_method(vm, vm.well_known_symbol_async_iterator()));
GC::Ptr<FunctionObject> using_sync_iterator;
// d. If usingAsyncIterator is undefined, then
if (!using_async_iterator) {
// i. Let usingSyncIterator be ? GetMethod(asyncItems, @@iterator).
using_sync_iterator = TRY(async_items.get_method(vm, vm.well_known_symbol_iterator()));
}
// e. Let iteratorRecord be undefined.
GC::Ptr<IteratorRecord> iterator_record;
// 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));
}
// 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.
auto iterator_record_impl = create_async_from_sync_iterator(vm, TRY(get_iterator_from_method(vm, async_items, *using_sync_iterator)));
iterator_record = vm.heap().allocate<IteratorRecord>(iterator_record_impl.iterator, iterator_record_impl.next_method, iterator_record_impl.done);
}
// h. If iteratorRecord is not undefined, then
if (iterator_record) {
GC::Ptr<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) {
// a. Let error be ThrowCompletion(a newly created TypeError object).
auto error = vm.throw_completion<TypeError>(ErrorType::ArrayMaxSize);
// b. Return ? AsyncIteratorClose(iteratorRecord, error).
return TRY(async_iterator_close(vm, *iterator_record, move(error)));
}
// 2. Let Pk be ! ToString(𝔽(k)).
auto property_key = PropertyKey { k };
// FIXME: There seems to be a bug here where we are not respecting array mutation. After resolving the first entry, the
// iterator should also take into account any other changes which are made to async_items (which does not seem to
// be happening).
// 3. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
auto next_result = TRY(JS::call(vm, iterator_record->next_method, iterator_record->iterator));
// 4. Set nextResult to ? Await(nextResult).
next_result = TRY(await(vm, next_result));
// 5. If nextResult is not an Object, throw a TypeError exception.
if (!next_result.is_object())
return vm.throw_completion<TypeError>(ErrorType::IterableNextBadReturn);
// 6. Let done be ? IteratorComplete(nextResult).
auto done = TRY(JS::iterator_complete(vm, next_result.as_object()));
// 7. If done is true,
if (done) {
// a. Perform ? Set(A, "length", 𝔽(k), true).
TRY(array->set(vm.names.length, Value(k), Object::ShouldThrowExceptions::Yes));
// b. Return Completion Record { [[Type]]: return, [[Value]]: A, [[Target]]: empty }.
return Completion { Completion::Type::Return, array };
}
// 8. Let nextValue be ? IteratorValue(nextResult).
auto next_value = TRY(iterator_value(vm, next_result.as_object()));
Value mapped_value;
// 9. If mapping is true, then
if (mapping) {
// a. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »).
auto mapped_value_or_error = JS::call(vm, mapfn, this_arg, next_value, Value(k));
// b. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord).
if (mapped_value_or_error.is_error()) {
TRY(async_iterator_close(vm, *iterator_record, mapped_value_or_error));
return mapped_value_or_error;
}
// c. Set mappedValue to Await(mappedValue).
mapped_value_or_error = await(vm, mapped_value_or_error.value());
// d. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord).
if (mapped_value_or_error.is_error()) {
TRY(async_iterator_close(vm, *iterator_record, mapped_value_or_error));
return mapped_value_or_error;
}
mapped_value = mapped_value_or_error.value();
}
// 10. Else, let mappedValue be nextValue.
else {
mapped_value = next_value;
}
// 11. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue).
auto define_status = array->create_data_property_or_throw(property_key, mapped_value);
// 12. If defineStatus is an abrupt completion, return ? AsyncIteratorClose(iteratorRecord, defineStatus).
if (define_status.is_error())
return TRY(iterator_close(vm, *iterator_record, define_status.release_error()));
// 13. Set k to k + 1.
}
}
// k. Else,
else {
// i. NOTE: asyncItems is neither an AsyncIterable nor an Iterable so assume it is an array-like object.
// ii. Let arrayLike be ! ToObject(asyncItems).
auto array_like = MUST(async_items.to_object(vm));
// iii. Let len be ? LengthOfArrayLike(arrayLike).
auto length = TRY(length_of_array_like(vm, array_like));
GC::Ptr<Object> array;
// iv. If IsConstructor(C) is true, then
if (constructor.is_constructor()) {
// 1. Let A be ? Construct(C, « 𝔽(len) »).
array = TRY(JS::construct(vm, constructor.as_function(), Value(length)));
}
// v. Else,
else {
// 1. Let A be ? ArrayCreate(len).
array = TRY(Array::create(realm, length));
}
// vi. Let k be 0.
// vii. Repeat, while k < len,
for (size_t k = 0; k < length; ++k) {
// 1. Let Pk be ! ToString(𝔽(k)).
auto property_key = PropertyKey { k };
// 2. Let kValue be ? Get(arrayLike, Pk).
auto k_value = TRY(array_like->get(property_key));
// 3. Set kValue to ? Await(kValue).
k_value = TRY(await(vm, k_value));
Value mapped_value;
// 4. If mapping is true, then
if (mapping) {
// a. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »).
mapped_value = TRY(JS::call(vm, mapfn, this_arg, k_value, Value(k)));
// b. Set mappedValue to ? Await(mappedValue).
mapped_value = TRY(await(vm, mapped_value));
}
// 5. Else, let mappedValue be kValue.
else {
mapped_value = k_value;
}
// 6. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
TRY(array->create_data_property_or_throw(property_key, mapped_value));
// 7. Set k to k + 1.
}
// viii. Perform ? Set(A, "length", 𝔽(len), true).
TRY(array->set(vm.names.length, Value(length), Object::ShouldThrowExceptions::Yes));
// ix. Return Completion Record { [[Type]]: return, [[Value]]: A, [[Target]]: empty }.
return Completion { Completion::Type::Return, array };
}
});
// 4. Perform AsyncFunctionStart(promiseCapability, fromAsyncClosure).
async_function_start(vm, promise_capability, *from_async_closure);
// 5. Return promiseCapability.[[Promise]].
return promise_capability->promise();
}
// 23.1.2.2 Array.isArray ( arg ), https://tc39.es/ecma262/#sec-array.isarray // 23.1.2.2 Array.isArray ( arg ), https://tc39.es/ecma262/#sec-array.isarray
JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::is_array) JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::is_array)
{ {

View file

@ -27,7 +27,6 @@ private:
virtual bool has_constructor() const override { return true; } virtual bool has_constructor() const override { return true; }
JS_DECLARE_NATIVE_FUNCTION(from); JS_DECLARE_NATIVE_FUNCTION(from);
JS_DECLARE_NATIVE_FUNCTION(from_async);
JS_DECLARE_NATIVE_FUNCTION(is_array); JS_DECLARE_NATIVE_FUNCTION(is_array);
JS_DECLARE_NATIVE_FUNCTION(of); JS_DECLARE_NATIVE_FUNCTION(of);

View file

@ -366,6 +366,7 @@ namespace JS {
P(negated) \ P(negated) \
P(NEGATIVE_INFINITY) \ P(NEGATIVE_INFINITY) \
P(next) \ P(next) \
P(nextMethod) \
P(normalize) \ P(normalize) \
P(notation) \ P(notation) \
P(notify) \ P(notify) \

View file

@ -5,6 +5,8 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <LibJS/Lexer.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/Accessor.h> #include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/AggregateErrorConstructor.h> #include <LibJS/Runtime/AggregateErrorConstructor.h>
#include <LibJS/Runtime/AggregateErrorPrototype.h> #include <LibJS/Runtime/AggregateErrorPrototype.h>
@ -77,6 +79,7 @@
#include <LibJS/Runtime/MapPrototype.h> #include <LibJS/Runtime/MapPrototype.h>
#include <LibJS/Runtime/MathObject.h> #include <LibJS/Runtime/MathObject.h>
#include <LibJS/Runtime/NativeFunction.h> #include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
#include <LibJS/Runtime/NumberConstructor.h> #include <LibJS/Runtime/NumberConstructor.h>
#include <LibJS/Runtime/NumberPrototype.h> #include <LibJS/Runtime/NumberPrototype.h>
#include <LibJS/Runtime/ObjectConstructor.h> #include <LibJS/Runtime/ObjectConstructor.h>
@ -132,6 +135,32 @@
#include <LibJS/Runtime/WeakSetPrototype.h> #include <LibJS/Runtime/WeakSetPrototype.h>
#include <LibJS/Runtime/WrapForValidIteratorPrototype.h> #include <LibJS/Runtime/WrapForValidIteratorPrototype.h>
// FIXME: Remove this asm hack when we upgrade to GCC 15.
#define INCLUDE_FILE_WITH_ASSEMBLY(name, file_path) \
asm(".global " #name "\n" #name ":\n" \
".incbin " #file_path "\n" \
".global " #name "_END\n" #name "_END:\n" \
".byte 0\n"); \
extern unsigned char const name[];
#if defined(AK_COMPILER_CLANG) || (defined(AK_COMPILER_GCC) && (__GNUC__ > 14))
static constexpr unsigned char ABSTRACT_OPERATIONS[] = {
# embed "JavaScriptImplementations/AbstractOperations.js" suffix(, )
0 // null terminator
};
#else
INCLUDE_FILE_WITH_ASSEMBLY(ABSTRACT_OPERATIONS, "LibJS/Runtime/JavaScriptImplementations/AbstractOperations.js")
#endif
#if defined(AK_COMPILER_CLANG) || (defined(AK_COMPILER_GCC) && (__GNUC__ > 14))
static constexpr unsigned char ARRAY_CONSTRUCTOR[] = {
# embed "JavaScriptImplementations/ArrayConstructor.js" suffix(, )
0 // null terminator
};
#else
INCLUDE_FILE_WITH_ASSEMBLY(ARRAY_CONSTRUCTOR, "LibJS/Runtime/JavaScriptImplementations/ArrayConstructor.js")
#endif
namespace JS { namespace JS {
GC_DEFINE_ALLOCATOR(Intrinsics); GC_DEFINE_ALLOCATOR(Intrinsics);
@ -175,6 +204,24 @@ GC::Ref<Intrinsics> Intrinsics::create(Realm& realm)
return *intrinsics; return *intrinsics;
} }
static Vector<NonnullRefPtr<FunctionDeclaration const>> parse_builtin_file(unsigned char const* script_text)
{
auto script_text_as_utf16 = Utf16String::from_utf8_without_validation({ script_text, strlen(reinterpret_cast<char const*>(script_text)) });
auto code = SourceCode::create("BuiltinFile"_string, move(script_text_as_utf16));
auto lexer = Lexer { move(code) };
auto parser = Parser { move(lexer) };
VERIFY(!parser.has_errors());
auto program = parser.parse_program(true);
Vector<NonnullRefPtr<FunctionDeclaration const>> declarations;
for (auto const& child : program->children()) {
if (auto const* function_declaration = as_if<FunctionDeclaration>(*child))
declarations.append(*function_declaration);
}
return declarations;
}
void Intrinsics::initialize_intrinsics(Realm& realm) void Intrinsics::initialize_intrinsics(Realm& realm)
{ {
auto& vm = this->vm(); auto& vm = this->vm();
@ -480,6 +527,16 @@ void Intrinsics::visit_edges(Visitor& visitor)
#undef __JS_ENUMERATE #undef __JS_ENUMERATE
visitor.visit(m_default_collator); visitor.visit(m_default_collator);
#define __JS_ENUMERATE(snake_name, functionName, length) \
visitor.visit(m_##snake_name##_abstract_operation_function);
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS
#undef __JS_ENUMERATE
#define __JS_ENUMERATE(snake_name, functionName, length) \
visitor.visit(m_##snake_name##_array_constructor_function);
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS
#undef __JS_ENUMERATE
} }
GC::Ref<Intl::Collator> Intrinsics::default_collator() GC::Ref<Intl::Collator> Intrinsics::default_collator()
@ -490,6 +547,38 @@ GC::Ref<Intl::Collator> Intrinsics::default_collator()
return *m_default_collator; return *m_default_collator;
} }
#define __JS_ENUMERATE(snake_name, functionName, length) \
GC::Ref<NativeJavaScriptBackedFunction> Intrinsics::snake_name##_abstract_operation_function() \
{ \
if (!m_##snake_name##_abstract_operation_function) { \
static auto abstract_operations_function_declarations = parse_builtin_file(ABSTRACT_OPERATIONS); \
auto snake_name##_function_declaration = abstract_operations_function_declarations.find_if([](auto const& function_declaration) { \
return function_declaration->name() == #functionName##sv; \
}); \
VERIFY(!snake_name##_function_declaration.is_end()); \
m_##snake_name##_abstract_operation_function = NativeJavaScriptBackedFunction::create(m_realm, *snake_name##_function_declaration, PropertyKey { #functionName##_utf16_fly_string, PropertyKey::StringMayBeNumber::No }, length); \
} \
return *m_##snake_name##_abstract_operation_function; \
}
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS
#undef __JS_ENUMERATE
#define __JS_ENUMERATE(snake_name, functionName, length) \
GC::Ref<NativeJavaScriptBackedFunction> Intrinsics::snake_name##_array_constructor_function() \
{ \
if (!m_##snake_name##_array_constructor_function) { \
static auto intrinsics_function_declarations = parse_builtin_file(ARRAY_CONSTRUCTOR); \
auto snake_name##_function_declaration = intrinsics_function_declarations.find_if([](auto const& function_declaration) { \
return function_declaration->name() == #functionName##sv; \
}); \
VERIFY(!snake_name##_function_declaration.is_end()); \
m_##snake_name##_array_constructor_function = NativeJavaScriptBackedFunction::create(m_realm, *snake_name##_function_declaration, PropertyKey { #functionName##_utf16_fly_string, PropertyKey::StringMayBeNumber::No }, length); \
} \
return *m_##snake_name##_array_constructor_function; \
}
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS
#undef __JS_ENUMERATE
// 10.2.4 AddRestrictedFunctionProperties ( F, realm ), https://tc39.es/ecma262/#sec-addrestrictedfunctionproperties // 10.2.4 AddRestrictedFunctionProperties ( F, realm ), https://tc39.es/ecma262/#sec-addrestrictedfunctionproperties
void add_restricted_function_properties(FunctionObject& function, Realm& realm) void add_restricted_function_properties(FunctionObject& function, Realm& realm)
{ {

View file

@ -14,6 +14,16 @@
namespace JS { namespace JS {
#define JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS \
__JS_ENUMERATE(async_iterator_close, AsyncIteratorClose, 3) \
__JS_ENUMERATE(get_method, GetMethod, 2) \
__JS_ENUMERATE(get_iterator_direct, GetIteratorDirect, 1) \
__JS_ENUMERATE(get_iterator_from_method, GetIteratorFromMethod, 2) \
__JS_ENUMERATE(iterator_complete, IteratorComplete, 1)
#define JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS \
__JS_ENUMERATE(from_async, fromAsync, 1)
class JS_API Intrinsics final : public Cell { class JS_API Intrinsics final : public Cell {
GC_CELL(Intrinsics, Cell); GC_CELL(Intrinsics, Cell);
GC_DECLARE_ALLOCATOR(Intrinsics); GC_DECLARE_ALLOCATOR(Intrinsics);
@ -127,6 +137,16 @@ public:
[[nodiscard]] GC::Ref<Intl::Collator> default_collator(); [[nodiscard]] GC::Ref<Intl::Collator> default_collator();
#define __JS_ENUMERATE(snake_name, functionName, length) \
GC::Ref<NativeJavaScriptBackedFunction> snake_name##_abstract_operation_function();
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS
#undef __JS_ENUMERATE
#define __JS_ENUMERATE(snake_name, functionName, length) \
GC::Ref<NativeJavaScriptBackedFunction> snake_name##_array_constructor_function();
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS
#undef __JS_ENUMERATE
private: private:
Intrinsics(Realm& realm) Intrinsics(Realm& realm)
: m_realm(realm) : m_realm(realm)
@ -249,6 +269,16 @@ private:
JS_ENUMERATE_ITERATOR_PROTOTYPES JS_ENUMERATE_ITERATOR_PROTOTYPES
#undef __JS_ENUMERATE #undef __JS_ENUMERATE
#define __JS_ENUMERATE(snake_name, functionName, length) \
GC::Ptr<NativeJavaScriptBackedFunction> m_##snake_name##_abstract_operation_function;
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS
#undef __JS_ENUMERATE
#define __JS_ENUMERATE(snake_name, functionName, length) \
GC::Ptr<NativeJavaScriptBackedFunction> m_##snake_name##_array_constructor_function;
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS
#undef __JS_ENUMERATE
GC::Ptr<Intl::Collator> m_default_collator; GC::Ptr<Intl::Collator> m_default_collator;
}; };

View file

@ -0,0 +1,95 @@
/**
* 7.3.10 GetMethod ( V, P ), https://tc39.es/ecma262/#sec-getmethod
*/
function GetMethod(value, property) {
// 1. Let func be ? GetV(V, P).
const function_ = value[property];
// 2. If func is either undefined or null, return undefined.
if (function_ === undefined || function_ === null) return undefined;
// 3. If IsCallable(func) is false, throw a TypeError exception.
if (!IsCallable(function_)) ThrowTypeError("Not a function");
// 4. Return func.
return function_;
}
/**
* 7.4.2 GetIteratorDirect ( obj ), https://tc39.es/ecma262/#sec-getiteratordirect
*/
function GetIteratorDirect(object) {
// 1. Let nextMethod be ? Get(obj, "next").
const nextMethod = object.next;
// 2. Let iteratorRecord be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }.
const iteratorRecord = NewObjectWithNoPrototype();
iteratorRecord.iterator = object;
iteratorRecord.nextMethod = nextMethod;
iteratorRecord.done = false;
// 3. Return iteratorRecord.
return iteratorRecord;
}
/**
* 7.4.3 GetIteratorFromMethod ( obj, method ), https://tc39.es/ecma262/#sec-getiteratorfrommethod
*/
function GetIteratorFromMethod(object, method) {
// 1. Let iterator be ? Call(method, obj).
const iterator = Call(method, object);
// 2. If iterator is not an Object, throw a TypeError exception.
ThrowIfNotObject(iterator);
// 3. Return ? GetIteratorDirect(iterator).
return GetIteratorDirect(iterator);
}
/**
* 7.4.7 IteratorComplete ( iteratorResult ) - https://tc39.es/ecma262/#sec-iteratorcomplete
*/
function IteratorComplete(iteratorResult) {
// 1. Return ToBoolean(? Get(iteratorResult, "done")).
return ToBoolean(iteratorResult.done);
}
/**
* 7.4.13 AsyncIteratorClose ( iteratorRecord, completion ) - https://tc39.es/ecma262/#sec-asynciteratorclose
*/
async function AsyncIteratorClose(iteratorRecord, completionValue, isThrowCompletion) {
// FIXME: 1. Assert: iteratorRecord.[[Iterator]] is an Object.
// 2. Let iterator be iteratorRecord.[[Iterator]].
const iterator = iteratorRecord.iterator;
let innerResult;
try {
// 3. Let innerResult be Completion(GetMethod(iterator, "return")).
innerResult = GetMethod(iterator, "return");
// 4. If innerResult is a normal completion, then
// a. Let return be innerResult.[[Value]].
// b. If return is undefined, return ? completion.
// NOTE: If isThrowCompletion is true, it will override this return in the finally block.
if (innerResult === undefined) return completionValue;
// c. Set innerResult to Completion(Call(return, iterator)).
// d. If innerResult is a normal completion, set innerResult to Completion(Await(innerResult.[[Value]])).
innerResult = await Call(innerResult, iterator);
} finally {
// 5. If completion is a throw completion, return ? completion.
if (isThrowCompletion) throw completionValue;
// 6. If innerResult is a throw completion, return ? innerResult.
// NOTE: If the try block threw, it will rethrow when leaving this finally block.
}
// 7. If innerResult.[[Value]] is not an Object, throw a TypeError exception.
ThrowIfNotObject(innerResult);
// 8. Return ? completion.
// NOTE: Because of step 5, this will not be a throw completion.
return completionValue;
}

View file

@ -0,0 +1,189 @@
/**
* 2.1.1.1 Array.fromAsync ( asyncItems [ , mapper [ , thisArg ] ] ), https://tc39.es/proposal-array-from-async/#sec-array.fromAsync
*/
async function fromAsync(asyncItems, mapper, thisArg) {
// 1. Let C be the this value.
const constructor = this;
let mapping;
// 2. If mapper is undefined, then
if (mapper === undefined) {
// a. Let mapping be false.
mapping = false;
}
// 3. Else,
else {
// a. If IsCallable(mapper) is false, throw a TypeError exception.
if (!IsCallable(mapper)) {
ThrowTypeError("mapper must be a function");
}
// b. Let mapping be true.
mapping = true;
}
// 4. Let usingAsyncIterator be ? GetMethod(asyncItems, %Symbol.asyncIterator%).
const usingAsyncIterator = GetMethod(asyncItems, SYMBOL_ASYNC_ITERATOR);
let usingSyncIterator = undefined;
// 5. If usingAsyncIterator is undefined, then
if (usingAsyncIterator === undefined) {
// a. Let usingSyncIterator be ? GetMethod(asyncItems, %Symbol.iterator%).
usingSyncIterator = GetMethod(asyncItems, SYMBOL_ITERATOR);
}
// 6. Let iteratorRecord be undefined.
let iteratorRecord = undefined;
// 7. If usingAsyncIterator is not undefined, then
if (usingAsyncIterator !== undefined) {
// a. Set iteratorRecord to ? GetIteratorFromMethod(asyncItems, usingAsyncIterator).
iteratorRecord = GetIteratorFromMethod(asyncItems, usingAsyncIterator);
}
// 8. Else if usingSyncIterator is not undefined, then
else if (usingSyncIterator !== undefined) {
// a. Set iteratorRecord to CreateAsyncFromSyncIterator(? GetIteratorFromMethod(asyncItems, usingSyncIterator)).
const iteratorFromMethod = GetIteratorFromMethod(asyncItems, usingSyncIterator);
iteratorRecord = CreateAsyncFromSyncIterator(
iteratorFromMethod.iterator,
iteratorFromMethod.nextMethod,
iteratorFromMethod.done
);
}
// 9. If iteratorRecord is not undefined, then
if (iteratorRecord !== undefined) {
let array;
// a. If IsConstructor(C) is true, then
if (IsConstructor(constructor)) {
// i. Let A be ? Construct(C).
array = new constructor();
}
// b. Else,
else {
// i. Let A be ! ArrayCreate(0).
array = [];
}
// c. Let k be 0.
// d. Repeat,
for (let k = 0; ; ++k) {
// i. If k ≥ 2**53 - 1, then
if (k >= MAX_ARRAY_LIKE_INDEX) {
// 1. Let error be ThrowCompletion(a newly created TypeError object).
const error = NewTypeError("Maximum array size exceeded");
// 2. Return ? AsyncIteratorClose(iteratorRecord, error).
return AsyncIteratorClose(iteratorRecord, error, true);
}
// ii. Let Pk be ! ToString(𝔽(k)).
// iii. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
// iv. Set nextResult to ? Await(nextResult).
const nextResult = await Call(iteratorRecord.nextMethod, iteratorRecord.iterator);
// v. If nextResult is not an Object, throw a TypeError exception.
ThrowIfNotObject(nextResult);
// vi. Let done be ? IteratorComplete(nextResult).
const done = IteratorComplete(nextResult);
// vii. If done is true, then
if (done) {
// 1. Perform ? Set(A, "length", 𝔽(k), true).
array.length = k;
// 2. Return A.
return array;
}
// viii. Let nextValue be ? IteratorValue(nextResult).
const nextValue = nextResult.value;
try {
let mappedValue;
// ix. If mapping is true, then
if (mapping) {
// 1. Let mappedValue be Completion(Call(mapper, thisArg, « nextValue, 𝔽(k) »)).
// 2. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord).
// 3. Set mappedValue to Completion(Await(mappedValue)).
// 4. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord).
mappedValue = await Call(mapper, thisArg, nextValue, k);
}
// x. Else,
else {
// 1. Let mappedValue be nextValue.
mappedValue = nextValue;
}
// xi. Let defineStatus be Completion(CreateDataPropertyOrThrow(A, Pk, mappedValue)).
// xii. IfAbruptCloseAsyncIterator(defineStatus, iteratorRecord).
CreateDataPropertyOrThrow(array, k, mappedValue);
} catch (exception) {
return AsyncIteratorClose(iteratorRecord, exception, true);
}
// xiii. Set k to k + 1.
}
}
// 10. Else,
else {
// a. NOTE: asyncItems is neither an AsyncIterable nor an Iterable so assume it is an array-like object.
// b. Let arrayLike be ! ToObject(asyncItems).
const arrayLike = ToObject(asyncItems);
// c. Let len be ? LengthOfArrayLike(arrayLike).
const length = ToLength(arrayLike.length);
let array;
// d. If IsConstructor(C) is true, then
if (IsConstructor(constructor)) {
// i. Let A be ? Construct(C, « 𝔽(len) »).
array = new constructor(length);
}
// e. Else,
else {
// i. Let A be ? ArrayCreate(len).
array = NewArrayWithLength(length);
}
// f. Let k be 0.
// g. Repeat, while k < len,
for (let k = 0; k < length; ++k) {
// i. Let Pk be ! ToString(𝔽(k)).
// ii. Let kValue be ? Get(arrayLike, Pk).
// iii. Set kValue to ? Await(kValue).
const kValue = await arrayLike[k];
let mappedValue;
// iv. If mapping is true, then
if (mapping) {
// 1. Let mappedValue be ? Call(mapper, thisArg, « kValue, 𝔽(k) »).
// 2. Set mappedValue to ? Await(mappedValue).
mappedValue = await Call(mapper, thisArg, kValue, k);
}
// v. Else,
else {
// 1. Let mappedValue be kValue.
mappedValue = kValue;
}
// vi. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
CreateDataPropertyOrThrow(array, k, mappedValue);
// vii. Set k to k + 1.
}
// h. Perform ? Set(A, "length", 𝔽(len), true).
array.length = length;
// i. Return A.
return array;
}
}

View file

@ -0,0 +1,98 @@
/**
* Returns if the given value is callable (i.e. is a function)
* @param value {any} Value to check is callable
* @returns true if callable, false otherwise
*/
declare function IsCallable(value: any): boolean;
/**
* Returns if the given value is a constructor
* @param value {any} Value to check is a constructor
* @returns true if is a constructor, false otherwise
*/
declare function IsConstructor(value: any): boolean;
/**
* Converts the given value to its object form if it's not already an object.
* @param value {any} Value to convert to an object
* @returns {@link value} in its object form
* @throws {TypeError} If {@link value} is null or undefined
*/
declare function ToObject(value: any): object;
/**
* Converts the given value to a boolean.
* @param value {any} Value to convert to a boolean
* @returns {@link value} Value converted to a boolean
*/
declare function ToBoolean(value: any): boolean;
/**
* Converts the given value to a length.
* @param value {any} Value to convert to a length
* @returns {@link value} Value converted to a length
*/
declare function ToLength(value: any): number;
/**
* Throws a {@link TypeError} with the given message.
* @param message The reason the error was thrown.
* @throws {TypeError} With the given message.
*/
declare function ThrowTypeError(message: string): never;
/**
* Throws if the given value is an object.
* @param value {any} Value to check is an object
* @throws {TypeError} If value is not an object
*/
declare function ThrowIfNotObject(value: any): void;
/**
* Creates a new object with no properties and no prototype.
*/
declare function NewObjectWithNoPrototype(): object;
/**
* Creates a new TypeError with the given message.
*/
declare function NewTypeError(message: string): TypeError;
/**
* Creates a new array with a starting length.
*/
declare function NewArrayWithLength(length: number): Array<any>;
/**
* Creates an AsyncFromSyncIterator object.
*/
declare function CreateAsyncFromSyncIterator(iterator: object, nextMethod: any, done: boolean): object;
/**
* Creates the given property on the given object with the given value.
*/
declare function CreateDataPropertyOrThrow(object: object, property: string | number, value: any): undefined;
/**
* Calls the given callee with `this` set to `thisValue` and with the passed in arguments.
* @param callee The function to call
* @param thisValue The value of `this` to use
* @param args The arguments to pass to the function
* @returns The result of calling the function
*/
declare function Call(callee: any, thisValue: any, ...args: any[]): any;
/**
* @defaultValue {@link Symbol.iterator}
*/
declare const SYMBOL_ITERATOR: symbol;
/**
* @defaultValue {@link Symbol.asyncIterator}
*/
declare const SYMBOL_ASYNC_ITERATOR: symbol;
/**
* @defaultValue 2 ** 53 - 1
*/
declare const MAX_ARRAY_LIKE_INDEX: number;

View file

@ -17,6 +17,7 @@
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/MapIteratorPrototype.h> #include <LibJS/Runtime/MapIteratorPrototype.h>
#include <LibJS/Runtime/NativeFunction.h> #include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
#include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/PropertyDescriptor.h> #include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/ProxyObject.h> #include <LibJS/Runtime/ProxyObject.h>
@ -1422,6 +1423,11 @@ void Object::define_native_function(Realm& realm, PropertyKey const& property_ke
realm.define_builtin(builtin.value(), function); realm.define_builtin(builtin.value(), function);
} }
void Object::define_native_javascript_backed_function(PropertyKey const& property_key, GC::Ref<NativeJavaScriptBackedFunction> function, i32, PropertyAttributes attributes)
{
define_direct_property(property_key, function, attributes);
}
// 20.1.2.3.1 ObjectDefineProperties ( O, Properties ), https://tc39.es/ecma262/#sec-objectdefineproperties // 20.1.2.3.1 ObjectDefineProperties ( O, Properties ), https://tc39.es/ecma262/#sec-objectdefineproperties
ThrowCompletionOr<Object*> Object::define_properties(Value properties) ThrowCompletionOr<Object*> Object::define_properties(Value properties)
{ {

View file

@ -204,6 +204,7 @@ public:
void define_native_function(Realm&, PropertyKey const&, ESCAPING Function<ThrowCompletionOr<Value>(VM&)>, i32 length, PropertyAttributes attributes, Optional<Bytecode::Builtin> builtin = {}); void define_native_function(Realm&, PropertyKey const&, ESCAPING Function<ThrowCompletionOr<Value>(VM&)>, i32 length, PropertyAttributes attributes, Optional<Bytecode::Builtin> builtin = {});
void define_native_accessor(Realm&, PropertyKey const&, ESCAPING Function<ThrowCompletionOr<Value>(VM&)> getter, ESCAPING Function<ThrowCompletionOr<Value>(VM&)> setter, PropertyAttributes attributes); void define_native_accessor(Realm&, PropertyKey const&, ESCAPING Function<ThrowCompletionOr<Value>(VM&)> getter, ESCAPING Function<ThrowCompletionOr<Value>(VM&)> setter, PropertyAttributes attributes);
void define_native_javascript_backed_function(PropertyKey const&, GC::Ref<NativeJavaScriptBackedFunction> function, i32 length, PropertyAttributes attributes);
virtual bool is_dom_node() const { return false; } virtual bool is_dom_node() const { return false; }
virtual bool is_dom_element() const { return false; } virtual bool is_dom_element() const { return false; }

View file

@ -101,4 +101,36 @@ describe("normal behavior", () => {
expect(counter).toBe(1); expect(counter).toBe(1);
}); });
asyncTest("mapper exception takes priority over iterator closure exception", async () => {
let exceptionOrder = [];
async function* iterator() {
try {
yield 1;
yield 2;
} finally {
exceptionOrder.push("iterator");
throw "iterator exception";
}
}
let exception = null;
try {
await Array.fromAsync(iterator(), value => {
if (value === 1) {
exceptionOrder.push("mapper");
throw "mapper exception";
}
return value;
});
} catch (e) {
exception = e;
}
expect(exceptionOrder).toEqual(["mapper", "iterator"]);
expect(exception).toBe("mapper exception");
});
}); });

View file

@ -135,6 +135,10 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_CXX_SIMULATE_ID MATCHES
add_cxx_compile_options(-Wno-implicit-const-int-float-conversion) add_cxx_compile_options(-Wno-implicit-const-int-float-conversion)
add_cxx_compile_options(-Wno-user-defined-literals) add_cxx_compile_options(-Wno-user-defined-literals)
add_cxx_compile_options(-Wno-unqualified-std-cast-call) add_cxx_compile_options(-Wno-unqualified-std-cast-call)
# Used for the #embed directive.
# FIXME: Remove this once #embed is no longer an extension.
add_cxx_compile_options(-Wno-c23-extensions)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# Only ignore expansion-to-defined for g++, clang's implementation doesn't complain about function-like macros # Only ignore expansion-to-defined for g++, clang's implementation doesn't complain about function-like macros
add_cxx_compile_options(-Wno-expansion-to-defined) add_cxx_compile_options(-Wno-expansion-to-defined)
@ -154,6 +158,11 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang$" AND CMAKE_CXX_SIMULATE_ID MATCHES
add_cxx_compile_options(-Wno-reserved-identifier) add_cxx_compile_options(-Wno-reserved-identifier)
add_cxx_compile_options(-Wno-user-defined-literals) add_cxx_compile_options(-Wno-user-defined-literals)
add_cxx_compile_options(-Wno-unqualified-std-cast-call) add_cxx_compile_options(-Wno-unqualified-std-cast-call)
# Used for the #embed directive.
# FIXME: Remove this once #embed is no longer an extension.
add_cxx_compile_options(-Wno-c23-extensions)
add_definitions(-D_CRT_SECURE_NO_WARNINGS) add_definitions(-D_CRT_SECURE_NO_WARNINGS)
# TODO: this seems wrong, but we use this kind of code too much # TODO: this seems wrong, but we use this kind of code too much