mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
LibJS: Parse async arrow functions
This commit is contained in:
parent
0619c34703
commit
de46a2cff1
Notes:
sideshowbarker
2024-07-18 00:53:25 +09:00
Author: https://github.com/davidot Commit: https://github.com/SerenityOS/serenity/commit/de46a2cff14 Pull-request: https://github.com/SerenityOS/serenity/pull/10926 Reviewed-by: https://github.com/linusg ✅
3 changed files with 155 additions and 25 deletions
|
@ -621,9 +621,12 @@ static bool is_simple_parameter_list(Vector<FunctionNode::Parameter> const& para
|
|||
});
|
||||
}
|
||||
|
||||
RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expect_parens)
|
||||
RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expect_parens, bool is_async)
|
||||
{
|
||||
if (!expect_parens) {
|
||||
if (is_async)
|
||||
VERIFY(match(TokenType::Async));
|
||||
|
||||
if (!expect_parens && !is_async) {
|
||||
// NOTE: This is a fast path where we try to fail early in case this can't possibly
|
||||
// be a match. The idea is to avoid the expensive parser state save/load mechanism.
|
||||
// The logic is duplicated below in the "real" !expect_parens branch.
|
||||
|
@ -643,6 +646,22 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
|
|||
load_state();
|
||||
};
|
||||
|
||||
auto function_kind = FunctionKind::Regular;
|
||||
|
||||
if (is_async) {
|
||||
consume(TokenType::Async);
|
||||
function_kind = FunctionKind::Async;
|
||||
if (m_state.current_token.trivia_contains_line_terminator())
|
||||
return nullptr;
|
||||
|
||||
// Since we have async it can be followed by paren open in the expect_parens case
|
||||
// so we also consume that token.
|
||||
if (expect_parens) {
|
||||
VERIFY(match(TokenType::ParenOpen));
|
||||
consume(TokenType::ParenOpen);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<FunctionNode::Parameter> parameters;
|
||||
i32 function_length = -1;
|
||||
if (expect_parens) {
|
||||
|
@ -652,7 +671,9 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
|
|||
// check if it's about a wrong token (something like duplicate parameter name must
|
||||
// not abort), know parsing failed and rollback the parser state.
|
||||
auto previous_syntax_errors = m_state.errors.size();
|
||||
parameters = parse_formal_parameters(function_length, FunctionNodeParseOptions::IsArrowFunction);
|
||||
TemporaryChange in_async_context(m_state.in_async_function_context, is_async || m_state.in_async_function_context);
|
||||
|
||||
parameters = parse_formal_parameters(function_length, FunctionNodeParseOptions::IsArrowFunction | (is_async ? FunctionNodeParseOptions::IsAsyncFunction : 0));
|
||||
if (m_state.errors.size() > previous_syntax_errors && m_state.errors[previous_syntax_errors].message.starts_with("Unexpected token"))
|
||||
return nullptr;
|
||||
if (!match(TokenType::ParenClose))
|
||||
|
@ -665,6 +686,8 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
|
|||
auto token = consume_identifier_reference();
|
||||
if (m_state.strict_mode && token.value().is_one_of("arguments"sv, "eval"sv))
|
||||
syntax_error("BindingIdentifier may not be 'arguments' or 'eval' in strict mode");
|
||||
if (is_async && token.value() == "await"sv)
|
||||
syntax_error("'await' is a reserved identifier in async functions");
|
||||
parameters.append({ FlyString { token.value() }, {} });
|
||||
}
|
||||
// If there's a newline between the closing paren and arrow it's not a valid arrow function,
|
||||
|
@ -687,9 +710,11 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
|
|||
|
||||
auto function_body_result = [&]() -> RefPtr<FunctionBody> {
|
||||
TemporaryChange change(m_state.in_arrow_function_context, true);
|
||||
TemporaryChange async_context_change(m_state.in_async_function_context, is_async);
|
||||
|
||||
if (match(TokenType::CurlyOpen)) {
|
||||
// Parse a function body with statements
|
||||
return parse_function_body(parameters, FunctionKind::Regular, contains_direct_call_to_eval);
|
||||
return parse_function_body(parameters, function_kind, contains_direct_call_to_eval);
|
||||
}
|
||||
if (match_expression()) {
|
||||
// Parse a function body which returns a single expression
|
||||
|
@ -730,7 +755,7 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
|
|||
|
||||
return create_ast_node<FunctionExpression>(
|
||||
{ m_state.current_token.filename(), rule_start.position(), position() }, "", move(body),
|
||||
move(parameters), function_length, FunctionKind::Regular, body->in_strict_mode(),
|
||||
move(parameters), function_length, function_kind, body->in_strict_mode(),
|
||||
/* might_need_arguments_object */ false, contains_direct_call_to_eval, /* is_arrow_function */ true);
|
||||
}
|
||||
|
||||
|
@ -1164,18 +1189,24 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression()
|
|||
if (match_unary_prefixed_expression())
|
||||
return { parse_unary_prefixed_expression() };
|
||||
|
||||
auto try_arrow_function_parse_or_fail = [this](Position const& position, bool expect_paren, bool is_async = false) -> RefPtr<FunctionExpression> {
|
||||
if (try_parse_arrow_function_expression_failed_at_position(position))
|
||||
return nullptr;
|
||||
auto arrow_function = try_parse_arrow_function_expression(expect_paren, is_async);
|
||||
if (arrow_function)
|
||||
return arrow_function;
|
||||
|
||||
set_try_parse_arrow_function_expression_failed_at_position(position, true);
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
switch (m_state.current_token.type()) {
|
||||
case TokenType::ParenOpen: {
|
||||
auto paren_position = position();
|
||||
consume(TokenType::ParenOpen);
|
||||
if ((match(TokenType::ParenClose) || match_identifier() || match(TokenType::TripleDot) || match(TokenType::CurlyOpen) || match(TokenType::BracketOpen))
|
||||
&& !try_parse_arrow_function_expression_failed_at_position(paren_position)) {
|
||||
|
||||
auto arrow_function_result = try_parse_arrow_function_expression(true);
|
||||
if (!arrow_function_result.is_null())
|
||||
if ((match(TokenType::ParenClose) || match_identifier() || match(TokenType::TripleDot) || match(TokenType::CurlyOpen) || match(TokenType::BracketOpen))) {
|
||||
if (auto arrow_function_result = try_arrow_function_parse_or_fail(paren_position, true))
|
||||
return { arrow_function_result.release_nonnull(), false };
|
||||
|
||||
set_try_parse_arrow_function_expression_failed_at_position(paren_position, true);
|
||||
}
|
||||
auto expression = parse_expression(0);
|
||||
consume(TokenType::ParenClose);
|
||||
|
@ -1204,13 +1235,9 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression()
|
|||
[[fallthrough]];
|
||||
case TokenType::Identifier: {
|
||||
read_as_identifier:;
|
||||
if (!try_parse_arrow_function_expression_failed_at_position(position())) {
|
||||
auto arrow_function_result = try_parse_arrow_function_expression(false);
|
||||
if (!arrow_function_result.is_null())
|
||||
return { arrow_function_result.release_nonnull(), false };
|
||||
if (auto arrow_function_result = try_arrow_function_parse_or_fail(position(), false))
|
||||
return { arrow_function_result.release_nonnull(), false };
|
||||
|
||||
set_try_parse_arrow_function_expression_failed_at_position(position(), true);
|
||||
}
|
||||
auto string = m_state.current_token.value();
|
||||
// This could be 'eval' or 'arguments' and thus needs a custom check (`eval[1] = true`)
|
||||
if (m_state.strict_mode && (string == "let" || is_strict_reserved_word(string)))
|
||||
|
@ -1230,10 +1257,24 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression()
|
|||
return { create_ast_node<NullLiteral>({ 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)
|
||||
case TokenType::Async: {
|
||||
auto lookahead_token = next_token();
|
||||
// No valid async function (arrow or not) can have a line terminator after the async since asi would kick in.
|
||||
if (lookahead_token.trivia_contains_line_terminator())
|
||||
goto read_as_identifier;
|
||||
[[fallthrough]];
|
||||
|
||||
if (lookahead_token.type() == TokenType::Function)
|
||||
return { parse_function_node<FunctionExpression>() };
|
||||
|
||||
if (lookahead_token.type() == TokenType::ParenOpen) {
|
||||
if (auto arrow_function_result = try_arrow_function_parse_or_fail(position(), true, true))
|
||||
return { arrow_function_result.release_nonnull(), false };
|
||||
} else if (lookahead_token.is_identifier_name()) {
|
||||
if (auto arrow_function_result = try_arrow_function_parse_or_fail(position(), false, true))
|
||||
return { arrow_function_result.release_nonnull(), false };
|
||||
}
|
||||
goto read_as_identifier;
|
||||
}
|
||||
case TokenType::Function:
|
||||
return { parse_function_node<FunctionExpression>() };
|
||||
case TokenType::BracketOpen:
|
||||
|
@ -3095,8 +3136,11 @@ NonnullRefPtr<IfStatement> Parser::parse_if_statement()
|
|||
NonnullRefPtr<Statement> Parser::parse_for_statement()
|
||||
{
|
||||
auto rule_start = push_start();
|
||||
auto match_for_in_of = [&]() {
|
||||
return match(TokenType::In) || (match(TokenType::Identifier) && m_state.current_token.original_value() == "of");
|
||||
auto match_of = [&](Token const& token) {
|
||||
return token.type() == TokenType::Identifier && token.original_value() == "of"sv;
|
||||
};
|
||||
auto match_for_in_of = [&] {
|
||||
return match(TokenType::In) || match_of(m_state.current_token);
|
||||
};
|
||||
|
||||
consume(TokenType::For);
|
||||
|
@ -3130,9 +3174,15 @@ NonnullRefPtr<Statement> Parser::parse_for_statement()
|
|||
}
|
||||
}
|
||||
} else if (match_expression()) {
|
||||
auto lookahead_token = next_token();
|
||||
bool starts_with_async_of = match(TokenType::Async) && match_of(lookahead_token);
|
||||
|
||||
init = parse_expression(0, Associativity::Right, { TokenType::In });
|
||||
if (match_for_in_of())
|
||||
if (match_for_in_of()) {
|
||||
if (starts_with_async_of && match_of(m_state.current_token))
|
||||
syntax_error("for-of loop may not start with async of");
|
||||
return parse_for_in_of_statement(*init);
|
||||
}
|
||||
} else {
|
||||
syntax_error("Unexpected token in for loop");
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ public:
|
|||
NonnullRefPtr<ImportStatement> parse_import_statement(Program& program);
|
||||
NonnullRefPtr<ExportStatement> parse_export_statement(Program& program);
|
||||
|
||||
RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens);
|
||||
RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens, bool is_async = false);
|
||||
RefPtr<Statement> try_parse_labelled_statement(AllowLabelledFunction allow_function);
|
||||
RefPtr<MetaProperty> try_parse_new_target_expression();
|
||||
|
||||
|
|
|
@ -41,6 +41,65 @@ test("function expression names equal to 'await'", () => {
|
|||
expect(`async function foo() { function await() {} }`).not.toEval();
|
||||
});
|
||||
|
||||
test("async function cannot use await in default parameters", () => {
|
||||
expect("async function foo(x = await 3) {}").not.toEval();
|
||||
expect("async function foo(x = await 3) {}").not.toEval();
|
||||
|
||||
// Even as a reference to some variable it is not allowed
|
||||
expect(`
|
||||
var await = 4;
|
||||
async function foo(x = await) {}
|
||||
`).not.toEval();
|
||||
});
|
||||
|
||||
describe("async arrow functions", () => {
|
||||
test("basic syntax", () => {
|
||||
expect("async () => await 3;").toEval();
|
||||
expect("async param => await param();").toEval();
|
||||
expect("async (param) => await param();").toEval();
|
||||
expect("async (a, b) => await a();").toEval();
|
||||
|
||||
expect("async () => { await 3; }").toEval();
|
||||
expect("async param => { await param(); }").toEval();
|
||||
expect("async (param) => { await param(); }").toEval();
|
||||
expect("async (a, b) => { await a(); }").toEval();
|
||||
|
||||
expect(`async
|
||||
() => await 3;`).not.toEval();
|
||||
|
||||
expect("async async => await async()").toEval();
|
||||
expect("async => async").toEval();
|
||||
expect("async => await async()").not.toEval();
|
||||
|
||||
expect("async (b = await) => await b;").not.toEval();
|
||||
expect("async (b = await 3) => await b;").not.toEval();
|
||||
|
||||
// Cannot escape the async keyword.
|
||||
expect("\\u0061sync () => await 3").not.toEval();
|
||||
|
||||
expect("for (async of => {};;) {}").toEval();
|
||||
expect("for (async of []) {}").not.toEval();
|
||||
});
|
||||
|
||||
test("async within a for-loop", () => {
|
||||
let called = false;
|
||||
// Unfortunately we cannot really test the more horrible case above.
|
||||
for (
|
||||
const f = async of => {
|
||||
return of;
|
||||
};
|
||||
;
|
||||
|
||||
) {
|
||||
expect(f(43)).toBeInstanceOf(Promise);
|
||||
|
||||
called = true;
|
||||
break;
|
||||
}
|
||||
expect(called).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
test("simple", () => {
|
||||
let executionValue = null;
|
||||
|
@ -72,3 +131,24 @@ test("basic functionality", () => {
|
|||
expect(resultValue).toBe("someValue");
|
||||
});
|
||||
});
|
||||
|
||||
describe("non async function declaration usage of async still works", () => {
|
||||
test("async as a function", () => {
|
||||
function async(value = 4) {
|
||||
return value;
|
||||
}
|
||||
|
||||
expect(async(0)).toBe(0);
|
||||
|
||||
// We use eval here since it otherwise cannot find the async function.
|
||||
const evalResult = eval("async(1)");
|
||||
expect(evalResult).toBe(1);
|
||||
});
|
||||
|
||||
test("async as a variable", () => {
|
||||
let async = 3;
|
||||
|
||||
const evalResult = eval("async >= 2");
|
||||
expect(evalResult).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue