mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-08 06:09:58 +00:00
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:
parent
a63b0cfaba
commit
0eceee0a05
Notes:
github-actions[bot]
2025-11-30 10:56:04 +00:00
Author: https://github.com/Lubrsi
Commit: 0eceee0a05
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6728
Reviewed-by: https://github.com/Hendiadyoin1
Reviewed-by: https://github.com/awesomekling
15 changed files with 942 additions and 233 deletions
|
|
@ -153,6 +153,20 @@ op CreateArguments < Instruction
|
|||
m_is_immutable: bool
|
||||
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
|
||||
@nothrow
|
||||
m_dst: Optional<Operand>
|
||||
|
|
@ -422,6 +436,18 @@ op InstanceOf < Instruction
|
|||
m_rhs: Operand
|
||||
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
|
||||
m_iterator_object: Operand
|
||||
m_iterator_next: Operand
|
||||
|
|
@ -640,6 +666,11 @@ op NewArray < Instruction
|
|||
m_elements: Operand[]
|
||||
endop
|
||||
|
||||
op NewArrayWithLength < Instruction
|
||||
m_dst: Operand
|
||||
m_array_length: Operand
|
||||
endop
|
||||
|
||||
op NewClass < Instruction
|
||||
m_length: u32
|
||||
m_dst: Operand
|
||||
|
|
@ -663,6 +694,11 @@ op NewObject < Instruction
|
|||
m_dst: Operand
|
||||
endop
|
||||
|
||||
op NewObjectWithNoPrototype < Instruction
|
||||
@nothrow
|
||||
m_dst: Operand
|
||||
endop
|
||||
|
||||
op NewPrimitiveArray < Instruction
|
||||
@nothrow
|
||||
m_length: u32
|
||||
|
|
@ -1041,6 +1077,22 @@ op ThrowIfTDZ < Instruction
|
|||
m_src: Operand
|
||||
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
|
||||
m_dst: Operand
|
||||
m_src: Operand
|
||||
|
|
|
|||
|
|
@ -1472,7 +1472,7 @@ ScopedOperand Generator::add_constant(Value value)
|
|||
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);
|
||||
for (auto const& argument : arguments) {
|
||||
|
|
@ -1486,6 +1486,249 @@ CodeGenerationErrorOr<void> Generator::generate_builtin_abstract_operation(Ident
|
|||
|
||||
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);
|
||||
return CodeGenerationError {
|
||||
&builtin_identifier,
|
||||
|
|
@ -1504,6 +1747,18 @@ CodeGenerationErrorOr<Optional<ScopedOperand>> Generator::maybe_generate_builtin
|
|||
if (!m_builtin_abstract_operations_enabled)
|
||||
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);
|
||||
return CodeGenerationError {
|
||||
&builtin_identifier,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Accessor.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/AsyncFromSyncIterator.h>
|
||||
#include <LibJS/Runtime/AsyncFromSyncIteratorPrototype.h>
|
||||
#include <LibJS/Runtime/BigInt.h>
|
||||
#include <LibJS/Runtime/CompletionCell.h>
|
||||
#include <LibJS/Runtime/DeclarativeEnvironment.h>
|
||||
|
|
@ -450,6 +452,18 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
|
|||
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) \
|
||||
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(ConcatString);
|
||||
HANDLE_INSTRUCTION(CopyObjectExcludingProperties);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(CreateAsyncFromSyncIterator);
|
||||
HANDLE_INSTRUCTION(CreateDataPropertyOrThrow);
|
||||
HANDLE_INSTRUCTION(CreateImmutableBinding);
|
||||
HANDLE_INSTRUCTION(CreateMutableBinding);
|
||||
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(Mul);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewArray);
|
||||
HANDLE_INSTRUCTION(NewArrayWithLength);
|
||||
HANDLE_INSTRUCTION(NewClass);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewFunction);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewObject);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewObjectWithNoPrototype);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewPrimitiveArray);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewRegExp);
|
||||
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(ThrowIfNullish);
|
||||
HANDLE_INSTRUCTION(ThrowIfTDZ);
|
||||
HANDLE_INSTRUCTION(ToLength);
|
||||
HANDLE_INSTRUCTION(ToObject);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(ToBoolean);
|
||||
HANDLE_INSTRUCTION(Typeof);
|
||||
HANDLE_INSTRUCTION(TypeofBinding);
|
||||
HANDLE_INSTRUCTION(UnaryMinus);
|
||||
|
|
@ -2030,6 +2051,14 @@ void NewPrimitiveArray::execute_impl(Bytecode::Interpreter& interpreter) const
|
|||
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
|
||||
{
|
||||
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()));
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ void ArrayConstructor::initialize(Realm& realm)
|
|||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
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.of, of, 0, attr);
|
||||
|
||||
|
|
@ -284,236 +284,6 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
|
|||
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
|
||||
JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::is_array)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ private:
|
|||
virtual bool has_constructor() const override { return true; }
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(from);
|
||||
JS_DECLARE_NATIVE_FUNCTION(from_async);
|
||||
JS_DECLARE_NATIVE_FUNCTION(is_array);
|
||||
JS_DECLARE_NATIVE_FUNCTION(of);
|
||||
|
||||
|
|
|
|||
|
|
@ -366,6 +366,7 @@ namespace JS {
|
|||
P(negated) \
|
||||
P(NEGATIVE_INFINITY) \
|
||||
P(next) \
|
||||
P(nextMethod) \
|
||||
P(normalize) \
|
||||
P(notation) \
|
||||
P(notify) \
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Lexer.h>
|
||||
#include <LibJS/Parser.h>
|
||||
#include <LibJS/Runtime/Accessor.h>
|
||||
#include <LibJS/Runtime/AggregateErrorConstructor.h>
|
||||
#include <LibJS/Runtime/AggregateErrorPrototype.h>
|
||||
|
|
@ -77,6 +79,7 @@
|
|||
#include <LibJS/Runtime/MapPrototype.h>
|
||||
#include <LibJS/Runtime/MathObject.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
|
||||
#include <LibJS/Runtime/NumberConstructor.h>
|
||||
#include <LibJS/Runtime/NumberPrototype.h>
|
||||
#include <LibJS/Runtime/ObjectConstructor.h>
|
||||
|
|
@ -132,6 +135,32 @@
|
|||
#include <LibJS/Runtime/WeakSetPrototype.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 {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(Intrinsics);
|
||||
|
|
@ -175,6 +204,24 @@ GC::Ref<Intrinsics> Intrinsics::create(Realm& realm)
|
|||
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)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
|
@ -480,6 +527,16 @@ void Intrinsics::visit_edges(Visitor& visitor)
|
|||
#undef __JS_ENUMERATE
|
||||
|
||||
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()
|
||||
|
|
@ -490,6 +547,38 @@ GC::Ref<Intl::Collator> Intrinsics::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
|
||||
void add_restricted_function_properties(FunctionObject& function, Realm& realm)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,6 +14,16 @@
|
|||
|
||||
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 {
|
||||
GC_CELL(Intrinsics, Cell);
|
||||
GC_DECLARE_ALLOCATOR(Intrinsics);
|
||||
|
|
@ -127,6 +137,16 @@ public:
|
|||
|
||||
[[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:
|
||||
Intrinsics(Realm& realm)
|
||||
: m_realm(realm)
|
||||
|
|
@ -249,6 +269,16 @@ private:
|
|||
JS_ENUMERATE_ITERATOR_PROTOTYPES
|
||||
#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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
98
Libraries/LibJS/Runtime/JavaScriptImplementations/Builtins.d.ts
vendored
Normal file
98
Libraries/LibJS/Runtime/JavaScriptImplementations/Builtins.d.ts
vendored
Normal 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;
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/MapIteratorPrototype.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibJS/Runtime/PropertyDescriptor.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);
|
||||
}
|
||||
|
||||
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
|
||||
ThrowCompletionOr<Object*> Object::define_properties(Value properties)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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_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_element() const { return false; }
|
||||
|
|
|
|||
|
|
@ -101,4 +101,36 @@ describe("normal behavior", () => {
|
|||
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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-user-defined-literals)
|
||||
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")
|
||||
# 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)
|
||||
|
|
@ -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-user-defined-literals)
|
||||
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)
|
||||
|
||||
# TODO: this seems wrong, but we use this kind of code too much
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue