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:
davidot 2022-12-23 01:45:29 +01:00 committed by Linus Groh
parent 541637e15a
commit bff038411a
Notes: sideshowbarker 2024-07-17 01:21:41 +09:00
6 changed files with 447 additions and 77 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

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