Skip to content

Commit 3966530

Browse files
authored
Merge pull request #799 from PassiveLogic/kr/stack-abi-generics-import
BridgeJS: Support generic functions on imported JS APIs
2 parents e10836b + bf00750 commit 3966530

132 files changed

Lines changed: 13443 additions & 2479 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Benchmarks/Sources/Generated/BridgeJS.swift

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2179,6 +2179,34 @@ fileprivate func _bjs_ArrayRoundtrip_wrap_extern(_ pointer: UnsafeMutableRawPoin
21792179
return _bjs_ArrayRoundtrip_wrap_extern(pointer)
21802180
}
21812181

2182+
extension SimpleStruct: BridgedSwiftGenericBridgeable {
2183+
@_spi(BridgeJS) public static let bridgeJSTypeHandle = SimpleStruct.bridgeJSMakeTypeHandle()
2184+
}
2185+
2186+
extension Address: BridgedSwiftGenericBridgeable {
2187+
@_spi(BridgeJS) public static let bridgeJSTypeHandle = Address.bridgeJSMakeTypeHandle()
2188+
}
2189+
2190+
extension Person: BridgedSwiftGenericBridgeable {
2191+
@_spi(BridgeJS) public static let bridgeJSTypeHandle = Person.bridgeJSMakeTypeHandle()
2192+
}
2193+
2194+
extension ComplexStruct: BridgedSwiftGenericBridgeable {
2195+
@_spi(BridgeJS) public static let bridgeJSTypeHandle = ComplexStruct.bridgeJSMakeTypeHandle()
2196+
}
2197+
2198+
extension Point: BridgedSwiftGenericBridgeable {
2199+
@_spi(BridgeJS) public static let bridgeJSTypeHandle = Point.bridgeJSMakeTypeHandle()
2200+
}
2201+
2202+
extension APIResult: BridgedSwiftGenericBridgeable {
2203+
@_spi(BridgeJS) public static let bridgeJSTypeHandle = APIResult.bridgeJSMakeTypeHandle()
2204+
}
2205+
2206+
extension ComplexResult: BridgedSwiftGenericBridgeable {
2207+
@_spi(BridgeJS) public static let bridgeJSTypeHandle = ComplexResult.bridgeJSMakeTypeHandle()
2208+
}
2209+
21822210
#if arch(wasm32)
21832211
@_extern(wasm, module: "Benchmarks", name: "bjs_benchmarkHelperNoop")
21842212
fileprivate func bjs_benchmarkHelperNoop_extern() -> Void
@@ -2238,4 +2266,25 @@ func _$benchmarkRunner(_ name: String, _ body: JSObject) throws(JSException) ->
22382266
if let error = _swift_js_take_exception() {
22392267
throw error
22402268
}
2241-
}
2269+
}
2270+
2271+
#if arch(wasm32)
2272+
@_extern(wasm, module: "bjs", name: "bjs_Benchmarks_register_type_handles")
2273+
fileprivate func _bjs_Benchmarks_register_type_handles_extern(_ base: UnsafePointer<Int32>?, _ count: Int32)
2274+
2275+
@_expose(wasm, "bjs_Benchmarks_register_type_handles")
2276+
public func _bjs_Benchmarks_register_type_handles() {
2277+
let typeIds: [Int32] = [
2278+
SimpleStruct.bridgeJSTypeID,
2279+
Address.bridgeJSTypeID,
2280+
Person.bridgeJSTypeID,
2281+
ComplexStruct.bridgeJSTypeID,
2282+
Point.bridgeJSTypeID,
2283+
APIResult.bridgeJSTypeID,
2284+
ComplexResult.bridgeJSTypeID,
2285+
]
2286+
typeIds.withUnsafeBufferPointer { buffer in
2287+
_bjs_Benchmarks_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count))
2288+
}
2289+
}
2290+
#endif

