diff --git a/README.md b/README.md index b023ced..fe8177f 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,12 @@ As such, we propose the adoption of a novel syntax to simplify this common patte ```js // in an async function: async function * g() { - async using handle = acquireFileHandle(); // async-block-scoped critical resource + using await handle = acquireFileHandle(); // async-block-scoped critical resource } // async cleanup -// in an `await using {}` block: -await using { - async using obj = g(); // block-scoped declaration +// in a block in an async context: +{ + using await obj = g(); // block-scoped declaration const r = await obj.next(); } // calls finally blocks in `g` and awaits result ``` @@ -141,7 +141,7 @@ This proposal is motivated by a number of cases: // avoids leaking `a` or `b` to outer scope // ensures `b` is disposed before `a` in case `b` depends on `a` // ensures `a` is disposed even if disposing `b` throws - async using a = ..., b = ...; + using await a = ..., b = ...; ... ``` - Non-blocking memory/IO applications: @@ -151,7 +151,7 @@ This proposal is motivated by a number of cases: export async function readData() { // wait for outstanding writer and take a read lock - async using lockHandle = await lock.read(); + using await lockHandle = await lock.read(); ... // any number of readers await ...; ... // still in read lock after `await` @@ -159,7 +159,7 @@ This proposal is motivated by a number of cases: export async function writeData(data) { // wait for all readers and take a write lock - async using lockHandle = await lock.write(); + using await lockHandle = await lock.write(); ... // only one writer await ...; ... // still in write lock after `await` @@ -192,37 +192,145 @@ This proposal is motivated by a number of cases: - `WeakRef` values - `FinalizationRegistry` entries - _Explicit Resource Management_ — Indicates a system whereby the lifetime of a "resource" is managed explicitly - by the user either **imperatively** (by directly calling a method like `Symbol.dispose` or `Symbol.asyncDispose`) or **declaratively** (through a block-scoped declaration like `using` or `async using`). + by the user either **imperatively** (by directly calling a method like `Symbol.dispose` or `Symbol.asyncDispose`) or **declaratively** (through a block-scoped declaration like `using` or `using await`). # Syntax -## `async using` Declarations +## `using await` Declarations ```js // an asynchronously-disposed, block-scoped resource -async using x = expr1; // resource w/ local binding -async using y = expr2, z = expr4; // multiple resources +using await x = expr1; // resource w/ local binding +using await y = expr2, z = expr4; // multiple resources ``` -An `async using` declaration can appear in the following contexts: +An `using await` declaration can appear in the following contexts: - The top level of a _Module_ anywhere _VariableStatement_ is allowed, as long as it is not immediately nested inside - of a _Block_, _CaseClause_, or _DefaultClause_. -- In the body of an async function or async generator, as long as it is not immediately nested inside of a _Block_, - _CaseClause_, or _DefaultClause_. -- In the head of a `for-await-of` statement. -- As an immediate child of an `await using` Block. + of a _CaseClause_ or _DefaultClause_. +- In the body of an async function or async generator anywhere a _VariableStatement_ is allowed, as long as it is not + immediately nested inside of a _CaseClause_ or _DefaultClause_. +- In the head of a `for-of` or `for-await-of` statement. + +## `using await` in `for-of` and `for-await-of` Statements + +```js +for (using await x of y) ... + +for await (using await x of y) ... +``` -## `await using` Blocks +You can use a `using await` declaration in a `for-of` or `for-await-of` statement inside of an async context to +explicitly bind each iterated value as an async disposable resource. `for-await-of` does not implicitly make a non-async +`using` declaration into an async `using await` declaration, as the `await` markers in `for-await-of` and `using await` +are explicit indicators for distinct cases: `for await` *only* indicates async iteration, while `using await` *only* +indicates async disposal. For example: ```js -await using { - // statements -} // resources added with `using` or `async using` are disposed, `await`-ing any results. +// sync iteration, sync disposal +for (using x of y) ; // no implicit `await` at end of each iteration + +// sync iteration, async disposal +for (using await x of y) ; // implicit `await` at end of each iteration + +// async iteration, sync disposal +for await (using x of y) ; // implicit `await` at end of each iteration + +// async iteration, async disposal +for await (using await x of y) ; // implicit `await` at end of each iteration +``` + +While there is some overlap in that the last three cases introduce some form of implicit `await` during execution, it +is intended that the presence or absence of the `await` modifier in a `using` declaration is an explicit indicator as to +whether we are expecting the iterated value to have an `@@asyncDispose` method. This distinction is in line with the +behavior of `for-of` and `for-await-of`: + +```js +const iter = { [Symbol.iterator]() { return [].values(); } }; +const asyncIter = { [Symbol.asyncIterator]() { return [].values(); } }; + +for (const x of iter) ; // ok: `iter` has @@iterator +for (const x of asyncIter) ; // throws: `asyncIter` does not have @@iterator + +for await (const x of iter) ; // ok: `iter` has @@iterator (fallback) +for await (const x of asyncIter) ; // ok: `asyncIter` has @@asyncIterator + +``` + +`using` and `using await` have the same distinction: + +```js +const res = { [Symbol.dispose]() {} }; +const asyncRes = { [Symbol.asyncDispose]() {} }; + +using x = res; // ok: `res` has @@dispose +using x = asyncRes; // throws: `asyncRes` does not have @@dispose + +using await x = res; // ok: `res` has @@dispose (fallback) +using await x = asyncres; // ok: `asyncRes` has @@asyncDispose +``` + +This results in a matrix of behaviors based on the presence of each `await` marker: + +```js +const res = { [Symbol.dispose]() {} }; +const asyncRes = { [Symbol.asyncDispose]() {} }; +const iter = { [Symbol.iterator]() { return [res, asyncRes].values(); } }; +const asyncIter = { [Symbol.asyncIterator]() { return [res, asyncRes].values(); } }; + +for (using x of iter) ; +// sync iteration, sync disposal +// - `iter` has @@iterator: ok +// - `res` has @@dispose: ok +// - `asyncRes` does not have @@dispose: *error* + +for (using x of asyncIter) ; +// sync iteration, sync disposal +// - `asyncIter` does not have @@iterator: *error* + +for (using await x of iter) ; +// sync iteration, async disposal +// - `iter` has @@iterator: ok +// - `res` has @@dispose (fallback): ok +// - `asyncRes` has @@asyncDispose: ok + +for (using await x of asyncIter) ; +// sync iteration, async disposal +// - `asyncIter` does not have @@iterator: error + +for await (using x of iter) ; +// async iteration, sync disposal +// - `iter` has @@iterator (fallback): ok +// - `res` has @@dispose: ok +// - `asyncRes` does not have @@dispose: error + +for await (using x of asyncIter) ; +// async iteration, sync disposal +// - `asyncIter` has @@asyncIterator: ok +// - `res` has @@dispose: ok +// - `asyncRes` does not have @@dispose: error + +for await (using await x of iter) ; +// async iteration, async disposal +// - `iter` has @@iterator (fallback): ok +// - `res` has @@dispose (fallback): ok +// - `asyncRes` does has @@asyncDispose: ok + +for await (using await x of asyncIter) ; +// async iteration, async disposal +// - `asyncIter` has @@asyncIterator: ok +// - `res` has @@dispose (fallback): ok +// - `asyncRes` does has @@asyncDispose: ok ``` -An `await using` Block is a _Statement_ that can appear anywhere a `for-await-of` statement is legal (i.e., at the top -level of a _Module_ or inside an async function or async generator body). +Or, in table form: + +| Syntax | Iteration | Disposal | +|:---------------------------------|:------------------------------:|:----------------------------:| +| `for (using x of y)` | `@@iterator` | `@@dispose` | +| `for (using await x of y)` | `@@iterator` | `@@asyncDispose`/`@@dispose` | +| `for await (using x of y)` | `@@asyncIterator`/`@@iterator` | `@@dispose` | +| `for await (using await x of y)` | `@@asyncIterator`/`@@iterator` | `@@asyncDispose`/`@@dispose` | # Grammar @@ -230,25 +338,25 @@ Please refer to the [specification text][Specification] for the most recent vers # Semantics -## `async using` Declarations +## `using await` Declarations -### `async using` Declarations with Explicit Local Bindings +### `using await` Declarations with Explicit Local Bindings ```grammarkdown UsingDeclaration : - `async` `using` BindingList `;` + `using` `await` BindingList `;` LexicalBinding : BindingIdentifier Initializer ``` -When an `async using` declaration is parsed with _BindingIdentifier_ _Initializer_, the bindings created in the -declaration are tracked for disposal at the end of the containing async function body, `await using` Block, or _Module_: +When an `using await` declaration is parsed with _BindingIdentifier_ _Initializer_, the bindings created in the +declaration are tracked for disposal at the end of the containing async function body, _Block_, or _Module_: ```js -await using { +{ ... // (1) - async using x = expr1; + using await x = expr1; ... // (2) } ``` @@ -297,30 +405,30 @@ The above example has similar runtime semantics as the following transposed repr } ``` -If exceptions are thrown both in the statements following the `async using` declaration and in the call to +If exceptions are thrown both in the statements following the `using await` declaration and in the call to `[Symbol.asyncDispose]()`, all exceptions are reported. -### `async using` Declarations with Multiple Resources +### `using await` Declarations with Multiple Resources -An `async using` declaration can mix multiple explicit bindings in the same declaration: +An `using await` declaration can mix multiple explicit bindings in the same declaration: ```js -await using { +{ ... - async using x = expr1, y = expr2; + using await x = expr1, y = expr2; ... } ``` -These bindings are again used to perform resource disposal when the `await using` Block or _Module_ exits, however in -this case each resource's `[Symbol.asyncDispose]()` is invoked in the reverse order of their declaration. This is -_approximately_ equivalent to the following: +These bindings are again used to perform resource disposal when the _Block_ or _Module_ exits, however in this case each +resource's `[Symbol.asyncDispose]()` is invoked in the reverse order of their declaration. This is _approximately_ +equivalent to the following: ```js -await using { +{ ... // (1) - async using x = expr1; - async using y = expr2; + using await x = expr1; + using await y = expr2; ... // (2) } ``` @@ -386,18 +494,17 @@ occur during binding initialization results in evaluation of the cleanup step. W the list, we track each resource in the order they are declared. As a result, we must release these resources in reverse order. -### `async using` Declarations and `null` or `undefined` Values +### `using await` Declarations and `null` or `undefined` Values -This proposal has opted to ignore `null` and `undefined` values provided to `async using` declarations. This is similar -to the behavior of `using` in the original -[Explicit Resource Management][using] proposal, which also +This proposal has opted to ignore `null` and `undefined` values provided to `using await` declarations. This is similar +to the behavior of `using` in the original [Explicit Resource Management][using] proposal, which also allows `null` and `undefined`, as well as C#, which also allows `null`,. One primary reason for this behavior is to simplify a common case where a resource might be optional, without requiring duplication of work or needless allocations: ```js -if (isResourceAvailable()) await using { - async using resource = getResource(); +if (isResourceAvailable()) { + using await resource = getResource(); ... // (1) resource.doSomething() ... // (2) @@ -412,22 +519,22 @@ else { Compared to: ```js -async using resource = isResourceAvailable() ? getResource() : undefined; +using await resource = isResourceAvailable() ? getResource() : undefined; ... // (1) do some work with or without resource resource?.doSomething(); ... // (2) do some other work with or without resource ``` -### `async using` Declarations and Values Without `[Symbol.asyncDispose]` or `[Symbol.dispose]` +### `using await` Declarations and Values Without `[Symbol.asyncDispose]` or `[Symbol.dispose]` If a resource does not have a callable `[Symbol.asyncDispose]` or `[Symbol.asyncDispose]` member, a `TypeError` would be thrown **immediately** when the resource is tracked. -### `async using` Declarations in `for-of` and `for-await-of` Loops +### `using await` Declarations in `for-of` and `for-await-of` Loops -A `using` declaration _may_ occur in the _ForDeclaration_ of a `for-await-of` loop: +A `using await` declaration _may_ occur in the _ForDeclaration_ of a `for-await-of` loop: ```js -for await (async using x of iterateResources()) { +for await (using await x of iterateResources()) { // use x } ``` @@ -436,7 +543,44 @@ In this case, the value bound to `x` in each iteration will be _asynchronously_ This will not dispose resources that are not iterated, such as if iteration is terminated early due to `return`, `break`, or `throw`. -`async using` declarations _may not_ be used in in the head of a `for-of` or `for-in` loop. +`using await` declarations _may not_ be used in in the head of a `for-of` or `for-in` loop. + +### Implicit Async Interleaving Points ("implicit `await`") + +The `using await` syntax introduces an implicit async interleaving point (i.e., an implicit `await`) whenever control +flow exits an async function body, _Block_, or _Module_ containing a `using await` declaration. This means that two +statements that currently execute in the same microtask, such as: + +```js +async function f() { + { + a(); + } // exit block + b(); // same microtask as call to `a()` +} +``` + +will instead execute in different microtasks if a `using await` declaration is introduced: + +```js +async function f() { + { + using await x = ...; + a(); + } // exit block, implicit `await` + b(); // different microtask from call to `a()`. +} +``` + +It is important that such an implicit interleaving point be adequately indicated within the syntax. We believe that +the presence of `using await` within such a block is an adequate indicator, since it should be fairly easy to recognize +a _Block_ containing a `using await` statement in well-formated code. + +It is also feasible for editors to use features such as syntax highlighting, editor decorations, and inlay hints to +further highlight such transitions, without needing to specify additional syntax. + +Further discussion around the `using await` syntax and how it pertains to implicit async interleaving points can be +found in [#1](https://github.com/tc39/proposal-async-explicit-resource-management/issues/1). # Examples @@ -444,16 +588,16 @@ The following show examples of using this proposal with various APIs, assuming t ### WHATWG Streams API ```js -await using { - async using stream = new ReadableStream(...); +{ + using await stream = new ReadableStream(...); ... } // 'stream' is canceled and result is awaited ``` ### NodeJS Streams ```js -await using { - async using writable = ...; +{ + using await writable = ...; writable.write(...); } // 'writable.end()' is called and its result is awaited ``` @@ -462,7 +606,7 @@ await using { ```js // roll back transaction if either action fails async function transfer(account1, account2) { - async using tx = transactionManager.startTransaction(account1, account2); + using await tx = transactionManager.startTransaction(account1, account2); await account1.debit(amount); await account2.credit(amount); @@ -480,8 +624,7 @@ This proposal adds the `asyncDispose` property to the `Symbol` constructor whose **Well-known Symbols** | Specification Name | \[\[Description]] | Value and Purpose | |:-|:-|:-| -| _@@dispose_ | *"Symbol.dispose"* | A method that explicitly disposes of resources held by the object. Called by the semantics of `using` declarations and by `DisposableStack` objects. | -| _@@asyncDispose_ | *"Symbol.asyncDispose"* | A method that asynchronosly explicitly disposes of resources held by the object. Called by the semantics of `async using` declarations and by `AsyncDisposableStack` objects. | +| _@@asyncDispose_ | *"Symbol.asyncDispose"* | A method that asynchronosly explicitly disposes of resources held by the object. Called by the semantics of `using await` declarations and by `AsyncDisposableStack` objects. | **TypeScript Definition** ```ts @@ -502,7 +645,7 @@ We propose to add `Symbol.asyncDispose` to the built-in `%AsyncIteratorPrototype } ``` -## The Common `AsyncDisposabl` Interface +## The Common `AsyncDisposable` Interface ### The `AsyncDisposable` Interface @@ -543,6 +686,7 @@ class AsyncDisposableStack { /** * Alias for `[Symbol.asyncDispose]()`. + * @returns {Promise}. */ disposeAsync(); @@ -632,11 +776,11 @@ callback will be executed when the stack's disposal method is executed. The ability to create a disposable resource from a callback has several benefits: -- It allows developers to leverage `async using` while working with existing resources that do not conform to the +- It allows developers to leverage `using await` while working with existing resources that do not conform to the `Symbol.asyncDispose` mechanic: ```js - await using { - async using stack = new AsyncDisposableStack(); + { + using await stack = new AsyncDisposableStack(); const stream = stack.adopt(createReadableStream(), async reader => await reader.close()); ... } @@ -645,7 +789,7 @@ The ability to create a disposable resource from a callback has several benefits `defer` statement: ```js async function f() { - async using stack = new AsyncDisposableStack(); + using await stack = new AsyncDisposableStack(); stack.defer(async () => await someAsyncCleanupOperaiton()); ... } @@ -678,7 +822,7 @@ class PluginHost { // Create an AsyncDisposableStack that is disposed when the constructor exits. // If construction succeeds, we move everything out of `stack` and into // `#disposables` to be disposed later. - async using stack = new AsyncDisposableStack(); + using await stack = new AsyncDisposableStack(); // Create an IPC adapter around process.send/process.on("message"). @@ -817,7 +961,7 @@ consideration. The actual implementation is at the discretion of the NodeJS main - SYG (@syg) added as reviewer * TC39 December 1st, 2022 (notes TBA) - Conclusion - - `async using` declarations, `Symbol.asyncDispose`, and `AsyncDisposableStack` remain at Stage 2 as an independent + - `using await` declarations, `Symbol.asyncDispose`, and `AsyncDisposableStack` remain at Stage 2 as an independent proposal. # TODO diff --git a/spec.emu b/spec.emu index 8f0e500..5e0932d 100644 --- a/spec.emu +++ b/spec.emu @@ -37,7 +37,8 @@ contributors: Ron Buckton, Ecma International

