Branch: refs/heads/main
Home: https://github.com/WebKit/WebKit
Commit: 92e52221d299ee0a0aa4359f476eb82d0c975e00
https://github.com/WebKit/WebKit/commit/92e52221d299ee0a0aa4359f476eb82d0c975e00
Author: Sosuke Suzuki <[email protected]>
Date: 2026-03-17 (Tue, 17 Mar 2026)
Changed paths:
A JSTests/stress/await-using-declaration-basic.js
A JSTests/stress/await-using-declaration-error.js
A JSTests/stress/await-using-declaration-for-of.js
A JSTests/stress/await-using-declaration-generator.js
A JSTests/stress/await-using-declaration-mixed.js
A JSTests/stress/await-using-declaration-syntax-errors.js
M JSTests/test262/config.yaml
M Source/JavaScriptCore/builtins/DisposableStackPrototype.js
M Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
M Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
M Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
M Source/JavaScriptCore/parser/ASTBuilder.h
M Source/JavaScriptCore/parser/Nodes.h
M Source/JavaScriptCore/parser/Parser.cpp
M Source/JavaScriptCore/parser/Parser.h
M Source/JavaScriptCore/parser/VariableEnvironment.cpp
M Source/JavaScriptCore/parser/VariableEnvironment.h
M Source/JavaScriptCore/runtime/CachedTypes.cpp
Log Message:
-----------
[JSC] Implement `await using` syntax from Explicit Resource Management
proposal
https://bugs.webkit.org/show_bug.cgi?id=309673
Reviewed by Yusuke Suzuki.
This patch implements `await using` declarations from the Explicit Resource
Management proposal, completing the syntax-level support for the proposal
alongside the previously landed synchronous `using` declarations.
An `await using` declaration invokes the @@asyncDispose method of a resource
when the enclosing scope is exited, awaiting the result. If @@asyncDispose is
absent, it falls back to @@dispose wrapped in a promise-returning closure per
the spec's GetDisposeMethod(V, async-dispose).
This patch extends the parser and bytecode compiler. No new bytecodes are
introduced; the existing op_yield / Generatorification machinery handles the
suspend points.
** Parser **
The parser recognizes `await [no LineTerminator here] using [no LineTerminator
here] BindingList` in contexts where `await` is a keyword: async functions,
async generators, and the top level of modules. Both no-LineTerminator
constraints are checked via two-token lookahead with savepoint restore. Like
sync `using`, it is a SyntaxError at script/eval top level and directly inside
switch case/default clauses; destructuring patterns are disallowed.
`await using` also appears in for-of heads, both `for (await using x of ...)`
and `for await (await using x of ...)`. Unlike sync `using`, the lookahead
permits `for (await using of of ...)` where the first `of` is a binding name.
Crucially, when an `await using` is seen, the parser calls setUsesAwait() on
the current function scope. Without this, the isAsyncFunctionWithoutAwait
optimization would inline the body into the wrapper, leaving no generator
register for emitAwait to use.
A separate boolean m_hasAwaitUsingDeclaration is added to VariableEnvironment
(rather than a new Traits bit, as uint16_t m_bits is already full) so the
bytecode generator can determine whether any slot in a given scope is async.
** Bytecode Compiler **
The UsingSlot structure gains two fields: a `reached` boolean register and an
`isAsync` compile-time flag. Unlike sync `using`, where `method == undefined`
alone suffices to skip a slot (covering both "not reached" and "null value"),
async requires distinguishing the two because `await using x = null` must still
produce exactly one Await(undefined) at disposal time, per the spec's
needsAwait/hasAwaited protocol.
The reached register is set to true only after @getAsyncDisposeMethod returns
successfully; if the method lookup throws, the slot is treated as never
reached, avoiding a spurious Await(undefined) before propagating the error.
The finally block maintains two runtime registers, needsAwait and hasAwaited,
mirroring DisposeResources steps 3-6. These ensure multiple null `await using`
declarations produce at most one Await(undefined). A compile-time cursor tracks
whether any async slot has been encountered yet in disposal order; Step 3.d
checks and the trailing Step 6 check are elided where statically unreachable,
reducing unnecessary yield points.
For a scope mixing sync and async:
{
using a = resource1; // sync
await using b = resource2; // async
}
The generated disposal sequence is:
// slot[1] (async, disposed first): call b[@@asyncDispose], await result
// - if method undefined (null value): needsAwait = true
// - if call succeeds: hasAwaited = true; await result
// slot[0] (sync):
// - Step 3.d: if needsAwait && !hasAwaited, Await(undefined)
// - call a[@@dispose]
// trailing: elided (slot[0] is sync)
** Built-in **
A new link-time constant @getAsyncDisposeMethod implements GetDisposeMethod
with async-dispose hint: it tries @@asyncDispose first, then falls back to
@@dispose wrapped in a closure that calls the sync method, discards its return
value, and resolves to undefined (or rejects on throw). This replaces the
previous @getAsyncDisposableMethod, which lacked the fallback.
Tests: JSTests/stress/await-using-declaration-basic.js
JSTests/stress/await-using-declaration-error.js
JSTests/stress/await-using-declaration-for-of.js
JSTests/stress/await-using-declaration-generator.js
JSTests/stress/await-using-declaration-mixed.js
JSTests/stress/await-using-declaration-syntax-errors.js
* JSTests/stress/await-using-declaration-basic.js: Added.
(shouldBe):
(async test):
(async testReverseOrder):
(async testNull.promise):
(async testMixed):
(async testSyncFallback):
(async testAsyncDisposeReturnValueAwaited):
(async testNotEvaluatedNoAwait.promise):
(async main):
* JSTests/stress/await-using-declaration-error.js: Added.
(shouldBe):
(async await):
(async testDisposeThrows):
(async testDisposeRejects):
(async testSuppressedError):
(async testBodyThrowThenDisposeThrow):
(async testSyncFallbackThrows):
(async testMethodLookupThrowNoAwait.async inner):
(async main):
* JSTests/stress/await-using-declaration-for-of.js: Added.
(shouldBe):
(async testForOf):
(async testForAwaitOf):
(async testForOfContinue):
(async testForOfBreak):
(async main):
(main.then):
* JSTests/stress/await-using-declaration-generator.js: Added.
(shouldBe):
(async testAsyncGenerator.async gen):
(async testAsyncGeneratorReturn.async gen):
(async testAsyncGeneratorNestedBlocks.async gen):
(async main):
* JSTests/stress/await-using-declaration-mixed.js: Added.
(shouldBe):
(async testSyncThenAsync):
(async testAsyncThenSync):
(async testInterleaved):
(async testMixedWithNullAwaitUsing.p):
(async testMixedSuppressedError):
(async main):
* JSTests/stress/await-using-declaration-syntax-errors.js: Added.
(shouldThrowSyntaxError):
(shouldThrowSyntaxError.f):
(async shouldThrowSyntaxError):
(async shouldNotThrowSyntaxError):
* JSTests/test262/config.yaml:
* Source/JavaScriptCore/builtins/DisposableStackPrototype.js:
(linkTimeConstant.createDisposableResource):
(linkTimeConstant.getAsyncDisposeMethod):
(linkTimeConstant.getAsyncDisposableMethod): Deleted.
* Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitPrepareDisposable):
(JSC::BytecodeGenerator::emitUsingBodyScope):
(JSC::BytecodeGenerator::emitBodyWithUsingIfNeeded):
* Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h:
* Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp:
(JSC::initializationModeForAssignmentContext):
(JSC::isUsingOrAwaitUsingAssignmentContext):
(JSC::AssignResolveNode::emitBytecode):
(JSC::BlockNode::emitBytecode):
(JSC::ForNode::emitBytecode):
(JSC::ForOfNode::emitBytecode):
(JSC::SwitchNode::emitBytecode):
(JSC::emitProgramNodeBytecode):
(JSC::EvalNode::emitBytecode):
(JSC::FunctionNode::emitBytecode):
(JSC::BindingNode::bindValue const):
* Source/JavaScriptCore/parser/ASTBuilder.h:
(JSC::ASTBuilder::createAssignResolve):
(JSC::ASTBuilder::createBindingLocation):
* Source/JavaScriptCore/parser/Nodes.h:
(JSC::VariableEnvironmentNode::hasAwaitUsingDeclaration const):
* Source/JavaScriptCore/parser/Parser.cpp:
(JSC::Parser<LexerType>::parseStatementListItem):
(JSC::Parser<LexerType>::parseVariableDeclaration):
(JSC::Parser<LexerType>::parseVariableDeclarationList):
(JSC::Parser<LexerType>::parseForStatement):
* Source/JavaScriptCore/parser/Parser.h:
(JSC::Scope::declareLexicalVariable):
* Source/JavaScriptCore/parser/VariableEnvironment.cpp:
(JSC::VariableEnvironment::swap):
* Source/JavaScriptCore/parser/VariableEnvironment.h:
(JSC::VariableEnvironment::VariableEnvironment):
(JSC::VariableEnvironment::hasAwaitUsingDeclaration const):
(JSC::VariableEnvironment::setHasAwaitUsingDeclaration):
* Source/JavaScriptCore/runtime/CachedTypes.cpp:
(JSC::CachedVariableEnvironment::encode):
(JSC::CachedVariableEnvironment::decode const):
Canonical link: https://commits.webkit.org/309389@main
To unsubscribe from these emails, change your notification settings at
https://github.com/WebKit/WebKit/settings/notifications