@@ -91,6 +91,17 @@ public class ExportSwift {
9191 }
9292 }
9393
94+ withSpan ( " Render Generic Bridgeable Conformances " ) { [ self ] in
95+ // Emitted unconditionally: a module cannot know whether a dependent
96+ // module declares a generic imported function that will be called
97+ // with one of this module's types, so every bridgeable `@JS` type
98+ // must conform to `BridgedSwiftGenericBridgeable`.
99+ let genericConformanceCodegen = GenericConformanceCodegen ( )
100+ for entry in skeleton. genericBridgeableTypeEntries {
101+ decls. append ( contentsOf: genericConformanceCodegen. renderConformance ( typeName: entry. swiftName) )
102+ }
103+ }
104+
94105 try withSpan ( " Render Async Promise Helpers " ) { [ self ] in
95106 let asyncResolveTypes = skeleton. asyncPromiseResolveReturnTypes
96107 if !asyncResolveTypes. isEmpty {
@@ -875,6 +886,73 @@ public class ExportSwift {
875886 }
876887}
877888
889+ // MARK: - GenericConformanceCodegen
890+
891+ /// Renders `BridgedSwiftGenericBridgeable` conformances for `@JS` types so they
892+ /// can be used as the generic argument of a generic imported `@JSFunction`.
893+ ///
894+ /// The conformance gives each type a unique `BridgeJSTypeHandle`; the handle
895+ /// object's address is the type's runtime type ID (pointer identity, so it is
896+ /// unique across all linked modules without relying on type names).
897+ struct GenericConformanceCodegen {
898+ func renderConformance( typeName: String ) -> [ DeclSyntax ] {
899+ let printer = CodeFragmentPrinter ( )
900+ printer. write ( " extension \( typeName) : BridgedSwiftGenericBridgeable { " )
901+ printer. indent {
902+ printer. write (
903+ " @_spi(BridgeJS) public static let bridgeJSTypeHandle = \( typeName) .bridgeJSMakeTypeHandle() "
904+ )
905+ }
906+ printer. write ( " } " )
907+ return [ " \( raw: printer. lines. joined ( separator: " \n " ) ) " ]
908+ }
909+ }
910+
911+ // MARK: - GenericTypeRegistrationCodegen
912+
913+ /// Renders the per-module type-handle registration function.
914+ ///
915+ /// The function is exported from the wasm module as
916+ /// `bjs_<Module>_register_type_handles`. When the JS glue calls it during
917+ /// initialization, it lowers the `bridgeJSTypeID` of every registered type into
918+ /// a buffer — in the canonical order defined by
919+ /// `BridgeJSSkeleton.typeRegistrationEntries` — and calls the JS import hook of
920+ /// the same name with `(base, count)`. The JS side emits its codec array in the
921+ /// same canonical order and pairs the two up index-by-index, so a type ID maps
922+ /// to its codec without any string-based lookup.
923+ public struct GenericTypeRegistrationCodegen {
924+ public init ( ) { }
925+
926+ public func render( for skeleton: BridgeJSSkeleton ) -> String ? {
927+ guard let entries = skeleton. typeRegistrationEntries else { return nil }
928+ let abiName = ABINameGenerator . typeRegistrationFunctionName ( moduleName: skeleton. moduleName)
929+ let printer = CodeFragmentPrinter ( )
930+ printer. write ( " #if arch(wasm32) " )
931+ printer. write ( " @_extern(wasm, module: \" bjs \" , name: \" \( abiName) \" ) " )
932+ printer. write ( " fileprivate func _ \( abiName) _extern(_ base: UnsafePointer<Int32>?, _ count: Int32) " )
933+ printer. nextLine ( )
934+ printer. write ( " @_expose(wasm, \" \( abiName) \" ) " )
935+ printer. write ( " public func _ \( abiName) () { " )
936+ printer. indent {
937+ printer. write ( " let typeIds: [Int32] = [ " )
938+ printer. indent {
939+ for entry in entries {
940+ printer. write ( " \( entry. swiftName) .bridgeJSTypeID, " )
941+ }
942+ }
943+ printer. write ( " ] " )
944+ printer. write ( " typeIds.withUnsafeBufferPointer { buffer in " )
945+ printer. indent {
946+ printer. write ( " _ \( abiName) _extern(buffer.baseAddress, Int32(buffer.count)) " )
947+ }
948+ printer. write ( " } " )
949+ }
950+ printer. write ( " } " )
951+ printer. write ( " #endif " )
952+ return printer. lines. joined ( separator: " \n " )
953+ }
954+ }
955+
878956// MARK: - StackCodegen
879957
880958/// Helper for stack-based lifting and lowering operations.
@@ -896,6 +974,10 @@ struct StackCodegen {
896974 return " JSObject.bridgeJSStackPop() "
897975 case . void, . namespaceEnum:
898976 return " () "
977+ case . generic:
978+ fatalError (
979+ " Generic parameters are only supported on imported declarations, not exported concrete-type codegen "
980+ )
899981 }
900982 }
901983
@@ -908,7 +990,7 @@ struct StackCodegen {
908990 return " \( raw: typeName) < \( raw: wrappedType. swiftType) >.bridgeJSStackPop() "
909991 case . jsObject( let className? ) :
910992 return " \( raw: typeName) <JSObject>.bridgeJSStackPop().map { \( raw: className) (unsafelyWrapping: $0) } "
911- case . nullable, . void, . namespaceEnum, . closure, . unsafePointer, . swiftProtocol:
993+ case . nullable, . void, . namespaceEnum, . closure, . unsafePointer, . swiftProtocol, . generic :
912994 fatalError ( " Invalid nullable wrapped type: \( wrappedType) " )
913995 }
914996 }
@@ -941,6 +1023,10 @@ struct StackCodegen {
9411023 return lowerArrayStatements ( elementType: elementType, accessor: accessor, varPrefix: varPrefix)
9421024 case . dictionary( let valueType) :
9431025 return lowerDictionaryStatements ( valueType: valueType, accessor: accessor, varPrefix: varPrefix)
1026+ case . generic:
1027+ fatalError (
1028+ " Generic parameters are only supported on imported declarations, not exported concrete-type codegen "
1029+ )
9441030 }
9451031 }
9461032
@@ -1596,12 +1682,32 @@ extension BridgeType {
15961682 case . associatedValueEnum:
15971683 return [ " _BridgedSwiftAssociatedValueEnum " ]
15981684 case . rawValueEnum, . void, . unsafePointer, . namespaceEnum,
1599- . swiftProtocol, . closure, . nullable, . array, . dictionary, . alias:
1685+ . swiftProtocol, . closure, . nullable, . array, . dictionary, . alias, . generic :
16001686 // Not supported yet.
16011687 return nil
16021688 }
16031689 }
16041690
1691+ /// Wrapped generic values bridge through the same `Array`/`Optional`/`Dictionary`
1692+ /// conditional conformances that concrete types use.
1693+ var genericStackPopExpression : String ? {
1694+ switch self {
1695+ case . generic( let name) : return " \( name) .bridgeJSStackPop() "
1696+ case . array( . generic( let name) ) : return " Array< \( name) >.bridgeJSStackPop() "
1697+ case . nullable( . generic( let name) , _) : return " Optional< \( name) >.bridgeJSStackPop() "
1698+ case . dictionary( . generic( let name) ) : return " Dictionary<String, \( name) >.bridgeJSStackPop() "
1699+ default : return nil
1700+ }
1701+ }
1702+
1703+ func genericStackPushStatement( value: String ) -> String ? {
1704+ switch self {
1705+ case . generic, . array( . generic) , . nullable( . generic, _) , . dictionary( . generic) :
1706+ return " \( value) .bridgeJSStackPush() "
1707+ default : return nil
1708+ }
1709+ }
1710+
16051711 var swiftType : String {
16061712 switch self {
16071713 case . bool: return " Bool "
@@ -1631,6 +1737,7 @@ extension BridgeType {
16311737 let closureType = " ( \( paramTypes) ) \( effectsStr) -> \( signature. returnType. swiftType) "
16321738 return useJSTypedClosure ? " JSTypedClosure< \( closureType) > " : closureType
16331739 case . alias( let name, _) : return name
1740+ case . generic( let name) : return name
16341741 }
16351742 }
16361743
@@ -1717,6 +1824,10 @@ extension BridgeType {
17171824 return LiftingIntrinsicInfo ( parameters: [ ] )
17181825 case . alias( _, let underlying) :
17191826 return try underlying. liftParameterInfo ( )
1827+ case . generic:
1828+ throw BridgeJSCoreError (
1829+ " Generic parameters are only supported on imported declarations, not exported concrete-type codegen "
1830+ )
17201831 }
17211832 }
17221833
@@ -1770,6 +1881,10 @@ extension BridgeType {
17701881 return . array
17711882 case . alias( _, let underlying) :
17721883 return try underlying. loweringReturnInfo ( )
1884+ case . generic:
1885+ throw BridgeJSCoreError (
1886+ " Generic parameters are only supported on imported declarations, not exported concrete-type codegen "
1887+ )
17731888 }
17741889 }
17751890}
0 commit comments