Introduction

This proposal introduces syntax and semantics around explicit resource management.

-

See the proposal repository for background material and discussion.

+

See the proposal repository for background material and discussion.

+

This document includes specification text also proposed in Explicit Resource Management to provide a comprehensive, holistic specification.

@@ -70,7 +71,7 @@ contributors: Ron Buckton, Ecma International `"Symbol.asyncDispose"` - A method that performs explicit resource cleanup on an object. Called by the semantics of AsyncDisposableStack objects. + A method that performs explicit resource cleanup on an object. Called by the semantics of the `using await` declaration and AsyncDisposableStack objects. @@ -867,6 +868,7 @@ contributors: Ron Buckton, Ecma International +

ECMAScript Specification Types

@@ -877,7 +879,7 @@ contributors: Ron Buckton, Ecma International InitializeReferencedBinding ( _V_: unknown, _W_: unknown, - _hint_: either ~normal~ or ~sync-dispose~, + _hint_: either ~normal~, ~sync-dispose~, or ~async-dispose~, ): either a normal completion containing ~unused~ or an abrupt completion
@@ -941,7 +943,7 @@ contributors: Ron Buckton, Ecma International ~sync-dispose~ or ~async-dispose~. - Indicates whether the resources was added by a `using` declaration or DisposableStack object (~sync-dispose~) or an AsyncDisposableStack object (~async-dispose~). + Indicates whether the resource was added by a `using` declaration or DisposableStack object (~sync-dispose~) or a `using await` declaration or AsyncDisposableStack object (~async-dispose~). @@ -949,7 +951,7 @@ contributors: Ron Buckton, Ecma International [[DisposeMethod]] - A function object. + A function object or *undefined*. A function object that will be called with [[ResourceValue]] as its *this* value when the resource disposed. @@ -973,8 +975,8 @@ contributors: Ron Buckton, Ecma International
1. If _method_ is not present then, - 1. If _V_ is *null* or *undefined*, return NormalCompletion(~empty~). - 1. If Type(_V_) is not Object, throw a *TypeError* exception. + 1. If _V_ is *null* or *undefined* and _hint_ is ~sync-dispose~, then + 1. Return NormalCompletion(~empty~). 1. Let _resource_ be ? CreateDisposableResource(_V_, _hint_). 1. Else, 1. If _V_ is *null* or *undefined*, then @@ -990,7 +992,7 @@ contributors: Ron Buckton, Ecma International

