diff --git a/test/language/statements/await-using/Symbol.asyncDispose-getter.js b/test/language/statements/await-using/Symbol.asyncDispose-getter.js new file mode 100644 index 00000000000..665e8a9867a --- /dev/null +++ b/test/language/statements/await-using/Symbol.asyncDispose-getter.js @@ -0,0 +1,89 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Invokes [Symbol.asyncDispose] getter +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + 2. Else, + ... + 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + 4. Return unused. + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + ... + b. Else, + i. If V is not an Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + ... + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + + GetMethod ( V, P ) + + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. ... + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + get [Symbol.asyncDispose]() { + return async function () { + this.disposed = true; + }; + } + }; + + { + await using _ = resource; + } + + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/Symbol.asyncDispose-method-called-with-correct-this.js b/test/language/statements/await-using/Symbol.asyncDispose-method-called-with-correct-this.js new file mode 100644 index 00000000000..3f2ac116377 --- /dev/null +++ b/test/language/statements/await-using/Symbol.asyncDispose-method-called-with-correct-this.js @@ -0,0 +1,54 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-block-runtime-semantics-evaluation +description: Initialized value is disposed with the correct 'this' value +info: | + RS: Evaluation + Block : { StatementList } + + ... + 5. Let blockValue be the result of evaluating StatementList. + 6. Set blockValue to DisposeResources(blockEnv.[[DisposeCapability]], blockValue). + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + ... + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + async [Symbol.asyncDispose]() { + assert.sameValue(this, resource); + } + }; + + { + await using _ = resource; + } +}); diff --git a/test/language/statements/await-using/Symbol.asyncDispose-method-not-async.js b/test/language/statements/await-using/Symbol.asyncDispose-method-not-async.js new file mode 100644 index 00000000000..d81b461337b --- /dev/null +++ b/test/language/statements/await-using/Symbol.asyncDispose-method-not-async.js @@ -0,0 +1,58 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-block-runtime-semantics-evaluation +description: Disposal succeeds even if [Symbol.disposeAsync] does not return a Promise. +info: | + RS: Evaluation + Block : { StatementList } + + ... + 5. Let blockValue be the result of evaluating StatementList. + 6. Set blockValue to DisposeResources(blockEnv.[[DisposeCapability]], blockValue). + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. ... + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + [Symbol.asyncDispose]() { + this.disposed = true; + } + }; + + { + await using _ = resource; + } + + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/Symbol.dispose-getter.js b/test/language/statements/await-using/Symbol.dispose-getter.js new file mode 100644 index 00000000000..19f8e3f6f43 --- /dev/null +++ b/test/language/statements/await-using/Symbol.dispose-getter.js @@ -0,0 +1,89 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Invokes [Symbol.dispose] getter +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + 2. Else, + ... + 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + 4. Return unused. + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + ... + b. Else, + i. If V is not an Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + ... + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + + GetMethod ( V, P ) + + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. ... + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + get [Symbol.dispose]() { + return function() { + this.disposed = true; + }; + } + }; + + { + await using _ = resource; + } + + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/Symbol.dispose-method-called-with-correct-this.js b/test/language/statements/await-using/Symbol.dispose-method-called-with-correct-this.js new file mode 100644 index 00000000000..71003d2e0a5 --- /dev/null +++ b/test/language/statements/await-using/Symbol.dispose-method-called-with-correct-this.js @@ -0,0 +1,54 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-block-runtime-semantics-evaluation +description: Initialized value is disposed with the correct 'this' value +info: | + RS: Evaluation + Block : { StatementList } + + ... + 5. Let blockValue be the result of evaluating StatementList. + 6. Set blockValue to DisposeResources(blockEnv.[[DisposeCapability]], blockValue). + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + ... + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function() { + var resource = { + disposed: false, + [Symbol.dispose]() { + assert.sameValue(this, resource); + } + }; + + { + await using _ = resource; + } +}); diff --git a/test/language/statements/await-using/await-using-Symbol.asyncDispose-allows-non-promise-return-value.js b/test/language/statements/await-using/await-using-Symbol.asyncDispose-allows-non-promise-return-value.js new file mode 100644 index 00000000000..1b2c95ca358 --- /dev/null +++ b/test/language/statements/await-using/await-using-Symbol.asyncDispose-allows-non-promise-return-value.js @@ -0,0 +1,47 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-disposeresources +description: > + `await using` allows a non-Promise return value from `[Symbol.asyncDispose]()` +info: | + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + [Symbol.asyncDispose]() { + } + }; + + { + await using _ = resource; + } +}); diff --git a/test/language/statements/await-using/await-using-Symbol.asyncDispose-allows-promiselike-return-value.js b/test/language/statements/await-using/await-using-Symbol.asyncDispose-allows-promiselike-return-value.js new file mode 100644 index 00000000000..1a4840e8ce6 --- /dev/null +++ b/test/language/statements/await-using/await-using-Symbol.asyncDispose-allows-promiselike-return-value.js @@ -0,0 +1,52 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-disposeresources +description: > + `await using` allows non-native `Promise`-like return value from `[Symbol.asyncDispose]()` +info: | + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + [Symbol.asyncDispose]() { + return { + then(resolve) { + resolve(); + } + }; + } + }; + + { + await using _ = resource; + } +}); diff --git a/test/language/statements/await-using/await-using-allows-null-initializer.js b/test/language/statements/await-using/await-using-allows-null-initializer.js new file mode 100644 index 00000000000..aff4f9ef5ca --- /dev/null +++ b/test/language/statements/await-using/await-using-allows-null-initializer.js @@ -0,0 +1,46 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Allows null in initializer of 'await using' +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + ... + ... + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await using x = null; +}); diff --git a/test/language/statements/await-using/await-using-allows-undefined-initializer.js b/test/language/statements/await-using/await-using-allows-undefined-initializer.js new file mode 100644 index 00000000000..ae2b7f822d1 --- /dev/null +++ b/test/language/statements/await-using/await-using-allows-undefined-initializer.js @@ -0,0 +1,46 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Allows undefined in initializer of 'await using' +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + ... + ... + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + using x = undefined; +}); diff --git a/test/language/statements/await-using/await-using-does-not-imply-await-if-not-evaluated.js b/test/language/statements/await-using/await-using-does-not-imply-await-if-not-evaluated.js new file mode 100644 index 00000000000..ae9fc7b369a --- /dev/null +++ b/test/language/statements/await-using/await-using-does-not-imply-await-if-not-evaluated.js @@ -0,0 +1,102 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: An 'await using' does not imply an Await occurs if the statement is not evaluated +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + ... + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + i. Set V to undefined. + ii. Set method to undefined. + ... + ... + 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }. + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var BREAK_EARLY = true; + var isRunningInSameMicrotask = true; + var wasStartedInSameMicrotask = false; + var didEvaluatePrecedingBlockStatementsInSameMicrotask = false; + var wasRunningInSameMicrotask = false; + + async function f() { + wasStartedInSameMicrotask = isRunningInSameMicrotask; + outer: { + didEvaluatePrecedingBlockStatementsInSameMicrotask = isRunningInSameMicrotask; + if (BREAK_EARLY) break outer; + await using _ = null; + } + wasRunningInSameMicrotask = isRunningInSameMicrotask; + } + + var p = f(); + isRunningInSameMicrotask = false; + await p; + + assert.sameValue(wasStartedInSameMicrotask, true, 'Expected async function containing `await using` to start in the same microtask'); + assert.sameValue(didEvaluatePrecedingBlockStatementsInSameMicrotask, true, 'Expected block statements preceding `await using` to be evaluated in the same microtask'); + assert.sameValue(wasRunningInSameMicrotask, true, 'Expected statements following the block containing unevaluated `await using` to evaluate in the same microtask'); +}); diff --git a/test/language/statements/await-using/await-using-implies-await-if-evaluated.js b/test/language/statements/await-using/await-using-implies-await-if-evaluated.js new file mode 100644 index 00000000000..354dadfbf3c --- /dev/null +++ b/test/language/statements/await-using/await-using-implies-await-if-evaluated.js @@ -0,0 +1,103 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: An 'await using' implies an Await occurs if the statement is evaluated, even if all initializers are 'null' or 'undefined' +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + ... + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + i. Set V to undefined. + ii. Set method to undefined. + ... + ... + 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }. + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var isRunningInSameMicrotask = true; + var wasStartedInSameMicrotask = false; + var didEvaluatePrecedingBlockStatementsInSameMicrotask = false; + var didEvaluateFollowingBlockStatementsInSameMicrotask = false; + var wasRunningInSameMicrotask = false; + + async function f() { + wasStartedInSameMicrotask = isRunningInSameMicrotask; + { + didEvaluatePrecedingBlockStatementsInSameMicrotask = isRunningInSameMicrotask; + await using _ = null; + didEvaluateFollowingBlockStatementsInSameMicrotask = isRunningInSameMicrotask; + } + wasRunningInSameMicrotask = isRunningInSameMicrotask; + } + + var p = f(); + isRunningInSameMicrotask = false; + await p; + + assert.sameValue(wasStartedInSameMicrotask, true, 'Expected async function containing `await using` to start in the same microtask'); + assert.sameValue(didEvaluatePrecedingBlockStatementsInSameMicrotask, true, 'Expected block statements preceding `await using` to be evaluated in the same microtask'); + assert.sameValue(didEvaluateFollowingBlockStatementsInSameMicrotask, true, 'Expected block statements following `await using` to be evaluated in the same microtask'); + assert.sameValue(wasRunningInSameMicrotask, false, 'Expected statements following the block containing evaluated `await using` to evaluate in a different microtask'); +}); diff --git a/test/language/statements/await-using/block-local-closure-get-before-initialization.js b/test/language/statements/await-using/block-local-closure-get-before-initialization.js new file mode 100644 index 00000000000..ac70f432941 --- /dev/null +++ b/test/language/statements/await-using/block-local-closure-get-before-initialization.js @@ -0,0 +1,22 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-declarative-environment-records-getbindingvalue-n-s +description: > + await using: block local closure [[Get]] before initialization. + (TDZ, Temporal Dead Zone) +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + function f() { return x + 1; } + + assert.throws(ReferenceError, function() { + f(); + }); + + await using x = null; +}); diff --git a/test/language/statements/await-using/block-local-use-before-initialization-in-declaration-statement.js b/test/language/statements/await-using/block-local-use-before-initialization-in-declaration-statement.js new file mode 100644 index 00000000000..a059f4b246b --- /dev/null +++ b/test/language/statements/await-using/block-local-use-before-initialization-in-declaration-statement.js @@ -0,0 +1,18 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-declarative-environment-records-getbindingvalue-n-s +description: > + await using: block local use before initialization in declaration statement. + (TDZ, Temporal Dead Zone) +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await assert.throwsAsync(ReferenceError, async function() { + await using x = x + 1; + }); +}); diff --git a/test/language/statements/await-using/block-local-use-before-initialization-in-prior-statement.js b/test/language/statements/await-using/block-local-use-before-initialization-in-prior-statement.js new file mode 100644 index 00000000000..769084b052c --- /dev/null +++ b/test/language/statements/await-using/block-local-use-before-initialization-in-prior-statement.js @@ -0,0 +1,18 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-declarative-environment-records-getbindingvalue-n-s +description: > + await using: block local use before initialization in prior statement. + (TDZ, Temporal Dead Zone) +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await assert.throwsAsync(ReferenceError, async function() { + x; await using x = null; + }); +}); diff --git a/test/language/statements/await-using/fn-name-arrow.js b/test/language/statements/await-using/fn-name-arrow.js new file mode 100644 index 00000000000..b9d10389311 --- /dev/null +++ b/test/language/statements/await-using/fn-name-arrow.js @@ -0,0 +1,28 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Assignment of function `name` attribute (ArrowFunction) +info: | + LexicalBinding : BindingIdentifier Initializer + + ... + 3. If IsAnonymousFunctionDefinition(Initializer) is true, then + a. Let value be NamedEvaluation of Initializer with argument bindingId + +flags: [async] +includes: [propertyHelper.js, asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +// NOTE: only way to verify is to patch `Function.prototype` so as not to trigger a TypeError from AddDisposableResource +Function.prototype[Symbol.dispose] = function () {} +asyncTest(async function () { + await using arrow = () => {}; + + assert.sameValue(arrow.name, 'arrow'); + verifyNotEnumerable(arrow, 'name'); + verifyNotWritable(arrow, 'name'); + verifyConfigurable(arrow, 'name'); +}); diff --git a/test/language/statements/await-using/fn-name-class.js b/test/language/statements/await-using/fn-name-class.js new file mode 100644 index 00000000000..61b862164ac --- /dev/null +++ b/test/language/statements/await-using/fn-name-class.js @@ -0,0 +1,31 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Assignment of function `name` attribute (ClassExpression) +info: | + LexicalBinding : BindingIdentifier Initializer + + ... + 3. If IsAnonymousFunctionDefinition(Initializer) is true, then + a. Let value be NamedEvaluation of Initializer with argument bindingId + +flags: [async] +includes: [propertyHelper.js, asyncHelpers.js] +features: [class, explicit-resource-management] +---*/ + +asyncTest(async function () { + await using xCls = class x { static async [Symbol.asyncDispose]() {} }; + await using cls = class { static async [Symbol.asyncDispose]() {} }; + await using xCls2 = class { static name() {} static async [Symbol.asyncDispose]() {} }; + + assert.notSameValue(xCls.name, 'xCls'); + assert.notSameValue(xCls2.name, 'xCls2'); + + assert.sameValue(cls.name, 'cls'); + verifyNotEnumerable(cls, 'name'); + verifyNotWritable(cls, 'name'); + verifyConfigurable(cls, 'name'); +}); diff --git a/test/language/statements/await-using/fn-name-cover.js b/test/language/statements/await-using/fn-name-cover.js new file mode 100644 index 00000000000..224bcd54f34 --- /dev/null +++ b/test/language/statements/await-using/fn-name-cover.js @@ -0,0 +1,32 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: > + Assignment of function `name` attribute (CoverParenthesizedExpression) +info: | + LexicalBinding : BindingIdentifier Initializer + + ... + 3. If IsAnonymousFunctionDefinition(Initializer) is true, then + a. Let value be NamedEvaluation of Initializer with argument bindingId + +flags: [async] +includes: [propertyHelper.js, asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +// NOTE: only way to verify is to patch `Function.prototype` so as not to trigger a TypeError from AddDisposableResource +Function.prototype[Symbol.dispose] = function () {} +asyncTest(async function () { + await using xCover = (0, function() {}); + await using cover = (function() {}); + + assert(xCover.name !== 'xCover'); + + assert.sameValue(cover.name, 'cover'); + verifyNotEnumerable(cover, 'name'); + verifyNotWritable(cover, 'name'); + verifyConfigurable(cover, 'name'); +}); diff --git a/test/language/statements/await-using/fn-name-fn.js b/test/language/statements/await-using/fn-name-fn.js new file mode 100644 index 00000000000..749c3e2e027 --- /dev/null +++ b/test/language/statements/await-using/fn-name-fn.js @@ -0,0 +1,31 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Assignment of function `name` attribute (FunctionExpression) +info: | + LexicalBinding : BindingIdentifier Initializer + + ... + 3. If IsAnonymousFunctionDefinition(Initializer) is true, then + a. Let value be NamedEvaluation of Initializer with argument bindingId + +flags: [async] +includes: [propertyHelper.js, asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +// NOTE: only way to verify is to patch `Function.prototype` so as not to trigger a TypeError from AddDisposableResource +Function.prototype[Symbol.dispose] = function () {} +asyncTest(async function() { + await using xFn = function x() {}; + await using fn = function() {}; + + assert(xFn.name !== 'xFn'); + + assert.sameValue(fn.name, 'fn'); + verifyNotEnumerable(fn, 'name'); + verifyNotWritable(fn, 'name'); + verifyConfigurable(fn, 'name'); +}); diff --git a/test/language/statements/await-using/fn-name-gen.js b/test/language/statements/await-using/fn-name-gen.js new file mode 100644 index 00000000000..929b2bedc1e --- /dev/null +++ b/test/language/statements/await-using/fn-name-gen.js @@ -0,0 +1,31 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Assignment of function `name` attribute (GeneratorExpression) +info: | + LexicalBinding : BindingIdentifier Initializer + + ... + 3. If IsAnonymousFunctionDefinition(Initializer) is true, then + a. Let value be NamedEvaluation of Initializer with argument bindingId + +flags: [async] +includes: [propertyHelper.js, asyncHelpers.js] +features: [generators,explicit-resource-management] +---*/ + +// NOTE: only way to verify is to patch `Function.prototype` so as not to trigger a TypeError from AddDisposableResource +Function.prototype[Symbol.dispose] = function () {} +asyncTest(async function() { + await using xGen = function* x() {}; + await using gen = function*() {}; + + assert(xGen.name !== 'xGen'); + + assert.sameValue(gen.name, 'gen'); + verifyNotEnumerable(gen, 'name'); + verifyNotWritable(gen, 'name'); + verifyConfigurable(gen, 'name'); +}); diff --git a/test/language/statements/await-using/function-local-closure-get-before-initialization.js b/test/language/statements/await-using/function-local-closure-get-before-initialization.js new file mode 100644 index 00000000000..f34a7a85f94 --- /dev/null +++ b/test/language/statements/await-using/function-local-closure-get-before-initialization.js @@ -0,0 +1,23 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-declarative-environment-records-getbindingvalue-n-s +description: > + await using: function local closure [[Get]] before initialization. + (TDZ, Temporal Dead Zone) + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function() { + function f() { return x + 1; } + + assert.throws(ReferenceError, function() { + f(); + }); + + await using x = null; +}); diff --git a/test/language/statements/await-using/function-local-use-before-initialization-in-declaration-statement.js b/test/language/statements/await-using/function-local-use-before-initialization-in-declaration-statement.js new file mode 100644 index 00000000000..c8988e3bef2 --- /dev/null +++ b/test/language/statements/await-using/function-local-use-before-initialization-in-declaration-statement.js @@ -0,0 +1,17 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-declarative-environment-records-getbindingvalue-n-s +description: > + await using: function local use before initialization in declaration statement. + (TDZ, Temporal Dead Zone) +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ +asyncTest(async function () { + await assert.throwsAsync(ReferenceError, async function() { + await using x = x + 1; + }); +}); diff --git a/test/language/statements/await-using/function-local-use-before-initialization-in-prior-statement.js b/test/language/statements/await-using/function-local-use-before-initialization-in-prior-statement.js new file mode 100644 index 00000000000..c7081a5e600 --- /dev/null +++ b/test/language/statements/await-using/function-local-use-before-initialization-in-prior-statement.js @@ -0,0 +1,18 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-declarative-environment-records-getbindingvalue-n-s +description: > + await using: function local use before initialization in prior statement. + (TDZ, Temporal Dead Zone) +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await assert.throwsAsync(ReferenceError, async function() { + x; await using x = null; + }); +}); diff --git a/test/language/statements/await-using/gets-initializer-Symbol.asyncDispose-property-once.js b/test/language/statements/await-using/gets-initializer-Symbol.asyncDispose-property-once.js new file mode 100644 index 00000000000..fefd6f35bc5 --- /dev/null +++ b/test/language/statements/await-using/gets-initializer-Symbol.asyncDispose-property-once.js @@ -0,0 +1,88 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Only reads `[Symbol.asyncDispose]` method once, when initialized. +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + BindingList : BindingList , LexicalBinding + + 1. Perform ? BindingEvaluation of BindingList with argument hint. + 2. Perform ? BindingEvaluation of LexicalBinding with argument hint. + 3. Return unused. + + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused + b. Let resource be ? CreateDisposableResource(V, hint). + ... + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + i. Set V to undefined + ii. Set method to undefined + b. Else, + i. If Type(V) is not Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + 2. Else, + a. ... + 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }. + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + asyncDisposeReadCount: 0, + get [Symbol.asyncDispose]() { + this.asyncDisposeReadCount++; + return async function() { }; + } + }; + + { + await using _ = resource; + } + + assert.sameValue(resource.asyncDisposeReadCount, 1, 'Expected [Symbol.asyncDispose] to have been read only once'); +}); diff --git a/test/language/statements/await-using/gets-initializer-Symbol.dispose-after-Symbol.asyncDispose-is-null.js b/test/language/statements/await-using/gets-initializer-Symbol.dispose-after-Symbol.asyncDispose-is-null.js new file mode 100644 index 00000000000..3f7b8e8d46b --- /dev/null +++ b/test/language/statements/await-using/gets-initializer-Symbol.dispose-after-Symbol.asyncDispose-is-null.js @@ -0,0 +1,92 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Reads `[Symbol.dispose]` method if `[Symbol.asyncDispose]` is null +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + BindingList : BindingList , LexicalBinding + + 1. Perform ? BindingEvaluation of BindingList with argument hint. + 2. Perform ? BindingEvaluation of LexicalBinding with argument hint. + 3. Return unused. + + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused + b. Let resource be ? CreateDisposableResource(V, hint). + ... + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + i. Set V to undefined + ii. Set method to undefined + b. Else, + i. If Type(V) is not Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + 2. Else, + a. ... + 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }. + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + +flags: [async] +includes: [asyncHelpers.js, deepEqual.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var order = []; + var resource = { + get [Symbol.asyncDispose]() { + order.push('Symbol.asyncDispose'); + return null; + }, + get [Symbol.dispose]() { + order.push('Symbol.dispose'); + return function() { }; + } + }; + + { + await using _ = resource; + } + + assert.deepEqual(order, ['Symbol.asyncDispose', 'Symbol.dispose'], 'Expected [Symbol.dispose] to have been read after [Symbol.asyncDispose] returns null'); +}); diff --git a/test/language/statements/await-using/gets-initializer-Symbol.dispose-after-Symbol.asyncDispose-is-undefined.js b/test/language/statements/await-using/gets-initializer-Symbol.dispose-after-Symbol.asyncDispose-is-undefined.js new file mode 100644 index 00000000000..466d17be6d1 --- /dev/null +++ b/test/language/statements/await-using/gets-initializer-Symbol.dispose-after-Symbol.asyncDispose-is-undefined.js @@ -0,0 +1,92 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Reads `[Symbol.dispose]` method if `[Symbol.asyncDispose]` is undefined. +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + BindingList : BindingList , LexicalBinding + + 1. Perform ? BindingEvaluation of BindingList with argument hint. + 2. Perform ? BindingEvaluation of LexicalBinding with argument hint. + 3. Return unused. + + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused + b. Let resource be ? CreateDisposableResource(V, hint). + ... + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + i. Set V to undefined + ii. Set method to undefined + b. Else, + i. If Type(V) is not Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + 2. Else, + a. ... + 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }. + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + +flags: [async] +includes: [asyncHelpers.js, deepEqual.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var order = []; + var resource = { + get [Symbol.asyncDispose]() { + order.push('Symbol.asyncDispose'); + return undefined; + }, + get [Symbol.dispose]() { + order.push('Symbol.dispose'); + return function() { }; + } + }; + + { + await using _ = resource; + } + + assert.deepEqual(order, ['Symbol.asyncDispose', 'Symbol.dispose'], 'Expected [Symbol.dispose] to have been read after [Symbol.asyncDispose] returns undefined'); +}); diff --git a/test/language/statements/await-using/gets-initializer-Symbol.dispose-property-once.js b/test/language/statements/await-using/gets-initializer-Symbol.dispose-property-once.js new file mode 100644 index 00000000000..4ef0f130512 --- /dev/null +++ b/test/language/statements/await-using/gets-initializer-Symbol.dispose-property-once.js @@ -0,0 +1,88 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Only reads `[Symbol.dispose]` method once, when initialized. +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + BindingList : BindingList , LexicalBinding + + 1. Perform ? BindingEvaluation of BindingList with argument hint. + 2. Perform ? BindingEvaluation of LexicalBinding with argument hint. + 3. Return unused. + + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused + b. Let resource be ? CreateDisposableResource(V, hint). + ... + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + i. Set V to undefined + ii. Set method to undefined + b. Else, + i. If Type(V) is not Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + 2. Else, + a. ... + 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }. + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposeReadCount: 0, + get [Symbol.dispose]() { + this.disposeReadCount++; + return function() { }; + } + }; + + { + await using _ = resource; + } + + assert.sameValue(resource.disposeReadCount, 1, 'Expected [Symbol.dispose] to have been read only once'); +}); diff --git a/test/language/statements/await-using/gets-initializer-does-not-read-Symbol.dispose-if-Symbol.asyncDispose-exists.js b/test/language/statements/await-using/gets-initializer-does-not-read-Symbol.dispose-if-Symbol.asyncDispose-exists.js new file mode 100644 index 00000000000..261638c3cb7 --- /dev/null +++ b/test/language/statements/await-using/gets-initializer-does-not-read-Symbol.dispose-if-Symbol.asyncDispose-exists.js @@ -0,0 +1,92 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Does not read `[Symbol.dispose]` if `[Symbol.asyncDispose]` is neither null or undefined. +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + BindingList : BindingList , LexicalBinding + + 1. Perform ? BindingEvaluation of BindingList with argument hint. + 2. Perform ? BindingEvaluation of LexicalBinding with argument hint. + 3. Return unused. + + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused + b. Let resource be ? CreateDisposableResource(V, hint). + ... + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + i. Set V to undefined + ii. Set method to undefined + b. Else, + i. If Type(V) is not Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + 2. Else, + a. ... + 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }. + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + +flags: [async] +includes: [asyncHelpers.js, deepEqual.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var order = []; + var resource = { + get [Symbol.asyncDispose]() { + order.push('Symbol.asyncDispose'); + return async function() {}; + }, + get [Symbol.dispose]() { + order.push('Symbol.dispose'); + return function() { }; + } + }; + + { + await using _ = resource; + } + + assert.deepEqual(order, ['Symbol.asyncDispose'], 'Expected [Symbol.dispose] to not have been read'); +}); diff --git a/test/language/statements/await-using/global-closure-get-before-initialization.js b/test/language/statements/await-using/global-closure-get-before-initialization.js new file mode 100644 index 00000000000..fd72301de80 --- /dev/null +++ b/test/language/statements/await-using/global-closure-get-before-initialization.js @@ -0,0 +1,22 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-declarative-environment-records-getbindingvalue-n-s +description: > + await using: global closure [[Get]] before initialization. + (TDZ, Temporal Dead Zone) +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function() { + function f() { return x + 1; } + + assert.throws(ReferenceError, function() { + f(); + }); + + await using x = null; +}); diff --git a/test/language/statements/await-using/global-use-before-initialization-in-declaration-statement.js b/test/language/statements/await-using/global-use-before-initialization-in-declaration-statement.js new file mode 100644 index 00000000000..db3a9c53e27 --- /dev/null +++ b/test/language/statements/await-using/global-use-before-initialization-in-declaration-statement.js @@ -0,0 +1,19 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-declarative-environment-records-getbindingvalue-n-s +description: > + await using: global use before initialization in declaration statement. + (TDZ, Temporal Dead Zone) +negative: + phase: runtime + type: ReferenceError +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await using x = x + 1; +}); diff --git a/test/language/statements/await-using/global-use-before-initialization-in-prior-statement.js b/test/language/statements/await-using/global-use-before-initialization-in-prior-statement.js new file mode 100644 index 00000000000..2ddf9bf6718 --- /dev/null +++ b/test/language/statements/await-using/global-use-before-initialization-in-prior-statement.js @@ -0,0 +1,19 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-declarative-environment-records-getbindingvalue-n-s +description: > + await using: global use before initialization in prior statement. + (TDZ, Temporal Dead Zone) +negative: + phase: runtime + type: ReferenceError +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function() { + x; await using x = null; +}); diff --git a/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-asyncfunctionbody.js b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-asyncfunctionbody.js new file mode 100644 index 00000000000..ca9cb62130f --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-asyncfunctionbody.js @@ -0,0 +1,105 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-asyncblockstart +description: Initialized value is disposed at end of AsyncFunctionBody +info: | + AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) + + 1. Assert: promiseCapability is a PromiseCapability Record. + 2. Let runningContext be the running execution context. + 3. Let closure be a new Abstract Closure with no parameters that captures promiseCapability and asyncBody and performs the following steps when called: + a. Let acAsyncContext be the running execution context. + b. Let result be Completion(Evaluation of asyncBody). + c. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done. + d. Remove acAsyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. + e. Let env be acAsyncContext's LexicalEnvironment. + f. Set result to DisposeResources(env.[[DisposeCapability]], result). + g. If result.[[Type]] is normal, then + i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »). + h. Else if result.[[Type]] is return, then + i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »). + i. Else, + i. Assert: result.[[Type]] is throw. + ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »). + j. Return unused. + 4. Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution context, closure will be called with no arguments. + 5. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + 6. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation. + 7. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context. + 8. Assert: result is a normal completion with a value of unused. The possible sources of this value are Await or, if the async function doesn't await anything, step 3.h above. + 9. Return unused. + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + + var resource = { + disposed: false, + async [Symbol.asyncDispose]() { + this.disposed = true; + } + }; + + var releaseF1; + var suspendFPromise1 = new Promise(function (resolve) { releaseF1 = resolve; }); + + var releaseBody; + var suspendBodyPromise = new Promise(function (resolve) { releaseBody = resolve; }); + + var releaseF2; + var suspendFPromise2 = new Promise(function (resolve) { releaseF2 = resolve; }); + + async function f() { + await using _ = resource; + await suspendFPromise1; + releaseBody(); + await suspendFPromise2; + } + + var resultPromise = f(); + + var wasDisposedWhileSuspended1 = resource.disposed; + + releaseF1(); + await suspendBodyPromise; + + var wasDisposedWhileSuspended2 = resource.disposed; + + releaseF2(); + await resultPromise; + + var isDisposedAfterCompleted = resource.disposed; + + assert.sameValue(wasDisposedWhileSuspended1, false, 'Expected resource to not have been disposed while async function is suspended during await'); + assert.sameValue(wasDisposedWhileSuspended2, false, 'Expected resource to not have been disposed while async function is suspended during await'); + assert.sameValue(isDisposedAfterCompleted, true, 'Expected resource to have been disposed after async function completed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-asyncgeneratorbody.js b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-asyncgeneratorbody.js new file mode 100644 index 00000000000..f21e7b71022 --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-asyncgeneratorbody.js @@ -0,0 +1,107 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-asyncgeneratorstart +description: Initialized value is disposed at end of AsyncGeneratorBody +info: | + AsyncGeneratorStart ( generator, generatorBody ) + + 1. Assert: generator.[[AsyncGeneratorState]] is undefined. + 2. Let genContext be the running execution context. + 3. Set the Generator component of genContext to generator. + 4. Let closure be a new Abstract Closure with no parameters that captures generatorBody and performs the following steps when called: + a. Let acGenContext be the running execution context. + b. Let acGenerator be the Generator component of acGenContext. + c. If generatorBody is a Parse Node, then + i. Let result be Completion(Evaluation of generatorBody). + d. Else, + i. Assert: generatorBody is an Abstract Closure with no parameters. + ii. Let result be Completion(generatorBody()). + e. Assert: If we return here, the async generator either threw an exception or performed either an implicit or explicit return. + f. Remove acGenContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. + g. Set acGenerator.[[AsyncGeneratorState]] to completed. + h. Let env be genContext's LexicalEnvironment. + i. If env is not undefined, then + i. Assert: env is a Declarative Environment Record + ii. Set result to DisposeResources(env.[[DisposeCapability]], result). + h. If result.[[Type]] is normal, set result to NormalCompletion(undefined). + i. If result.[[Type]] is return, set result to NormalCompletion(result.[[Value]]). + j. Perform AsyncGeneratorCompleteStep(acGenerator, result, true). + k. Perform AsyncGeneratorDrainQueue(acGenerator). + l. Return undefined. + 5. Set the code evaluation state of genContext such that when evaluation is resumed for that execution context, closure will be called with no arguments. + 6. Set generator.[[AsyncGeneratorContext]] to genContext. + 7. Set generator.[[AsyncGeneratorState]] to suspendedStart. + 8. Set generator.[[AsyncGeneratorQueue]] to a new empty List. + 9. Return unused. + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + + var resource = { + disposed: false, + async [Symbol.asyncDispose]() { + this.disposed = true; + } + }; + + var releaseF; + var suspendFPromise = new Promise(function (resolve) { releaseF = resolve; }); + + async function * f() { + await using _ = resource; + yield; + await suspendFPromise; + } + + var g = f(); + + var wasDisposedBeforeAsyncGeneratorStarted = resource.disposed; + + await g.next(); + + var wasDisposedWhileSuspendedForYield = resource.disposed; + + var nextPromise = g.next(); + + var wasDisposedWhileSuspendedForAwait = resource.disposed; + + releaseF(); + await nextPromise; + + var isDisposedAfterCompleted = resource.disposed; + + assert.sameValue(wasDisposedBeforeAsyncGeneratorStarted, false, 'Expected resource to not have been disposed prior to async generator start'); + assert.sameValue(wasDisposedWhileSuspendedForYield, false, 'Expected resource to not have been disposed while async generator function is suspended for yield'); + assert.sameValue(wasDisposedWhileSuspendedForAwait, false, 'Expected resource to not have been disposed while async generator function is suspended during await'); + assert.sameValue(isDisposedAfterCompleted, true, 'Expected resource to have been disposed after async generator function completed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-block.js b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-block.js new file mode 100644 index 00000000000..7063accc43e --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-block.js @@ -0,0 +1,58 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-block-runtime-semantics-evaluation +description: Initialized value is disposed at end of Block +info: | + RS: Evaluation + Block : { StatementList } + + ... + 5. Let blockValue be the result of evaluating StatementList. + 6. Set blockValue to DisposeResources(blockEnv.[[DisposeCapability]], blockValue). + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + async [Symbol.asyncDispose]() { + this.disposed = true; + } + }; + + { + await using _ = resource; + } + + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-each-iteration-of-forofstatement.js b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-each-iteration-of-forofstatement.js new file mode 100644 index 00000000000..d56df22929f --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-each-iteration-of-forofstatement.js @@ -0,0 +1,84 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset +description: Initialized value is disposed at end of each iteration of ForOfStatement +info: | + ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, lhsKind, labelSet [ , iteratorKind ] ) + + 1. If iteratorKind is not present, set iteratorKind to sync. + 2. Let oldEnv be the running execution context's LexicalEnvironment. + 3. Let V be undefined. + 4. If IsAwaitUsingDeclaration of lhs is true, then + a. Let hint be async-dispose. + 5. Else, if IsUsingDeclaration of lhs is true, then + a. Let hint be sync-dispose. + 6. Else, + a. Let hint be normal. + 7. Let destructuring be IsDestructuring of lhs. + 8. If destructuring is true and if lhsKind is assignment, then + a. Assert: lhs is a LeftHandSideExpression. + b. Let assignmentPattern be the AssignmentPattern that is covered by lhs. + 9. Repeat, + ... + j. Let result be Completion(Evaluation of stmt). + k. If iterationEnv is not undefined, then + i. Set result to Completion(DisposeResources(iterationEnv.[[DisposeCapability]], result)). + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + async [Symbol.asyncDispose]() { + this.disposed = true; + } + }; + + var wasDisposedBeforeBody = false; + var wasDisposedBeforeIteration = false; + var wasDisposedAfterIteration = false; + + function * g() { + wasDisposedBeforeIteration = resource.disposed; + yield resource; + wasDisposedAfterIteration = resource.disposed; + } + + for (await using _ of g()) { + wasDisposedBeforeBody = resource.disposed; + } + + assert.sameValue(wasDisposedBeforeIteration, false, 'Expected resource to not been disposed before the for-of loop has received a value'); + assert.sameValue(wasDisposedBeforeBody, false, 'Expected resource to not been disposed while for-of loop is still iterating'); + assert.sameValue(wasDisposedAfterIteration, true, 'Expected resource to have been disposed after the for-of loop advanced to the next value'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-forstatement.js b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-forstatement.js new file mode 100644 index 00000000000..14a65421500 --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-forstatement.js @@ -0,0 +1,62 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-runtime-semantics-forloopevaluation +description: Initialized value is disposed at end of ForStatement +info: | + RS: ForLoopEvaluation + ForStatement : for ( LexicalDeclaration Expressionopt ; Expressionopt ) Statement + + ... + 12. Let bodyResult be Completion(ForBodyEvaluation(test, increment, Statement, perIterationLets, labelSet)). + 13. Set bodyResult to Completion(DisposeResources(loopEnv.[[DisposeCapability]], bodyResult)). + 14. Assert: If bodyResult.[[Type]] is normal, then bodyResult.[[Value]] is not empty. + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + async [Symbol.asyncDispose]() { + this.disposed = true; + } + }; + + var i = 0; + var wasDisposedInForStatement; + for (await using _ = resource; i < 1; i++) { + wasDisposedInForStatement = resource.disposed; + } + + assert.sameValue(wasDisposedInForStatement, false, 'Expected resource to not been disposed while for loop is still iterating'); + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-switchstatement.js b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-switchstatement.js new file mode 100644 index 00000000000..0726ca441fd --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-at-end-of-switchstatement.js @@ -0,0 +1,61 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-switch-statement-runtime-semantics-evaluation +description: Initialized value is disposed at end of a SwitchStatement +info: | + RS: Evaluation + SwitchStatement : switch ( Expression ) CaseBlock + + ... + 7. Let R be Completion(CaseBlockEvaluation of CaseBlock with argument switchValue). + 8. Set R to Completion(DisposeResources(blockEnv.[[DisposeCapability]], R)). + 9. Assert: If R.[[Type]] is normal, then R.[[Value]] is not empty. + .. + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + async [Symbol.asyncDispose]() { + this.disposed = true; + } + }; + + switch (0) { + default: + await using _ = resource; + break; + } + + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-if-subsequent-initializer-throws-in-forstatement-head.js b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-if-subsequent-initializer-throws-in-forstatement-head.js new file mode 100644 index 00000000000..19903e1210c --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-if-subsequent-initializer-throws-in-forstatement-head.js @@ -0,0 +1,68 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-runtime-semantics-forloopevaluation +description: Initialized value is disposed at end of FunctionBody +info: | + RS: ForLoopEvaluation + ForStatement : for ( LexicalDeclaration Expressionopt ; Expressionopt ) Statement + + ... + 7. Let forDcl be Completion(Evaluation of LexicalDeclaration). + 8. If forDcl is an abrupt completion, then + a. Set forDcl to Completion(DisposeResources(loopEnv.[[DisposeCapability]], forDcl)). + b. Assert: forDcl is an abrupt completion. + c. Set the running execution context's LexicalEnvironment to oldEnv. + d. Return ? forDcl. + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + async [Symbol.asyncDispose]() { + this.disposed = true; + } + }; + + function getResource() { + throw new Error(); + } + + await assert.throwsAsync(Error, async function () { + var i = 0; + for (await using _1 = resource, _2 = getResource(); i < 1; i++) { + } + }, 'for'); + + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-if-subsequent-initializer-throws.js b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-if-subsequent-initializer-throws.js new file mode 100644 index 00000000000..5864c90653a --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.asyncDispose-called-if-subsequent-initializer-throws.js @@ -0,0 +1,62 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-block-runtime-semantics-evaluation +description: Initialized value is disposed even if subsequent initializer throws +info: | + RS: Evaluation + Block : { StatementList } + + ... + 5. Let blockValue be the result of evaluating StatementList. + 6. Set blockValue to DisposeResources(blockEnv.[[DisposeCapability]], blockValue). + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + async [Symbol.asyncDispose]() { + this.disposed = true; + } + }; + + function getResource() { + throw new Error(); + } + + await assert.throwsAsync(Error, async function () { + await using _1 = resource, _2 = getResource(); + }); + + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-asyncfunctionbody.js b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-asyncfunctionbody.js new file mode 100644 index 00000000000..f5ebe03ed42 --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-asyncfunctionbody.js @@ -0,0 +1,105 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-asyncblockstart +description: Initialized value is disposed at end of AsyncFunctionBody +info: | + AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ) + + 1. Assert: promiseCapability is a PromiseCapability Record. + 2. Let runningContext be the running execution context. + 3. Let closure be a new Abstract Closure with no parameters that captures promiseCapability and asyncBody and performs the following steps when called: + a. Let acAsyncContext be the running execution context. + b. Let result be Completion(Evaluation of asyncBody). + c. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done. + d. Remove acAsyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. + e. Let env be acAsyncContext's LexicalEnvironment. + f. Set result to DisposeResources(env.[[DisposeCapability]], result). + g. If result.[[Type]] is normal, then + i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »). + h. Else if result.[[Type]] is return, then + i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »). + i. Else, + i. Assert: result.[[Type]] is throw. + ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »). + j. Return unused. + 4. Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution context, closure will be called with no arguments. + 5. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + 6. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation. + 7. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context. + 8. Assert: result is a normal completion with a value of unused. The possible sources of this value are Await or, if the async function doesn't await anything, step 3.h above. + 9. Return unused. + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + + var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } + }; + + var releaseF1; + var suspendFPromise1 = new Promise(function (resolve) { releaseF1 = resolve; }); + + var releaseBody; + var suspendBodyPromise = new Promise(function (resolve) { releaseBody = resolve; }); + + var releaseF2; + var suspendFPromise2 = new Promise(function (resolve) { releaseF2 = resolve; }); + + async function f() { + await using _ = resource; + await suspendFPromise1; + releaseBody(); + await suspendFPromise2; + } + + var resultPromise = f(); + + var wasDisposedWhileSuspended1 = resource.disposed; + + releaseF1(); + await suspendBodyPromise; + + var wasDisposedWhileSuspended2 = resource.disposed; + + releaseF2(); + await resultPromise; + + var isDisposedAfterCompleted = resource.disposed; + + assert.sameValue(wasDisposedWhileSuspended1, false, 'Expected resource to not have been disposed while async function is suspended during await'); + assert.sameValue(wasDisposedWhileSuspended2, false, 'Expected resource to not have been disposed while async function is suspended during await'); + assert.sameValue(isDisposedAfterCompleted, true, 'Expected resource to have been disposed after async function completed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-asyncgeneratorbody.js b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-asyncgeneratorbody.js new file mode 100644 index 00000000000..16d33abbd9e --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-asyncgeneratorbody.js @@ -0,0 +1,107 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-asyncgeneratorstart +description: Initialized value is disposed at end of AsyncGeneratorBody +info: | + AsyncGeneratorStart ( generator, generatorBody ) + + 1. Assert: generator.[[AsyncGeneratorState]] is undefined. + 2. Let genContext be the running execution context. + 3. Set the Generator component of genContext to generator. + 4. Let closure be a new Abstract Closure with no parameters that captures generatorBody and performs the following steps when called: + a. Let acGenContext be the running execution context. + b. Let acGenerator be the Generator component of acGenContext. + c. If generatorBody is a Parse Node, then + i. Let result be Completion(Evaluation of generatorBody). + d. Else, + i. Assert: generatorBody is an Abstract Closure with no parameters. + ii. Let result be Completion(generatorBody()). + e. Assert: If we return here, the async generator either threw an exception or performed either an implicit or explicit return. + f. Remove acGenContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. + g. Set acGenerator.[[AsyncGeneratorState]] to completed. + h. Let env be genContext's LexicalEnvironment. + i. If env is not undefined, then + i. Assert: env is a Declarative Environment Record + ii. Set result to DisposeResources(env.[[DisposeCapability]], result). + h. If result.[[Type]] is normal, set result to NormalCompletion(undefined). + i. If result.[[Type]] is return, set result to NormalCompletion(result.[[Value]]). + j. Perform AsyncGeneratorCompleteStep(acGenerator, result, true). + k. Perform AsyncGeneratorDrainQueue(acGenerator). + l. Return undefined. + 5. Set the code evaluation state of genContext such that when evaluation is resumed for that execution context, closure will be called with no arguments. + 6. Set generator.[[AsyncGeneratorContext]] to genContext. + 7. Set generator.[[AsyncGeneratorState]] to suspendedStart. + 8. Set generator.[[AsyncGeneratorQueue]] to a new empty List. + 9. Return unused. + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + + var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } + }; + + var releaseF; + var suspendFPromise = new Promise(function (resolve) { releaseF = resolve; }); + + async function * f() { + await using _ = resource; + yield; + await suspendFPromise; + } + + var g = f(); + + var wasDisposedBeforeAsyncGeneratorStarted = resource.disposed; + + await g.next(); + + var wasDisposedWhileSuspendedForYield = resource.disposed; + + var nextPromise = g.next(); + + var wasDisposedWhileSuspendedForAwait = resource.disposed; + + releaseF(); + await nextPromise; + + var isDisposedAfterCompleted = resource.disposed; + + assert.sameValue(wasDisposedBeforeAsyncGeneratorStarted, false, 'Expected resource to not have been disposed prior to async generator start'); + assert.sameValue(wasDisposedWhileSuspendedForYield, false, 'Expected resource to not have been disposed while async generator function is suspended for yield'); + assert.sameValue(wasDisposedWhileSuspendedForAwait, false, 'Expected resource to not have been disposed while async generator function is suspended during await'); + assert.sameValue(isDisposedAfterCompleted, true, 'Expected resource to have been disposed after async generator function completed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-block.js b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-block.js new file mode 100644 index 00000000000..6d0e11aa707 --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-block.js @@ -0,0 +1,58 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-block-runtime-semantics-evaluation +description: Initialized value is disposed at end of Block +info: | + RS: Evaluation + Block : { StatementList } + + ... + 5. Let blockValue be the result of evaluating StatementList. + 6. Set blockValue to DisposeResources(blockEnv.[[DisposeCapability]], blockValue). + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } + }; + + { + await using _ = resource; + } + + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-each-iteration-of-forofstatement.js b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-each-iteration-of-forofstatement.js new file mode 100644 index 00000000000..6280f40b949 --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-each-iteration-of-forofstatement.js @@ -0,0 +1,84 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset +description: Initialized value is disposed at end of each iteration of ForOfStatement +info: | + ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, lhsKind, labelSet [ , iteratorKind ] ) + + 1. If iteratorKind is not present, set iteratorKind to sync. + 2. Let oldEnv be the running execution context's LexicalEnvironment. + 3. Let V be undefined. + 4. If IsAwaitUsingDeclaration of lhs is true, then + a. Let hint be async-dispose. + 5. Else, if IsUsingDeclaration of lhs is true, then + a. Let hint be sync-dispose. + 6. Else, + a. Let hint be normal. + 7. Let destructuring be IsDestructuring of lhs. + 8. If destructuring is true and if lhsKind is assignment, then + a. Assert: lhs is a LeftHandSideExpression. + b. Let assignmentPattern be the AssignmentPattern that is covered by lhs. + 9. Repeat, + ... + j. Let result be Completion(Evaluation of stmt). + k. If iterationEnv is not undefined, then + i. Set result to Completion(DisposeResources(iterationEnv.[[DisposeCapability]], result)). + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } + }; + + var wasDisposedBeforeBody = false; + var wasDisposedBeforeIteration = false; + var wasDisposedAfterIteration = false; + + function * g() { + wasDisposedBeforeIteration = resource.disposed; + yield resource; + wasDisposedAfterIteration = resource.disposed; + } + + for (await using _ of g()) { + wasDisposedBeforeBody = resource.disposed; + } + + assert.sameValue(wasDisposedBeforeIteration, false, 'Expected resource to not been disposed before the for-of loop has received a value'); + assert.sameValue(wasDisposedBeforeBody, false, 'Expected resource to not been disposed while for-of loop is still iterating'); + assert.sameValue(wasDisposedAfterIteration, true, 'Expected resource to have been disposed after the for-of loop advanced to the next value'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-forstatement.js b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-forstatement.js new file mode 100644 index 00000000000..14a65421500 --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-forstatement.js @@ -0,0 +1,62 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-runtime-semantics-forloopevaluation +description: Initialized value is disposed at end of ForStatement +info: | + RS: ForLoopEvaluation + ForStatement : for ( LexicalDeclaration Expressionopt ; Expressionopt ) Statement + + ... + 12. Let bodyResult be Completion(ForBodyEvaluation(test, increment, Statement, perIterationLets, labelSet)). + 13. Set bodyResult to Completion(DisposeResources(loopEnv.[[DisposeCapability]], bodyResult)). + 14. Assert: If bodyResult.[[Type]] is normal, then bodyResult.[[Value]] is not empty. + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + async [Symbol.asyncDispose]() { + this.disposed = true; + } + }; + + var i = 0; + var wasDisposedInForStatement; + for (await using _ = resource; i < 1; i++) { + wasDisposedInForStatement = resource.disposed; + } + + assert.sameValue(wasDisposedInForStatement, false, 'Expected resource to not been disposed while for loop is still iterating'); + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-switchstatement.js b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-switchstatement.js new file mode 100644 index 00000000000..4e58e2635d6 --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.dispose-called-at-end-of-switchstatement.js @@ -0,0 +1,61 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-switch-statement-runtime-semantics-evaluation +description: Initialized value is disposed at end of a SwitchStatement +info: | + RS: Evaluation + SwitchStatement : switch ( Expression ) CaseBlock + + ... + 7. Let R be Completion(CaseBlockEvaluation of CaseBlock with argument switchValue). + 8. Set R to Completion(DisposeResources(blockEnv.[[DisposeCapability]], R)). + 9. Assert: If R.[[Type]] is normal, then R.[[Value]] is not empty. + .. + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } + }; + + switch (0) { + default: + await using _ = resource; + break; + } + + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.dispose-called-if-subsequent-initializer-throws-in-forstatement-head.js b/test/language/statements/await-using/initializer-Symbol.dispose-called-if-subsequent-initializer-throws-in-forstatement-head.js new file mode 100644 index 00000000000..73c4fba5da5 --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.dispose-called-if-subsequent-initializer-throws-in-forstatement-head.js @@ -0,0 +1,68 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-runtime-semantics-forloopevaluation +description: Initialized value is disposed at end of FunctionBody +info: | + RS: ForLoopEvaluation + ForStatement : for ( LexicalDeclaration Expressionopt ; Expressionopt ) Statement + + ... + 7. Let forDcl be Completion(Evaluation of LexicalDeclaration). + 8. If forDcl is an abrupt completion, then + a. Set forDcl to Completion(DisposeResources(loopEnv.[[DisposeCapability]], forDcl)). + b. Assert: forDcl is an abrupt completion. + c. Set the running execution context's LexicalEnvironment to oldEnv. + d. Return ? forDcl. + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } + }; + + function getResource() { + throw new Error(); + } + + await assert.throwsAsync(Error, async function () { + var i = 0; + for (await using _1 = resource, _2 = getResource(); i < 1; i++) { + } + }, 'for'); + + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/initializer-Symbol.dispose-called-if-subsequent-initializer-throws.js b/test/language/statements/await-using/initializer-Symbol.dispose-called-if-subsequent-initializer-throws.js new file mode 100644 index 00000000000..2872231fd5c --- /dev/null +++ b/test/language/statements/await-using/initializer-Symbol.dispose-called-if-subsequent-initializer-throws.js @@ -0,0 +1,62 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-block-runtime-semantics-evaluation +description: Initialized value is disposed even if subsequent initializer throws +info: | + RS: Evaluation + Block : { StatementList } + + ... + 5. Let blockValue be the result of evaluating StatementList. + 6. Set blockValue to DisposeResources(blockEnv.[[DisposeCapability]], blockValue). + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } + }; + + function getResource() { + throw new Error(); + } + + await assert.throwsAsync(Error, async function () { + await using _1 = resource, _2 = getResource(); + }); + + assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); +}); diff --git a/test/language/statements/await-using/multiple-resources-disposed-in-reverse-order.js b/test/language/statements/await-using/multiple-resources-disposed-in-reverse-order.js new file mode 100644 index 00000000000..f6376b9ab45 --- /dev/null +++ b/test/language/statements/await-using/multiple-resources-disposed-in-reverse-order.js @@ -0,0 +1,59 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-disposeresources +description: Multiple resources are disposed in the reverse of the order in which they were added +info: | + RS: Evaluation + Block : { StatementList } + + ... + 5. Let blockValue be the result of evaluating StatementList. + 6. Set blockValue to DisposeResources(blockEnv.[[DisposeCapability]], blockValue). + ... + + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function() { + var disposed = []; + var resource1 = { async [Symbol.asyncDispose]() { disposed.push(this); } }; + var resource2 = { [Symbol.dispose]() { disposed.push(this); } }; + var resource3 = { async [Symbol.asyncDispose]() { disposed.push(this); } }; + + { + await using _1 = resource1, _2 = resource2; + await using _3 = resource3; + } + + assert.sameValue(disposed[0], resource3); + assert.sameValue(disposed[1], resource2); + assert.sameValue(disposed[2], resource1); +}); diff --git a/test/language/statements/await-using/puts-initializer-on-top-of-disposableresourcestack-multiple-bindings.js b/test/language/statements/await-using/puts-initializer-on-top-of-disposableresourcestack-multiple-bindings.js new file mode 100644 index 00000000000..61bd3990601 --- /dev/null +++ b/test/language/statements/await-using/puts-initializer-on-top-of-disposableresourcestack-multiple-bindings.js @@ -0,0 +1,73 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: > + Puts initialized value on the top of the environment's [[DisposableResourceStack]] with multiple lexical bindings + in a single 'await using' declaration +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + BindingList : BindingList , LexicalBinding + + 1. Perform ? BindingEvaluation of BindingList with argument hint. + 2. Perform ? BindingEvaluation of LexicalBinding with argument hint. + 3. Return unused. + + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + 3. Else, + ... + 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + 4. Return unused. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function() { + var disposed = []; + var resource1 = { + async [Symbol.asyncDispose]() { + disposed.push(this); + } + }; + var resource2 = { + [Symbol.dispose]() { + disposed.push(this); + } + }; + { + await using r1 = resource1, r2 = resource2; + } + assert.sameValue(2, disposed.length); + assert.sameValue(disposed[0], resource2, 'Expected resource2 to be the first disposed resource'); + assert.sameValue(disposed[1], resource1, 'Expected resource1 to be the second disposed resource'); +}); diff --git a/test/language/statements/await-using/puts-initializer-on-top-of-disposableresourcestack-subsequent-usings.js b/test/language/statements/await-using/puts-initializer-on-top-of-disposableresourcestack-subsequent-usings.js new file mode 100644 index 00000000000..bb4040a9c9d --- /dev/null +++ b/test/language/statements/await-using/puts-initializer-on-top-of-disposableresourcestack-subsequent-usings.js @@ -0,0 +1,68 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: > + Puts initialized value on the top of the environment's [[DisposableResourceStack]] with multiple subsequent 'await using' + declarations in the same block scope +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + 3. Else, + ... + 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + 4. Return unused. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function() { + var disposed = []; + var resource1 = { + async [Symbol.asyncDispose]() { + disposed.push(this); + } + }; + var resource2 = { + [Symbol.dispose]() { + disposed.push(this); + } + }; + { + await using r1 = resource1; + await using r2 = resource2; + } + assert.sameValue(2, disposed.length); + assert.sameValue(disposed[0], resource2, 'Expected resource2 to be the first disposed resource'); + assert.sameValue(disposed[1], resource1, 'Expected resource1 to be the second disposed resource'); +}); diff --git a/test/language/statements/await-using/redeclaration-error-from-within-strict-mode-function-await-using.js b/test/language/statements/await-using/redeclaration-error-from-within-strict-mode-function-await-using.js new file mode 100644 index 00000000000..68e843004b6 --- /dev/null +++ b/test/language/statements/await-using/redeclaration-error-from-within-strict-mode-function-await-using.js @@ -0,0 +1,16 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-declarative-environment-records-initializebinding-n-v +description: > + Redeclaration error within strict mode function inside non-strict code. +negative: + phase: parse + type: SyntaxError +flags: [noStrict, explicit-resource-management] +---*/ + +$DONOTEVALUATE(); +(async function() { 'use strict'; { await using f = null; var f; } }) + diff --git a/test/language/statements/await-using/throws-error-as-is-if-only-one-error-during-disposal.js b/test/language/statements/await-using/throws-error-as-is-if-only-one-error-during-disposal.js new file mode 100644 index 00000000000..5f64b4e1157 --- /dev/null +++ b/test/language/statements/await-using/throws-error-as-is-if-only-one-error-during-disposal.js @@ -0,0 +1,56 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-disposeresources +description: > + Rethrows an error as-is if it is the only error thrown during evaluation of subsequent statements following 'await using' + or from disposal. +info: | + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function() { + class MyError extends Error {} + await assert.throwsAsync(MyError, async function () { + await using _1 = { async [Symbol.asyncDispose]() { throw new MyError(); } }; + await using _2 = { [Symbol.dispose]() { } }; + }); + + await assert.throwsAsync(MyError, async function () { + await using _1 = { async [Symbol.asyncDispose]() { } }; + await using _2 = { [Symbol.dispose]() { throw new MyError(); } }; + }); + + await assert.throwsAsync(MyError, async function () { + await using _1 = { async [Symbol.asyncDispose]() { } }; + await using _2 = { [Symbol.dispose]() { } }; + throw new MyError(); + }); +}); diff --git a/test/language/statements/await-using/throws-if-initializer-Symbol.asyncDispose-property-is-null.js b/test/language/statements/await-using/throws-if-initializer-Symbol.asyncDispose-property-is-null.js new file mode 100644 index 00000000000..ba5e61f10d8 --- /dev/null +++ b/test/language/statements/await-using/throws-if-initializer-Symbol.asyncDispose-property-is-null.js @@ -0,0 +1,78 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Throws if initialized value's Symbol.asyncDispose property is null and Symbol.dispose is not present +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + 2. Else, + ... + 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + 4. Return unused. + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + ... + b. Else, + i. If V is not an Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + ... + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + + GetMethod ( V, P ) + + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. ... + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await assert.throwsAsync(TypeError, async function () { + await using x = { [Symbol.asyncDispose]: null }; + }); +}); diff --git a/test/language/statements/await-using/throws-if-initializer-Symbol.asyncDispose-property-is-undefined.js b/test/language/statements/await-using/throws-if-initializer-Symbol.asyncDispose-property-is-undefined.js new file mode 100644 index 00000000000..714f0864677 --- /dev/null +++ b/test/language/statements/await-using/throws-if-initializer-Symbol.asyncDispose-property-is-undefined.js @@ -0,0 +1,78 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Throws if initialized value's Symbol.asyncDispose property is undefined and Symbol.dispose is not present +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + 2. Else, + ... + 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + 4. Return unused. + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + ... + b. Else, + i. If V is not an Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + ... + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + + GetMethod ( V, P ) + + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. ... + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await assert.throwsAsync(TypeError, async function () { + await using x = { [Symbol.asyncDispose]: undefined }; + }); +}); diff --git a/test/language/statements/await-using/throws-if-initializer-Symbol.asyncDispose-property-not-callable.js b/test/language/statements/await-using/throws-if-initializer-Symbol.asyncDispose-property-not-callable.js new file mode 100644 index 00000000000..e00db2d7d7c --- /dev/null +++ b/test/language/statements/await-using/throws-if-initializer-Symbol.asyncDispose-property-not-callable.js @@ -0,0 +1,99 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Throws if initialized value's Symbol.asyncDispose property is not callable +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + 2. Else, + ... + 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + 4. Return unused. + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + i. Set V to undefined. + ii. Set method to undefined. + b. Else, + i. If V is not an Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + 2. Else, + ... + 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }. + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + + GetMethod ( V, P ) + + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. If IsCallable(func) is false, throw a TypeError exception. + 4. Return func. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await assert.throwsAsync(TypeError, async function() { + await using x = { [Symbol.asyncDispose]: true }; + }, 'true'); + + await assert.throwsAsync(TypeError, async function() { + await using x = { [Symbol.asyncDispose]: false }; + }, 'false'); + + await assert.throwsAsync(TypeError, async function() { + await using x = { [Symbol.asyncDispose]: 1 }; + }, 'number'); + + await assert.throwsAsync(TypeError, async function() { + await using x = { [Symbol.asyncDispose]: 'object' }; + }, 'string'); + + var s = Symbol(); + await assert.throwsAsync(TypeError, async function() { + await using x = { [Symbol.asyncDispose]: s }; + }, 'symbol'); +}); diff --git a/test/language/statements/await-using/throws-if-initializer-Symbol.dispose-property-is-null.js b/test/language/statements/await-using/throws-if-initializer-Symbol.dispose-property-is-null.js new file mode 100644 index 00000000000..41c92ed7170 --- /dev/null +++ b/test/language/statements/await-using/throws-if-initializer-Symbol.dispose-property-is-null.js @@ -0,0 +1,78 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Throws if initialized value's Symbol.dispose property is null +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + 2. Else, + ... + 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + 4. Return unused. + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + ... + b. Else, + i. If V is not an Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + ... + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + + GetMethod ( V, P ) + + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. ... + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await assert.throwsAsync(TypeError, async function () { + await using x = { [Symbol.dispose]: null }; + }); +}); diff --git a/test/language/statements/await-using/throws-if-initializer-Symbol.dispose-property-is-undefined.js b/test/language/statements/await-using/throws-if-initializer-Symbol.dispose-property-is-undefined.js new file mode 100644 index 00000000000..33e3bdac8aa --- /dev/null +++ b/test/language/statements/await-using/throws-if-initializer-Symbol.dispose-property-is-undefined.js @@ -0,0 +1,78 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Throws if initialized value's Symbol.dispose property is undefined +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + 2. Else, + ... + 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + 4. Return unused. + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + ... + b. Else, + i. If V is not an Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + ... + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + + GetMethod ( V, P ) + + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. ... + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await assert.throwsAsync(TypeError, async function () { + await using x = { [Symbol.dispose]: undefined }; + }); +}); diff --git a/test/language/statements/await-using/throws-if-initializer-Symbol.dispose-property-not-callable.js b/test/language/statements/await-using/throws-if-initializer-Symbol.dispose-property-not-callable.js new file mode 100644 index 00000000000..402dd4b332e --- /dev/null +++ b/test/language/statements/await-using/throws-if-initializer-Symbol.dispose-property-not-callable.js @@ -0,0 +1,99 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Throws if initialized value's Symbol.dispose property is not callable +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + 2. Else, + ... + 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + 4. Return unused. + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + i. Set V to undefined. + ii. Set method to undefined. + b. Else, + i. If V is not an Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + 2. Else, + ... + 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }. + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + + GetMethod ( V, P ) + + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. If IsCallable(func) is false, throw a TypeError exception. + 4. Return func. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await assert.throwsAsync(TypeError, async function () { + await using x = { [Symbol.dispose]: true }; + }, 'true'); + + await assert.throwsAsync(TypeError, async function () { + await using x = { [Symbol.dispose]: false }; + }, 'false'); + + await assert.throwsAsync(TypeError, async function () { + await using x = { [Symbol.dispose]: 1 }; + }, 'number'); + + await assert.throwsAsync(TypeError, async function () { + await using x = { [Symbol.dispose]: 'object' }; + }, 'string'); + + var s = Symbol(); + await assert.throwsAsync(TypeError, async function () { + await using x = { [Symbol.dispose]: s }; + }, 'symbol'); +}); diff --git a/test/language/statements/await-using/throws-if-initializer-missing-both-Symbol.asyncDispose-and-Symbol.dispose.js b/test/language/statements/await-using/throws-if-initializer-missing-both-Symbol.asyncDispose-and-Symbol.dispose.js new file mode 100644 index 00000000000..aa01d44c6ee --- /dev/null +++ b/test/language/statements/await-using/throws-if-initializer-missing-both-Symbol.asyncDispose-and-Symbol.dispose.js @@ -0,0 +1,79 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-let-and-const-declarations-runtime-semantics-evaluation +description: Throws if initialized value is missing both the Symbol.asyncDispose and Symbol.dispose properties +info: | + RS: Evaluation + AwaitUsingDeclaration : CoverAwaitExpressionAndAwaitUsingDeclarationHead BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument async-dispose. + 2. Return empty. + + RS: BindingEvaluation + BindingList : BindingList , LexicalBinding + + 1. Perform ? BindingEvaluation of BindingList with argument hint. + 2. Perform ? BindingEvaluation of LexicalBinding with argument hint. + 3. Return unused. + + LexicalBinding : BindingIdentifier Initializer + + ... + 5. Return ? InitializeReferencedBinding(lhs, value, hint). + + InitializeReferencedBinding ( V, W ) + + ... + 4. Return ? base.InitializeBinding(V.[[ReferencedName]], W). + + InitializeBinding ( N, V, hint ) + + ... + 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). + ... + + AddDisposableResource ( disposeCapability, V, hint [, method ] ) + + 1. If method is not present then, + a. If V is either null or undefined and hint is sync-dispose, then + i. Return unused. + b. Let resource be ? CreateDisposableResource(V, hint). + 3. Else, + ... + 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + 4. Return unused. + + CreateDisposableResource ( V, hint [ , method ] ) + + 1. If method is not present, then + a. If V is either null or undefined, then + ... + b. Else, + i. If V is not an Object, throw a TypeError exception. + ii. Set method to ? GetDisposeMethod(V, hint). + iii. If method is undefined, throw a TypeError exception. + ... + + GetDisposeMethod ( V, hint ) + + 1. If hint is async-dispose, then + a. Let method be ? GetMethod(V, @@asyncDispose). + b. If method is undefined, then + i. Set method to ? GetMethod(V, @@dispose). + 2. Else, + a. Let method be ? GetMethod(V, @@dispose). + 3. Return method. + + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function () { + await assert.throwsAsync(TypeError, async function () { + await using x = {}; + }); +}); diff --git a/test/language/statements/await-using/throws-suppressederror-if-multiple-errors-during-disposal.js b/test/language/statements/await-using/throws-suppressederror-if-multiple-errors-during-disposal.js new file mode 100644 index 00000000000..adef24f93a7 --- /dev/null +++ b/test/language/statements/await-using/throws-suppressederror-if-multiple-errors-during-disposal.js @@ -0,0 +1,57 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-disposeresources +description: > + Throws a nested hierarchy of SuppressedErrors if multiple errors were thrown during evaluation of subsequent statements following 'await using' + and/or from disposal. +info: | + DisposeResources ( disposeCapability, completion ) + + 1. For each resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + a. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). + b. If result.[[Type]] is throw, then + i. If completion.[[Type]] is throw, then + 1. Set result to result.[[Value]]. + 2. Let suppressed be completion.[[Value]]. + 3. Let error be a newly created SuppressedError object. + 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + 5. Perform ! CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + 6. Set completion to ThrowCompletion(error). + ii. Else, + 1. Set completion to result. + 2. Return completion. + + Dispose ( V, hint, method ) + + 1. If method is undefined, let result be undefined. + 2. Else, let result be ? Call(method, V). + 3. If hint is async-dispose, then + a. Perform ? Await(result). + 4. Return undefined. + +flags: [async] +includes: [asyncHelpers.js] +features: [explicit-resource-management] +---*/ + +asyncTest(async function() { + class MyError extends Error {} + const error1 = new MyError(); + const error2 = new MyError(); + const error3 = new MyError(); + + try { + await using _1 = { async [Symbol.asyncDispose]() { throw error1; } }; + await using _2 = { [Symbol.dispose]() { throw error2; } }; + throw error3; + } + catch (e) { + assert(e instanceof SuppressedError, "Expected an SuppressedError to have been thrown"); + assert.sameValue(e.error, error1, "Expected the outermost suppressing error to have been 'error1'"); + assert(e.suppressed instanceof SuppressedError, "Expected the outermost suppressed error to have been a SuppressedError"); + assert.sameValue(e.suppressed.error, error2, "Expected the innermost suppressing error to have been 'error2'"); + assert.sameValue(e.suppressed.suppressed, error3, "Expected the innermost suppressed error to have been 'error3'"); + } +});