Examples/Embedded/Package.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ let package = Package(
1616
swiftSettings: [
1717
.enableExperimentalFeature("Extern")
1818
],
19+
plugins: [
20+
.plugin(name: "BridgeJS", package: "JavaScriptKit")
21+
]
1922
)
2023
],
2124
swiftLanguageModes: [.v5]

Examples/Embedded/Sources/EmbeddedApp/main.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import JavaScriptKit
22

3+
@JS struct CounterLabel {
4+
var count: Int
5+
var text: String
6+
}
7+
8+
@JSFunction func echoValue<T: BridgedSwiftGenericBridgeable>(_ value: T) throws(JSException) -> T
9+
310
let alert = JSObject.global.alert.object!
411
let document = JSObject.global.document
512

@@ -46,6 +53,17 @@ _ = encoderContainer.appendChild(textInputElement)
4653
_ = encoderContainer.appendChild(encodeResultElement)
4754
_ = document.body.appendChild(encoderContainer)
4855

56+
let genericResultElement = document.createElement("pre")
57+
do {
58+
let number = try echoValue(42)
59+
let text = try echoValue("hello")
60+
let label = try echoValue(CounterLabel(count: number, text: text))
61+
genericResultElement.innerText = .string("Generic import round-trip: \(label.text) \(label.count)")
62+
} catch {
63+
genericResultElement.innerText = "Generic import round-trip failed"
64+
}
65+
_ = document.body.appendChild(genericResultElement)
66+
4967
func print(_ message: String) {
5068
_ = JSObject.global.console.log(message)
5169
}

Examples/Embedded/index.html

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
<body>
99
<script type="module">
1010
import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js";
11-
init();
11+
init({
12+
getImports() {
13+
return {
14+
echoValue: (value) => value,
15+
};
16+
},
17+
});
1218
</script>
1319
</body>
1420

Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,18 @@ fileprivate func _bjs_PlayBridgeJS_wrap_extern(_ pointer: UnsafeMutableRawPointe
231231
return _bjs_PlayBridgeJS_wrap_extern(pointer)
232232
}
233233

234+
extension PlayBridgeJSOutput: BridgedSwiftGenericBridgeable {
235+
@_spi(BridgeJS) public static let bridgeJSTypeHandle = PlayBridgeJSOutput.bridgeJSMakeTypeHandle()
236+
}
237+
238+
extension PlayBridgeJSDiagnostic: BridgedSwiftGenericBridgeable {
239+
@_spi(BridgeJS) public static let bridgeJSTypeHandle = PlayBridgeJSDiagnostic.bridgeJSMakeTypeHandle()
240+
}
241+
242+
extension PlayBridgeJSResult: BridgedSwiftGenericBridgeable {
243+
@_spi(BridgeJS) public static let bridgeJSTypeHandle = PlayBridgeJSResult.bridgeJSMakeTypeHandle()
244+
}
245+
234246
#if arch(wasm32)
235247
@_extern(wasm, module: "PlayBridgeJS", name: "bjs_createTS2Swift")
236248
fileprivate func bjs_createTS2Swift_extern() -> Int32
@@ -274,4 +286,21 @@ func _$TS2Swift_convert(_ self: JSObject, _ ts: String) throws(JSException) -> S
274286
throw error
275287
}
276288
return String.bridgeJSLiftReturn(ret)
277-
}
289+
}
290+
291+
#if arch(wasm32)
292+
@_extern(wasm, module: "bjs", name: "bjs_PlayBridgeJS_register_type_handles")
293+
fileprivate func _bjs_PlayBridgeJS_register_type_handles_extern(_ base: UnsafePointer<Int32>?, _ count: Int32)
294+
295+
@_expose(wasm, "bjs_PlayBridgeJS_register_type_handles")
296+
public func _bjs_PlayBridgeJS_register_type_handles() {
297+
let typeIds: [Int32] = [
298+
PlayBridgeJSOutput.bridgeJSTypeID,
299+
PlayBridgeJSDiagnostic.bridgeJSTypeID,
300+
PlayBridgeJSResult.bridgeJSTypeID,
301+
]
302+
typeIds.withUnsafeBufferPointer { buffer in
303+
_bjs_PlayBridgeJS_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count))
304+
}
305+
}
306+
#endif

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+
| 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) |
102102

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

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ public class ExportSwift {
9191
}
9292
}
9393