CreateDisposableResource ( - _V_ : an Object or *undefined*, + _V_ : an ECMAScript language value, _hint_ : either ~sync-dispose~ or ~async-dispose~, optional _method_ : a function object, ) @@ -998,9 +1000,13 @@ contributors: Ron Buckton, Ecma International
1. If _method_ is not present, then - 1. If _V_ is *undefined*, throw a *TypeError* exception. - 1. Set _method_ to ? GetDisposeMethod(_V_, _hint_). - 1. If _method_ is *undefined*, throw a *TypeError* exception. + 1. If _V_ is *null* or *undefined*, then + 1. Set _V_ to *undefined*. + 1. Set _method_ to *undefined*. + 1. Else, + 1. If Type(_V_) is not Object, throw a *TypeError* exception. + 1. Set _method_ to ? GetDisposeMethod(_V_, _hint_). + 1. If _method_ is *undefined*, throw a *TypeError* exception. 1. Else, 1. If IsCallable(_method_) is *false*, throw a *TypeError* exception. 1. Return the DisposableResource Record { [[ResourceValue]]: _V_, [[Hint]]: _hint_, [[DisposeMethod]]: _method_ }. @@ -1031,13 +1037,14 @@ contributors: Ron Buckton, Ecma International Dispose ( _V_ : an Object or *undefined*, _hint_ : either ~sync-dispose~ or ~async-dispose~, - _method_ : a function object, + _method_ : a function object or *undefined*, )

