diff --git a/test/language/statements/using/Symbol.dispose-getter.js b/test/language/statements/using/Symbol.dispose-getter.js new file mode 100644 index 00000000000..cb9fa36c037 --- /dev/null +++ b/test/language/statements/using/Symbol.dispose-getter.js @@ -0,0 +1,87 @@ +// 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 + UsingDeclaration : using BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument sync-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 + ... + 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. + +features: [explicit-resource-management] +---*/ + +var resource = { + disposed: false, + get [Symbol.dispose]() { + return function() { + this.disposed = true; + }; + } +}; + +{ + using _ = resource; +} + +assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); diff --git a/test/language/statements/using/Symbol.dispose-method-called-with-correct-this.js b/test/language/statements/using/Symbol.dispose-method-called-with-correct-this.js new file mode 100644 index 00000000000..0759263c16a --- /dev/null +++ b/test/language/statements/using/Symbol.dispose-method-called-with-correct-this.js @@ -0,0 +1,50 @@ +// 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). + ... + +features: [explicit-resource-management] +---*/ + +var resource = { + disposed: false, + [Symbol.dispose]() { + assert.sameValue(this, resource); + } +}; + +{ + using _ = resource; +} diff --git a/test/language/statements/using/block-local-closure-get-before-initialization.js b/test/language/statements/using/block-local-closure-get-before-initialization.js new file mode 100644 index 00000000000..469f439932e --- /dev/null +++ b/test/language/statements/using/block-local-closure-get-before-initialization.js @@ -0,0 +1,20 @@ +// 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: > + using: block local closure [[Get]] before initialization. + (TDZ, Temporal Dead Zone) +features: [explicit-resource-management] +---*/ +{ + function f() { return x + 1; } + + assert.throws(ReferenceError, function() { + f(); + }); + + using x = null; +} + diff --git a/test/language/statements/using/block-local-use-before-initialization-in-declaration-statement.js b/test/language/statements/using/block-local-use-before-initialization-in-declaration-statement.js new file mode 100644 index 00000000000..d7c9277d579 --- /dev/null +++ b/test/language/statements/using/block-local-use-before-initialization-in-declaration-statement.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-getbindingvalue-n-s +description: > + using: block local use before initialization in declaration statement. + (TDZ, Temporal Dead Zone) +features: [explicit-resource-management] +---*/ + +assert.throws(ReferenceError, function() { + { + using x = x + 1; + } +}); diff --git a/test/language/statements/using/block-local-use-before-initialization-in-prior-statement.js b/test/language/statements/using/block-local-use-before-initialization-in-prior-statement.js new file mode 100644 index 00000000000..3eb788fc7b7 --- /dev/null +++ b/test/language/statements/using/block-local-use-before-initialization-in-prior-statement.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-getbindingvalue-n-s +description: > + using: block local use before initialization in prior statement. + (TDZ, Temporal Dead Zone) +features: [explicit-resource-management] +---*/ + +assert.throws(ReferenceError, function() { + { + x; using x = null; + } +}); diff --git a/test/language/statements/using/cptn-value.js b/test/language/statements/using/cptn-value.js new file mode 100644 index 00000000000..72ff9379b97 --- /dev/null +++ b/test/language/statements/using/cptn-value.js @@ -0,0 +1,26 @@ +// 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: Returns an empty completion +info: | + UsingDeclaration : using BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument sync-dispose. + 2. Return empty. + +features: [explicit-resource-management] +---*/ + +assert.sameValue( + eval('{using test262id1 = null;}'), undefined, 'Single declaration' +); +assert.sameValue( + eval('{using test262id2 = null, test262id3 = null;}'), + undefined, + 'Multiple declarations' +); + +assert.sameValue(eval('4; {using test262id5 = null;}'), 4); +assert.sameValue(eval('6; {using test262id7 = null, test262id8 = null;}'), 6); diff --git a/test/language/statements/using/fn-name-arrow.js b/test/language/statements/using/fn-name-arrow.js new file mode 100644 index 00000000000..8b46e92aa90 --- /dev/null +++ b/test/language/statements/using/fn-name-arrow.js @@ -0,0 +1,26 @@ +// 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 +includes: [propertyHelper.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 () {} +{ + using arrow = () => {}; + + assert.sameValue(arrow.name, 'arrow'); + verifyNotEnumerable(arrow, 'name'); + verifyNotWritable(arrow, 'name'); + verifyConfigurable(arrow, 'name'); +} diff --git a/test/language/statements/using/fn-name-class.js b/test/language/statements/using/fn-name-class.js new file mode 100644 index 00000000000..b9a95fe7a75 --- /dev/null +++ b/test/language/statements/using/fn-name-class.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 (ClassExpression) +info: | + LexicalBinding : BindingIdentifier Initializer + + ... + 3. If IsAnonymousFunctionDefinition(Initializer) is true, then + a. Let value be NamedEvaluation of Initializer with argument bindingId +includes: [propertyHelper.js] +features: [class, explicit-resource-management] +---*/ +{ + using xCls = class x { static [Symbol.dispose]() {} }; + using cls = class { static [Symbol.dispose]() {} }; + using xCls2 = class { static name() {} static [Symbol.dispose]() {} }; + + 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/using/fn-name-cover.js b/test/language/statements/using/fn-name-cover.js new file mode 100644 index 00000000000..d87fc7f3e84 --- /dev/null +++ b/test/language/statements/using/fn-name-cover.js @@ -0,0 +1,30 @@ +// 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 +includes: [propertyHelper.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 () {} +{ + using xCover = (0, function() {}); + 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/using/fn-name-fn.js b/test/language/statements/using/fn-name-fn.js new file mode 100644 index 00000000000..f00e784fe18 --- /dev/null +++ b/test/language/statements/using/fn-name-fn.js @@ -0,0 +1,29 @@ +// 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 +includes: [propertyHelper.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 () {} +{ + using xFn = function x() {}; + 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/using/fn-name-gen.js b/test/language/statements/using/fn-name-gen.js new file mode 100644 index 00000000000..63eaa908d35 --- /dev/null +++ b/test/language/statements/using/fn-name-gen.js @@ -0,0 +1,29 @@ +// 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 +includes: [propertyHelper.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 () {} +{ + using xGen = function* x() {}; + 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/using/function-local-closure-get-before-initialization.js b/test/language/statements/using/function-local-closure-get-before-initialization.js new file mode 100644 index 00000000000..a9455db1565 --- /dev/null +++ b/test/language/statements/using/function-local-closure-get-before-initialization.js @@ -0,0 +1,20 @@ +// 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: > + using: function local closure [[Get]] before initialization. + (TDZ, Temporal Dead Zone) +features: [explicit-resource-management] +---*/ + +(function() { + function f() { return x + 1; } + + assert.throws(ReferenceError, function() { + f(); + }); + + using x = null; +}()); diff --git a/test/language/statements/using/function-local-use-before-initialization-in-declaration-statement.js b/test/language/statements/using/function-local-use-before-initialization-in-declaration-statement.js new file mode 100644 index 00000000000..9551bb03f0a --- /dev/null +++ b/test/language/statements/using/function-local-use-before-initialization-in-declaration-statement.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-getbindingvalue-n-s +description: > + using: function local use before initialization in declaration statement. + (TDZ, Temporal Dead Zone) +features: [explicit-resource-management] +---*/ + +assert.throws(ReferenceError, function() { + (function() { + using x = x + 1; + }()); +}); diff --git a/test/language/statements/using/function-local-use-before-initialization-in-prior-statement.js b/test/language/statements/using/function-local-use-before-initialization-in-prior-statement.js new file mode 100644 index 00000000000..bde53e16424 --- /dev/null +++ b/test/language/statements/using/function-local-use-before-initialization-in-prior-statement.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-getbindingvalue-n-s +description: > + using: function local use before initialization in prior statement. + (TDZ, Temporal Dead Zone) +features: [explicit-resource-management] +---*/ + +assert.throws(ReferenceError, function() { + (function() { + x; using x = null; + }()); +}); diff --git a/test/language/statements/using/gets-initializer-Symbol.dispose-property-once.js b/test/language/statements/using/gets-initializer-Symbol.dispose-property-once.js new file mode 100644 index 00000000000..18d18f67877 --- /dev/null +++ b/test/language/statements/using/gets-initializer-Symbol.dispose-property-once.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-let-and-const-declarations-runtime-semantics-evaluation +description: Only reads `[Symbol.dispose]` method once, when initialized. +info: | + RS: Evaluation + UsingDeclaration : using BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument sync-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. + +features: [explicit-resource-management] +---*/ + +var resource = { + disposeReadCount: 0, + get [Symbol.dispose]() { + this.disposeReadCount++; + return function() { }; + } +}; + +{ + using _ = resource; +} + +assert.sameValue(resource.disposeReadCount, 1, 'Expected [Symbol.dispose] to have been read only once'); diff --git a/test/language/statements/using/global-closure-get-before-initialization.js b/test/language/statements/using/global-closure-get-before-initialization.js new file mode 100644 index 00000000000..e2f6436fac9 --- /dev/null +++ b/test/language/statements/using/global-closure-get-before-initialization.js @@ -0,0 +1,20 @@ +// 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: > + using: global closure [[Get]] before initialization. + (TDZ, Temporal Dead Zone) +features: [explicit-resource-management] +---*/ + +{ + function f() { return x + 1; } + + assert.throws(ReferenceError, function() { + f(); + }); + + using x = null; +} diff --git a/test/language/statements/using/global-use-before-initialization-in-declaration-statement.js b/test/language/statements/using/global-use-before-initialization-in-declaration-statement.js new file mode 100644 index 00000000000..d74401f32c0 --- /dev/null +++ b/test/language/statements/using/global-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: > + using: global use before initialization in declaration statement. + (TDZ, Temporal Dead Zone) +negative: + phase: runtime + type: ReferenceError +features: [explicit-resource-management] +---*/ + +{ + using x = x + 1; +} diff --git a/test/language/statements/using/global-use-before-initialization-in-prior-statement.js b/test/language/statements/using/global-use-before-initialization-in-prior-statement.js new file mode 100644 index 00000000000..33222306cca --- /dev/null +++ b/test/language/statements/using/global-use-before-initialization-in-prior-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: > + using: global use before initialization in prior statement. + (TDZ, Temporal Dead Zone) +negative: + phase: runtime + type: ReferenceError +features: [explicit-resource-management] +---*/ + +{ + x; using x = null; +} diff --git a/test/language/statements/using/initializer-disposed-at-end-of-asyncfunctionbody.js b/test/language/statements/using/initializer-disposed-at-end-of-asyncfunctionbody.js new file mode 100644 index 00000000000..a690233a467 --- /dev/null +++ b/test/language/statements/using/initializer-disposed-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. ... + 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() { + 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/using/initializer-disposed-at-end-of-asyncgeneratorbody.js b/test/language/statements/using/initializer-disposed-at-end-of-asyncgeneratorbody.js new file mode 100644 index 00000000000..ba2ffe421cb --- /dev/null +++ b/test/language/statements/using/initializer-disposed-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. ... + 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() { + 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/using/initializer-disposed-at-end-of-block.js b/test/language/statements/using/initializer-disposed-at-end-of-block.js new file mode 100644 index 00000000000..3ce3a97e652 --- /dev/null +++ b/test/language/statements/using/initializer-disposed-at-end-of-block.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 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. ... + 4. Return undefined. + +features: [explicit-resource-management] +---*/ + +var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } +}; + +{ + using _ = resource; +} + +assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); diff --git a/test/language/statements/using/initializer-disposed-at-end-of-each-iteration-of-forofstatementjs b/test/language/statements/using/initializer-disposed-at-end-of-each-iteration-of-forofstatementjs new file mode 100644 index 00000000000..391643bce0b --- /dev/null +++ b/test/language/statements/using/initializer-disposed-at-end-of-each-iteration-of-forofstatementjs @@ -0,0 +1,80 @@ +// 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. ... + 4. Return undefined. + +features: [explicit-resource-management] +---*/ + +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 (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/using/initializer-disposed-at-end-of-forstatement.js b/test/language/statements/using/initializer-disposed-at-end-of-forstatement.js new file mode 100644 index 00000000000..8a2dc03df45 --- /dev/null +++ b/test/language/statements/using/initializer-disposed-at-end-of-forstatement.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-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. ... + 4. Return undefined. + +features: [explicit-resource-management] +---*/ + +var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } +}; + +var i = 0; +var wasDisposedInForStatement; +for (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/using/initializer-disposed-at-end-of-functionbody.js b/test/language/statements/using/initializer-disposed-at-end-of-functionbody.js new file mode 100644 index 00000000000..6f498fb0d82 --- /dev/null +++ b/test/language/statements/using/initializer-disposed-at-end-of-functionbody.js @@ -0,0 +1,55 @@ +// 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-evaluatefunctionbody +description: Initialized value is disposed at end of FunctionBody +info: | + FunctionBody : FunctionStatementList + + ... + 3. Let result be Completion(Evaluation of FunctionStatementList). + 4. Let env be the running execution context's LexicalEnvironment. + 5. Return ? DisposeResources(env.[[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. ... + 4. Return undefined. + +features: [explicit-resource-management] +---*/ + +var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } +}; + +function f() { + using _ = resource; +} + +f(); + +assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); diff --git a/test/language/statements/using/initializer-disposed-at-end-of-generatorbody.js b/test/language/statements/using/initializer-disposed-at-end-of-generatorbody.js new file mode 100644 index 00000000000..a451b291901 --- /dev/null +++ b/test/language/statements/using/initializer-disposed-at-end-of-generatorbody.js @@ -0,0 +1,90 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-generatorstart +description: Initialized value is disposed at end of GeneratorBody +info: | + GeneratorStart ( generator, generatorBody ) + + 1. Assert: The value of generator.[[GeneratorState]] 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 generatorBody(). + e. Assert: If we return here, the 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.[[GeneratorState]] to completed. + h. NOTE: Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with acGenerator can be discarded at this point. + i. Let env be genContext's LexicalEnvironment. + j. If env is not undefined, then + i. Assert: env is a Declarative Environment Record. + i. Set result to DisposeResources(env.[[DisposeCapability]], result). + k. If result.[[Type]] is normal, then + i. Let resultValue be undefined. + l. Else if result.[[Type]] is return, then + i. Let resultValue be result.[[Value]]. + m. Else, + i. Assert: result.[[Type]] is throw. + ii. Return ? result. + n. Return CreateIterResultObject(resultValue, true). + 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.[[GeneratorContext]] to genContext. + 7. Set generator.[[GeneratorState]] to suspendedStart. + 8. 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. ... + 4. Return undefined. + +features: [explicit-resource-management] +---*/ + +var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } +}; + +function * f() { + using _ = resource; + yield; +} + +var g = f(); +var wasDisposedBeforeGeneratorStarted = resource.disposed; +g.next(); +var wasDisposedWhileSuspended = resource.disposed; +assert.sameValue(g.next().done, true); +var isDisposedAfterGeneratorCompleted = resource.disposed; + +assert.sameValue(wasDisposedBeforeGeneratorStarted, false, 'Expected resource to not have been disposed prior to generator start'); +assert.sameValue(wasDisposedWhileSuspended, false, 'Expected resource to not have been disposed while generator is suspended'); +assert.sameValue(isDisposedAfterGeneratorCompleted, true, 'Expected resource to have been disposed after generator completed'); diff --git a/test/language/statements/using/initializer-disposed-at-end-of-switchstatement.js b/test/language/statements/using/initializer-disposed-at-end-of-switchstatement.js new file mode 100644 index 00000000000..dda3a8ca923 --- /dev/null +++ b/test/language/statements/using/initializer-disposed-at-end-of-switchstatement.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-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. ... + 4. Return undefined. + +features: [explicit-resource-management] +---*/ + +var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } +}; + +switch (0) { + default: + using _ = resource; + break; +} + +assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); diff --git a/test/language/statements/using/initializer-disposed-if-subsequent-initializer-throws-in-forstatement-head.js b/test/language/statements/using/initializer-disposed-if-subsequent-initializer-throws-in-forstatement-head.js new file mode 100644 index 00000000000..d773ca81b5c --- /dev/null +++ b/test/language/statements/using/initializer-disposed-if-subsequent-initializer-throws-in-forstatement-head.js @@ -0,0 +1,64 @@ +// 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. ... + 4. Return undefined. + +features: [explicit-resource-management] +---*/ + +var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } +}; + +function getResource() { + throw new Error(); +} + +assert.throws(Error, function () { + var i = 0; + for (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/using/initializer-disposed-if-subsequent-initializer-throws.js b/test/language/statements/using/initializer-disposed-if-subsequent-initializer-throws.js new file mode 100644 index 00000000000..8c5a5db821b --- /dev/null +++ b/test/language/statements/using/initializer-disposed-if-subsequent-initializer-throws.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 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. ... + 4. Return undefined. + +features: [explicit-resource-management] +---*/ + +var resource = { + disposed: false, + [Symbol.dispose]() { + this.disposed = true; + } +}; + +function getResource() { + throw new Error(); +} + +assert.throws(Error, function () { + using _1 = resource, _2 = getResource(); +}); + +assert.sameValue(resource.disposed, true, 'Expected resource to have been disposed'); diff --git a/test/language/statements/using/multiple-resources-disposed-in-reverse-order.js b/test/language/statements/using/multiple-resources-disposed-in-reverse-order.js new file mode 100644 index 00000000000..7c7ffc899db --- /dev/null +++ b/test/language/statements/using/multiple-resources-disposed-in-reverse-order.js @@ -0,0 +1,55 @@ +// 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. ... + 4. Return undefined. + +features: [explicit-resource-management] +---*/ + +var disposed = []; +var resource1 = { [Symbol.dispose]() { disposed.push(this); } }; +var resource2 = { [Symbol.dispose]() { disposed.push(this); } }; +var resource3 = { [Symbol.dispose]() { disposed.push(this); } }; + +{ + using _1 = resource1, _2 = resource2; + using _3 = resource3; +} + +assert.sameValue(disposed[0], resource3); +assert.sameValue(disposed[1], resource2); +assert.sameValue(disposed[2], resource1); diff --git a/test/language/statements/using/puts-initializer-on-top-of-disposableresourcestack-multiple-bindings.js b/test/language/statements/using/puts-initializer-on-top-of-disposableresourcestack-multiple-bindings.js new file mode 100644 index 00000000000..1aede973b5e --- /dev/null +++ b/test/language/statements/using/puts-initializer-on-top-of-disposableresourcestack-multiple-bindings.js @@ -0,0 +1,69 @@ +// 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 'using' declaration +info: | + RS: Evaluation + UsingDeclaration : using BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument sync-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. + +features: [explicit-resource-management] +---*/ + +var disposed = []; +var resource1 = { + [Symbol.dispose]() { + disposed.push(this); + } +}; +var resource2 = { + [Symbol.dispose]() { + disposed.push(this); + } +}; +{ + 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/using/puts-initializer-on-top-of-disposableresourcestack-subsequent-usings.js b/test/language/statements/using/puts-initializer-on-top-of-disposableresourcestack-subsequent-usings.js new file mode 100644 index 00000000000..e04af6525df --- /dev/null +++ b/test/language/statements/using/puts-initializer-on-top-of-disposableresourcestack-subsequent-usings.js @@ -0,0 +1,64 @@ +// 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 'using' + declarations in the same block scope +info: | + RS: Evaluation + UsingDeclaration : using BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument sync-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. + +features: [explicit-resource-management] +---*/ + +var disposed = []; +var resource1 = { + [Symbol.dispose]() { + disposed.push(this); + } +}; +var resource2 = { + [Symbol.dispose]() { + disposed.push(this); + } +}; +{ + using r1 = resource1; + 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/using/redeclaration-error-from-within-strict-mode-function-using.js b/test/language/statements/using/redeclaration-error-from-within-strict-mode-function-using.js new file mode 100644 index 00000000000..838423f3595 --- /dev/null +++ b/test/language/statements/using/redeclaration-error-from-within-strict-mode-function-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(); +(function() { 'use strict'; { using f = null; var f; } }) + diff --git a/test/language/statements/using/static-init-await-binding-invalid.js b/test/language/statements/using/static-init-await-binding-invalid.js new file mode 100644 index 00000000000..1a9de298513 --- /dev/null +++ b/test/language/statements/using/static-init-await-binding-invalid.js @@ -0,0 +1,27 @@ +// Copyright (C) 2023 Ron Buckton. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-class-definitions-static-semantics-early-errors +description: BindingIdentifier may not be `await` within class static blocks +info: | + BindingIdentifier : Identifier + + [...] + - It is a Syntax Error if the code matched by this production is nested, + directly or indirectly (but not crossing function or static initialization + block boundaries), within a ClassStaticBlock and the StringValue of + Identifier is "await". +negative: + phase: parse + type: SyntaxError +features: [class-static-block, explicit-resource-management] +---*/ + +$DONOTEVALUATE(); + +class C { + static { + using await = null; + } +} diff --git a/test/language/statements/using/static-init-await-binding-valid.js b/test/language/statements/using/static-init-await-binding-valid.js new file mode 100644 index 00000000000..62d31d9f79f --- /dev/null +++ b/test/language/statements/using/static-init-await-binding-valid.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-class-definitions-static-semantics-early-errors +description: The `await` keyword is interpreted as an identifier within arrow function bodies +info: | + ClassStaticBlockBody : ClassStaticBlockStatementList + + [...] + - It is a Syntax Error if ContainsAwait of ClassStaticBlockStatementList is true. +features: [class-static-block, explicit-resource-management] +---*/ + +class C { + static { + (() => { using await = null; }); + } +} diff --git a/test/language/statements/using/throws-error-as-is-if-only-one-error-during-disposal.js b/test/language/statements/using/throws-error-as-is-if-only-one-error-during-disposal.js new file mode 100644 index 00000000000..98e65f4ef50 --- /dev/null +++ b/test/language/statements/using/throws-error-as-is-if-only-one-error-during-disposal.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: > + Rethrows an error as-is if it is the only error thrown during evaluation of subsequent statements following '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. + +features: [explicit-resource-management] +---*/ + +class MyError extends Error {} +assert.throws(MyError, function () { + using _1 = { [Symbol.dispose]() { throw new MyError(); } }; + using _2 = { [Symbol.dispose]() { } }; +}); + +assert.throws(MyError, function () { + using _1 = { [Symbol.dispose]() { } }; + using _2 = { [Symbol.dispose]() { throw new MyError(); } }; +}); + +assert.throws(MyError, function () { + using _1 = { [Symbol.dispose]() { } }; + using _2 = { [Symbol.dispose]() { } }; + throw new MyError(); +}); diff --git a/test/language/statements/using/throws-if-initializer-Symbol.dispose-property-is-null.js b/test/language/statements/using/throws-if-initializer-Symbol.dispose-property-is-null.js new file mode 100644 index 00000000000..75172e39283 --- /dev/null +++ b/test/language/statements/using/throws-if-initializer-Symbol.dispose-property-is-null.js @@ -0,0 +1,72 @@ +// 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 + UsingDeclaration : using BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument sync-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 + ... + 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. ... + +features: [explicit-resource-management] +---*/ + +assert.throws(TypeError, function () { + using x = { [Symbol.dispose]: null }; +}); diff --git a/test/language/statements/using/throws-if-initializer-Symbol.dispose-property-is-undefined.js b/test/language/statements/using/throws-if-initializer-Symbol.dispose-property-is-undefined.js new file mode 100644 index 00000000000..fb496778c0b --- /dev/null +++ b/test/language/statements/using/throws-if-initializer-Symbol.dispose-property-is-undefined.js @@ -0,0 +1,72 @@ +// 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 + UsingDeclaration : using BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument sync-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 + ... + 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. ... + +features: [explicit-resource-management] +---*/ + +assert.throws(TypeError, function () { + using x = { [Symbol.dispose]: undefined }; +}); diff --git a/test/language/statements/using/throws-if-initializer-Symbol.dispose-property-not-callable.js b/test/language/statements/using/throws-if-initializer-Symbol.dispose-property-not-callable.js new file mode 100644 index 00000000000..c804d80af21 --- /dev/null +++ b/test/language/statements/using/throws-if-initializer-Symbol.dispose-property-not-callable.js @@ -0,0 +1,93 @@ +// 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 + UsingDeclaration : using BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument sync-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 + ... + 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. + +features: [explicit-resource-management] +---*/ + +assert.throws(TypeError, function() { + using x = { [Symbol.dispose]: true }; +}, 'true'); + +assert.throws(TypeError, function() { + using x = { [Symbol.dispose]: false }; +}, 'false'); + +assert.throws(TypeError, function() { + using x = { [Symbol.dispose]: 1 }; +}, 'number'); + +assert.throws(TypeError, function() { + using x = { [Symbol.dispose]: 'object' }; +}, 'string'); + +var s = Symbol(); +assert.throws(TypeError, function() { + using x = { [Symbol.dispose]: s }; +}, 'symbol'); diff --git a/test/language/statements/using/throws-if-initializer-missing-Symbol.dispose.js b/test/language/statements/using/throws-if-initializer-missing-Symbol.dispose.js new file mode 100644 index 00000000000..9ad18b502ba --- /dev/null +++ b/test/language/statements/using/throws-if-initializer-missing-Symbol.dispose.js @@ -0,0 +1,65 @@ +// 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 Symbol.dispose property +info: | + RS: Evaluation + UsingDeclaration : using BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument sync-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. + ... + + +features: [explicit-resource-management] +---*/ + +assert.throws(TypeError, function () { + using x = {}; +}); diff --git a/test/language/statements/using/throws-suppressederror-if-multiple-errors-during-disposal.js b/test/language/statements/using/throws-suppressederror-if-multiple-errors-during-disposal.js new file mode 100644 index 00000000000..1d512b20f59 --- /dev/null +++ b/test/language/statements/using/throws-suppressederror-if-multiple-errors-during-disposal.js @@ -0,0 +1,53 @@ +// 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 '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. + +features: [explicit-resource-management] +---*/ + +class MyError extends Error {} +const error1 = new MyError(); +const error2 = new MyError(); +const error3 = new MyError(); + +try { + using _1 = { [Symbol.dispose]() { throw error1; } }; + 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'"); +} diff --git a/test/language/statements/using/using-allows-null-initializer.js b/test/language/statements/using/using-allows-null-initializer.js new file mode 100644 index 00000000000..15593f537b0 --- /dev/null +++ b/test/language/statements/using/using-allows-null-initializer.js @@ -0,0 +1,44 @@ +// 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 'using' +info: | + RS: Evaluation + UsingDeclaration : using BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument sync-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. + ... + ... + +features: [explicit-resource-management] +---*/ + +{ + using x = null; +} diff --git a/test/language/statements/using/using-allows-undefined-initializer.js b/test/language/statements/using/using-allows-undefined-initializer.js new file mode 100644 index 00000000000..717591ffe46 --- /dev/null +++ b/test/language/statements/using/using-allows-undefined-initializer.js @@ -0,0 +1,44 @@ +// 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 'using' +info: | + RS: Evaluation + UsingDeclaration : using BindingList ; + + 1. Perform ? BindingEvaluation of BindingList with argument sync-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. + ... + ... + +features: [explicit-resource-management] +---*/ + +{ + using x = undefined; +}