Skip to content

Commit ef81857

Browse files
committed
Docs: Document BridgeJS generic functions
1 parent 9a6fc87 commit ef81857

6 files changed

Lines changed: 138 additions & 3 deletions

File tree

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-
| Generics | - | - | [#398](https://github.com/swiftwasm/JavaScriptKit/issues/398) |
101+
| Generics (top-level functions) | `<T>(value: T, type: BridgeType<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 imports
36+
37+
An imported generic `@JSFunction` (`func parse<T: _BridgedSwiftGenericBridgeable>(...)`) lets one piece of glue serve many concrete types. The generic value crosses using the type's own stack ABI, and a runtime type ID (interned once via `swift_js_resolve_type_id`) selects the matching JS codec, so the type-agnostic glue can lower and lift the right representation without a specialized path per call site.
38+
39+
## Generic exports
40+
41+
An exported generic `@JS` function reuses the same stack ABI and codec table, with the dispatch reversed. The WebAssembly entry point is a concrete `@_expose` thunk that takes the runtime type ID as a trailing `Int32`. It looks the ID up in a codegen-emitted registry of `_BridgedSwiftGenericBridgeable` types, reifies `T` through an opened existential, and runs an unspecialized helper that pops the argument from the stack, calls your function, and pushes the result. The JavaScript wrapper resolves the `BridgeType<T>` token to the same interned ID and uses the matching codec to lower the argument and lift the return. Because this depends on existential types, generic exports are excluded under Embedded Swift.
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/Exporting-Swift-Function.md

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,77 @@ export type Exports = {
136136
}
137137
```
138138
139+
### Generic functions
140+
141+
A `@JS` function can be generic over a type parameter constrained to `_BridgedSwiftGenericBridgeable`. The concrete type chosen at the call site crosses the bridge:
142+
143+
```swift
144+
import JavaScriptKit
145+
146+
@JS public func identity<T: _BridgedSwiftGenericBridgeable>(_ value: T) -> T {
147+
return value
148+
}
149+
```
150+
151+
`T` must be a bridgeable type: a supported primitive (`Bool`, any fixed-width integer such as `Int`/`UInt`/`Int8``UInt64`, `Float`, `Double`, `String`, or `JSValue`), or a `@JS` struct, `final @JS class`, or `@JS enum` in your module. You do not write any conformance yourself; marking a type `@JS` makes it usable as `T` (see <doc:Supported-Types>).
152+
153+
Because TypeScript erases generics, the JavaScript caller passes a `BridgeType<T>` token as the last argument so the bridge can select the right type at runtime. The tokens come from a generated `BridgeTypes` map exported at the top level of `bridge-js.js`; import it directly rather than reading it from the `exports` object:
154+
155+
```javascript
156+
import { BridgeTypes } from "./bridge-js.js";
157+
158+
const x = exports.identity(42, BridgeTypes.Int);
159+
const p = exports.identity({ x: 1, y: 2 }, BridgeTypes.MyPoint);
160+
```
161+
162+
Concrete parameters keep their positions; the token is always appended last. The non-generic parameters of a generic `@JS` function may be any supported bridged type, including value types such as `@JS` structs, arrays, dictionaries, and associated-value enums alongside the generic parameter. The generated TypeScript declarations look like:
163+
164+
```typescript
165+
export type BridgeType<T> = string & { readonly __bridgeType?: (value: T) => void };
166+
export const BridgeTypes: { Bool: BridgeType<boolean>; Int: BridgeType<number>; Float: BridgeType<number>; Double: BridgeType<number>; String: BridgeType<string>; MyPoint: BridgeType<MyPoint>; };
167+
export type Exports = {
168+
identity<T>(value: T, typeT: BridgeType<T>): T;
169+
}
170+
```
171+
172+
A single bare `T` may be used in more than one parameter, such as `pair<T>(_ a: T, _ b: T) -> T`; each generic value is lowered with the same token, and the arguments are not reordered.
173+
174+
A function may also declare multiple distinct generic parameters, such as `combine<T, U>(_ a: T, _ b: U) -> T`. Each distinct generic parameter takes its own `BridgeType` token, appended after the regular arguments in declaration order, and each generic value crosses the bridge with its own type's codec:
175+
176+
```swift
177+
@JS public func combine<T: _BridgedSwiftGenericBridgeable, U: _BridgedSwiftGenericBridgeable>(_ a: T, _ b: U) -> T {
178+
a
179+
}
180+
```
181+
182+
```javascript
183+
import { BridgeTypes } from "./bridge-js.js";
184+
185+
const first = exports.combine(7, "hello", BridgeTypes.Int, BridgeTypes.String);
186+
```
187+
188+
```typescript
189+
export type Exports = {
190+
combine<T, U>(a: T, b: U, typeT: BridgeType<T>, typeU: BridgeType<U>): T;
191+
}
192+
```
193+
194+
The generic parameter may also be wrapped as `[T]`, `T?`, or `[String: T]` in parameters and the result:
195+
196+
```swift
197+
@JS public func firstOrNil<T: _BridgedSwiftGenericBridgeable>(_ values: [T]) -> T? {
198+
values.first
199+
}
200+
```
201+
202+
```typescript
203+
export type Exports = {
204+
firstOrNil<T>(values: T[], typeT: BridgeType<T>): T | null;
205+
}
206+
```
207+
208+
The result must be one of the declared generic parameters (such as `T` or `U`), a supported wrapper of one (`[T]`, `T?`, `[String: T]`), or `Void`. A generic parameter that is never used in any parameter is rejected, and returning a concrete non-`Void` type from a generic `@JS` function is not supported. Generic `@JS` functions must be top-level and synchronous (see <doc:Unsupported-Features>).
209+
139210
## Supported Features
140211
141212
| Swift Feature | Status |
@@ -148,6 +219,6 @@ export type Exports = {
148219
| Throwing JS exception: `func x() throws(JSException)` | ✅ |
149220
| Throwing any exception: `func x() throws` | ❌ |
150221
| Async methods: `func x() async` | ✅ |
151-
| Generics | ❌ |
222+
| Generic parameter/result types (constrained to `_BridgedSwiftGenericBridgeable`) | ✅ |
152223
| Opaque types: `func x() -> some P`, `func y(_: some P)` | ❌ |
153224
| Default parameter values: `func x(_ foo: String = "")` | ✅ (See <doc:Exporting-Swift-Default-Parameters>) |

Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-JavaScript/Importing-JS-Function.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,41 @@ If you used `from: .global`, do not pass the function in `getImports()`; the run
4141

4242
Bound functions are `throws(JSException)`. Call them with `try` or `try?`; they throw when the JavaScript implementation throws.
4343

44+
## Generic functions
45+
46+
A `@JSFunction` can be generic over a type parameter constrained to `_BridgedSwiftGenericBridgeable`. The concrete type chosen at the call site then crosses the bridge:
47+
48+
```swift
49+
@JSFunction func parse<T: _BridgedSwiftGenericBridgeable>(_ json: String) throws(JSException) -> T
50+
51+
let user: User = try parse(jsonString)
52+
```
53+
54+
`T` must be a bridgeable type: a supported primitive (`Bool`, any fixed-width integer such as `Int`/`UInt`/`Int8``UInt64`, `Float`, `Double`, `String`, or `JSValue`), or a `@JS` struct, `final @JS class`, or `@JS enum` in your module. You do not write any conformance yourself; marking a type `@JS` makes it usable as `T` (see <doc:Supported-Types>). Generics are not supported for `async`, `@JSClass` members, or `where` clauses (see <doc:Unsupported-Features>).
55+
56+
A generic type parameter may be used in more than one parameter, an imported function may declare more than one distinct generic parameter, and a generic result type may be used on a function that takes no generic parameters (the JavaScript implementation produces the value):
57+
58+
```swift
59+
@JSFunction func pickFirst<T: _BridgedSwiftGenericBridgeable>(_ a: T, _ b: T) throws(JSException) -> T
60+
61+
@JSFunction func makeValue<T: _BridgedSwiftGenericBridgeable>() throws(JSException) -> T
62+
63+
@JSFunction func combine<T: _BridgedSwiftGenericBridgeable, U: _BridgedSwiftGenericBridgeable>(_ a: T, _ b: U) throws(JSException) -> U
64+
```
65+
66+
The generic parameter may also be wrapped as `[T]`, `T?`, or `[String: T]` in parameters and the result:
67+
68+
```swift
69+
@JSFunction func roundTrip<T: _BridgedSwiftGenericBridgeable>(_ values: [T]) throws(JSException) -> [T]
70+
71+
@JSFunction func lookup<T: _BridgedSwiftGenericBridgeable>(_ values: [String: T]) throws(JSException) -> T?
72+
```
73+
4474
## Supported features
4575

4676
| Feature | Status |
4777
|:--|:--|
4878
| Primitive parameter/result types (e.g. `Double`, `Bool`) ||
4979
| `String` parameter/result type ||
80+
| Generic parameter/result types (constrained to `_BridgedSwiftGenericBridgeable`) ||
5081
| Async function ||
51-
| Generics ||

Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ When using `JSTypedArray<T>` (or convenience typealiases) in `@JS` signatures, t
3131

3232
See <doc:Exporting-Swift-Array> for usage details.
3333

34+
## Generic type parameters
35+
36+
A generic type parameter constrained to `_BridgedSwiftGenericBridgeable` is supported as a parameter or result type in both directions: an imported `@JSFunction` (see <doc:Importing-JS-Function>) and an exported `@JS` function (see <doc:Exporting-Swift-Function>). The types that satisfy that constraint are:
37+
38+
- `Bool`, `Float`, `Double`, `String`, and `JSValue`
39+
- every fixed-width integer: `Int`, `UInt`, `Int8`/`Int16`/`Int32`/`Int64`, and `UInt8`/`UInt16`/`UInt32`/`UInt64`
40+
- any `@JS` struct, `final @JS class`, or `@JS enum` (case, raw-value, or associated-value) in your module
41+
42+
You do not write the conformance by hand: marking a type `@JS` (or using a built-in primitive) is what makes it usable as the generic argument.
43+
44+
The generic parameter may be used bare (`T`) or wrapped in `[T]`, `T?`, or `[String: T]`. Other or nested wrappings (for example `[T?]`, `[[T]]`, or `[Int: T]`) are not supported. `JSObject` cannot be used as the generic argument; use `JSValue` instead.
45+
3446
## See Also
3547

3648
- <doc:Generating-from-TypeScript>

Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,17 @@ While using `@JS` types from another Swift module is supported, it is not possib
3434
### Exporting Swift: types from another Swift package
3535

3636
Types defined in a separate Swift package cannot yet be referenced from `@JS` declarations in your package.
37+
38+
## Generics
39+
40+
Generic functions are supported in both directions, through a type parameter constrained to `_BridgedSwiftGenericBridgeable`: an imported `@JSFunction` (see <doc:Importing-JS-Function>) and an exported `@JS` function (see <doc:Exporting-Swift-Function>). A function may declare one or more distinct generic parameters, such as `combine<T, U>(_ a: T, _ b: U) -> T`. The following forms are not supported and produce build-time diagnostics:
41+
42+
- `async` generic functions.
43+
- Generic methods inside `@JSClass` types or static members (generics are top-level only).
44+
- `where` clauses on a generic declaration.
45+
- A declared generic parameter that is not used in any parameter.
46+
- An exported generic function that returns a concrete, non-`Void` type. The result of an exported generic function must be one of the declared generic parameters or `Void`.
47+
48+
The generic parameter may be used bare (`T`) or wrapped in `[T]`, `T?`, or `[String: T]`. Nested or other wrappings, such as `[T?]`, `[[T]]`, `T??`, or `[Int: T]`, are not supported and produce build-time diagnostics. `JSObject` cannot be used as the generic argument (it is a non-final class); use `JSValue` instead.
49+
50+
Exported generic functions additionally require runtime existential support, so they are not available under Embedded Swift. Imported generics remain available there.

0 commit comments

Comments
 (0)