diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index f4387762af..8158063e93 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -1921,7 +1921,7 @@ void BindingPattern::dump(int indent) const void FunctionNode::dump(int indent, String const& class_name) const { print_indent(indent); - outln("{}{} '{}'", class_name, m_kind == FunctionKind::Generator ? "*" : "", name()); + outln("{}{}{} '{}'", class_name, m_kind == FunctionKind::Async ? " async" : "", m_kind == FunctionKind::Generator ? "*" : "", name()); if (m_contains_direct_call_to_eval) { print_indent(indent + 1); outln("\033[31;1m(direct eval)\033[0m"); diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index b8e7036385..c8f0e213f6 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -36,6 +36,7 @@ class VariableDeclaration; enum class FunctionKind { Generator, Regular, + Async, }; template diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 3b1c4218c2..607863fa85 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -38,6 +38,8 @@ set(SOURCES Runtime/ArrayIterator.cpp Runtime/ArrayIteratorPrototype.cpp Runtime/ArrayPrototype.cpp + Runtime/AsyncFunctionConstructor.cpp + Runtime/AsyncFunctionPrototype.cpp Runtime/AtomicsObject.cpp Runtime/BigInt.cpp Runtime/BigIntConstructor.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index f2a374aed4..c589c601ed 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -17,6 +17,7 @@ __JS_ENUMERATE(AggregateError, aggregate_error, AggregateErrorPrototype, AggregateErrorConstructor, void) \ __JS_ENUMERATE(Array, array, ArrayPrototype, ArrayConstructor, void) \ __JS_ENUMERATE(ArrayBuffer, array_buffer, ArrayBufferPrototype, ArrayBufferConstructor, void) \ + __JS_ENUMERATE(AsyncFunction, async_function, AsyncFunctionPrototype, AsyncFunctionConstructor, void) \ __JS_ENUMERATE(BigIntObject, bigint, BigIntPrototype, BigIntConstructor, void) \ __JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor, void) \ __JS_ENUMERATE(DataView, data_view, DataViewPrototype, DataViewConstructor, void) \ diff --git a/Userland/Libraries/LibJS/Lexer.cpp b/Userland/Libraries/LibJS/Lexer.cpp index b46f5daa83..907e4920dd 100644 --- a/Userland/Libraries/LibJS/Lexer.cpp +++ b/Userland/Libraries/LibJS/Lexer.cpp @@ -30,6 +30,7 @@ Lexer::Lexer(StringView source, StringView filename, size_t line_number, size_t , m_parsed_identifiers(adopt_ref(*new ParsedIdentifiers)) { if (s_keywords.is_empty()) { + s_keywords.set("async", TokenType::Async); s_keywords.set("await", TokenType::Await); s_keywords.set("break", TokenType::Break); s_keywords.set("case", TokenType::Case); diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index f2f275dd44..11a2a85467 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -502,6 +502,8 @@ NonnullRefPtr Parser::parse_program(bool starts_in_strict_mode) NonnullRefPtr Parser::parse_declaration() { auto rule_start = push_start(); + if (m_state.current_token.type() == TokenType::Async && next_token().type() == TokenType::Function) + return parse_function_node(); switch (m_state.current_token.type()) { case TokenType::Class: return parse_class_declaration(); @@ -572,7 +574,7 @@ NonnullRefPtr Parser::parse_statement(AllowLabelledFunction allow_lab return result.release_nonnull(); } if (match_expression()) { - if (match(TokenType::Function) || match(TokenType::Class)) + if (match(TokenType::Function) || (match(TokenType::Async) && next_token().type() == TokenType::Function) || match(TokenType::Class)) syntax_error(String::formatted("{} declaration not allowed in single-statement context", m_state.current_token.name())); if (match(TokenType::Let) && next_token().type() == TokenType::BracketOpen) syntax_error(String::formatted("let followed by [ is not allowed in single-statement context")); @@ -748,7 +750,7 @@ RefPtr Parser::try_parse_labelled_statement(AllowLabelledFunction all return {}; } - if (m_state.current_token.value() == "await"sv && m_program_type == Program::Type::Module) { + if (m_state.current_token.value() == "await"sv && (m_program_type == Program::Type::Module || m_state.in_async_function_context)) { return {}; } @@ -792,6 +794,8 @@ RefPtr Parser::try_parse_labelled_statement(AllowLabelledFunction all m_state.current_scope_pusher->add_declaration(function_declaration); if (function_declaration->kind() == FunctionKind::Generator) syntax_error("Generator functions cannot be defined in labelled statements"); + if (function_declaration->kind() == FunctionKind::Async) + syntax_error("Async functions cannot be defined in labelled statements"); labelled_statement = move(function_declaration); } else { @@ -899,6 +903,7 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ bool is_static = false; bool is_constructor = false; bool is_generator = false; + bool is_async = false; auto method_kind = ClassMethod::Kind::Method; if (match(TokenType::Semicolon)) { @@ -906,6 +911,11 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ continue; } + if (match(TokenType::Async)) { + consume(); + is_async = true; + } + if (match(TokenType::Asterisk)) { consume(); is_generator = true; @@ -913,10 +923,14 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ StringView name; if (match_property_key() || match(TokenType::PrivateIdentifier)) { - if (!is_generator && m_state.current_token.original_value() == "static"sv) { + if (!is_generator && !is_async && m_state.current_token.original_value() == "static"sv) { if (match(TokenType::Identifier)) { consume(); is_static = true; + if (match(TokenType::Async)) { + consume(); + is_async = true; + } if (match(TokenType::Asterisk)) { consume(); is_generator = true; @@ -995,12 +1009,17 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ // It is a Syntax Error if PropName of MethodDefinition is "prototype". if (is_static && name == "prototype"sv) syntax_error("Classes may not have a static property named 'prototype'"); - } else if ((match(TokenType::ParenOpen) || match(TokenType::Equals)) && (is_static || method_kind != ClassMethod::Kind::Method)) { + } else if ((match(TokenType::ParenOpen) || match(TokenType::Equals)) && (is_static || is_async || method_kind != ClassMethod::Kind::Method)) { switch (method_kind) { case ClassMethod::Kind::Method: - VERIFY(is_static); - name = "static"; - is_static = false; + if (is_async) { + name = "async"; + is_async = false; + } else { + VERIFY(is_static); + name = "static"; + is_static = false; + } break; case ClassMethod::Kind::Getter: name = "get"; @@ -1022,6 +1041,7 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ TemporaryChange continue_context_rollback(m_state.in_continue_context, false); TemporaryChange function_context_rollback(m_state.in_function_context, false); TemporaryChange generator_function_context_rollback(m_state.in_generator_function_context, false); + TemporaryChange async_function_context_rollback(m_state.in_async_function_context, false); TemporaryChange in_class_field_initializer_rollback(m_state.in_class_field_initializer, true); ScopePusher static_init_scope = ScopePusher::static_init_block_scope(*this, *static_init_block); @@ -1042,6 +1062,8 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ syntax_error("Classes may not have more than one constructor"); if (is_generator) syntax_error("Class constructor may not be a generator"); + if (is_async) + syntax_error("Class constructor may not be async"); is_constructor = true; } @@ -1057,6 +1079,8 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ parse_options |= FunctionNodeParseOptions::IsSetterFunction; if (is_generator) parse_options |= FunctionNodeParseOptions::IsGeneratorFunction; + if (is_async) + parse_options |= FunctionNodeParseOptions::IsAsyncFunction; auto function = parse_function_node(parse_options); if (is_constructor) { constructor = move(function); @@ -1065,7 +1089,7 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ } else { syntax_error("No key for class method"); } - } else if (is_generator) { + } else if (is_generator || is_async) { expected("ParenOpen"); consume(); } else if (property_key.is_null()) { @@ -1156,6 +1180,8 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression() auto& function = static_cast(*expression); if (function.kind() == FunctionKind::Generator && function.name() == "yield"sv) syntax_error("function is not allowed to be called 'yield' in this context", function.source_range().start); + if (function.kind() == FunctionKind::Async && function.name() == "await"sv) + syntax_error("function is not allowed to be called 'await' in this context", function.source_range().start); } return { move(expression) }; } @@ -1170,7 +1196,7 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression() syntax_error("'super' keyword unexpected here"); return { create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }) }; case TokenType::EscapedKeyword: - if (m_state.strict_mode || (m_state.current_token.value() != "let"sv && (m_state.in_generator_function_context || m_state.current_token.value() != "yield"sv))) + if (m_state.strict_mode || (m_state.current_token.value() != "let"sv && (m_state.in_generator_function_context || m_state.current_token.value() != "yield"sv) && (m_state.in_async_function_context || m_state.current_token.value() != "await"sv))) syntax_error("Keyword must not contain escaped characters"); [[fallthrough]]; case TokenType::Identifier: { @@ -1201,6 +1227,10 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression() return { create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }) }; case TokenType::CurlyOpen: return { parse_object_expression() }; + case TokenType::Async: + if (next_token().type() != TokenType::Function) + goto read_as_identifier; + [[fallthrough]]; case TokenType::Function: return { parse_function_node() }; case TokenType::BracketOpen: @@ -1409,10 +1439,14 @@ NonnullRefPtr Parser::parse_object_expression() consume(); property_type = ObjectProperty::Type::KeyValue; property_name = parse_property_key(); - function_kind = FunctionKind ::Generator; + function_kind = FunctionKind::Generator; } else if (match_identifier()) { auto identifier = consume(); - if (identifier.original_value() == "get"sv && match_property_key()) { + if (identifier.original_value() == "async" && match_property_key()) { + property_type = ObjectProperty::Type::KeyValue; + property_name = parse_property_key(); + function_kind = FunctionKind::Async; + } else if (identifier.original_value() == "get"sv && match_property_key()) { property_type = ObjectProperty::Type::Getter; property_name = parse_property_key(); } else if (identifier.original_value() == "set"sv && match_property_key()) { @@ -1451,6 +1485,8 @@ NonnullRefPtr Parser::parse_object_expression() parse_options |= FunctionNodeParseOptions::IsSetterFunction; if (function_kind == FunctionKind::Generator) parse_options |= FunctionNodeParseOptions::IsGeneratorFunction; + if (function_kind == FunctionKind::Async) + parse_options |= FunctionNodeParseOptions::IsAsyncFunction; auto function = parse_function_node(parse_options); properties.append(create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, *property_name, function, property_type, true)); } else if (match(TokenType::Colon)) { @@ -1892,6 +1928,7 @@ RefPtr Parser::synthesize_binding_pattern(Expression const& expr parser.m_state.in_function_context = m_state.in_function_context; parser.m_state.in_formal_parameter_context = m_state.in_formal_parameter_context; parser.m_state.in_generator_function_context = m_state.in_generator_function_context; + parser.m_state.in_async_function_context = m_state.in_async_function_context; parser.m_state.in_arrow_function_context = m_state.in_arrow_function_context; parser.m_state.in_break_context = m_state.in_break_context; parser.m_state.in_continue_context = m_state.in_continue_context; @@ -2116,6 +2153,9 @@ NonnullRefPtr Parser::parse_function_body(Vector Parser::parse_function_body(Vector Parser::parse_function_node(u8 parse_options) TemporaryChange might_need_arguments_object_rollback(m_state.function_might_need_arguments_object, false); constexpr auto is_function_expression = IsSame; - auto function_kind = (parse_options & FunctionNodeParseOptions::IsGeneratorFunction) != 0 ? FunctionKind::Generator : FunctionKind::Regular; + FunctionKind function_kind; + if ((parse_options & FunctionNodeParseOptions::IsGeneratorFunction) != 0 && (parse_options & FunctionNodeParseOptions::IsAsyncFunction) != 0) + TODO(); + else if ((parse_options & FunctionNodeParseOptions::IsGeneratorFunction) != 0) + function_kind = FunctionKind::Generator; + else if ((parse_options & FunctionNodeParseOptions::IsAsyncFunction) != 0) + function_kind = FunctionKind::Async; + else + function_kind = FunctionKind::Regular; String name; if (parse_options & FunctionNodeParseOptions::CheckForFunctionAndName) { + if (function_kind == FunctionKind::Regular && match(TokenType::Async) && !next_token().trivia_contains_line_terminator()) { + function_kind = FunctionKind::Async; + consume(TokenType::Async); + parse_options = parse_options | FunctionNodeParseOptions::IsAsyncFunction; + } consume(TokenType::Function); - if (function_kind == FunctionKind::Regular) { - function_kind = match(TokenType::Asterisk) ? FunctionKind::Generator : FunctionKind::Regular; - if (function_kind == FunctionKind::Generator) { - consume(TokenType::Asterisk); - parse_options = parse_options | FunctionNodeParseOptions::IsGeneratorFunction; - } + if (function_kind == FunctionKind::Regular && match(TokenType::Asterisk)) { + function_kind = FunctionKind::Generator; + consume(TokenType::Asterisk); + parse_options = parse_options | FunctionNodeParseOptions::IsGeneratorFunction; } if (FunctionNodeType::must_have_name() || match_identifier()) @@ -2193,6 +2247,7 @@ NonnullRefPtr Parser::parse_function_node(u8 parse_options) check_identifier_name_for_assignment_validity(name); } TemporaryChange generator_change(m_state.in_generator_function_context, function_kind == FunctionKind::Generator); + TemporaryChange async_change(m_state.in_async_function_context, function_kind == FunctionKind::Async); consume(TokenType::ParenOpen); i32 function_length = -1; @@ -2557,6 +2612,13 @@ NonnullRefPtr Parser::parse_variable_declaration(bool for_l if (m_state.strict_mode) syntax_error("Identifier must not be a reserved word in strict mode ('yield')"); + target = create_ast_node( + { m_state.current_token.filename(), rule_start.position(), position() }, + consume().value()); + } else if (!m_state.in_async_function_context && match(TokenType::Async)) { + if (m_program_type == Program::Type::Module) + syntax_error("Identifier must not be a reserved word in modules ('async')"); + target = create_ast_node( { m_state.current_token.filename(), rule_start.position(), position() }, consume().value()); @@ -2913,7 +2975,7 @@ NonnullRefPtr Parser::parse_catch_clause() if (match(TokenType::ParenOpen)) { should_expect_parameter = true; consume(); - if (match_identifier_name() && (!match(TokenType::Yield) || !m_state.in_generator_function_context)) + if (match_identifier_name() && (!match(TokenType::Yield) || !m_state.in_generator_function_context) && (!match(TokenType::Async) || !m_state.in_async_function_context)) parameter = consume().value(); else pattern_parameter = parse_binding_pattern(AllowDuplicates::No, AllowMemberExpressions::No); @@ -2978,6 +3040,8 @@ NonnullRefPtr Parser::parse_if_statement() auto& function_declaration = static_cast(*declaration); if (function_declaration.kind() == FunctionKind::Generator) syntax_error("Generator functions can only be declared in top-level or within a block"); + if (function_declaration.kind() == FunctionKind::Async) + syntax_error("Async functions can only be declared in top-level or within a block"); block->append(move(declaration)); return block; }; @@ -3162,6 +3226,7 @@ bool Parser::match_expression() const || type == TokenType::BracketOpen || type == TokenType::ParenOpen || type == TokenType::Function + || type == TokenType::Async || type == TokenType::This || type == TokenType::Super || type == TokenType::RegexLiteral @@ -3280,7 +3345,8 @@ bool Parser::match_declaration() const return type == TokenType::Function || type == TokenType::Class || type == TokenType::Const - || type == TokenType::Let; + || type == TokenType::Let + || (type == TokenType::Async && next_token().type() == TokenType::Function); } Token Parser::next_token() const @@ -3327,13 +3393,14 @@ bool Parser::match_identifier() const if (m_state.current_token.value() == "yield"sv) return !m_state.strict_mode && !m_state.in_generator_function_context; if (m_state.current_token.value() == "await"sv) - return m_program_type != Program::Type::Module; + return m_program_type != Program::Type::Module && !m_state.in_async_function_context; return true; } return m_state.current_token.type() == TokenType::Identifier + || m_state.current_token.type() == TokenType::Async || (m_state.current_token.type() == TokenType::Let && !m_state.strict_mode) - || (m_state.current_token.type() == TokenType::Await && m_program_type != Program::Type::Module) + || (m_state.current_token.type() == TokenType::Await && m_program_type != Program::Type::Module && !m_state.in_async_function_context) || (m_state.current_token.type() == TokenType::Yield && !m_state.in_generator_function_context && !m_state.strict_mode); // See note in Parser::parse_identifier(). } @@ -3414,6 +3481,15 @@ Token Parser::consume_identifier() return consume(); } + if (match(TokenType::Await)) { + if (m_program_type == Program::Type::Module || m_state.in_async_function_context) + syntax_error("Identifier must not be a reserved word in modules ('await')"); + return consume(); + } + + if (match(TokenType::Async)) + return consume(); + expected("Identifier"); return consume(); } @@ -3453,6 +3529,9 @@ Token Parser::consume_identifier_reference() return consume(); } + if (match(TokenType::Async)) + return consume(); + expected(Token::name(TokenType::Identifier)); return consume(); } @@ -3722,11 +3801,10 @@ NonnullRefPtr Parser::parse_export_statement(Program& program) auto class_expression = parse_class_expression(false); local_name = class_expression->name(); expression = move(class_expression); - } else if (match(TokenType::Function)) { + } else if (match(TokenType::Function) || (match(TokenType::Async) && next_token().type() == TokenType::Function)) { auto func_expr = parse_function_node(); local_name = func_expr->name(); expression = move(func_expr); - // TODO: Allow async function } else if (match_expression()) { expression = parse_expression(2); consume_or_insert_semicolon(); diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index ba932ca374..3bcd52b626 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -31,6 +31,7 @@ struct FunctionNodeParseOptions { IsSetterFunction = 1 << 4, IsArrowFunction = 1 << 5, IsGeneratorFunction = 1 << 6, + IsAsyncFunction = 1 << 7, }; }; @@ -251,6 +252,7 @@ private: bool in_function_context { false }; bool in_formal_parameter_context { false }; bool in_generator_function_context { false }; + bool in_async_function_context { false }; bool in_arrow_function_context { false }; bool in_break_context { false }; bool in_continue_context { false }; diff --git a/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.cpp b/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.cpp new file mode 100644 index 0000000000..f069c6545e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace JS { + +AsyncFunctionConstructor::AsyncFunctionConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.AsyncFunction.as_string(), *global_object.function_prototype()) +{ +} + +void AsyncFunctionConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + + // 27.7.2.2 AsyncFunction.prototype, https://tc39.es/ecma262/#sec-async-function-constructor-prototype + define_direct_property(vm.names.prototype, global_object.async_function_prototype(), 0); + + define_direct_property(vm.names.length, Value(1), Attribute::Configurable); +} + +// 27.7.1.1 AsyncFunction ( p1, p2, … , pn, body ), https://tc39.es/ecma262/#sec-async-function-constructor-arguments +ThrowCompletionOr AsyncFunctionConstructor::call() +{ + return TRY(construct(*this)); +} + +// 27.7.1.1 AsyncFunction ( p1, p2, … , pn, body ), https://tc39.es/ecma262/#sec-async-function-constructor-arguments +ThrowCompletionOr AsyncFunctionConstructor::construct(FunctionObject& new_target) +{ + auto& vm = this->vm(); + auto function = TRY(FunctionConstructor::create_dynamic_function_node(global_object(), new_target, FunctionKind::Async)); + + OwnPtr local_interpreter; + Interpreter* interpreter = vm.interpreter_if_exists(); + + if (!interpreter) { + local_interpreter = Interpreter::create_with_existing_realm(*realm()); + interpreter = local_interpreter.ptr(); + } + + VM::InterpreterExecutionScope scope(*interpreter); + auto result = function->execute(*interpreter, global_object()); + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + VERIFY(result.is_object() && is(result.as_object())); + return &result.as_object(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.h b/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.h new file mode 100644 index 0000000000..9258740675 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace JS { + +class AsyncFunctionConstructor final : public NativeFunction { + JS_OBJECT(AsyncFunctionConstructor, NativeFunction); + +public: + explicit AsyncFunctionConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~AsyncFunctionConstructor() override = default; + + virtual ThrowCompletionOr call() override; + virtual ThrowCompletionOr construct(FunctionObject& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.cpp b/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.cpp new file mode 100644 index 0000000000..ad3a7ba896 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS { + +AsyncFunctionPrototype::AsyncFunctionPrototype(GlobalObject& global_object) + : Object(*global_object.function_prototype()) +{ +} + +void AsyncFunctionPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + + // 27.7.3.2 AsyncFunction.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-async-function-prototype-properties-toStringTag + define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, vm.names.AsyncFunction.as_string()), Attribute::Configurable); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.h b/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.h new file mode 100644 index 0000000000..ac8d91654e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS { + +class AsyncFunctionPrototype final : public Object { + JS_OBJECT(AsyncFunctionPrototype, Object); + +public: + explicit AsyncFunctionPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~AsyncFunctionPrototype() override = default; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 735cd32d8a..e3b02dfc7e 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -15,11 +15,14 @@ #include #include #include +#include #include #include #include #include #include +#include +#include #include namespace JS { @@ -34,6 +37,9 @@ ECMAScriptFunctionObject* ECMAScriptFunctionObject::create(GlobalObject& global_ case FunctionKind::Generator: prototype = global_object.generator_function_prototype(); break; + case FunctionKind::Async: + prototype = global_object.async_function_prototype(); + break; } return global_object.heap().allocate(global_object, move(name), ecmascript_code, move(parameters), m_function_length, parent_scope, private_scope, *prototype, kind, is_strict, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function); } @@ -96,6 +102,8 @@ void ECMAScriptFunctionObject::initialize(GlobalObject& global_object) // prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) set_prototype(global_object.generator_object_prototype()); break; + case FunctionKind::Async: + break; } define_direct_property(vm.names.prototype, prototype, Attribute::Writable); } @@ -659,6 +667,84 @@ void ECMAScriptFunctionObject::ordinary_call_bind_this(ExecutionContext& callee_ MUST(verify_cast(local_env)->bind_this_value(global_object(), this_value)); } +// 27.7.5.1 AsyncFunctionStart ( promiseCapability, asyncFunctionBody ), https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start +void ECMAScriptFunctionObject::async_function_start(PromiseCapability const& promise_capability) +{ + auto& vm = this->vm(); + + // 1. Let runningContext be the running execution context. + auto& running_context = vm.running_execution_context(); + + // 2. Let asyncContext be a copy of runningContext. + auto async_context = running_context.copy(); + + // 3. NOTE: Copying the execution state is required for AsyncBlockStart to resume its execution. It is ill-defined to resume a currently executing context. + + // 4. Perform ! AsyncBlockStart(promiseCapability, asyncFunctionBody, asyncContext). + async_block_start(promise_capability, async_context); +} + +// 27.7.5.2 AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ), https://tc39.es/ecma262/#sec-asyncblockstart +void ECMAScriptFunctionObject::async_block_start(PromiseCapability const& promise_capability, ExecutionContext& async_context) +{ + auto& vm = this->vm(); + + // 1. Assert: promiseCapability is a PromiseCapability Record. + + // 2. Let runningContext be the running execution context. + auto& running_context = vm.running_execution_context(); + + // 3. Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution context the following steps will be performed: + auto* execution_steps = NativeFunction::create(global_object(), "", [async_body = m_ecmascript_code, &promise_capability](auto& vm, auto& global_object) -> ThrowCompletionOr { + // a. Let result be the result of evaluating asyncBody. + auto result = async_body->execute(vm.interpreter(), global_object); + + // b. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done. + + // c. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. + vm.pop_execution_context(); + + // NOTE: Running the AST node should eventually return a completion. + // Until it does, we assume "return" and include the undefined fallback from the call site. + // d. If result.[[Type]] is normal, then + if (false) { + // i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »). + MUST(call(global_object, promise_capability.resolve, js_undefined(), js_undefined())); + } + // e. Else if result.[[Type]] is return, then + else if (!vm.exception()) { + // i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »). + MUST(call(global_object, promise_capability.resolve, js_undefined(), result.value_or(js_undefined()))); + } + // f. Else, + else { + // i. Assert: result.[[Type]] is throw. + + // ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »). + auto reason = vm.exception()->value(); + vm.clear_exception(); + vm.stop_unwind(); + MUST(call(global_object, promise_capability.reject, js_undefined(), reason)); + } + // g. Return. + return js_undefined(); + }); + + // 4. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + vm.push_execution_context(async_context, global_object()); + + // 5. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation. + auto result = vm.call(*execution_steps, async_context.this_value.is_empty() ? js_undefined() : async_context.this_value); + + // 6. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context. + VERIFY(&vm.running_execution_context() == &running_context); + + // 7. Assert: result is a normal completion with a value of undefined. The possible sources of completion values are Await or, if the async function doesn't await anything, step 3.g above. + VERIFY(result.has_value() && result.value().is_undefined()); + + // 8. Return. +} + // 10.2.1.4 OrdinaryCallEvaluateBody ( F, argumentsList ), https://tc39.es/ecma262/#sec-ordinarycallevaluatebody Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body() { @@ -666,6 +752,8 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body() auto* bytecode_interpreter = Bytecode::Interpreter::current(); if (bytecode_interpreter) { + if (m_kind == FunctionKind::Async) + return vm.throw_completion(global_object(), ErrorType::NotImplemented, "Async function execution in Bytecode interpreter"); // FIXME: pass something to evaluate default arguments with TRY(function_declaration_instantiation(nullptr)); if (!m_bytecode_executable.has_value()) { @@ -690,8 +778,8 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body() return normal_completion(TRY(GeneratorObject::create(global_object(), result, this, vm.running_execution_context().lexical_environment, bytecode_interpreter->snapshot_frame()))); } else { - if (m_kind != FunctionKind::Regular) - return vm.throw_completion(global_object(), ErrorType::NotImplemented, "Non regular function execution in AST interpreter"); + if (m_kind == FunctionKind::Generator) + return vm.throw_completion(global_object(), ErrorType::NotImplemented, "Generator function execution in AST interpreter"); OwnPtr local_interpreter; Interpreter* ast_interpreter = vm.interpreter_if_exists(); @@ -702,14 +790,36 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body() VM::InterpreterExecutionScope scope(*ast_interpreter); - TRY(function_declaration_instantiation(ast_interpreter)); + if (m_kind == FunctionKind::Regular) { + TRY(function_declaration_instantiation(ast_interpreter)); - auto result = m_ecmascript_code->execute(*ast_interpreter, global_object()); - if (auto* exception = vm.exception()) - return throw_completion(exception->value()); - // NOTE: Running the AST node should eventually return a completion. - // Until it does, we assume "return" and include the undefined fallback from the call site. - return { Completion::Type::Return, result.value_or(js_undefined()), {} }; + auto result = m_ecmascript_code->execute(*ast_interpreter, global_object()); + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + // NOTE: Running the AST node should eventually return a completion. + // Until it does, we assume "return" and include the undefined fallback from the call site. + return { Completion::Type::Return, result.value_or(js_undefined()), {} }; + } else if (m_kind == FunctionKind::Async) { + // 1. Let promiseCapability be ! NewPromiseCapability(%Promise%). + auto promise_capability = MUST(new_promise_capability(global_object(), global_object().promise_constructor())); + + // 2. Let declResult be FunctionDeclarationInstantiation(functionObject, argumentsList). + auto declaration_result = function_declaration_instantiation(ast_interpreter); + + // 3. If declResult is not an abrupt completion, then + if (!declaration_result.is_throw_completion() || !declaration_result.throw_completion().is_abrupt()) { + // a. Perform ! AsyncFunctionStart(promiseCapability, FunctionBody). + async_function_start(promise_capability); + } + // 4. Else, + else { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « declResult.[[Value]] »). + MUST(call(global_object(), promise_capability.reject, js_undefined(), declaration_result.throw_completion().value())); + } + + // 5. Return Completion { [[Type]]: return, [[Value]]: promiseCapability.[[Promise]], [[Target]]: empty }. + return Completion { Completion::Type::Return, promise_capability.promise, {} }; + } } VERIFY_NOT_REACHED(); } diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h index dfe121f844..158775d398 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h @@ -73,7 +73,7 @@ public: bool has_simple_parameter_list() const { return m_has_simple_parameter_list; } // Equivalent to absence of [[Construct]] - virtual bool has_constructor() const override { return !(m_is_arrow_function || m_kind == FunctionKind::Generator); } + virtual bool has_constructor() const override { return m_kind == FunctionKind::Regular && !m_is_arrow_function; } protected: virtual bool is_strict_mode() const final { return m_strict; } @@ -87,6 +87,9 @@ private: void prepare_for_ordinary_call(ExecutionContext& callee_context, Object* new_target); void ordinary_call_bind_this(ExecutionContext&, Value this_argument); + void async_function_start(PromiseCapability const&); + void async_block_start(PromiseCapability const&, ExecutionContext&); + ThrowCompletionOr function_declaration_instantiation(Interpreter*); // Internal Slots of ECMAScript Function Objects, https://tc39.es/ecma262/#table-internal-slots-of-ecmascript-function-objects diff --git a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h index 344205b917..5e0ce04953 100644 --- a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h +++ b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h @@ -22,6 +22,30 @@ struct ExecutionContext { { } + [[nodiscard]] ExecutionContext copy() const + { + ExecutionContext copy { arguments.copy() }; + + copy.function = function; + copy.realm = realm; + copy.lexical_environment = lexical_environment; + copy.variable_environment = variable_environment; + copy.private_environment = private_environment; + copy.current_node = current_node; + copy.function_name = function_name; + copy.this_value = this_value; + copy.is_strict_mode = is_strict_mode; + + return copy; + } + +private: + explicit ExecutionContext(MarkedValueList existing_arguments) + : arguments(move(existing_arguments)) + { + } + +public: FunctionObject* function { nullptr }; // [[Function]] Realm* realm { nullptr }; // [[Realm]] Environment* lexical_environment { nullptr }; // [[LexicalEnvironment]] diff --git a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp index c5405a13f1..a39eae0703 100644 --- a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp @@ -54,7 +54,8 @@ ThrowCompletionOr> FunctionConstructor::create_dynami body_source = TRY(vm.argument(vm.argument_count() - 1).to_string(global_object)); } auto is_generator = kind == FunctionKind::Generator; - auto source = String::formatted("function{} anonymous({}\n) {{\n{}\n}}", is_generator ? "*" : "", parameters_source, body_source); + auto is_async = kind == FunctionKind::Async; + auto source = String::formatted("{}function{} anonymous({}\n) {{\n{}\n}}", is_async ? "async " : "", is_generator ? "*" : "", parameters_source, body_source); auto parser = Parser(Lexer(source)); auto function = parser.parse_function_node(); if (parser.has_errors()) { diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index ae4ced6548..8a4647c7f0 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Userland/Libraries/LibJS/Token.cpp b/Userland/Libraries/LibJS/Token.cpp index 5a268d2808..94fb0c593b 100644 --- a/Userland/Libraries/LibJS/Token.cpp +++ b/Userland/Libraries/LibJS/Token.cpp @@ -224,6 +224,7 @@ bool Token::is_identifier_name() const return m_type == TokenType::Identifier || m_type == TokenType::EscapedKeyword || m_type == TokenType::Await + || m_type == TokenType::Async || m_type == TokenType::BoolLiteral || m_type == TokenType::Break || m_type == TokenType::Case