@@ -90,11 +90,75 @@ public class ExportSwift {
9090 decls. append ( contentsOf: try renderSingleExportedClass ( klass: klass) )
9191 }
9292 }
93+
94+ try withSpan ( " Render Async Promise Helpers " ) { [ self ] in
95+ let asyncResolveTypes = skeleton. asyncPromiseResolveReturnTypes
96+ if !asyncResolveTypes. isEmpty {
97+ decls. append ( contentsOf: try renderPromiseRejectHelper ( ) )
98+ for type in asyncResolveTypes {
99+ decls. append ( contentsOf: try renderPromiseResolveHelper ( type) )
100+ }
101+ }
102+ }
93103 return withSpan ( " Format Export Glue " ) {
94104 return decls. map { $0. description } . joined ( separator: " \n \n " )
95105 }
96106 }
97107
108+ /// Generates the per-type `Promise_resolve_<mangled>` settlement helper.
109+ private func renderPromiseResolveHelper( _ type: BridgeType ) throws -> [ DeclSyntax ] {
110+ try renderPromiseSettleHelper (
111+ functionName: " Promise_resolve_ \( type. mangleTypeName) " ,
112+ externName: " promise_resolve_ \( moduleName) _ \( type. mangleTypeName) " ,
113+ valueType: type
114+ )
115+ }
116+
117+ /// Generates the shared `Promise_reject` settlement helper.
118+ private func renderPromiseRejectHelper( ) throws -> [ DeclSyntax ] {
119+ try renderPromiseSettleHelper (
120+ functionName: " Promise_reject " ,
121+ externName: " promise_reject_ \( moduleName) " ,
122+ valueType: . jsValue
123+ )
124+ }
125+
126+ /// Generates a `@JSFunction func <functionName>(_ promise: JSObject, _ value: T)` and its
127+ /// glue, lowering `value` through the standard imported-parameter ABI.
128+ private func renderPromiseSettleHelper(
129+ functionName: String ,
130+ externName: String ,
131+ valueType: BridgeType
132+ ) throws -> [ DeclSyntax ] {
133+ let effects = Effects ( isAsync: false , isThrows: true )
134+ let parameters = [
135+ Parameter ( label: nil , name: " promise " , type: . jsObject( nil ) ) ,
136+ Parameter ( label: nil , name: " value " , type: valueType) ,
137+ ]
138+ let builder = try ImportTS . CallJSEmission (
139+ moduleName: " bjs " ,
140+ abiName: externName,
141+ effects: effects,
142+ returnType: . void,
143+ context: . importTS
144+ )
145+ for parameter in parameters {
146+ try builder. lowerParameter ( param: parameter)
147+ }
148+ try builder. call ( )
149+ try builder. liftReturnValue ( )
150+
151+ let macroDecl : DeclSyntax =
152+ " @JSFunction func \( raw: functionName) (_ promise: JSObject, _ value: \( raw: valueType. swiftType) ) throws(JSException) "
153+ let glueDecl = builder. renderThunkDecl (
154+ name: " _$ \( functionName) " ,
155+ parameters: parameters,
156+ returnType: . void,
157+ effects: effects
158+ )
159+ return [ macroDecl, builder. renderImportDecl ( ) , glueDecl]
160+ }
161+
98162 class ExportedThunkBuilder {
99163 var body : [ CodeBlockItemSyntax ] = [ ]
100164 var liftedParameterExprs : [ ExprSyntax ] = [ ]
@@ -104,6 +168,12 @@ public class ExportSwift {
104168 var externDecls : [ DeclSyntax ] = [ ]
105169 let effects : Effects
106170
171+ /// When set, the async thunk settles via `_bjs_makePromise` rather than the `.jsValue` path.
172+ var asyncResolveReturnType : BridgeType ?
173+
174+ /// Stack-using parameter lifts hoisted ahead of the deferred async closure.
175+ var asyncHoistedBindings : [ CodeBlockItemSyntax ] = [ ]
176+
107177 init ( effects: Effects ) {
108178 self . effects = effects
109179 }
@@ -200,6 +270,9 @@ public class ExportSwift {
200270 }
201271
202272 if effects. isAsync, returnType != . void {
273+ if asyncResolveReturnType != nil {
274+ return CodeBlockItemSyntax ( item: . init( StmtSyntax ( " return \( raw: callExpr) " ) ) )
275+ }
203276 return CodeBlockItemSyntax ( item: . init( StmtSyntax ( " return \( raw: callExpr) .jsValue " ) ) )
204277 }
205278
@@ -244,6 +317,22 @@ public class ExportSwift {
244317 param. type. isStackUsingParameter ? index : nil
245318 }
246319
320+ if effects. isAsync {
321+ // Drain stack parameters before the deferred `Task` or the shared stack is corrupted.
322+ for index in stackParamIndices. reversed ( ) {
323+ let param = parameters [ index]
324+ let expr = liftedParameterExprs [ index]
325+ let varName = " _tmp_ \( param. name) "
326+ var binding : CodeBlockItemSyntax = " let \( raw: varName) = \( expr) "
327+ if !asyncHoistedBindings. isEmpty {
328+ binding = binding. with ( \. leadingTrivia, . newline)
329+ }
330+ asyncHoistedBindings. append ( binding)
331+ liftedParameterExprs [ index] = ExprSyntax ( DeclReferenceExprSyntax ( baseName: . identifier( varName) ) )
332+ }
333+ return
334+ }
335+
247336 guard stackParamIndices. count > 1 else { return }
248337
249338 for index in stackParamIndices. reversed ( ) {
@@ -328,21 +417,31 @@ public class ExportSwift {
328417 }
329418 }
330419
420+ /// A throwing async body needs an explicit closure type, otherwise Swift infers
421+ /// `throws(any Error)` instead of `throws(JSException)`.
422+ /// See: https://github.com/swiftlang/swift/issues/76165
423+ private func asyncThrowsClosureHead( returnSpelling: String ? ) -> String {
424+ guard effects. isThrows else { return " " }
425+ let returns = returnSpelling. map { " -> \( $0) " } ?? " "
426+ return " () async throws(JSException) \( returns) in "
427+ }
428+
331429 func render( abiName: String ) -> DeclSyntax {
332430 let body : CodeBlockItemListSyntax
333- if effects. isAsync {
334- // Explicit closure type annotation needed when throws is present
335- // so Swift infers throws(JSException) instead of throws(any Error )
336- // See: https://github.com/swiftlang/swift/issues/76165
337- let closureHead : String
338- if effects . isThrows {
339- let hasReturn = self . body. contains { $0 . description . contains ( " return " ) }
340- let ret = hasReturn ? " -> JSValue " : " "
341- closureHead = " () async throws(JSException) \( ret ) in "
342- } else {
343- closureHead = " "
344- }
431+ if effects. isAsync, let resolveType = asyncResolveReturnType {
432+ let resolveName = " Promise_resolve_ \( resolveType . mangleTypeName ) "
433+ let closureHead = asyncThrowsClosureHead ( returnSpelling : resolveType . swiftType )
434+ body = """
435+ \( CodeBlockItemListSyntax ( asyncHoistedBindings ) )
436+ return _bjs_makePromise(resolve: \( raw : resolveName ) , reject: Promise_reject) { \( raw : closureHead )
437+ \( CodeBlockItemListSyntax ( self . body) )
438+ }
439+ "" "
440+ } else if effects . isAsync {
441+ let hasReturn = self . body . contains { $0 . description . contains ( " return " ) }
442+ let closureHead = asyncThrowsClosureHead ( returnSpelling : hasReturn ? " JSValue " : nil )
345443 body = """
444+ \( CodeBlockItemListSyntax ( asyncHoistedBindings) )
346445 let ret = JSPromise.async { \( raw: closureHead)
347446 \( CodeBlockItemListSyntax ( self . body) )
348447 }.jsObject
@@ -506,8 +605,42 @@ public class ExportSwift {
506605 return decls
507606 }
508607
608+ private func configureAsyncResolve(
609+ _ builder: ExportedThunkBuilder ,
610+ returnType: BridgeType ,
611+ effects: Effects
612+ ) throws {
613+ guard effects. isAsync else { return }
614+ if returnType. usesAsyncPromiseResolve {
615+ builder. asyncResolveReturnType = returnType
616+ return
617+ }
618+ // Diagnose types with neither a `.jsValue` nor a `_bjs_makePromise` representation.
619+ if Self . asyncReturnLacksBridging ( returnType) {
620+ throw BridgeJSCoreError (
621+ " Returning ' \( returnType. swiftType) ' from an async exported function is not yet supported "
622+ )
623+ }
624+ }
625+
626+ private static func asyncReturnLacksBridging( _ type: BridgeType ) -> Bool {
627+ switch type {
628+ case . associatedValueEnum, . swiftProtocol, . namespaceEnum:
629+ return true
630+ case . nullable( let wrapped, _) :
631+ return asyncReturnLacksBridging ( wrapped)
632+ case . array( let element) :
633+ return asyncReturnLacksBridging ( element)
634+ case . dictionary( let value) :
635+ return asyncReturnLacksBridging ( value)
636+ default :
637+ return false
638+ }
639+ }
640+
509641 func renderSingleExportedFunction( function: ExportedFunction ) throws -> DeclSyntax {
510642 let builder = ExportedThunkBuilder ( effects: function. effects)
643+ try configureAsyncResolve ( builder, returnType: function. returnType, effects: function. effects)
511644 for param in function. parameters {
512645 try builder. liftParameter ( param: param)
513646 }
@@ -551,6 +684,7 @@ public class ExportSwift {
551684 instanceSelfType: BridgeType
552685 ) throws -> DeclSyntax {
553686 let builder = ExportedThunkBuilder ( effects: method. effects)
687+ try configureAsyncResolve ( builder, returnType: method. returnType, effects: method. effects)
554688 if !method. effects. isStatic {
555689 try builder. liftParameter ( param: Parameter ( label: nil , name: " _self " , type: instanceSelfType) )
556690 }
0 commit comments