@@ -90,11 +90,77 @@ 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+ // `Void` can't cross the bridge as a parameter, so the void helper takes only the promise.
135+ var parameters = [ Parameter ( label: nil , name: " promise " , type: . jsObject( nil ) ) ]
136+ if valueType != . void {
137+ parameters. append ( Parameter ( label: nil , name: " value " , type: valueType) )
138+ }
139+ let builder = try ImportTS . CallJSEmission (
140+ moduleName: " bjs " ,
141+ abiName: externName,
142+ effects: effects,
143+ returnType: . void,
144+ context: . importTS
145+ )
146+ for parameter in parameters {
147+ try builder. lowerParameter ( param: parameter)
148+ }
149+ try builder. call ( )
150+ try builder. liftReturnValue ( )
151+
152+ let valueParam = valueType == . void ? " " : " , _ value: \( valueType. swiftType) "
153+ let macroDecl : DeclSyntax =
154+ " @JSFunction func \( raw: functionName) (_ promise: JSObject \( raw: valueParam) ) throws(JSException) "
155+ let glueDecl = builder. renderThunkDecl (
156+ name: " _$ \( functionName) " ,
157+ parameters: parameters,
158+ returnType: . void,
159+ effects: effects
160+ )
161+ return [ macroDecl, builder. renderImportDecl ( ) , glueDecl]
162+ }
163+
98164 class ExportedThunkBuilder {
99165 var body : [ CodeBlockItemSyntax ] = [ ]
100166 var liftedParameterExprs : [ ExprSyntax ] = [ ]
@@ -104,8 +170,22 @@ public class ExportSwift {
104170 var externDecls : [ DeclSyntax ] = [ ]
105171 let effects : Effects
106172
107- init ( effects: Effects ) {
173+ /// The async return type settled through `_bjs_makePromise`'s `Promise_resolve_<mangled>`
174+ /// helper. Set for every `async` thunk.
175+ var asyncResolveReturnType : BridgeType ?
176+
177+ /// Stack-using parameter lifts hoisted ahead of the deferred async closure.
178+ var asyncHoistedBindings : [ CodeBlockItemSyntax ] = [ ]
179+
180+ init ( effects: Effects , returnType: BridgeType ) throws {
108181 self . effects = effects
182+ guard effects. isAsync else { return }
183+ guard returnType. isAsyncResolvable else {
184+ throw BridgeJSCoreError (
185+ " Returning ' \( returnType. swiftType) ' from an async exported function is not yet supported "
186+ )
187+ }
188+ self . asyncResolveReturnType = returnType
109189 }
110190
111191 private func append( _ item: CodeBlockItemSyntax ) {
@@ -200,7 +280,7 @@ public class ExportSwift {
200280 }
201281
202282 if effects. isAsync, returnType != . void {
203- return CodeBlockItemSyntax ( item: . init( StmtSyntax ( " return \( raw: callExpr) .jsValue " ) ) )
283+ return CodeBlockItemSyntax ( item: . init( StmtSyntax ( " return \( raw: callExpr) " ) ) )
204284 }
205285
206286 if returnType == . void {
@@ -244,6 +324,22 @@ public class ExportSwift {
244324 param. type. isStackUsingParameter ? index : nil
245325 }
246326
327+ if effects. isAsync {
328+ // Drain stack parameters before the deferred `Task` or the shared stack is corrupted.
329+ for index in stackParamIndices. reversed ( ) {
330+ let param = parameters [ index]
331+ let expr = liftedParameterExprs [ index]
332+ let varName = " _tmp_ \( param. name) "
333+ var binding : CodeBlockItemSyntax = " let \( raw: varName) = \( expr) "
334+ if !asyncHoistedBindings. isEmpty {
335+ binding = binding. with ( \. leadingTrivia, . newline)
336+ }
337+ asyncHoistedBindings. append ( binding)
338+ liftedParameterExprs [ index] = ExprSyntax ( DeclReferenceExprSyntax ( baseName: . identifier( varName) ) )
339+ }
340+ return
341+ }
342+
247343 guard stackParamIndices. count > 1 else { return }
248344
249345 for index in stackParamIndices. reversed ( ) {
@@ -293,8 +389,7 @@ public class ExportSwift {
293389 return
294390 }
295391 if effects. isAsync {
296- // The return value of async function (T of `(...) async -> T`) is
297- // handled by the JSPromise.async, so we don't need to do anything here.
392+ // The async return value is lowered by the generated `Promise_resolve_*` helper.
298393 return
299394 }
300395
@@ -328,25 +423,25 @@ public class ExportSwift {
328423 }
329424 }
330425
426+ /// A throwing async body needs an explicit closure type, otherwise Swift infers
427+ /// `throws(any Error)` instead of `throws(JSException)`.
428+ /// See: https://github.com/swiftlang/swift/issues/76165
429+ private func asyncThrowsClosureHead( returnSpelling: String ? ) -> String {
430+ guard effects. isThrows else { return " " }
431+ let returns = returnSpelling. map { " -> \( $0) " } ?? " "
432+ return " () async throws(JSException) \( returns) in "
433+ }
434+
331435 func render( abiName: String ) -> DeclSyntax {
332436 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- }
437+ if effects. isAsync, let resolveType = asyncResolveReturnType {
438+ let resolveName = " Promise_resolve_ \( resolveType. mangleTypeName) "
439+ let closureHead = asyncThrowsClosureHead ( returnSpelling: resolveType. swiftType)
345440 body = """
346- let ret = JSPromise.async { \( raw: closureHead)
441+ \( CodeBlockItemListSyntax ( asyncHoistedBindings) )
442+ return _bjs_makePromise(resolve: \( raw: resolveName) , reject: Promise_reject) { \( raw: closureHead)
347443 \( CodeBlockItemListSyntax ( self . body) )
348- }.jsObject
349- return ret.bridgeJSLowerReturn()
444+ }
350445 """
351446 } else if effects. isThrows {
352447 body = """
@@ -457,7 +552,10 @@ public class ExportSwift {
457552 let className = context. className
458553 let isStatic = context. isStatic
459554
460- let getterBuilder = ExportedThunkBuilder ( effects: Effects ( isAsync: false , isThrows: false , isStatic: isStatic) )
555+ let getterBuilder = try ExportedThunkBuilder (
556+ effects: Effects ( isAsync: false , isThrows: false , isStatic: isStatic) ,
557+ returnType: property. type
558+ )
461559
462560 if !isStatic {
463561 try getterBuilder. liftParameter (
@@ -476,8 +574,9 @@ public class ExportSwift {
476574
477575 // Generate property setter if not readonly
478576 if !property. isReadonly {
479- let setterBuilder = ExportedThunkBuilder (
480- effects: Effects ( isAsync: false , isThrows: false , isStatic: isStatic)
577+ let setterBuilder = try ExportedThunkBuilder (
578+ effects: Effects ( isAsync: false , isThrows: false , isStatic: isStatic) ,
579+ returnType: . void
481580 )
482581
483582 // Lift parameters based on property type
@@ -507,7 +606,7 @@ public class ExportSwift {
507606 }
508607
509608 func renderSingleExportedFunction( function: ExportedFunction ) throws -> DeclSyntax {
510- let builder = ExportedThunkBuilder ( effects: function. effects)
609+ let builder = try ExportedThunkBuilder ( effects: function. effects, returnType : function . returnType )
511610 for param in function. parameters {
512611 try builder. liftParameter ( param: param)
513612 }
@@ -536,7 +635,7 @@ public class ExportSwift {
536635 callName: String ,
537636 returnType: BridgeType
538637 ) throws -> DeclSyntax {
539- let builder = ExportedThunkBuilder ( effects: constructor. effects)
638+ let builder = try ExportedThunkBuilder ( effects: constructor. effects, returnType : returnType )
540639 for param in constructor. parameters {
541640 try builder. liftParameter ( param: param)
542641 }
@@ -550,7 +649,7 @@ public class ExportSwift {
550649 ownerTypeName: String ,
551650 instanceSelfType: BridgeType
552651 ) throws -> DeclSyntax {
553- let builder = ExportedThunkBuilder ( effects: method. effects)
652+ let builder = try ExportedThunkBuilder ( effects: method. effects, returnType : method . returnType )
554653 if !method. effects. isStatic {
555654 try builder. liftParameter ( param: Parameter ( label: nil , name: " _self " , type: instanceSelfType) )
556655 }
0 commit comments