94+
withSpan("Render Generic Bridgeable Conformances") { [self] in
95+
let genericConformanceCodegen = GenericConformanceCodegen()
96+
for entry in skeleton.genericBridgeableTypeEntries {
97+
decls.append(contentsOf: genericConformanceCodegen.renderConformance(typeName: entry.swiftName))
98+
}
99+
}
100+
94101
try withSpan("Render Async Promise Helpers") { [self] in
95102
let asyncResolveTypes = skeleton.asyncPromiseResolveReturnTypes
96103
if !asyncResolveTypes.isEmpty {
@@ -875,6 +882,57 @@ public class ExportSwift {
875882
}
876883
}
877884

885+
// MARK: - GenericConformanceCodegen
886+
887+
struct GenericConformanceCodegen {
888+
func renderConformance(typeName: String) -> [DeclSyntax] {
889+
let printer = CodeFragmentPrinter()
890+
printer.write("extension \(typeName): BridgedSwiftGenericBridgeable {")
891+
printer.indent {
892+
printer.write(
893+
"@_spi(BridgeJS) public static let bridgeJSTypeHandle = \(typeName).bridgeJSMakeTypeHandle()"
894+
)
895+
}
896+
printer.write("}")
897+
return ["\(raw: printer.lines.joined(separator: "\n"))"]
898+
}
899+
}
900+
901+
// MARK: - GenericTypeRegistrationCodegen
902+
903+
public struct GenericTypeRegistrationCodegen {
904+
public init() {}
905+
906+
public func render(for skeleton: BridgeJSSkeleton) -> String? {
907+
guard let entries = skeleton.typeRegistrationEntries else { return nil }
908+
let abiName = ABINameGenerator.typeRegistrationFunctionName(moduleName: skeleton.moduleName)
909+
let printer = CodeFragmentPrinter()
910+
printer.write("#if arch(wasm32)")
911+
printer.write("@_extern(wasm, module: \"bjs\", name: \"\(abiName)\")")
912+
printer.write("fileprivate func _\(abiName)_extern(_ base: UnsafePointer<Int32>?, _ count: Int32)")
913+
printer.nextLine()
914+
printer.write("@_expose(wasm, \"\(abiName)\")")
915+
printer.write("public func _\(abiName)() {")
916+
printer.indent {
917+
printer.write("let typeIds: [Int32] = [")
918+
printer.indent {
919+
for entry in entries {
920+
printer.write("\(entry.swiftName).bridgeJSTypeID,")
921+
}
922+
}
923+
printer.write("]")
924+
printer.write("typeIds.withUnsafeBufferPointer { buffer in")
925+
printer.indent {
926+
printer.write("_\(abiName)_extern(buffer.baseAddress, Int32(buffer.count))")
927+
}
928+
printer.write("}")
929+
}
930+
printer.write("}")
931+
printer.write("#endif")
932+
return printer.lines.joined(separator: "\n")
933+
}
934+
}
935+
878936
// MARK: - StackCodegen
879937

880938
/// Helper for stack-based lifting and lowering operations.
@@ -896,6 +954,10 @@ struct StackCodegen {
896954
return "JSObject.bridgeJSStackPop()"
897955
case .void, .namespaceEnum:
898956
return "()"
957+
case .generic:
958+
fatalError(
959+
"Generic parameters are only supported on imported declarations, not exported concrete-type codegen"
960+
)
899961
}
900962
}
901963