- 1. Let _result_ be ? Call(_method_, _V_). - 1. If _hint_ is ~async-dispose~ and _result_ is not *undefined*, then + 1. If _method_ is *undefined*, let _result_ be *undefined*. + 1. Else, let _result_ be ? Call(_method_, _V_). + 1. If _hint_ is ~async-dispose~, then 1. Perform ? Await(_result_). 1. Return *undefined*. @@ -1060,8 +1067,8 @@ contributors: Ron Buckton, Ecma International 1. Set _result_ to _result_.[[Value]]. 1. Let _suppressed_ be _completion_.[[Value]]. 1. Let _error_ be a newly created *SuppressedError* object. - 1. Perform ! DefinePropertyOrThrow(_error_, *"error"*, PropertyDescriptor { [[Configurable]]: *true*, [[Enumerable]]: *false*, [[Writable]]: *true*, [[Value]]: _result_ }). - 1. Perform ! DefinePropertyOrThrow(_error_, *"suppressed"*, PropertyDescriptor { [[Configurable]]: *true*, [[Enumerable]]: *false*, [[Writable]]: *true*, [[Value]]: _suppressed_ }). + 1. Perform ! CreateNonEnumerableDataPropertyOrThrow(_error_, *"error"*, _result_). + 1. Perform ! CreateNonEnumerableDataPropertyOrThrow(_error_, *"suppressed"*, _suppressed_). 1. Set _completion_ to ThrowCompletion(_error_). 1. Else, 1. Set _completion_ to _result_. @@ -1104,6 +1111,7 @@ contributors: Ron Buckton, Ecma International UsingDeclaration : `using` BindingList `;` + `using await` BindingList `;` 1. Return the BoundNames of |BindingList|. @@ -1400,6 +1408,134 @@ contributors: Ron Buckton, Ecma International

