Skip to content

Commit 317896e

Browse files
committed
Docs: Describe generic Swift exports
1 parent cb9c071 commit 317896e

13 files changed

Lines changed: 179 additions & 10 deletions

‎Plugins/BridgeJS/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ graph LR
9898
| `Dictionary<K, V>` | `Record<K, V>` | - | [#495](https://github.com/swiftwasm/JavaScriptKit/issues/495) |
9999
| `Set<T>` | `Set<T>` | - | [#397](https://github.com/swiftwasm/JavaScriptKit/issues/397) |
100100
| `Foundation.URL` | `string` | - | [#496](https://github.com/swiftwasm/JavaScriptKit/issues/496) |
101-
| Generic function or method (`T`, `[T]`, `T?`, `[String: T]`) | `<T>(value: T): T` | Depends on `T` | ✅ imports only ([#398](https://github.com/swiftwasm/JavaScriptKit/issues/398) for exports) |
101+
| Generic function or method (`T`, `[T]`, `T?`, `[String: T]`) | `<T>(value: T, type: BridgeType<T>)` | Depends on `T` | ✅ |
102102

103103
### Import-specific (TypeScript -> Swift)
104104

‎Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ So even if you cache the property name (e.g. with `CachedJSStrings`), you are st
3232

3333
BridgeJS avoids this by generating **separate** access paths per property or method. Each generated getter/setter or function call has a stable shape at the engine level, so the IC can stay monomorphic or polymorphic and the fast path is used.
3434

35+
## Generic functions
36+
37+
Generic imports and exports share the stack ABI and JavaScript codec table. Each bridgeable type owns a `BridgeJSTypeHandle`; its address is the runtime type ID. Module registration pairs these IDs with codecs in a shared, canonical order.
38+
39+
For exports, JavaScript callers pass `BridgeTypes` tokens. The wrapper resolves them to type IDs before lowering arguments. The Swift entry thunk recovers the metatypes and opens their existentials to call the generic implementation. This requires runtime existential support, so generic exports are unavailable under Embedded Swift; generic imports remain supported.
40+
41+
For `@JS` protocol constraints, the linker collects declared conformances and protocol inheritance. The wrapper rejects unknown or non-conforming tokens with `TypeError` before touching the stacks, and the Swift thunk checks the recovered metatype. Calling these WebAssembly entry points directly with arbitrary type IDs is unsupported.
42+
3543
## What to read next
3644

3745
- ABI and binary interface details will be documented in this section as they stabilize.

‎Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Configure your package and build for JavaScript as described in <doc:Setting-up-
1515
## Topics
1616

1717
- <doc:Exporting-Swift-Function>
18+
- <doc:Exporting-Swift-Generics>
1819
- <doc:Exporting-Swift-Class>
1920
- <doc:Exporting-Swift-Struct>
2021
- <doc:Exporting-Swift-Array>

‎Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,6 @@ Identity mode improves performance for reuse-heavy workloads (same objects cross
234234
| Static/class methods: `static func`, `class func` | ✅ (See <doc:Exporting-Swift-Static-Functions> ) |
235235
| Extension methods/properties | ✅ |
236236
| Subscripts: `subscript()` | ❌ |
237-
| Generics | ❌ |
237+
| Generic methods (instance and static): `func wrap<T: BridgedSwiftGenericBridgeable>(_ v: T) -> T` (see <doc:Exporting-Swift-Function>) | ✅ |
238+
| Generic classes: `class Box<T>` | ❌ |
239+
| Generic initializers (see <doc:Exporting-Swift-Generics>) | ✅ |

‎Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Enum.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,4 +544,5 @@ This differs from classes, which use reference semantics and share state across
544544
| Associated values: Arrays | ✅ |
545545
| Associated values: Optionals of all supported types | ✅ |
546546
| Extension static functions/properties | ✅ |
547-
| Generics | ❌ |
547+
| Generic static methods (see <doc:Exporting-Swift-Function>) | ✅ |
548+
| Generic enums: `enum Result<T>` | ❌ |

‎Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ export type Exports = {
157157
}
158158
```
159159
160+
### Generic functions
161+
162+
Generic functions and methods support concrete results, async calls, and generic callback parameters. See <doc:Exporting-Swift-Generics> for type tokens, examples, and protocol constraints.
163+
160164
## Supported Features
161165
162166
| Swift Feature | Status |
@@ -169,6 +173,6 @@ export type Exports = {
169173
| Throwing JS exception: `func x() throws(JSException)` | ✅ |
170174
| Throwing any exception: `func x() throws` | ❌ |
171175
| Async methods: `func x() async` | ✅ |
172-
| Generics | ❌ |
176+
| Generic parameter/result types (constrained to `BridgedSwiftGenericBridgeable`) | ✅ |
173177
| Opaque types: `func x() -> some P`, `func y(_: some P)` | ❌ |
174-
| Default parameter values: `func x(_ foo: String = "")` | ✅ (See <doc:Exporting-Swift-Default-Parameters>) |
178+
| Default parameter values: `func x(_ foo: String = "")` | ✅ (See <doc:Exporting-Swift-Default-Parameters>) |
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Exporting Generic Swift APIs
2+
3+
Expose generic functions, methods, initializers, and callbacks to JavaScript.
4+
5+
## Overview
6+
7+
Constrain each type parameter to `BridgedSwiftGenericBridgeable`. JavaScript callers select its concrete Swift type with a token from the generated `BridgeTypes` export.
8+
9+
### Typed Swift-owned state
10+
11+
One generic API can store and retrieve different bridged types without separate bindings for each type:
12+
13+
```swift
14+
import JavaScriptKit
15+
16+
@JS public final class ValueStore {
17+
private var values: [String: any BridgedSwiftGenericBridgeable] = [:]
18+
19+
@JS public init<T: BridgedSwiftGenericBridgeable>(_ key: String, _ value: T) {
20+
values[key] = value
21+
}
22+
23+
@JS public func put<T: BridgedSwiftGenericBridgeable>(_ key: String, _ value: T) {
24+
values[key] = value
25+
}
26+
27+
@JS public func get<T: BridgedSwiftGenericBridgeable>(_ key: String) -> T? {
28+
values[key] as? T
29+
}
30+
}
31+
```
32+
33+
```javascript
34+
import { BridgeTypes } from "./bridge-js.js";
35+
36+
const store = new exports.ValueStore("count", 42, BridgeTypes.Int);
37+
store.put("title", "Draft", BridgeTypes.String);
38+
store.get("count", BridgeTypes.Int); // 42
39+
store.get("title", BridgeTypes.String); // "Draft"
40+
store.get("count", BridgeTypes.String); // null: stored as Int
41+
store.release();
42+
```
43+
44+
Pass one token per generic parameter after the regular arguments, in declaration order. Tokens use Swift names and underscore-separated namespaces, such as `BridgeTypes.API_Building`, even when the type has a separate JavaScript name. Import `BridgeTypes` from the generated module, not the `exports` object.
45+
46+
Generic values may use `T`, `[T]`, `T?`, or `[String: T]`. Concrete parameters and results are also supported, such as `count<T>(_ values: [T]) -> Int`. See <doc:Supported-Types> for concrete types usable as `T`.
47+
48+
### Async functions and callbacks
49+
50+
Generic exports may be `async` and `throws(JSException)`. Callback parameters can use the same generic types and may be async, throwing, or `@escaping`:
51+
52+
```swift
53+
@JS public func transform<T: BridgedSwiftGenericBridgeable, U: BridgedSwiftGenericBridgeable>(
54+
_ value: T,
55+
_ body: (T) async throws(JSException) -> U
56+
) async throws(JSException) -> U {
57+
try await body(value)
58+
}
59+
```
60+
61+
```javascript
62+
const text = await exports.transform(
63+
42, async value => `Result: ${value}`, BridgeTypes.Int, BridgeTypes.String
64+
);
65+
```
66+
67+
Swift may retain an `@escaping` callback and invoke it after the registering call returns. The bridge keeps the JavaScript function alive until Swift releases the closure. Invoke it on the same JavaScript runtime/thread. Use `throws(JSException)` when a callback can throw or reject its promise.
68+
69+
### Initializers and constraints
70+
71+
Generic class initializers use `new`, as shown above; generic struct initializers use `exports.Type.init(...)`. A struct must also provide the nongeneric memberwise initializer used to reconstruct its stored fields. For a public struct, that initializer must be public or `@usableFromInline`.
72+
73+
Additional `@JS` protocol constraints use compositions such as `T: BridgedSwiftGenericBridgeable & GraphNode`. Alternatively, let the protocol inherit the bridging requirement:
74+
75+
```swift
76+
@JS public protocol GraphNode: BridgedSwiftGenericBridgeable {
77+
var id: String { get }
78+
}
79+
80+
@JS public func identity<T: GraphNode>(_ node: T) -> T { node }
81+
```
82+
83+
The inherited requirement is recognized through protocol refinement and dependency modules. Concrete exported types still receive their bridging conformance automatically. Write `any GraphNode` when using the protocol as an existential value.
84+
85+
The generated TypeScript signature preserves the constraint. Unknown or non-conforming tokens throw `TypeError` before arguments cross the bridge. See <doc:Exporting-Swift-Protocols> and <doc:Unsupported-Features> for remaining restrictions.

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,29 @@ struct AnyCounter: Counter, _BridgedSwiftProtocolWrapper {
220220
| Optional properties | ✅ |
221221
| Optional protocol methods | ❌ |
222222
| Associated types | ❌ |
223-
| Protocol inheritance | ❌ |
223+
| Protocol inheritance between `@JS` protocols: `@JS protocol Refined: Base` | ✅ |
224224
| Protocol composition: `Protocol1 & Protocol2` | ❌ |
225-
| Generics | ❌ |
225+
| Generic protocol requirements: `func map<T>(_ v: T)` | ❌ |
226+
| Use as a generic constraint: `<T: BridgedSwiftGenericBridgeable & MyProtocol>` (see <doc:Exporting-Swift-Function>) | ✅ |
226227

227228
> Note: Protocol type support matches that of regular `@JS func` and `@JS class` exports. See <doc:Exporting-Swift-Function>, <doc:Exporting-Swift-Optional>, and <doc:Exporting-Swift-Enum> for more information.
229+
230+
## Protocol Inheritance
231+
232+
A `@JS` protocol may refine another `@JS` protocol declared in the same module. The generated wrapper for the refined protocol exposes the inherited requirements as well, so a JavaScript object implementing the refined protocol must provide the full surface:
233+
234+
```swift
235+
@JS protocol GraphNode {
236+
var id: String { get }
237+
}
238+
239+
@JS protocol Site: GraphNode {
240+
var region: String { get }
241+
}
242+
243+
@JS func describe(_ site: Site) -> String {
244+
"\(site.id) in \(site.region)" // both requirements are available
245+
}
246+
```
247+
248+
Refinement also carries over to generic constraints: a type conforming to `Site` satisfies a generic parameter constrained to `GraphNode` (see <doc:Exporting-Swift-Function>). Refining a protocol from another module is not supported.

‎Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Static-Functions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,5 +162,5 @@ const result: string = Utils.String.uppercase("world");
162162
| Class `class func` | ✅ |
163163
| Enum `static func` | ✅ |
164164
| Namespace enum `static func` | ✅ |
165-
| Generic static functions | ❌ |
165+
| Generic static functions: `static func make<T: BridgedSwiftGenericBridgeable>(_ v: T) -> T` (see <doc:Exporting-Swift-Function>) | ✅ |
166166
| Protocol static requirements | ❌ |

‎Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ This differs from classes, which use reference semantics and share state across
169169
| Static properties | ✅ |
170170
| Extension methods/properties | ✅ |
171171
| Property observers (`willSet`, `didSet`) | ❌ |
172-
| Generics | ❌ |
172+
| Generic methods (instance and static) (see <doc:Exporting-Swift-Function>) | ✅ |
173+
| Generic structs: `struct Pair<T>` | ❌ |
174+
| Generic initializers with a nongeneric memberwise initializer (see <doc:Exporting-Swift-Generics>) | ✅ |
173175
| Conformances | ❌ |
174176

175177
## See Also

0 commit comments

Comments
 (0)