@@ -908,7 +970,7 @@ struct StackCodegen {
908970
return "\(raw: typeName)<\(raw: wrappedType.swiftType)>.bridgeJSStackPop()"
909971
case .jsObject(let className?):
910972
return "\(raw: typeName)<JSObject>.bridgeJSStackPop().map { \(raw: className)(unsafelyWrapping: $0) }"
911-
case .nullable, .void, .namespaceEnum, .closure, .unsafePointer, .swiftProtocol:
973+
case .nullable, .void, .namespaceEnum, .closure, .unsafePointer, .swiftProtocol, .generic:
912974
fatalError("Invalid nullable wrapped type: \(wrappedType)")
913975
}
914976
}
@@ -941,6 +1003,10 @@ struct StackCodegen {
9411003
return lowerArrayStatements(elementType: elementType, accessor: accessor, varPrefix: varPrefix)
9421004
case .dictionary(let valueType):
9431005
return lowerDictionaryStatements(valueType: valueType, accessor: accessor, varPrefix: varPrefix)
1006+
case .generic:
1007+
fatalError(
1008+
"Generic parameters are only supported on imported declarations, not exported concrete-type codegen"
1009+
)
9441010
}
9451011
}
9461012

@@ -1596,12 +1662,34 @@ extension BridgeType {
15961662
case .associatedValueEnum:
15971663
return ["_BridgedSwiftAssociatedValueEnum"]
15981664
case .rawValueEnum, .void, .unsafePointer, .namespaceEnum,
1599-
.swiftProtocol, .closure, .nullable, .array, .dictionary, .alias:
1665+
.swiftProtocol, .closure, .nullable, .array, .dictionary, .alias, .generic:
16001666
// Not supported yet.
16011667
return nil
16021668
}
16031669
}
16041670

1671+
/// Stack expressions for bare `T` and `T?`, the only generic shapes that
1672+
/// cannot reuse the concrete emission: `bridgeJSLowerParameter()` names
1673+
/// per-type members that the generic constraint erases to the stack, so
1674+
/// `bridgeJSStackPush()`/`bridgeJSStackPop()` is the shared spelling.
1675+
/// `[T]` and `[String: T]` go through the ordinary paths via the `Array`
1676+
/// and `Dictionary` stack conformances.
1677+
var genericStackPopExpression: String? {
1678+
switch self {
1679+
case .generic(let name): return "\(name).bridgeJSStackPop()"
1680+
case .nullable(.generic(let name), _): return "Optional<\(name)>.bridgeJSStackPop()"
1681+
default: return nil
1682+
}
1683+
}
1684+
1685+
func genericStackPushStatement(value: String) -> String? {
1686+
switch self {
1687+
case .generic, .nullable(.generic, _):
1688+
return "\(value).bridgeJSStackPush()"
1689+
default: return nil
1690+
}
1691+
}
1692+
16051693
var swiftType: String {
16061694
switch self {
16071695
case .bool: return "Bool"
@@ -1631,6 +1719,7 @@ extension BridgeType {
16311719
let closureType = "(\(paramTypes))\(effectsStr) -> \(signature.returnType.swiftType)"
16321720
return useJSTypedClosure ? "JSTypedClosure<\(closureType)>" : closureType
16331721
case .alias(let name, _): return name
1722+
case .generic(let name): return name
16341723
}
16351724
}
16361725

@@ -1717,6 +1806,10 @@ extension BridgeType {
17171806
return LiftingIntrinsicInfo(parameters: [])
17181807
case .alias(_, let underlying):
17191808
return try underlying.liftParameterInfo()
1809+
case .generic:
1810+
throw BridgeJSCoreError(
1811+
"Generic parameters are only supported on imported declarations, not exported concrete-type codegen"
1812+
)
17201813
}
17211814
}
17221815

@@ -1770,6 +1863,10 @@ extension BridgeType {
17701863
return .array
17711864
case .alias(_, let underlying):
17721865
return try underlying.loweringReturnInfo()
1866+
case .generic:
1867+
throw BridgeJSCoreError(
1868+
"Generic parameters are only supported on imported declarations, not exported concrete-type codegen"
1869+
)
17731870
}
17741871
}
17751872
}

0 commit comments

Comments
 (0)