mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
LibJS: Add using declaration support in for and for of loops
The using declarations have kind of special behavior in for loops so this is seperated.
This commit is contained in:
parent
541637e15a
commit
bff038411a
Notes:
sideshowbarker
2024-07-17 01:21:41 +09:00
Author: https://github.com/davidot Commit: https://github.com/SerenityOS/serenity/commit/bff038411a Pull-request: https://github.com/SerenityOS/serenity/pull/16630 Reviewed-by: https://github.com/alimpfard Reviewed-by: https://github.com/linusg ✅
6 changed files with 447 additions and 77 deletions
|
@ -6,3 +6,4 @@ Userland/Libraries/LibJS/Tests/modules/failing.mjs
|
|||
# FIXME: Remove once prettier is updated to support using declarations.
|
||||
Userland/Libraries/LibJS/Tests/modules/top-level-dispose.mjs
|
||||
Userland/Libraries/LibJS/Tests/using-declaration.js
|
||||
Userland/Libraries/LibJS/Tests/using-for-loops.js
|
||||
|
|
|
@ -764,39 +764,66 @@ Completion ForStatement::loop_evaluation(Interpreter& interpreter, Vector<Deprec
|
|||
|
||||
// Note we don't always set a new environment but to use RAII we must do this here.
|
||||
auto* old_environment = interpreter.lexical_environment();
|
||||
ScopeGuard restore_old_environment = [&] {
|
||||
interpreter.vm().running_execution_context().lexical_environment = old_environment;
|
||||
};
|
||||
|
||||
size_t per_iteration_bindings_size = 0;
|
||||
GCPtr<DeclarativeEnvironment> loop_env;
|
||||
|
||||
if (m_init) {
|
||||
if (is<VariableDeclaration>(*m_init) && static_cast<VariableDeclaration const&>(*m_init).declaration_kind() != DeclarationKind::Var) {
|
||||
auto loop_environment = new_declarative_environment(*old_environment);
|
||||
auto& declaration = static_cast<VariableDeclaration const&>(*m_init);
|
||||
declaration.for_each_bound_name([&](auto const& name) {
|
||||
if (declaration.declaration_kind() == DeclarationKind::Const) {
|
||||
MUST(loop_environment->create_immutable_binding(vm, name, true));
|
||||
Declaration const* declaration = nullptr;
|
||||
|
||||
if (is<VariableDeclaration>(*m_init) && static_cast<VariableDeclaration const&>(*m_init).declaration_kind() != DeclarationKind::Var)
|
||||
declaration = static_cast<VariableDeclaration const*>(m_init.ptr());
|
||||
else if (is<UsingDeclaration>(*m_init))
|
||||
declaration = static_cast<UsingDeclaration const*>(m_init.ptr());
|
||||
|
||||
if (declaration) {
|
||||
loop_env = new_declarative_environment(*old_environment);
|
||||
auto is_const = declaration->is_constant_declaration();
|
||||
declaration->for_each_bound_name([&](auto const& name) {
|
||||
if (is_const) {
|
||||
MUST(loop_env->create_immutable_binding(vm, name, true));
|
||||
} else {
|
||||
MUST(loop_environment->create_mutable_binding(vm, name, false));
|
||||
MUST(loop_env->create_mutable_binding(vm, name, false));
|
||||
++per_iteration_bindings_size;
|
||||
}
|
||||
});
|
||||
|
||||
interpreter.vm().running_execution_context().lexical_environment = loop_environment;
|
||||
interpreter.vm().running_execution_context().lexical_environment = loop_env;
|
||||
}
|
||||
|
||||
(void)TRY(m_init->execute(interpreter));
|
||||
}
|
||||
|
||||
// 10. Let bodyResult be Completion(ForBodyEvaluation(the first Expression, the second Expression, Statement, perIterationLets, labelSet)).
|
||||
auto body_result = for_body_evaluation(interpreter, label_set, per_iteration_bindings_size);
|
||||
|
||||
// 11. Set bodyResult to DisposeResources(loopEnv, bodyResult).
|
||||
if (loop_env)
|
||||
body_result = dispose_resources(vm, loop_env.ptr(), body_result);
|
||||
|
||||
// 12. Set the running execution context's LexicalEnvironment to oldEnv.
|
||||
interpreter.vm().running_execution_context().lexical_environment = old_environment;
|
||||
|
||||
// 13. Return ? bodyResult.
|
||||
return body_result;
|
||||
}
|
||||
|
||||
// 14.7.4.3 ForBodyEvaluation ( test, increment, stmt, perIterationBindings, labelSet ), https://tc39.es/ecma262/#sec-forbodyevaluation
|
||||
// 6.3.1.2 ForBodyEvaluation ( test, increment, stmt, perIterationBindings, labelSet ), https://tc39.es/proposal-explicit-resource-management/#sec-forbodyevaluation
|
||||
Completion ForStatement::for_body_evaluation(JS::Interpreter& interpreter, Vector<DeprecatedFlyString> const& label_set, size_t per_iteration_bindings_size) const
|
||||
{
|
||||
auto& vm = interpreter.vm();
|
||||
|
||||
// 14.7.4.4 CreatePerIterationEnvironment ( perIterationBindings ), https://tc39.es/ecma262/#sec-createperiterationenvironment
|
||||
// NOTE: Our implementation of this AO is heavily dependent on DeclarativeEnvironment using a Vector with constant indices.
|
||||
// For performance, we can take advantage of the fact that the declarations of the initialization statement are created
|
||||
// in the same order each time CreatePerIterationEnvironment is invoked.
|
||||
auto create_per_iteration_environment = [&]() {
|
||||
auto create_per_iteration_environment = [&]() -> GCPtr<DeclarativeEnvironment> {
|
||||
// 1. If perIterationBindings has any elements, then
|
||||
if (per_iteration_bindings_size == 0)
|
||||
return;
|
||||
if (per_iteration_bindings_size == 0) {
|
||||
// 2. Return unused.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// a. Let lastIterationEnv be the running execution context's LexicalEnvironment.
|
||||
auto* last_iteration_env = verify_cast<DeclarativeEnvironment>(interpreter.lexical_environment());
|
||||
|
@ -820,49 +847,66 @@ Completion ForStatement::loop_evaluation(Interpreter& interpreter, Vector<Deprec
|
|||
// f. Set the running execution context's LexicalEnvironment to thisIterationEnv.
|
||||
interpreter.vm().running_execution_context().lexical_environment = this_iteration_env;
|
||||
|
||||
// 2. Return unused.
|
||||
// g. Return thisIterationEnv.
|
||||
return this_iteration_env;
|
||||
};
|
||||
|
||||
// 14.7.4.3 ForBodyEvaluation ( test, increment, stmt, perIterationBindings, labelSet ), https://tc39.es/ecma262/#sec-forbodyevaluation
|
||||
|
||||
// 1. Let V be undefined.
|
||||
auto last_value = js_undefined();
|
||||
|
||||
// 2. Perform ? CreatePerIterationEnvironment(perIterationBindings).
|
||||
create_per_iteration_environment();
|
||||
// 2. Let thisIterationEnv be ? CreatePerIterationEnvironment(perIterationBindings).
|
||||
auto this_iteration_env = create_per_iteration_environment();
|
||||
|
||||
// 3. Repeat,
|
||||
while (true) {
|
||||
// a. If test is not [empty], then
|
||||
if (m_test) {
|
||||
// i. Let testRef be the result of evaluating test.
|
||||
// ii. Let testValue be ? GetValue(testRef).
|
||||
auto test_value = TRY(m_test->execute(interpreter)).release_value();
|
||||
// ii. Let testValue be Completion(GetValue(testRef)).
|
||||
auto test_value = m_test->execute(interpreter);
|
||||
|
||||
// iii. If ToBoolean(testValue) is false, return V.
|
||||
if (!test_value.to_boolean())
|
||||
return last_value;
|
||||
// iii. If testValue is an abrupt completion, then
|
||||
if (test_value.is_abrupt()) {
|
||||
// 1. Return ? DisposeResources(thisIterationEnv, testValue).
|
||||
return TRY(dispose_resources(vm, this_iteration_env, test_value));
|
||||
}
|
||||
// iv. Else,
|
||||
// 1. Set testValue to testValue.[[Value]].
|
||||
VERIFY(test_value.value().has_value());
|
||||
|
||||
// iii. If ToBoolean(testValue) is false, return ? DisposeResources(thisIterationEnv, Completion(V)).
|
||||
if (!test_value.release_value().value().to_boolean())
|
||||
return TRY(dispose_resources(vm, this_iteration_env, test_value));
|
||||
}
|
||||
|
||||
// b. Let result be the result of evaluating stmt.
|
||||
auto result = m_body->execute(interpreter);
|
||||
|
||||
// c. If LoopContinues(result, labelSet) is false, return ? UpdateEmpty(result, V).
|
||||
// c. Perform ? DisposeResources(thisIterationEnv, result).
|
||||
TRY(dispose_resources(vm, this_iteration_env, result));
|
||||
|
||||
// d. If LoopContinues(result, labelSet) is false, return ? UpdateEmpty(result, V).
|
||||
if (!loop_continues(result, label_set))
|
||||
return result.update_empty(last_value);
|
||||
|
||||
// d. If result.[[Value]] is not empty, set V to result.[[Value]].
|
||||
// e. If result.[[Value]] is not empty, set V to result.[[Value]].
|
||||
if (result.value().has_value())
|
||||
last_value = *result.value();
|
||||
|
||||
// e. Perform ? CreatePerIterationEnvironment(perIterationBindings).
|
||||
create_per_iteration_environment();
|
||||
// f. Set thisIterationEnv to ? CreatePerIterationEnvironment(perIterationBindings).
|
||||
this_iteration_env = create_per_iteration_environment();
|
||||
|
||||
// f. If increment is not [empty], then
|
||||
// g. If increment is not [empty], then
|
||||
if (m_update) {
|
||||
// i. Let incRef be the result of evaluating increment.
|
||||
// ii. Perform ? GetValue(incRef).
|
||||
(void)TRY(m_update->execute(interpreter));
|
||||
// ii. Let incrResult be Completion(GetValue(incrRef)).
|
||||
auto inc_ref = m_update->execute(interpreter);
|
||||
|
||||
// ii. If incrResult is an abrupt completion, then
|
||||
if (inc_ref.is_abrupt()) {
|
||||
// 1. Return ? DisposeResources(thisIterationEnv, incrResult).
|
||||
return TRY(dispose_resources(vm, this_iteration_env, inc_ref));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -914,6 +958,10 @@ struct ForInOfHeadState {
|
|||
auto& declaration = static_cast<VariableDeclaration const&>(*expression_lhs);
|
||||
VERIFY(declaration.declarations().first().target().has<NonnullRefPtr<Identifier>>());
|
||||
lhs_reference = TRY(declaration.declarations().first().target().get<NonnullRefPtr<Identifier>>()->to_reference(interpreter));
|
||||
} else if (is<UsingDeclaration>(*expression_lhs)) {
|
||||
auto& declaration = static_cast<UsingDeclaration const&>(*expression_lhs);
|
||||
VERIFY(declaration.declarations().first().target().has<NonnullRefPtr<Identifier>>());
|
||||
lhs_reference = TRY(declaration.declarations().first().target().get<NonnullRefPtr<Identifier>>()->to_reference(interpreter));
|
||||
} else {
|
||||
VERIFY(is<Identifier>(*expression_lhs) || is<MemberExpression>(*expression_lhs) || is<CallExpression>(*expression_lhs));
|
||||
auto& expression = static_cast<Expression const&>(*expression_lhs);
|
||||
|
@ -923,14 +971,18 @@ struct ForInOfHeadState {
|
|||
}
|
||||
// h. Else,
|
||||
else {
|
||||
VERIFY(expression_lhs && is<VariableDeclaration>(*expression_lhs));
|
||||
VERIFY(expression_lhs && (is<VariableDeclaration>(*expression_lhs) || is<UsingDeclaration>(*expression_lhs)));
|
||||
iteration_environment = new_declarative_environment(*interpreter.lexical_environment());
|
||||
|
||||
auto& for_declaration = static_cast<VariableDeclaration const&>(*expression_lhs);
|
||||
auto& for_declaration = static_cast<Declaration const&>(*expression_lhs);
|
||||
DeprecatedFlyString first_name;
|
||||
|
||||
// 14.7.5.4 Runtime Semantics: ForDeclarationBindingInstantiation, https://tc39.es/ecma262/#sec-runtime-semantics-fordeclarationbindinginstantiation
|
||||
// 1. For each element name of the BoundNames of ForBinding, do
|
||||
for_declaration.for_each_bound_name([&](auto const& name) {
|
||||
if (first_name.is_empty())
|
||||
first_name = name;
|
||||
|
||||
// a. If IsConstantDeclaration of LetOrConst is true, then
|
||||
if (for_declaration.is_constant_declaration()) {
|
||||
// i. Perform ! environment.CreateImmutableBinding(name, true).
|
||||
|
@ -945,18 +997,28 @@ struct ForInOfHeadState {
|
|||
interpreter.vm().running_execution_context().lexical_environment = iteration_environment;
|
||||
|
||||
if (!destructuring) {
|
||||
VERIFY(for_declaration.declarations().first().target().has<NonnullRefPtr<Identifier>>());
|
||||
lhs_reference = MUST(interpreter.vm().resolve_binding(for_declaration.declarations().first().target().get<NonnullRefPtr<Identifier>>()->string()));
|
||||
VERIFY(!first_name.is_empty());
|
||||
lhs_reference = MUST(interpreter.vm().resolve_binding(first_name));
|
||||
}
|
||||
}
|
||||
|
||||
// i. If destructuring is false, then
|
||||
if (!destructuring) {
|
||||
VERIFY(lhs_reference.has_value());
|
||||
if (lhs_kind == LexicalBinding)
|
||||
return lhs_reference->initialize_referenced_binding(vm, next_value);
|
||||
else
|
||||
if (lhs_kind == LexicalBinding) {
|
||||
// 2. If IsUsingDeclaration of lhs is true, then
|
||||
if (is<UsingDeclaration>(expression_lhs)) {
|
||||
// a. Let status be Completion(InitializeReferencedBinding(lhsRef, nextValue, sync-dispose)).
|
||||
return lhs_reference->initialize_referenced_binding(vm, next_value, Environment::InitializeBindingHint::SyncDispose);
|
||||
}
|
||||
// 3. Else,
|
||||
else {
|
||||
// a. Let status be Completion(InitializeReferencedBinding(lhsRef, nextValue, normal)).
|
||||
return lhs_reference->initialize_referenced_binding(vm, next_value, Environment::InitializeBindingHint::Normal);
|
||||
}
|
||||
} else {
|
||||
return lhs_reference->put_value(vm, next_value);
|
||||
}
|
||||
}
|
||||
|
||||
// j. Else,
|
||||
|
@ -984,7 +1046,7 @@ static ThrowCompletionOr<ForInOfHeadState> for_in_of_head_execute(Interpreter& i
|
|||
auto& vm = interpreter.vm();
|
||||
|
||||
ForInOfHeadState state(lhs);
|
||||
if (auto* ast_ptr = lhs.get_pointer<NonnullRefPtr<ASTNode>>(); ast_ptr && is<VariableDeclaration>(*(*ast_ptr))) {
|
||||
if (auto* ast_ptr = lhs.get_pointer<NonnullRefPtr<ASTNode>>(); ast_ptr && is<Declaration>(ast_ptr->ptr())) {
|
||||
// Runtime Semantics: ForInOfLoopEvaluation, for any of:
|
||||
// ForInOfStatement : for ( var ForBinding in Expression ) Statement
|
||||
// ForInOfStatement : for ( ForDeclaration in Expression ) Statement
|
||||
|
@ -994,24 +1056,34 @@ static ThrowCompletionOr<ForInOfHeadState> for_in_of_head_execute(Interpreter& i
|
|||
// 14.7.5.6 ForIn/OfHeadEvaluation ( uninitializedBoundNames, expr, iterationKind ), https://tc39.es/ecma262/#sec-runtime-semantics-forinofheadevaluation
|
||||
Environment* new_environment = nullptr;
|
||||
|
||||
auto& variable_declaration = static_cast<VariableDeclaration const&>(*(*ast_ptr));
|
||||
VERIFY(variable_declaration.declarations().size() == 1);
|
||||
state.destructuring = variable_declaration.declarations().first().target().has<NonnullRefPtr<BindingPattern>>();
|
||||
if (variable_declaration.declaration_kind() == DeclarationKind::Var) {
|
||||
state.lhs_kind = ForInOfHeadState::VarBinding;
|
||||
auto& variable = variable_declaration.declarations().first();
|
||||
// B.3.5 Initializers in ForIn Statement Heads, https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads
|
||||
if (variable.init()) {
|
||||
VERIFY(variable.target().has<NonnullRefPtr<Identifier>>());
|
||||
auto& binding_id = variable.target().get<NonnullRefPtr<Identifier>>()->string();
|
||||
auto reference = TRY(interpreter.vm().resolve_binding(binding_id));
|
||||
auto result = TRY(interpreter.vm().named_evaluation_if_anonymous_function(*variable.init(), binding_id));
|
||||
TRY(reference.put_value(vm, result));
|
||||
if (is<VariableDeclaration>(ast_ptr->ptr())) {
|
||||
auto& variable_declaration = static_cast<VariableDeclaration const&>(*(*ast_ptr));
|
||||
VERIFY(variable_declaration.declarations().size() == 1);
|
||||
state.destructuring = variable_declaration.declarations().first().target().has<NonnullRefPtr<BindingPattern>>();
|
||||
if (variable_declaration.declaration_kind() == DeclarationKind::Var) {
|
||||
state.lhs_kind = ForInOfHeadState::VarBinding;
|
||||
auto& variable = variable_declaration.declarations().first();
|
||||
// B.3.5 Initializers in ForIn Statement Heads, https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads
|
||||
if (variable.init()) {
|
||||
VERIFY(variable.target().has<NonnullRefPtr<Identifier>>());
|
||||
auto& binding_id = variable.target().get<NonnullRefPtr<Identifier>>()->string();
|
||||
auto reference = TRY(interpreter.vm().resolve_binding(binding_id));
|
||||
auto result = TRY(interpreter.vm().named_evaluation_if_anonymous_function(*variable.init(), binding_id));
|
||||
TRY(reference.put_value(vm, result));
|
||||
}
|
||||
} else {
|
||||
state.lhs_kind = ForInOfHeadState::LexicalBinding;
|
||||
new_environment = new_declarative_environment(*interpreter.lexical_environment());
|
||||
variable_declaration.for_each_bound_name([&](auto const& name) {
|
||||
MUST(new_environment->create_mutable_binding(vm, name, false));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
VERIFY(is<UsingDeclaration>(ast_ptr->ptr()));
|
||||
auto& declaration = static_cast<UsingDeclaration const&>(*(*ast_ptr));
|
||||
state.lhs_kind = ForInOfHeadState::LexicalBinding;
|
||||
new_environment = new_declarative_environment(*interpreter.lexical_environment());
|
||||
variable_declaration.for_each_bound_name([&](auto const& name) {
|
||||
declaration.for_each_bound_name([&](auto const& name) {
|
||||
MUST(new_environment->create_mutable_binding(vm, name, false));
|
||||
});
|
||||
}
|
||||
|
@ -1096,16 +1168,24 @@ Completion ForInStatement::loop_evaluation(Interpreter& interpreter, Vector<Depr
|
|||
// l. Let result be the result of evaluating stmt.
|
||||
auto result = m_body->execute(interpreter);
|
||||
|
||||
// m. Set the running execution context's LexicalEnvironment to oldEnv.
|
||||
// NOTE: Because of optimizations we only create a new lexical environment if there are bindings
|
||||
// so we should only dispose if that is the case.
|
||||
if (vm.running_execution_context().lexical_environment != old_environment) {
|
||||
VERIFY(is<DeclarativeEnvironment>(vm.running_execution_context().lexical_environment));
|
||||
// m. Set result to DisposeResources(iterationEnv, result).
|
||||
result = dispose_resources(vm, static_cast<DeclarativeEnvironment*>(vm.running_execution_context().lexical_environment), result);
|
||||
}
|
||||
|
||||
// n. Set the running execution context's LexicalEnvironment to oldEnv.
|
||||
vm.running_execution_context().lexical_environment = old_environment;
|
||||
|
||||
// n. If LoopContinues(result, labelSet) is false, then
|
||||
// o. If LoopContinues(result, labelSet) is false, then
|
||||
if (!loop_continues(result, label_set)) {
|
||||
// 1. Return UpdateEmpty(result, V).
|
||||
return result.update_empty(last_value);
|
||||
}
|
||||
|
||||
// o. If result.[[Value]] is not empty, set V to result.[[Value]].
|
||||
// p. If result.[[Value]] is not empty, set V to result.[[Value]].
|
||||
if (result.value().has_value())
|
||||
last_value = *result.value();
|
||||
|
||||
|
@ -1154,6 +1234,11 @@ Completion ForOfStatement::loop_evaluation(Interpreter& interpreter, Vector<Depr
|
|||
// l. Let result be the result of evaluating stmt.
|
||||
auto result = m_body->execute(interpreter);
|
||||
|
||||
if (vm.running_execution_context().lexical_environment != old_environment) {
|
||||
VERIFY(is<DeclarativeEnvironment>(vm.running_execution_context().lexical_environment));
|
||||
result = dispose_resources(vm, static_cast<DeclarativeEnvironment*>(vm.running_execution_context().lexical_environment), result);
|
||||
}
|
||||
|
||||
// m. Set the running execution context's LexicalEnvironment to oldEnv.
|
||||
vm.running_execution_context().lexical_environment = old_environment;
|
||||
|
||||
|
|
|
@ -922,6 +922,8 @@ public:
|
|||
virtual Bytecode::CodeGenerationErrorOr<void> generate_labelled_evaluation(Bytecode::Generator&, Vector<DeprecatedFlyString> const&) const override;
|
||||
|
||||
private:
|
||||
Completion for_body_evaluation(Interpreter&, Vector<DeprecatedFlyString> const&, size_t per_iteration_bindings_size) const;
|
||||
|
||||
RefPtr<ASTNode> m_init;
|
||||
RefPtr<Expression> m_test;
|
||||
RefPtr<Expression> m_update;
|
||||
|
@ -1736,6 +1738,8 @@ public:
|
|||
|
||||
virtual bool is_lexical_declaration() const override { return true; }
|
||||
|
||||
NonnullRefPtrVector<VariableDeclarator> const& declarations() const { return m_declarations; }
|
||||
|
||||
private:
|
||||
NonnullRefPtrVector<VariableDeclarator> m_declarations;
|
||||
};
|
||||
|
|
|
@ -3573,7 +3573,38 @@ NonnullRefPtr<Statement> Parser::parse_for_statement()
|
|||
|
||||
RefPtr<ASTNode> init;
|
||||
if (!match(TokenType::Semicolon)) {
|
||||
if (match_variable_declaration()) {
|
||||
|
||||
auto match_for_using_declaration = [&] {
|
||||
if (!match(TokenType::Identifier) || m_state.current_token.original_value() != "using"sv)
|
||||
return false;
|
||||
|
||||
auto lookahead = next_token();
|
||||
if (lookahead.trivia_contains_line_terminator())
|
||||
return false;
|
||||
|
||||
if (lookahead.original_value() == "of"sv)
|
||||
return false;
|
||||
|
||||
return token_is_identifier(lookahead);
|
||||
};
|
||||
|
||||
if (match_for_using_declaration()) {
|
||||
auto declaration = parse_using_declaration(IsForLoopVariableDeclaration::Yes);
|
||||
|
||||
if (match_of(m_state.current_token)) {
|
||||
if (declaration->declarations().size() != 1)
|
||||
syntax_error("Must have exactly one declaration in for using of");
|
||||
else if (declaration->declarations().first().init())
|
||||
syntax_error("Using declaration cannot have initializer");
|
||||
|
||||
return parse_for_in_of_statement(move(declaration), is_await_loop);
|
||||
}
|
||||
|
||||
if (match(TokenType::In))
|
||||
syntax_error("Using declaration not allowed in for-in loop");
|
||||
|
||||
init = move(declaration);
|
||||
} else if (match_variable_declaration()) {
|
||||
auto declaration = parse_variable_declaration(IsForLoopVariableDeclaration::Yes);
|
||||
if (declaration->declaration_kind() == DeclarationKind::Var) {
|
||||
m_state.current_scope_pusher->add_declaration(declaration);
|
||||
|
@ -3586,15 +3617,22 @@ NonnullRefPtr<Statement> Parser::parse_for_statement()
|
|||
});
|
||||
}
|
||||
|
||||
init = move(declaration);
|
||||
if (match_for_in_of())
|
||||
return parse_for_in_of_statement(*init, is_await_loop);
|
||||
if (static_cast<VariableDeclaration&>(*init).declaration_kind() == DeclarationKind::Const) {
|
||||
for (auto& variable : static_cast<VariableDeclaration&>(*init).declarations()) {
|
||||
if (match_for_in_of()) {
|
||||
if (declaration->declarations().size() > 1)
|
||||
syntax_error("Multiple declarations not allowed in for..in/of");
|
||||
else if (declaration->declarations().size() < 1)
|
||||
syntax_error("Need exactly one variable declaration in for..in/of");
|
||||
|
||||
return parse_for_in_of_statement(move(declaration), is_await_loop);
|
||||
}
|
||||
if (declaration->declaration_kind() == DeclarationKind::Const) {
|
||||
for (auto const& variable : declaration->declarations()) {
|
||||
if (!variable.init())
|
||||
syntax_error("Missing initializer in 'const' variable declaration");
|
||||
}
|
||||
}
|
||||
|
||||
init = move(declaration);
|
||||
} else if (match_expression()) {
|
||||
auto lookahead_token = next_token();
|
||||
bool starts_with_async_of = match(TokenType::Async) && match_of(lookahead_token);
|
||||
|
@ -3641,11 +3679,8 @@ NonnullRefPtr<Statement> Parser::parse_for_in_of_statement(NonnullRefPtr<ASTNode
|
|||
|
||||
if (is<VariableDeclaration>(*lhs)) {
|
||||
auto& declaration = static_cast<VariableDeclaration&>(*lhs);
|
||||
if (declaration.declarations().size() > 1) {
|
||||
syntax_error("Multiple declarations not allowed in for..in/of");
|
||||
} else if (declaration.declarations().size() < 1) {
|
||||
syntax_error("Need exactly one variable declaration in for..in/of");
|
||||
} else {
|
||||
// Syntax errors for wrong amounts of declaration should have already been hit.
|
||||
if (!declaration.declarations().is_empty()) {
|
||||
// AnnexB extension B.3.5 Initializers in ForIn Statement Heads, https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads
|
||||
auto& variable = declaration.declarations().first();
|
||||
if (variable.init()) {
|
||||
|
@ -3655,7 +3690,7 @@ NonnullRefPtr<Statement> Parser::parse_for_in_of_statement(NonnullRefPtr<ASTNode
|
|||
has_annexB_for_in_init_extension = true;
|
||||
}
|
||||
}
|
||||
} else if (!lhs->is_identifier() && !is<MemberExpression>(*lhs) && !is<CallExpression>(*lhs)) {
|
||||
} else if (!lhs->is_identifier() && !is<MemberExpression>(*lhs) && !is<CallExpression>(*lhs) && !is<UsingDeclaration>(*lhs)) {
|
||||
bool valid = false;
|
||||
if (is<ObjectExpression>(*lhs) || is<ArrayExpression>(*lhs)) {
|
||||
auto synthesized_binding_pattern = synthesize_binding_pattern(static_cast<Expression const&>(*lhs));
|
||||
|
@ -3915,21 +3950,26 @@ bool Parser::match_variable_declaration() const
|
|||
|
||||
bool Parser::match_identifier() const
|
||||
{
|
||||
if (m_state.current_token.type() == TokenType::EscapedKeyword) {
|
||||
if (m_state.current_token.value() == "let"sv)
|
||||
return token_is_identifier(m_state.current_token);
|
||||
}
|
||||
|
||||
bool Parser::token_is_identifier(Token const& token) const
|
||||
{
|
||||
if (token.type() == TokenType::EscapedKeyword) {
|
||||
if (token.value() == "let"sv)
|
||||
return !m_state.strict_mode;
|
||||
if (m_state.current_token.value() == "yield"sv)
|
||||
if (token.value() == "yield"sv)
|
||||
return !m_state.strict_mode && !m_state.in_generator_function_context;
|
||||
if (m_state.current_token.value() == "await"sv)
|
||||
if (token.value() == "await"sv)
|
||||
return m_program_type != Program::Type::Module && !m_state.await_expression_is_valid && !m_state.in_class_static_init_block;
|
||||
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.await_expression_is_valid && !m_state.in_class_static_init_block)
|
||||
|| (m_state.current_token.type() == TokenType::Yield && !m_state.in_generator_function_context && !m_state.strict_mode); // See note in Parser::parse_identifier().
|
||||
return token.type() == TokenType::Identifier
|
||||
|| token.type() == TokenType::Async
|
||||
|| (token.type() == TokenType::Let && !m_state.strict_mode)
|
||||
|| (token.type() == TokenType::Await && m_program_type != Program::Type::Module && !m_state.await_expression_is_valid && !m_state.in_class_static_init_block)
|
||||
|| (token.type() == TokenType::Yield && !m_state.in_generator_function_context && !m_state.strict_mode); // See note in Parser::parse_identifier().
|
||||
}
|
||||
|
||||
bool Parser::match_identifier_name() const
|
||||
|
|
|
@ -227,6 +227,7 @@ private:
|
|||
bool try_match_using_declaration() const;
|
||||
bool match_variable_declaration() const;
|
||||
bool match_identifier() const;
|
||||
bool token_is_identifier(Token const&) const;
|
||||
bool match_identifier_name() const;
|
||||
bool match_property_key() const;
|
||||
bool is_private_identifier_valid() const;
|
||||
|
|
239
Userland/Libraries/LibJS/Tests/using-for-loops.js
Normal file
239
Userland/Libraries/LibJS/Tests/using-for-loops.js
Normal file
|
@ -0,0 +1,239 @@
|
|||
describe("basic usage", () => {
|
||||
test("using in normal for loop", () => {
|
||||
let isDisposed = false;
|
||||
let lastI = -1;
|
||||
for (
|
||||
using x = {
|
||||
i: 0,
|
||||
tick() {
|
||||
this.i++;
|
||||
},
|
||||
done() {
|
||||
return this.i === 3;
|
||||
},
|
||||
[Symbol.dispose]() {
|
||||
isDisposed = true;
|
||||
},
|
||||
};
|
||||
!x.done();
|
||||
x.tick()
|
||||
) {
|
||||
expect(isDisposed).toBeFalse();
|
||||
expect(x.i).toBeGreaterThan(lastI);
|
||||
lastI = x.i;
|
||||
}
|
||||
|
||||
expect(isDisposed).toBeTrue();
|
||||
expect(lastI).toBe(2);
|
||||
});
|
||||
|
||||
test("using in normal for loop with expression body", () => {
|
||||
let isDisposed = false;
|
||||
let outerI = 0;
|
||||
for (
|
||||
using x = {
|
||||
i: 0,
|
||||
tick() {
|
||||
this.i++;
|
||||
outerI++;
|
||||
},
|
||||
done() {
|
||||
return this.i === 3;
|
||||
},
|
||||
[Symbol.dispose]() {
|
||||
isDisposed = true;
|
||||
},
|
||||
};
|
||||
!x.done();
|
||||
x.tick()
|
||||
)
|
||||
expect(isDisposed).toBeFalse();
|
||||
|
||||
expect(isDisposed).toBeTrue();
|
||||
expect(outerI).toBe(3);
|
||||
});
|
||||
|
||||
test("using in for of loop", () => {
|
||||
const disposable = [];
|
||||
const values = [];
|
||||
|
||||
function createDisposable(value) {
|
||||
return {
|
||||
value: value,
|
||||
[Symbol.dispose]() {
|
||||
expect(this.value).toBe(value);
|
||||
disposable.push(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (using a of [createDisposable('a'), createDisposable('b'), createDisposable('c')]) {
|
||||
expect(disposable).toEqual(values);
|
||||
values.push(a.value);
|
||||
}
|
||||
|
||||
expect(disposable).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
test("using in for of loop with expression body", () => {
|
||||
let disposableCalls = 0;
|
||||
let i = 0;
|
||||
|
||||
const obj = {
|
||||
[Symbol.dispose]() {
|
||||
disposableCalls++;
|
||||
}
|
||||
};
|
||||
|
||||
for (using a of [obj, obj, obj])
|
||||
expect(disposableCalls).toBe(i++);
|
||||
|
||||
expect(disposableCalls).toBe(3);
|
||||
});
|
||||
|
||||
test("can have multiple declaration in normal for loop", () => {
|
||||
let disposed = 0;
|
||||
const a = {
|
||||
[Symbol.dispose]() {
|
||||
disposed++;
|
||||
}
|
||||
}
|
||||
|
||||
expect(disposed).toBe(0);
|
||||
for (using b = a, c = a; false;)
|
||||
expect().fail();
|
||||
|
||||
expect(disposed).toBe(2);
|
||||
});
|
||||
|
||||
test("can have using in block in for loop", () => {
|
||||
const disposed = [];
|
||||
const values = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
using a = {
|
||||
val: i,
|
||||
[Symbol.dispose]() {
|
||||
expect(i).toBe(this.val);
|
||||
disposed.push(i);
|
||||
},
|
||||
};
|
||||
expect(disposed).toEqual(values);
|
||||
values.push(i);
|
||||
}
|
||||
expect(disposed).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
test("can have using in block in for-in loop", () => {
|
||||
const disposed = [];
|
||||
const values = [];
|
||||
for (const i in ['a', 'b', 'c']) {
|
||||
using a = {
|
||||
val: i,
|
||||
[Symbol.dispose]() {
|
||||
expect(i).toBe(this.val);
|
||||
disposed.push(i);
|
||||
},
|
||||
};
|
||||
expect(disposed).toEqual(values);
|
||||
values.push(i);
|
||||
}
|
||||
expect(disposed).toEqual(["0", "1", "2"]);
|
||||
});
|
||||
|
||||
test("dispose is called even if throw in for of loop", () => {
|
||||
let disposableCalls = 0;
|
||||
|
||||
const obj = {
|
||||
[Symbol.dispose]() {
|
||||
expect()
|
||||
disposableCalls++;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
for (using a of [obj])
|
||||
throw new ExpectationError("Expected in for-of");
|
||||
|
||||
expect().fail("Should have thrown");
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(ExpectationError);
|
||||
expect(e.message).toBe("Expected in for-of");
|
||||
expect(disposableCalls).toBe(1);
|
||||
}
|
||||
|
||||
expect(disposableCalls).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("using is still a valid variable in loops", () => {
|
||||
test("for loops var", () => {
|
||||
let enteredLoop = false;
|
||||
for (var using = 1; using < 2; using++) {
|
||||
enteredLoop = true;
|
||||
}
|
||||
expect(enteredLoop).toBeTrue();
|
||||
});
|
||||
|
||||
test("for loops const", () => {
|
||||
let enteredLoop = false;
|
||||
for (const using = 1; using < 2; ) {
|
||||
enteredLoop = true;
|
||||
break;
|
||||
}
|
||||
expect(enteredLoop).toBeTrue();
|
||||
});
|
||||
|
||||
test("for loops let", () => {
|
||||
let enteredLoop = false;
|
||||
for (let using = 1; using < 2; using++) {
|
||||
enteredLoop = true;
|
||||
}
|
||||
expect(enteredLoop).toBeTrue();
|
||||
});
|
||||
|
||||
test("using in", () => {
|
||||
let enteredLoop = false;
|
||||
for (using in [1]) {
|
||||
enteredLoop = true;
|
||||
expect(using).toBe("0");
|
||||
}
|
||||
expect(enteredLoop).toBeTrue();
|
||||
});
|
||||
|
||||
test("using of", () => {
|
||||
let enteredLoop = false;
|
||||
for (using of [1]) {
|
||||
enteredLoop = true;
|
||||
expect(using).toBe(1);
|
||||
}
|
||||
expect(enteredLoop).toBeTrue();
|
||||
});
|
||||
|
||||
test("using using of", () => {
|
||||
let enteredLoop = false;
|
||||
for (using using of [null]) {
|
||||
enteredLoop = true;
|
||||
expect(using).toBeNull();
|
||||
}
|
||||
expect(enteredLoop).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe("syntax errors", () => {
|
||||
test("cannot have using as for loop body", () => {
|
||||
expect("for (;;) using a = {};").not.toEval();
|
||||
expect("for (x in []) using a = {};").not.toEval();
|
||||
expect("for (x of []) using a = {};").not.toEval();
|
||||
});
|
||||
|
||||
test("must have one declaration without initializer in for loop", () => {
|
||||
expect("for (using x = {} of []) {}").not.toEval();
|
||||
expect("for (using x, y of []) {}").not.toEval();
|
||||
});
|
||||
|
||||
test("cannot have using in for-in loop", () => {
|
||||
expect("for (using x in []) {}").not.toEval();
|
||||
expect("for (using of in []) {}").not.toEval();
|
||||
expect("for (using in of []) {}").not.toEval();
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue