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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue