Ver código fonte

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.
davidot 2 anos atrás
pai
commit
bff038411a

+ 1 - 0
.prettierignore

@@ -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

+ 141 - 56
Userland/Libraries/LibJS/AST.cpp

@@ -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;
 

+ 4 - 0
Userland/Libraries/LibJS/AST.h

@@ -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;
 };

+ 61 - 21
Userland/Libraries/LibJS/Parser.cpp

@@ -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

+ 1 - 0
Userland/Libraries/LibJS/Parser.h

@@ -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 - 0
Userland/Libraries/LibJS/Tests/using-for-loops.js

@@ -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();
+    });
+});