It is not necessary to treat `export default` |AssignmentExpression| as a constant declaration because there is no syntax that permits assignment to the internal bound name used to reference a module's default object.

+ + + +

Static Semantics: IsUsingDeclaration ( ): a Boolean

+
+
+ LexicalDeclaration : LetOrConst BindingList `;` + + 1. Return *false*. + + LexicalDeclaration : UsingDeclaration + + 1. Return *true*. + + ForDeclaration : LetOrConst ForBinding + + 1. Return *false*. + + + ForDeclaration : + `using` ForBinding + `using` `await` ForBinding + + + 1. Return *true*. + + + FunctionDeclaration : + `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}` + `function` `(` FormalParameters `)` `{` FunctionBody `}` + + GeneratorDeclaration : + `function` `*` BindingIdentifier `(` FormalParameters `)` `{` GeneratorBody `}` + `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}` + + AsyncGeneratorDeclaration : + `async` `function` `*` BindingIdentifier `(` FormalParameters `)` `{` AsyncGeneratorBody `}` + `async` `function` `*` `(` FormalParameters `)` `{` AsyncGeneratorBody `}` + + AsyncFunctionDeclaration : + `async` `function` BindingIdentifier `(` FormalParameters `)` `{` AsyncFunctionBody `}` + `async` `function` `(` FormalParameters `)` `{` AsyncFunctionBody `}` + + + 1. Return *false*. + + + ClassDeclaration : + `class` BindingIdentifier ClassTail + `class` ClassTail + + + 1. Return *false*. + + + ExportDeclaration : + `export` ExportFromClause FromClause `;` + `export` NamedExports `;` + `export` `default` AssignmentExpression `;` + + + 1. Return *false*. + +
+ + +

Static Semantics: IsUsingAwaitDeclaration ( ): a Boolean

