Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

BridgeJS: Improved @JS Protocol support - properties, Optional, enums #460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
krodak merged 6 commits into swiftwasm:main from PassiveLogic:feat/protocol-support
Oct 24, 2025

Conversation

@krodak
Copy link
Member

@krodak krodak commented Oct 23, 2025

Introduction

This PR extends support for @JS protocol by adding support for protocol properties, Optional and additional support on types not previously supported, because of lack of the ImportTS side.

Overview

  • Added support for get/set properties in @JS protocol declarations
  • Properties can now be accessed and modified on protocol implementations from JavaScript
  • Supports all exportable types including primitives, strings, enums (case/raw value/associated value), Optionals and Swift heap objects

Testing

Added comprehensive tests in Tests/BridgeJSRuntimeTests/ExportAPITests.swift:

  • Protocol with various optional property types
  • Direct property access from Swift side
  • Protocol implementation testing
  • All optional type combinations
    As I run into quite few issues along the way, I wanted to keep large test set, especially due to amount of new lifting / lowering methods added and use cases to test.

Documentation

Updated Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Protocols.md

Technical Details

I had few attempts at proper support for optionals and properties, but this is what I ended up with, was not able to simplify approach further while keeping it sensible, but any suggestions on improvements here are welcomed 🙏🏻

The implementation uses two different strategies for handling optional types in protocol properties, determined by WebAssembly ABI constraints:

  1. Sentinel Values (Single Return Value)
    Used when the WASM ABI can distinguish null from valid values using a sentinel:
  • Optional<Bool>: Uses -1 for nil, 0 for false, 1 for true
  • Optional<CaseEnum>: Uses -1 for nil, 0+ for enum cases
  • Optional<AssociatedValueEnum>: Uses -1 for nil, 0+ for case IDs
  • Optional<SwiftHeapObject>: Uses 0 (null pointer) for nil
  1. Side-Channel
    Used when WASM ABI cannot distinguish null from valid values (e.g., any valid value is possible):
  • Optional<Int>
  • Optional<Float>
  • Optional<Double>
  • Optional<String>
  • Optional<RawValueEnum>

Side-channel uses global storage variables (tmpRetOptionalInt, tmpRetString, etc.) that are set by the getter and read by the lifting code.

Some new methods were required in BridgeJSIntrinsic, besides the one we used for most of the lifting / lowering:

  1. bridgeJSLowerParameterWithPresence(): Converts optional to (isSome, value) tuple for protocol setters/parameters
  2. bridgeJSLiftReturnFromSideChannel(): Lifts optionals from side-channel storage for protocol property getters
    For sentinel-based lifting for types that support it, I added implementation for bridgeJSLiftReturn(_ value: T):

MaxDesiatov reacted with thumbs up emoji
@krodak krodak self-assigned this Oct 23, 2025
Copy link
Member

Thanks! The additional complication is a bit sad but it makes sense in terms of the performance. I think it might be worth researching the wasm-bindgen's approach here. It might give us some insight to come up with a better solution.

Copy link
Member Author

krodak commented Oct 23, 2025
edited
Loading

I know it seems like lots of additional intrinsics and new approaches, but I think most of it arises mostly from the fact that for most of the new types and features we've added since August, they support Swift -> JS side and not the other way around. So eventually we need to add JS -> Swift side support and not sure whether this could be further simplified (e.g. we could use side channels for everything for clarity but if we don't need to, then I think sentinel value seems better, but can't be used for all types) and most of what was needed to support properties in protocols and supporting all exportable types in methods was to actual bridge that gap in order to be able to import JS functionality in Swift Any wrapper.

Initially I overloaded existing intrinsics for ImportTS, as I think we could reuse the approach when we do JS -> Swift implementation for Optional etc, but went with dedicated methods to not created confusion (that JS -> Swift import is working), even though hard limitation is the check during lifting / lowering.

So let's consider whether this approach is fine for now 🙏🏻

I'll have a look in the meantime as wasm-bindgen 🫡

Copy link
Member Author

krodak commented Oct 23, 2025

@kateinoigakukun regarding failing tests, I think I'm again missing something, I went through Contributing.md docs again and I always make sure to build examples using script in Utility and run tests for both BridgeJS and PackageToJS, but it seems that I can't force refresh for some of the build files, even running swift test --package-path ./Plugins/PackageToJS locally, but to no avail.

I tried to manually run tests for failing example package in ./Examples/Testing, according to README.md there, using:
swift package --disable-sandbox --swift-sdk wasm32-unknown-wasi js test, but it fails due to older macOS version requirement, so I don't think that's the issue 😅

Copy link
Member

Oh, I might have never run tests on macOS before.

@krodak krodak force-pushed the feat/protocol-support branch from 3c2a981 to e7e1b79 Compare October 23, 2025 17:38
Copy link
Member

@kateinoigakukun kateinoigakukun left a comment

Choose a reason for hiding this comment

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

Let's merge after CI passed 👍

@krodak krodak merged commit 689fdd2 into swiftwasm:main Oct 24, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@kateinoigakukun kateinoigakukun kateinoigakukun approved these changes

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /