Skip to content

BridgeJS: Support throws and async for closures#766

Merged
krodak merged 3 commits into
swiftwasm:mainfrom
PassiveLogic:kr/closures-throws-async
Jun 11, 2026
Merged

BridgeJS: Support throws and async for closures#766
krodak merged 3 commits into
swiftwasm:mainfrom
PassiveLogic:kr/closures-throws-async

Conversation

@krodak

@krodak krodak commented Jun 11, 2026

Copy link
Copy Markdown
Member

Overview

Adds throws(JSException) and async (and async throws(JSException)) support to BridgeJS-bridged closures, in both directions: closures Swift receives from JavaScript, and closures Swift hands to JavaScript. Previously closures were limited to synchronous, non-throwing signatures (#537).

Async settlement reuses the _bjs_makePromise + per-type Promise_resolve_<mangled> / Promise_reject machinery introduced in #758, so every bridged return type is inherited for free, including the associated-value enums added in #764; no parallel mechanism is introduced.

1. Effect-aware closure mangling. Closure ABI symbol names encode effects with the Swift ABI operators (Ya for async, K for throws), so signatures that differ only by effects no longer collide.

2. Throwing closures (both directions). A JavaScript callback's thrown JSException propagates into Swift; a Swift closure's thrown JSException is routed back to JavaScript. Only throws(JSException) is supported; plain throws is diagnosed.

3. Async closures (both directions). A JS-to-Swift callback is awaited via _bjs_awaitPromise; a Swift-to-JS closure returns a Promise settled via _bjs_makePromise. TypeScript surfaces these as (args) => Promise<R>. Supported return types mirror async functions, including @JS struct, enums (associated-value included), and Optional/Array/Dictionary compositions; the remaining unsupported async return types (protocols, namespace enums) are diagnosed rather than miscompiled.

4. Lifetime. The closure box survives across suspension without an explicit pin: the extracted closure value, captured by the settling Task, keeps it alive even if JavaScript releases the closure mid-flight. Covered by release-race and concurrent-invocation tests.

Commit structure

The work is split so the parts can be reviewed (or dropped) independently:

Commit 1 is the feature itself, self-contained.

Commit 2 adds a build-time warning for async throwing closures handed to JavaScript (introducing a warning severity to the BridgeJS diagnostics). It was written when the swiftlang/swift#89320 miscompile made that signature lose thrown errors at runtime; with commit 3 in place the warning no longer fires for a real risk, so this commit can be dropped, or kept if the warning-severity infrastructure is useful on its own.

Commit 3 works around swiftlang/swift#89320 by boxing JSException's stored properties into a private final class, shrinking the struct to a single stored reference so it travels in the direct typed-error convention and the miscompiled indirect-error path is never taken. The public API is unchanged; the cost is one heap allocation per thrown exception, on the error path only. With it, the previously gated Swift-to-JS async throws reject assertions run unconditionally and pass, and the JSClosure.async reject path gains its previously missing test. Removal once swiftlang/swift#89715 ships is tracked in #767 and is a library-internal layout change, not a breaking API change. Original exploration: PassiveLogic#13. The codegen-level counterpart for zero-parameter async throwing exports landed in #760 (tracked for removal in #761).

Tests

Codegen snapshots and end-to-end runtime round-trips (both directions, throwing and async, resolve and reject, Void, @JS struct, associated-value enum returns, the release-race, and concurrent calls) pass.

@krodak krodak self-assigned this Jun 11, 2026
@krodak krodak force-pushed the kr/closures-throws-async branch from 142d0c7 to c020734 Compare June 11, 2026 09:42
@krodak krodak requested a review from kateinoigakukun June 11, 2026 09:44

@kateinoigakukun kateinoigakukun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@krodak krodak merged commit 73a125d into swiftwasm:main Jun 11, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants