Skip to content

BridgeJS: Support optional @JSClass as exported function parameters#754

Open
krodak wants to merge 1 commit into
swiftwasm:mainfrom
PassiveLogic:kr/fix-optional-jsclass-param
Open

BridgeJS: Support optional @JSClass as exported function parameters#754
krodak wants to merge 1 commit into
swiftwasm:mainfrom
PassiveLogic:kr/fix-optional-jsclass-param

Conversation

@krodak
Copy link
Copy Markdown
Member

@krodak krodak commented Jun 3, 2026

Overview

Fixes #751. BridgeJS code generation failed to compile when a @JS exported function took (or returned) an optional @JSClass parameter such as JSAbortSignal?, producing no exact matches in call to static method 'bridgeJSLiftParameter' for Optional<JSAbortSignal>.

Root cause

#738 moved optional jsObject to the stack ABI and updated the JS glue, the ImportTS side, and the runtime lowering. However the export parameter direction in ExportSwift was never updated: liftParameterInfo() for .nullable(.jsObject) still emitted direct ABI params and the lift call Optional<T>.bridgeJSLiftParameter(isSome, value).

  • For @JSClass types there is no such two-argument overload, so generated wasm thunks failed to compile.
  • For plain JSObject? it compiled, but silently mismatched the JS side, which now pushes the presence flag and object id onto the bridge stack. This went unnoticed because the only adjacent test had an empty body and asserted doesNotThrow without reading the value back.

Changes

1. ExportSwift: optional jsObject export params use the stack ABI. .nullable(.jsObject) (including @JSClass) now reports isStackUsingParameter and produces no direct ABI parameters, so the thunk lifts via the no-argument Optional<T>.bridgeJSLiftParameter() that pops the presence flag and object id off the bridge stack, matching the JS lowering.

2. Runtime: add Optional<_JSBridgedClass>.bridgeJSLowerReturn(). @JSClass wrappers already conform to _BridgedSwiftStackType, so lifting and stack push/pop were already available via the generic extension; only the export return path needed a dedicated lowering.

Before:

let ret = roundTripOptionalImportedClass(v: Optional<Foo>.bridgeJSLiftParameter(vIsSome, vValue))

After:

let ret = roundTripOptionalImportedClass(v: Optional<Foo>.bridgeJSLiftParameter())

Tests

  • New end-to-end runtime test exercising an optional imported @JSClass directly as an exported function parameter and return value, asserting the round-tripped value for both the present and null cases (the previous coverage only checked import-direction members via text snapshots, which are never compiled for wasm32).
  • Added export-direction snapshot coverage for optional JSObject? and optional @JSClass?.
  • Regenerated in-package snapshots and the AoT-committed bindings. Snapshot suites pass across the swift-syntax versions in the CI matrix, the BridgeJS TypeScript declaration check passes, and the full wasm test suite passes.

Optional jsObject was moved to the stack ABI in swiftwasm#738, but the export
parameter direction in ExportSwift was never updated and still emitted
direct ABI params with `Optional<T>.bridgeJSLiftParameter(isSome, value)`.
For `@JSClass` types no such overload exists, so generated thunks failed
to compile; for plain `JSObject?` it compiled but mismatched the JS
stack-push lowering.

Make `.nullable(.jsObject)` export parameters use the stack ABI (no
direct ABI params, no-argument `Optional<T>.bridgeJSLiftParameter()`),
and add the missing `bridgeJSLowerReturn()` for `Optional<_JSBridgedClass>`
so optional `@JSClass` values round-trip as both parameters and return
values.

Fixes swiftwasm#751
@krodak krodak self-assigned this Jun 3, 2026
@krodak krodak requested a review from kateinoigakukun June 3, 2026 11:22
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.

BridgeJS: Optional @JSClass parameter fails lift/lower for generated bindings

1 participant