+
+
+ LexicalDeclaration : LetOrConst BindingList `;` + + 1. Return *false*. + + UsingDeclaration : `using` BindingList `;` + + 1. Return *false*. + + UsingDeclaration : `using` `await` BindingList `;` + + 1. Return *true*. + + ForDeclaration : `using` ForBinding + + 1. Return *false*. + + ForDeclaration : `using` `await` ForBinding + + 1. Return *true*. + + + FunctionDeclaration : + `function` BindingIdentifier `(` FormalParameters `)` `{` FunctionBody `}` + `function` `(` FormalParameters `)` `{` FunctionBody `}` + + GeneratorDeclaration : + `function` `*` BindingIdentifier `(` FormalParameters `)` `{` GeneratorBody `}` + `function` `*` `(` FormalParameters `)` `{` GeneratorBody `}` + + AsyncGeneratorDeclaration : + `async` `function` `*` BindingIdentifier `(` FormalParameters `)` `{` AsyncGeneratorBody `}` + `async` `function` `*` `(` FormalParameters `)` `{` AsyncGeneratorBody `}` + + AsyncFunctionDeclaration : + `async` `function` BindingIdentifier `(` FormalParameters `)` `{` AsyncFunctionBody `}` + `async` `function` `(` FormalParameters `)` `{` AsyncFunctionBody `}` + + + 1. Return *false*. + + + ClassDeclaration : + `class` BindingIdentifier ClassTail + `class` ClassTail + + + 1. Return *false*. + + + ExportDeclaration : + `export` ExportFromClause FromClause `;` + `export` NamedExports `;` + `export` `default` AssignmentExpression `;` + + + 1. Return *false*. + +
+
@@ -1636,7 +1772,7 @@ contributors: Ron Buckton, Ecma International InitializeBinding(N, V, _hint_) - Set the value of an already existing but uninitialized binding in an Environment Record. The String value _N_ is the text of the bound name. _V_ is the value for the binding and is a value of any ECMAScript language type. _hint_ indicates whether the binding came from either a `using` declaration (~sync-dispose~) or a regular variable declaration (~normal~). + Set the value of an already existing but uninitialized binding in an Environment Record. The String value _N_ is the text of the bound name. _V_ is the value for the binding and is a value of any ECMAScript language type. _hint_ indicates whether the binding came from either a `using` declaration (~sync-dispose~), a `using await` declaration (~async-dispose~), or a regular variable declaration (~normal~). @@ -1694,7 +1830,7 @@ contributors: Ron Buckton, Ecma International

Declarative Environment Records

Each declarative Environment Record is associated with an ECMAScript program scope containing variable, constant, let, class, module, import, and/or function declarations. A declarative Environment Record binds the set of identifiers defined by the declarations contained within its scope.

-

Every declarative Environment Record also has a [[DisposableResourceStack]] field, which is a List of DisposableResource Records. This list is a stack of resources tracked by the `using` declarations that must be disposed when the Evaluation step that constructed the Environment Record has completed.

+

Every declarative Environment Record also has a [[DisposableResourceStack]] field, which is a List of DisposableResource Records. This list is a stack of resources tracked by the `using` declarations and `using await` declarations that must be disposed when the Evaluation step that constructed the Environment Record has completed.

The behaviour of the concrete specification methods for declarative Environment Records is defined by the following algorithms.

