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

Reply via email to