Skip to content

Commit

Permalink
LibJS: Add using declaration support in for and for of loops
Browse files Browse the repository at this point in the history
The using declarations have kind of special behavior in for loops so
this is seperated.
  • Loading branch information
davidot committed Dec 23, 2022
1 parent 87a43e4 commit 5053ef7
Show file tree
Hide file tree
Showing 6 changed files with 443 additions and 75 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Expand Up @@ -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
188 changes: 135 additions & 53 deletions Userland/Libraries/LibJS/AST.cpp
Expand Up @@ -764,39 +764,66 @@ Completion ForStatement::loop_evaluation(Interpreter& interpreter, Vector<FlyStr

// 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<FlyString> 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());
Expand All @@ -820,49 +847,66 @@ Completion ForStatement::loop_evaluation(Interpreter& interpreter, Vector<FlyStr
// 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));
}
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -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);
FlyString 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).
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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));
});
}
Expand Down Expand Up @@ -1096,6 +1168,11 @@ Completion ForInStatement::loop_evaluation(Interpreter& interpreter, Vector<FlyS
// 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;

Expand Down Expand Up @@ -1154,6 +1231,11 @@ Completion ForOfStatement::loop_evaluation(Interpreter& interpreter, Vector<FlyS
// 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;

Expand Down
4 changes: 4 additions & 0 deletions Userland/Libraries/LibJS/AST.h
Expand Up @@ -922,6 +922,8 @@ class ForStatement final : public IterationStatement {
virtual Bytecode::CodeGenerationErrorOr<void> generate_labelled_evaluation(Bytecode::Generator&, Vector<FlyString> const&) const override;

private:
Completion for_body_evaluation(Interpreter&, Vector<FlyString> const&, size_t per_iteration_bindings_size) const;

RefPtr<ASTNode> m_init;
RefPtr<Expression> m_test;
RefPtr<Expression> m_update;
Expand Down Expand Up @@ -1736,6 +1738,8 @@ class UsingDeclaration final : public Declaration {

virtual bool is_lexical_declaration() const override { return true; }

NonnullRefPtrVector<VariableDeclarator> const& declarations() const { return m_declarations; }

private:
NonnullRefPtrVector<VariableDeclarator> m_declarations;
};
Expand Down

0 comments on commit 5053ef7

Please sign in to comment.