@@ -1702,7 +1838,7 @@ contributors: Ron Buckton, Ecma International InitializeBinding ( _N_: a String, _V_: an ECMAScript language value, - _hint_: either ~normal~ or ~sync-dispose~, + _hint_: either ~normal~, ~sync-dispose~, or ~async-dispose~, ): a normal completion containing ~unused~
@@ -1730,7 +1866,7 @@ contributors: Ron Buckton, Ecma International InitializeBinding ( _N_: a String, _V_: an ECMAScript language value, - _hint_: either ~normal~ or ~sync-dispose~, + _hint_: either ~normal~, ~sync-dispose~, or ~async-dispose~, ): either a normal completion containing ~unused~ or a throw completion
@@ -1760,7 +1896,7 @@ contributors: Ron Buckton, Ecma International InitializeBinding ( _N_: a String, _V_: an ECMAScript language value, - _hint_: either ~normal~ or ~sync-dispose~, + _hint_: either ~normal~, ~sync-dispose~, or ~async-dispose~, ): either a normal completion containing ~unused~ or a throw completion
@@ -1983,6 +2119,7 @@ contributors: Ron Buckton, Ecma International 1. Let _fn_ be the sole element of the BoundNames of _d_. 1. Let _fo_ be InstantiateFunctionObject of _d_ with arguments _env_ and _privateEnv_. 1. [id="step-blockdeclarationinstantiation-initializebinding"] Perform _env_.InitializeBinding(_fn_, _fo_, ~normal~). NOTE: This step is replaced in section . + 1. Return _hint_. @@ -2003,7 +2140,8 @@ contributors: Ron Buckton, Ecma International UsingDeclaration[In, Yield, Await] : - `using` [no LineTerminator here] BindingList[?In, ?Yield, ?Await, +Using] `;` + `using` [no LineTerminator here] [lookahead != `await`] BindingList[?In, ?Yield, ?Await, +Using] `;` + [+Await] `using` [no LineTerminator here] `await` [no LineTerminator here] BindingList[?In, ?Yield, +Await, +Using] `;` BindingList[In, Yield, Await, Using] : @@ -2022,11 +2160,15 @@ contributors: Ron Buckton, Ecma International UsingDeclaration : `using` BindingList `;` + `using` `await` BindingList `;`
  • It is a Syntax Error if the BoundNames of |BindingList| contains *"let"*.
  • +
  • + It is a Syntax Error if the BoundNames of |BindingList| contains *"await"*. +
  • It is a Syntax Error if the BoundNames of |BindingList| contains any duplicate entries.
  • @@ -2064,6 +2206,13 @@ contributors: Ron Buckton, Ecma International 1. ReturnIfAbrupt(_next_). 1. Return ~empty~. + + UsingDeclaration : `using` `await` BindingList `;` + + 1. Let _next_ be BindingEvaluation of |BindingList| with parameter ~async-dispose~. + 1. ReturnIfAbrupt(_next_). + 1. Return ~empty~. + @@ -2108,7 +2257,7 @@ contributors: Ron Buckton, Ecma International

    Runtime Semantics: BindingEvaluation ( - _hint_: either ~normal~ or ~sync-dispose~ + _hint_: either ~normal~, ~sync-dispose~, or ~async-dispose~. ): either a normal completion containing ~unused~ or an abrupt completion

    @@ -2265,71 +2414,6 @@ contributors: Ron Buckton, Ecma International - -

    - ForBodyEvaluation ( - _test_: unknown, - _increment_: unknown, - _stmt_: unknown, - _perIterationBindings_: unknown, - _labelSet_: unknown, - ): either a normal completion containing an ECMAScript language value or an abrupt completion -

    -
    -
    - - 1. Let _V_ be *undefined*. - 1. Perform ? CreatePerIterationEnvironment(_perIterationBindings_). - 1. Let _thisIterationEnv_ be ? CreatePerIterationEnvironment(_perIterationBindings_). - 1. Repeat, - 1. If _test_ is not ~[empty]~, then - 1. Let _testRef_ be the result of evaluating _test_. - 1. Let _testValue_ be ? GetValue(_testRef_). - 1. Let _testValue_ be Completion(GetValue(_testRef_)). - 1. If _testValue_ is an abrupt completion, then - 1. Return ? DisposeResources(_thisIterationEnv_, _testValue_). - 1. Else, - 1. Set _testValue_ to _testValue_.[[Value]]. - 1. If ToBoolean(_testValue_) is *false*, return _V_. - 1. If ToBoolean(_testValue_) is *false*, return ? DisposeResources(_thisIterationEnv_, Completion(_V_)). - 1. Let _result_ be the result of evaluating _stmt_. - 1. Perform ? DisposeResources(_thisIterationEnv_, _result_). - 1. If LoopContinues(_result_, _labelSet_) is *false*, return ? UpdateEmpty(_result_, _V_). - 1. If _result_.[[Value]] is not ~empty~, set _V_ to _result_.[[Value]]. - 1. Perform ? CreatePerIterationEnvironment(_perIterationBindings_). - 1. Set _thisIterationEnv_ to ? CreatePerIterationEnvironment(_perIterationBindings_). - 1. If _increment_ is not ~[empty]~, then - 1. Let _incRef_ be the result of evaluating _increment_. - 1. Perform ? GetValue(_incRef_). - 1. Let _incrResult_ be Completion(GetValue(_incrRef_)). - 1. If _incrResult_ is an abrupt completion, then - 1. Return ? DisposeResources(_thisIterationEnv_, _incrResult_). - -
    - - -

    - CreatePerIterationEnvironment ( - _perIterationBindings_: unknown, - ): either a normal completion containing either a Declarative Environment Record or ~unused~ or a throw completion -

    -
    -
    - - 1. If _perIterationBindings_ has any elements, then - 1. Let _lastIterationEnv_ be the running execution context's LexicalEnvironment. - 1. Let _outer_ be _lastIterationEnv_.[[OuterEnv]]. - 1. Assert: _outer_ is not *null*. - 1. Let _thisIterationEnv_ be NewDeclarativeEnvironment(_outer_). - 1. For each element _bn_ of _perIterationBindings_, do - 1. Perform ! _thisIterationEnv_.CreateMutableBinding(_bn_, *false*). - 1. Let _lastValue_ be ? _lastIterationEnv_.GetBindingValue(_bn_, *true*). - 1. Perform _thisIterationEnv_.InitializeBinding(_bn_, _lastValue_, ~normal~). - 1. Set the running execution context's LexicalEnvironment to _thisIterationEnv_. - 1. Return _thisIterationEnv_. - 1. Return ~unused~. - -
    @@ -2349,7 +2433,8 @@ contributors: Ron Buckton, Ecma International ForDeclaration[Yield, Await, Using] : LetOrConst ForBinding[?Yield, ?Await, ~Using] - [+Using] `using` [no LineTerminator here] ForBinding[?Yield, ?Await, +Using] + [+Using] `using` [no LineTerminator here] [lookahead != `await`] ForBinding[?Yield, ?Await, +Using] + [+Using, +Await] `using` [no LineTerminator here] `await` [no LineTerminator here] ForBinding[?Yield, +Await, +Using] ForBinding[Yield, Await, Using] : BindingIdentifier[?Yield, ?Await] @@ -2375,7 +2460,11 @@ contributors: Ron Buckton, Ecma International 1. Perform ! _environment_.CreateMutableBinding(_name_, *false*). - ForDeclaration : `using` ForBinding + + ForDeclaration : + `using` ForBinding + `using` `await` ForBinding + 1. Assert: _environment_ is a declarative Environment Record. 1. For each element _name_ of the BoundNames of |ForBinding|, do @@ -2402,6 +2491,12 @@ contributors: Ron Buckton, Ecma International 1. If _iteratorKind_ is not present, set _iteratorKind_ to ~sync~. 1. Let _oldEnv_ be the running execution context's LexicalEnvironment. 1. Let _V_ be *undefined*. + 1. If IsUsingAwaitDeclaration of _lhs_ is *true*, then + 1. Let _hint_ be ~async-dispose~. + 1. Else, if IsUsingDeclaration of _lhs_ is *true*, then + 1. Let _hint_ be ~sync-dispose~. + 1. Else, + 1. Let _hint_ be ~normal~. 1. Let _destructuring_ be IsDestructuring of _lhs_. 1. If _destructuring_ is *true* and if _lhsKind_ is ~assignment~, then 1. Assert: _lhs_ is a |LeftHandSideExpression|. @@ -2431,11 +2526,7 @@ contributors: Ron Buckton, Ecma International 1. If _lhsRef_ is an abrupt completion, then 1. Let _status_ be _lhsRef_. 1. Else if _lhsKind_ is ~lexicalBinding~, then - 1. Let _status_ be Completion(InitializeReferencedBinding(_lhsRef_, _nextValue_)). - 1. If IsUsingDeclaration of _lhs_ is *true*, then - 1. Let _status_ be Completion(InitializeReferencedBinding(_lhsRef_, _nextValue_, ~sync-dispose~)). - 1. Else, - 1. Let _status_ be Completion(InitializeReferencedBinding(_lhsRef_, _nextValue_, ~normal~)). + 1. Let _status_ be Completion(InitializeReferencedBinding(_lhsRef_, _nextValue_, _hint_)). 1. Else, 1. Let _status_ be Completion(PutValue(_lhsRef_, _nextValue_)). 1. Else, @@ -2873,7 +2964,7 @@ contributors: Ron Buckton, Ecma International -

    A `using` declaration that precedes a call in the same |Block|, |CaseBlock|, |ForStatement|, |ForInOfStatement|, |FunctionBody|, |GeneratorBody|, |AsyncGeneratorBody|, |AsyncFunctionBody|, or |ClassStaticBlockBody| prevents that call from being a possible tail position call.

    +

    A `using` declaration or `using await` declaration that precedes a call in the same |Block|, |CaseBlock|, |ForStatement|, |ForInOfStatement|, |FunctionBody|, |GeneratorBody|, |AsyncGeneratorBody|, |AsyncFunctionBody|, or |ClassStaticBlockBody| prevents that call from being a possible tail position call.

    @@ -3411,8 +3502,8 @@ contributors: Ron Buckton, Ecma International 1. Let _msg_ be ? ToString(_message_). 1. Perform CreateNonEnumerableDataPropertyOrThrow(_O_, *"message"*, _msg_). 1. Perform ? InstallErrorCause(_O_, _options_). - 1. Perform ! DefinePropertyOrThrow(_O_, *"error"*, PropertyDescriptor { [[Configurable]]: *true*, [[Enumerable]]: *false*, [[Writable]]: *true*, [[Value]]: _error_ }). - 1. Perform ! DefinePropertyOrThrow(_O_, *"suppressed"*, PropertyDescriptor { [[Configurable]]: *true*, [[Enumerable]]: *false*, [[Writable]]: *true*, [[Value]]: _suppressed_ }). + 1. Perform ! CreateNonEnumerableDataPropertyOrThrow(_O_, *"error"*, _error_). + 1. Perform ! CreateNonEnumerableDataPropertyOrThrow(_O_, *"suppressed"*, _suppressed_). 1. Return _O_.
    @@ -3595,6 +3686,7 @@ contributors: Ron Buckton, Ecma International

    Invoking this method notifies the AsyncDisposable object that the caller does not intend to continue to use this object. This method should perform any necessary logic to perform explicit clean-up of the resource including, but not limited to, file system handles, streams, host objects, etc. When an exception is thrown from this method, it typically means that the resource could not be explicitly freed. An AsyncDisposable object is not considered "disposed" until the resulting Promise has been fulfilled.

    If called more than once on the same object, the function should not throw an exception. However, this requirement is not enforced.

    +

    When using an AsyncDisposable object, it is good practice to create the instance with a `using await` declaration, as the resource will be automatically disposed when the |Block| or |Module| immediately containing the declaration has been evaluated.

    @@ -3687,13 +3779,7 @@ contributors: Ron Buckton, Ecma International 1. Let _disposableStack_ be the *this* value. 1. Perform ? RequireInternalSlot(_disposableStack_, [[DisposableState]]). 1. If _disposableStack_.[[DisposableState]] is ~disposed~, throw a *ReferenceError* exception. - 1. If _value_ is neither *null* nor *undefined*, then - 1. If Type(_value_) is not Object, throw a *TypeError* exception. - 1. Let _method_ be GetDisposeMethod(_value_, ~sync-dispose~). - 1. If _method_ is *undefined*, then - 1. Throw a *TypeError* exception. - 1. Else, - 1. Perform ? AddDisposableResource(_disposableStack_, _value_, ~sync-dispose~, _method_). + 1. Perform ? AddDisposableResource(_disposableStack_, _value_, ~sync-dispose~). 1. Return _value_. @@ -3893,13 +3979,7 @@ contributors: Ron Buckton, Ecma International 1. Let _asyncDisposableStack_ be the *this* value. 1. Perform ? RequireInternalSlot(_asyncDisposableStack_, [[AsyncDisposableState]]). 1. If _asyncDisposableStack_.[[AsyncDisposableState]] is ~disposed~, throw a *ReferenceError* exception. - 1. If _value_ is neither *null* nor *undefined*, then - 1. If Type(_value_) is not Object, throw a *TypeError* exception. - 1. Let _method_ be GetDisposeMethod(_value_, ~async-dispose~). - 1. If _method_ is *undefined*, then - 1. Throw a *TypeError* exception. - 1. Else, - 1. Perform ? AddDisposableResource(_disposableStack_, _value_, ~async-dispose~, _method_). + 1. Perform ? AddDisposableResource(_disposableStack_, _value_, ~async-dispose~). 1. Return _value_.