From 14c1336e758f7cc1567f353563f60d1d72a6834a Mon Sep 17 00:00:00 2001 From: William Taylor Date: Wed, 14 Oct 2026 15:49:47 +1100 Subject: [PATCH 1/2] BridgeJS: Support exporting protocol methods with async and throws --- .../Sources/BridgeJSCore/ExportSwift.swift | 2 +- .../Sources/BridgeJSLink/BridgeJSLink.swift | 22 +- .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 9 + .../Inputs/MacroSwift/Protocol.swift | 12 + .../BridgeJSCodegenTests/Protocol.json | 149 +++++++ .../BridgeJSCodegenTests/Protocol.swift | 412 ++++++++++++++++++ .../BridgeJSLinkTests/AsyncImport.js | 38 +- .../BridgeJSLinkTests/AsyncStaticImport.js | 12 +- .../BridgeJSLinkTests/Protocol.d.ts | 6 + .../BridgeJSLinkTests/Protocol.js | 191 ++++++++ .../BridgeJSLinkTests/SwiftClosure.js | 38 +- .../BridgeJSLinkTests/SwiftClosureImports.js | 10 +- .../Exporting-Swift-Protocols.md | 2 + .../AsyncImportTests.swift | 15 + .../BridgeJSRuntimeTests/ExportAPITests.swift | 39 ++ .../Generated/BridgeJS.swift | 264 +++++++++++ .../Generated/JavaScript/BridgeJS.json | 327 ++++++++++++++ .../JavaScript/AsyncImportTests.mjs | 6 + .../JavaScript/ClosureAsyncTests.mjs | 8 + Tests/prelude.mjs | 130 +++++- 20 files changed, 1654 insertions(+), 38 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index df8c589ea..7d402aa9c 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -1439,7 +1439,7 @@ struct ProtocolCodegen { let signature = SwiftSignatureBuilder.buildFunctionSignature( parameters: method.parameters, returnType: method.returnType, - effects: nil + effects: method.effects ) // Build extern declaration using helper function diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 8f1c84622..c97bdf293 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -2908,7 +2908,15 @@ extension BridgeJSLink { let s = JSGlueVariableScope.reservedSwift printer.write("const resolve = \(s).memory.getObject(resolveRef);") printer.write("const reject = \(s).memory.getObject(rejectRef);") - printer.write(contentsOf: body) + printer.write("try {") + printer.indent { + printer.write(contentsOf: body) + } + printer.write("} catch (error) {") + printer.indent { + printer.write("reject(error);") + } + printer.write("}") } printer.write("}") return printer.lines @@ -2930,7 +2938,7 @@ extension BridgeJSLink { private func call(callExpr: String) throws { if effects.isAsync { - body.write("\(callExpr).then(resolve, reject);") + body.write("Promise.resolve(\(callExpr)).then(resolve, reject);") return } if let name = returnType.referencedGenericName { @@ -2997,14 +3005,6 @@ extension BridgeJSLink { return try call(calleeExpr: calleeExpr) } - /// Generates an async method call with resolve/reject closure refs. - func callAsyncMethod(name: String) { - let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)" - let calleeExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name) - let callExpr = "\(calleeExpr)(\(parameterForwardings.joined(separator: ", ")))" - body.write("\(callExpr).then(resolve, reject);") - } - func callStaticMethod(on objectExpr: String, name: String) throws { let calleeExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name) return try call(calleeExpr: calleeExpr) @@ -4235,7 +4235,7 @@ extension BridgeJSLink { method: ExportedFunction ) throws { let thunkBuilder = ImportedThunkBuilder( - effects: Effects(isAsync: false, isThrows: false), + effects: method.effects, returnType: method.returnType, context: .exportSwift, intrinsicRegistry: intrinsicRegistry diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 8da9f2e4e..d52e9122d 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -607,6 +607,7 @@ public protocol BridgeSkeletonVisitor { accessLevel: BridgeJSAccessLevel ) mutating func visitImportedFunction(_ function: ImportedFunctionSkeleton) + mutating func visitProtocolMethod(_ method: ExportedFunction, accessLevel: BridgeJSAccessLevel) } public extension BridgeSkeletonVisitor { @@ -616,6 +617,7 @@ public extension BridgeSkeletonVisitor { accessLevel: BridgeJSAccessLevel ) {} mutating func visitImportedFunction(_ function: ImportedFunctionSkeleton) {} + mutating func visitProtocolMethod(_ method: ExportedFunction, accessLevel: BridgeJSAccessLevel) {} } public struct BridgeSkeletonWalker { @@ -681,6 +683,7 @@ public struct BridgeSkeletonWalker { } for proto in skeleton.protocols { for method in proto.methods { + visitor.visitProtocolMethod(method, accessLevel: currentAccessLevel) walk(method) } for property in proto.properties { @@ -1870,6 +1873,12 @@ public struct ClosureSignatureCollectorVisitor: BridgeSkeletonVisitor { ) } + public mutating func visitProtocolMethod(_ method: ExportedFunction, accessLevel: BridgeJSAccessLevel) { + guard method.effects.isAsync else { return } + + recordInjectedSignatures(forReturnType: method.returnType, accessLevel: accessLevel) + } + private mutating func recordInjectedSignatures( forReturnType returnType: BridgeType, accessLevel: BridgeJSAccessLevel diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Protocol.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Protocol.swift index bdb700d69..8ddb3dfbd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Protocol.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Protocol.swift @@ -59,6 +59,10 @@ import JavaScriptKit func createEnum() -> ExampleEnum func handleResult(_ result: Result) func getResult() -> Result + func validate(_ value: Int) throws(JSException) -> Int + func loadLabel(_ key: String) async throws(JSException) -> String + func reset() async throws(JSException) + func fetchCount() async -> Int } @JS class MyViewController { @@ -95,6 +99,14 @@ import JavaScriptKit @JS func sendHelper(_ helper: Helper) { delegate.onHelperUpdated(helper) } + + @JS func validateValue(_ value: Int) throws(JSException) -> Int { + return try delegate.validate(value) + } + + @JS func loadLabel(_ key: String) async throws(JSException) -> String { + return try await delegate.loadLabel(key) + } } // Protocol array support diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.json index bc443c6c0..4fbec6b49 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.json @@ -228,6 +228,62 @@ "returnType" : { "void" : { + } + } + }, + { + "abiName" : "bjs_MyViewController_validateValue", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "name" : "validateValue", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_MyViewController_loadLabel", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : true + }, + "name" : "loadLabel", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + } } } @@ -831,6 +887,99 @@ "_0" : "Result" } } + }, + { + "abiName" : "bjs_MyViewControllerDelegate_validate", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "name" : "validate", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_MyViewControllerDelegate_loadLabel", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : true + }, + "name" : "loadLabel", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_MyViewControllerDelegate_reset", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : true + }, + "name" : "reset", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_MyViewControllerDelegate_fetchCount", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : false + }, + "name" : "fetchCount", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } } ], "name" : "MyViewControllerDelegate", diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift index a91524da5..3c4ba2a78 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift @@ -1,3 +1,250 @@ +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModules7JSValueV_y") +fileprivate func invoke_js_callback_TestModule_10TestModules7JSValueV_y_extern(_ callback: Int32, _ param0Kind: Int32, _ param0Payload1: Int32, _ param0Payload2: Float64) -> Void +#else +fileprivate func invoke_js_callback_TestModule_10TestModules7JSValueV_y_extern(_ callback: Int32, _ param0Kind: Int32, _ param0Payload1: Int32, _ param0Payload2: Float64) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModules7JSValueV_y(_ callback: Int32, _ param0Kind: Int32, _ param0Payload1: Int32, _ param0Payload2: Float64) -> Void { + return invoke_js_callback_TestModule_10TestModules7JSValueV_y_extern(callback, param0Kind, param0Payload1, param0Payload2) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "make_swift_closure_TestModule_10TestModules7JSValueV_y") +fileprivate func make_swift_closure_TestModule_10TestModules7JSValueV_y_extern(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 +#else +fileprivate func make_swift_closure_TestModule_10TestModules7JSValueV_y_extern(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func make_swift_closure_TestModule_10TestModules7JSValueV_y(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 { + return make_swift_closure_TestModule_10TestModules7JSValueV_y_extern(boxPtr, file, line) +} + +private enum _BJS_Closure_10TestModules7JSValueV_y { + static func bridgeJSLift(_ callbackId: Int32) -> (sending JSValue) -> Void { + let callback = JSObject.bridgeJSLiftParameter(callbackId) + return { [callback] param0 in + #if arch(wasm32) + let (param0Kind, param0Payload1, param0Payload2) = param0.bridgeJSLowerParameter() + let callbackValue = callback.bridgeJSLowerParameter() + invoke_js_callback_TestModule_10TestModules7JSValueV_y(callbackValue, param0Kind, param0Payload1, param0Payload2) + #else + fatalError("Only available on WebAssembly") + #endif + } + } +} + +extension JSTypedClosure where Signature == (sending JSValue) -> Void { + static func sending(fileID: StaticString = #fileID, line: UInt32 = #line, _ body: @escaping (sending JSValue) -> Void) -> Self { + Self( + makeClosure: make_swift_closure_TestModule_10TestModules7JSValueV_y, + body: body, + fileID: fileID, + line: line + ) + } +} + +@_expose(wasm, "invoke_swift_closure_TestModule_10TestModules7JSValueV_y") +@_cdecl("invoke_swift_closure_TestModule_10TestModules7JSValueV_y") +public func _invoke_swift_closure_TestModule_10TestModules7JSValueV_y(_ boxPtr: UnsafeMutableRawPointer, _ param0Kind: Int32, _ param0Payload1: Int32, _ param0Payload2: Float64) -> Void { + #if arch(wasm32) + let param0 = JSValue.bridgeJSLiftParameter(param0Kind, param0Payload1, param0Payload2) + let closure = Unmanaged<_BridgeJSTypedClosureBox<(sending JSValue) -> Void>>.fromOpaque(boxPtr).takeUnretainedValue().closure + closure(param0) + #else + fatalError("Only available on WebAssembly") + #endif +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModulesSS_y") +fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void +#else +fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModulesSS_y_extern(callback, param0Word0, param0Word1, param0Word2) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "make_swift_closure_TestModule_10TestModulesSS_y") +fileprivate func make_swift_closure_TestModule_10TestModulesSS_y_extern(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 +#else +fileprivate func make_swift_closure_TestModule_10TestModulesSS_y_extern(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func make_swift_closure_TestModule_10TestModulesSS_y(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 { + return make_swift_closure_TestModule_10TestModulesSS_y_extern(boxPtr, file, line) +} + +private enum _BJS_Closure_10TestModulesSS_y { + static func bridgeJSLift(_ callbackId: Int32) -> (sending String) -> Void { + let callback = JSObject.bridgeJSLiftParameter(callbackId) + return { [callback] param0 in + #if arch(wasm32) + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in + let callbackValue = callback.bridgeJSLowerParameter() + invoke_js_callback_TestModule_10TestModulesSS_y(callbackValue, param0Word0, param0Word1, param0Word2) + } + #else + fatalError("Only available on WebAssembly") + #endif + } + } +} + +extension JSTypedClosure where Signature == (sending String) -> Void { + static func sending(fileID: StaticString = #fileID, line: UInt32 = #line, _ body: @escaping (sending String) -> Void) -> Self { + Self( + makeClosure: make_swift_closure_TestModule_10TestModulesSS_y, + body: body, + fileID: fileID, + line: line + ) + } +} + +@_expose(wasm, "invoke_swift_closure_TestModule_10TestModulesSS_y") +@_cdecl("invoke_swift_closure_TestModule_10TestModulesSS_y") +public func _invoke_swift_closure_TestModule_10TestModulesSS_y(_ boxPtr: UnsafeMutableRawPointer, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { + #if arch(wasm32) + let param0 = String.bridgeJSLiftParameter(param0Bytes, param0Length) + let closure = Unmanaged<_BridgeJSTypedClosureBox<(sending String) -> Void>>.fromOpaque(boxPtr).takeUnretainedValue().closure + closure(param0) + #else + fatalError("Only available on WebAssembly") + #endif +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModulesSi_y") +fileprivate func invoke_js_callback_TestModule_10TestModulesSi_y_extern(_ callback: Int32, _ param0: Int32) -> Void +#else +fileprivate func invoke_js_callback_TestModule_10TestModulesSi_y_extern(_ callback: Int32, _ param0: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModulesSi_y(_ callback: Int32, _ param0: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModulesSi_y_extern(callback, param0) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "make_swift_closure_TestModule_10TestModulesSi_y") +fileprivate func make_swift_closure_TestModule_10TestModulesSi_y_extern(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 +#else +fileprivate func make_swift_closure_TestModule_10TestModulesSi_y_extern(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func make_swift_closure_TestModule_10TestModulesSi_y(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 { + return make_swift_closure_TestModule_10TestModulesSi_y_extern(boxPtr, file, line) +} + +private enum _BJS_Closure_10TestModulesSi_y { + static func bridgeJSLift(_ callbackId: Int32) -> (sending Int) -> Void { + let callback = JSObject.bridgeJSLiftParameter(callbackId) + return { [callback] param0 in + #if arch(wasm32) + let param0Value = param0.bridgeJSLowerParameter() + let callbackValue = callback.bridgeJSLowerParameter() + invoke_js_callback_TestModule_10TestModulesSi_y(callbackValue, param0Value) + #else + fatalError("Only available on WebAssembly") + #endif + } + } +} + +extension JSTypedClosure where Signature == (sending Int) -> Void { + static func sending(fileID: StaticString = #fileID, line: UInt32 = #line, _ body: @escaping (sending Int) -> Void) -> Self { + Self( + makeClosure: make_swift_closure_TestModule_10TestModulesSi_y, + body: body, + fileID: fileID, + line: line + ) + } +} + +@_expose(wasm, "invoke_swift_closure_TestModule_10TestModulesSi_y") +@_cdecl("invoke_swift_closure_TestModule_10TestModulesSi_y") +public func _invoke_swift_closure_TestModule_10TestModulesSi_y(_ boxPtr: UnsafeMutableRawPointer, _ param0: Int32) -> Void { + #if arch(wasm32) + let param0 = Int.bridgeJSLiftParameter(param0) + let closure = Unmanaged<_BridgeJSTypedClosureBox<(sending Int) -> Void>>.fromOpaque(boxPtr).takeUnretainedValue().closure + closure(param0) + #else + fatalError("Only available on WebAssembly") + #endif +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModuley_y") +fileprivate func invoke_js_callback_TestModule_10TestModuley_y_extern(_ callback: Int32) -> Void +#else +fileprivate func invoke_js_callback_TestModule_10TestModuley_y_extern(_ callback: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuley_y(_ callback: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModuley_y_extern(callback) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "make_swift_closure_TestModule_10TestModuley_y") +fileprivate func make_swift_closure_TestModule_10TestModuley_y_extern(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 +#else +fileprivate func make_swift_closure_TestModule_10TestModuley_y_extern(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func make_swift_closure_TestModule_10TestModuley_y(_ boxPtr: UnsafeMutableRawPointer, _ file: UnsafePointer, _ line: UInt32) -> Int32 { + return make_swift_closure_TestModule_10TestModuley_y_extern(boxPtr, file, line) +} + +private enum _BJS_Closure_10TestModuley_y { + static func bridgeJSLift(_ callbackId: Int32) -> () -> Void { + let callback = JSObject.bridgeJSLiftParameter(callbackId) + return { [callback] in + #if arch(wasm32) + let callbackValue = callback.bridgeJSLowerParameter() + invoke_js_callback_TestModule_10TestModuley_y(callbackValue) + #else + fatalError("Only available on WebAssembly") + #endif + } + } +} + +extension JSTypedClosure where Signature == () -> Void { + init(fileID: StaticString = #fileID, line: UInt32 = #line, _ body: @escaping () -> Void) { + self.init( + makeClosure: make_swift_closure_TestModule_10TestModuley_y, + body: body, + fileID: fileID, + line: line + ) + } +} + +@_expose(wasm, "invoke_swift_closure_TestModule_10TestModuley_y") +@_cdecl("invoke_swift_closure_TestModule_10TestModuley_y") +public func _invoke_swift_closure_TestModule_10TestModuley_y(_ boxPtr: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let closure = Unmanaged<_BridgeJSTypedClosureBox<() -> Void>>.fromOpaque(boxPtr).takeUnretainedValue().closure + closure() + #else + fatalError("Only available on WebAssembly") + #endif +} + extension MyViewControllerDelegate where Self: _BridgedSwiftProtocolWrapper { func onSomethingHappened() -> Void { let jsObjectValue = jsObject.bridgeJSLowerParameter() @@ -75,6 +322,39 @@ extension MyViewControllerDelegate where Self: _BridgedSwiftProtocolWrapper { return Result.bridgeJSLiftReturn(ret) } + func validate(_ value: Int) throws(JSException) -> Int { + let valueValue = value.bridgeJSLowerParameter() + let jsObjectValue = jsObject.bridgeJSLowerParameter() + let ret = bjs_MyViewControllerDelegate_validate(jsObjectValue, valueValue) + if let error = _swift_js_take_exception() { throw error } + return Int.bridgeJSLiftReturn(ret) + } + + func loadLabel(_ key: String) async throws(JSException) -> String { + let resolved = try await _bjs_awaitPromise(makeResolveClosure: { JSTypedClosure<(sending String) -> Void>.sending($0) }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in + key.bridgeJSWithLoweredParameter { (keyWord0, keyWord1, keyWord2) in + let jsObjectValue = jsObject.bridgeJSLowerParameter() + bjs_MyViewControllerDelegate_loadLabel(resolveRef, rejectRef, jsObjectValue, keyWord0, keyWord1, keyWord2) + } + } + return resolved + } + + func reset() async throws(JSException) -> Void { + try await _bjs_awaitPromise(makeResolveClosure: { JSTypedClosure<() -> Void>($0) }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in + let jsObjectValue = jsObject.bridgeJSLowerParameter() + bjs_MyViewControllerDelegate_reset(resolveRef, rejectRef, jsObjectValue) + } + } + + func fetchCount() async -> Int { + let resolved = try! await _bjs_awaitPromise(makeResolveClosure: { JSTypedClosure<(sending Int) -> Void>.sending($0) }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in + let jsObjectValue = jsObject.bridgeJSLowerParameter() + bjs_MyViewControllerDelegate_fetchCount(resolveRef, rejectRef, jsObjectValue) + } + return resolved + } + var eventCount: Int { get { let jsObjectValue = jsObject.bridgeJSLowerParameter() @@ -369,6 +649,54 @@ fileprivate func bjs_MyViewControllerDelegate_getResult_extern(_ jsObject: Int32 return bjs_MyViewControllerDelegate_getResult_extern(jsObject) } +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_MyViewControllerDelegate_validate") +fileprivate func bjs_MyViewControllerDelegate_validate_extern(_ jsObject: Int32, _ value: Int32) -> Int32 +#else +fileprivate func bjs_MyViewControllerDelegate_validate_extern(_ jsObject: Int32, _ value: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_MyViewControllerDelegate_validate(_ jsObject: Int32, _ value: Int32) -> Int32 { + return bjs_MyViewControllerDelegate_validate_extern(jsObject, value) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_MyViewControllerDelegate_loadLabel") +fileprivate func bjs_MyViewControllerDelegate_loadLabel_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32, _ keyWord0: Int32, _ keyWord1: Int32, _ keyWord2: Int32) -> Void +#else +fileprivate func bjs_MyViewControllerDelegate_loadLabel_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32, _ keyWord0: Int32, _ keyWord1: Int32, _ keyWord2: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_MyViewControllerDelegate_loadLabel(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32, _ keyWord0: Int32, _ keyWord1: Int32, _ keyWord2: Int32) -> Void { + return bjs_MyViewControllerDelegate_loadLabel_extern(resolveRef, rejectRef, jsObject, keyWord0, keyWord1, keyWord2) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_MyViewControllerDelegate_reset") +fileprivate func bjs_MyViewControllerDelegate_reset_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void +#else +fileprivate func bjs_MyViewControllerDelegate_reset_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_MyViewControllerDelegate_reset(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void { + return bjs_MyViewControllerDelegate_reset_extern(resolveRef, rejectRef, jsObject) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_MyViewControllerDelegate_fetchCount") +fileprivate func bjs_MyViewControllerDelegate_fetchCount_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void +#else +fileprivate func bjs_MyViewControllerDelegate_fetchCount_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_MyViewControllerDelegate_fetchCount(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void { + return bjs_MyViewControllerDelegate_fetchCount_extern(resolveRef, rejectRef, jsObject) +} + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_MyViewControllerDelegate_eventCount_get") fileprivate func bjs_MyViewControllerDelegate_eventCount_get_extern(_ jsObject: Int32) -> Int32 @@ -888,6 +1216,47 @@ public func _bjs_MyViewController_sendHelper(_ _self: UnsafeMutableRawPointer, _ #endif } +@_expose(wasm, "bjs_MyViewController_validateValue") +@_cdecl("bjs_MyViewController_validateValue") +public func _bjs_MyViewController_validateValue(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Int32 { + #if arch(wasm32) + let value = Int.bridgeJSLiftParameter(value) + let _self = MyViewController.bridgeJSLiftParameter(_self) + do { + let ret = try _self.validateValue(_: value) + return ret.bridgeJSLowerReturn() + } catch let error { + if let error = error.thrownValue.object { + withExtendedLifetime(error) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } else { + let jsError = JSError(message: error.description) + withExtendedLifetime(jsError.jsObject) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } + return 0 + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MyViewController_loadLabel") +@_cdecl("bjs_MyViewController_loadLabel") +public func _bjs_MyViewController_loadLabel(_ _self: UnsafeMutableRawPointer, _ keyBytes: Int32, _ keyLength: Int32) -> Int32 { + #if arch(wasm32) + let key = String.bridgeJSLiftParameter(keyBytes, keyLength) + let _self = MyViewController.bridgeJSLiftParameter(_self) + return _bjs_makePromise(resolve: Promise_resolve_SS, reject: Promise_reject) { () async throws(JSException) -> String in + return try await _self.loadLabel(_: key) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_MyViewController_delegate_get") @_cdecl("bjs_MyViewController_delegate_get") public func _bjs_MyViewController_delegate_get(_ _self: UnsafeMutableRawPointer) -> Int32 { @@ -1096,6 +1465,49 @@ extension Priority: BridgedSwiftGenericBridgeable { @_spi(BridgeJS) public static let bridgeJSTypeHandle = Priority.bridgeJSMakeTypeHandle() } +@JSFunction func Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException) + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "promise_reject_TestModule") +fileprivate func promise_reject_TestModule_extern(_ promise: Int32, _ valueKind: Int32, _ valuePayload1: Int32, _ valuePayload2: Float64) -> Void +#else +fileprivate func promise_reject_TestModule_extern(_ promise: Int32, _ valueKind: Int32, _ valuePayload1: Int32, _ valuePayload2: Float64) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func promise_reject_TestModule(_ promise: Int32, _ valueKind: Int32, _ valuePayload1: Int32, _ valuePayload2: Float64) -> Void { + return promise_reject_TestModule_extern(promise, valueKind, valuePayload1, valuePayload2) +} + +func _$Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException) -> Void { + let (valueKind, valuePayload1, valuePayload2) = value.bridgeJSLowerParameter() + let promiseValue = promise.bridgeJSLowerParameter() + promise_reject_TestModule(promiseValue, valueKind, valuePayload1, valuePayload2) + if let error = _swift_js_take_exception() { throw error } +} + +@JSFunction func Promise_resolve_SS(_ promise: JSObject, _ value: String) throws(JSException) + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "promise_resolve_TestModule_SS") +fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void +#else +fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func promise_resolve_TestModule_SS(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return promise_resolve_TestModule_SS_extern(promise, valueWord0, valueWord1, valueWord2) +} + +func _$Promise_resolve_SS(_ promise: JSObject, _ value: String) throws(JSException) -> Void { + value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in + let promiseValue = promise.bridgeJSLowerParameter() + promise_resolve_TestModule_SS(promiseValue, valueWord0, valueWord1, valueWord2) + } + if let error = _swift_js_take_exception() { throw error } +} + #if arch(wasm32) @_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js index 5b2a085ac..856c42435 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js @@ -474,33 +474,57 @@ export async function createInstantiator(options, swift) { TestModule["bjs_asyncReturnVoid"] = function bjs_asyncReturnVoid(resolveRef, rejectRef) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - imports.asyncReturnVoid().then(resolve, reject); + try { + Promise.resolve(imports.asyncReturnVoid()).then(resolve, reject); + } catch (error) { + reject(error); + } } TestModule["bjs_asyncRoundTripInt"] = function bjs_asyncRoundTripInt(resolveRef, rejectRef, v) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - imports.asyncRoundTripInt(v).then(resolve, reject); + try { + Promise.resolve(imports.asyncRoundTripInt(v)).then(resolve, reject); + } catch (error) { + reject(error); + } } TestModule["bjs_asyncRoundTripString"] = function bjs_asyncRoundTripString(resolveRef, rejectRef, vWord0, vWord1, vWord2) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - const string = decodeString(vWord0, vWord1, vWord2); - imports.asyncRoundTripString(string).then(resolve, reject); + try { + const string = decodeString(vWord0, vWord1, vWord2); + Promise.resolve(imports.asyncRoundTripString(string)).then(resolve, reject); + } catch (error) { + reject(error); + } } TestModule["bjs_asyncRoundTripBool"] = function bjs_asyncRoundTripBool(resolveRef, rejectRef, v) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - imports.asyncRoundTripBool(v !== 0).then(resolve, reject); + try { + Promise.resolve(imports.asyncRoundTripBool(v !== 0)).then(resolve, reject); + } catch (error) { + reject(error); + } } TestModule["bjs_asyncRoundTripDouble"] = function bjs_asyncRoundTripDouble(resolveRef, rejectRef, v) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - imports.asyncRoundTripDouble(v).then(resolve, reject); + try { + Promise.resolve(imports.asyncRoundTripDouble(v)).then(resolve, reject); + } catch (error) { + reject(error); + } } TestModule["bjs_asyncRoundTripJSObject"] = function bjs_asyncRoundTripJSObject(resolveRef, rejectRef, v) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - imports.asyncRoundTripJSObject(swift.memory.getObject(v)).then(resolve, reject); + try { + Promise.resolve(imports.asyncRoundTripJSObject(swift.memory.getObject(v))).then(resolve, reject); + } catch (error) { + reject(error); + } } }, setInstance: (i) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js index 7a265486b..36d839ed9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js @@ -391,12 +391,20 @@ export async function createInstantiator(options, swift) { TestModule["bjs_AsyncBox_asyncStaticRoundTrip_static"] = function bjs_AsyncBox_asyncStaticRoundTrip_static(resolveRef, rejectRef, v) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - imports.AsyncBox.asyncStaticRoundTrip(v).then(resolve, reject); + try { + Promise.resolve(imports.AsyncBox.asyncStaticRoundTrip(v)).then(resolve, reject); + } catch (error) { + reject(error); + } } TestModule["bjs_AsyncBox_asyncStaticVoid_static"] = function bjs_AsyncBox_asyncStaticVoid_static(resolveRef, rejectRef) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - imports.AsyncBox.asyncStaticVoid().then(resolve, reject); + try { + Promise.resolve(imports.AsyncBox.asyncStaticVoid()).then(resolve, reject); + } catch (error) { + reject(error); + } } }, setInstance: (i) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts index a413fa500..e9b099f70 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts @@ -17,6 +17,10 @@ export interface MyViewControllerDelegate { createEnum(): ExampleEnumTag; handleResult(result: ResultTag): void; getResult(): ResultTag; + validate(value: number): number; + loadLabel(key: string): Promise; + reset(): Promise; + fetchCount(): Promise; eventCount: number; readonly delegateName: string; optionalName: string | null; @@ -87,6 +91,8 @@ export interface MyViewController extends SwiftHeapObject { updateLabel(prefix: string, suffix: string): void; checkEvenCount(): boolean; sendHelper(helper: Helper): void; + validateValue(value: number): number; + loadLabel(key: string): Promise; delegate: MyViewControllerDelegate; secondDelegate: MyViewControllerDelegate | null; } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js index 96f132fda..f07007c79 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js @@ -380,6 +380,31 @@ export async function createInstantiator(options, swift) { return jsValue; } + const swiftClosureRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => { + if (state.unregistered) { return; } + instance?.exports?.bjs_release_swift_closure(state.pointer); + }); + const makeClosure = (pointer, file, line, func) => { + const state = { pointer, file, line, unregistered: false }; + const real = (...args) => { + if (state.unregistered) { + const bytes = new Uint8Array(memory.buffer, state.file >>> 0); + let length = 0; + while (bytes[length] !== 0) { length += 1; } + const fileID = decodeUTF8(state.file, length); + throw new Error(`Attempted to call a released JSTypedClosure created at ${fileID}:${state.line}`); + } + return func(...args); + }; + real.__unregister = () => { + if (state.unregistered) { return; } + state.unregistered = true; + swiftClosureRegistry.unregister(state); + }; + swiftClosureRegistry.register(real, state, state); + return swift.memory.retain(real); + }; + const __bjs_codec_M10TestModuleT24MyViewControllerDelegate = { lower: (v) => { const objId = swift.memory.retain(v); @@ -512,6 +537,22 @@ export async function createInstantiator(options, swift) { promise[__bjs_promiseSettlers] = { resolve, reject }; return swift.memory.retain(promise); } + bjs["promise_resolve_TestModule_SS"] = function(promise, valueWord0, valueWord1, valueWord2) { + try { + const string = decodeString(valueWord0, valueWord1, valueWord2); + swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(string); + } catch (error) { + setException(error); + } + } + bjs["promise_reject_TestModule"] = function(promise, valueKind, valuePayload1, valuePayload2) { + try { + const jsValue = __bjs_jsValueLift(valueKind, valuePayload1, valuePayload2); + swift.memory.getObject(promise)[__bjs_promiseSettlers].reject(jsValue); + } catch (error) { + setException(error); + } + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; @@ -602,6 +643,95 @@ export async function createInstantiator(options, swift) { return pointer || 0; } bjs["swift_js_closure_unregister"] = function(funcRef) {} + bjs["swift_js_closure_unregister"] = function(funcRef) { + const func = swift.memory.getObject(funcRef); + func.__unregister(); + } + bjs["invoke_js_callback_TestModule_10TestModules7JSValueV_y"] = function(callbackId, param0Kind, param0Payload1, param0Payload2) { + try { + const callback = swift.memory.getObject(callbackId); + const jsValue = __bjs_jsValueLift(param0Kind, param0Payload1, param0Payload2); + callback(jsValue); + } catch (error) { + setException(error); + } + } + bjs["make_swift_closure_TestModule_10TestModules7JSValueV_y"] = function(boxPtr, file, line) { + const lower_closure_TestModule_10TestModules7JSValueV_y = function(param0) { + const [param0Kind, param0Payload1, param0Payload2] = __bjs_jsValueLower(param0); + instance.exports.invoke_swift_closure_TestModule_10TestModules7JSValueV_y(boxPtr, param0Kind, param0Payload1, param0Payload2); + if (tmpRetException) { + const error = swift.memory.getObject(tmpRetException); + swift.memory.release(tmpRetException); + tmpRetException = undefined; + throw error; + } + }; + return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModules7JSValueV_y); + } + bjs["invoke_js_callback_TestModule_10TestModulesSS_y"] = function(callbackId, param0Word0, param0Word1, param0Word2) { + try { + const callback = swift.memory.getObject(callbackId); + const string = decodeString(param0Word0, param0Word1, param0Word2); + callback(string); + } catch (error) { + setException(error); + } + } + bjs["make_swift_closure_TestModule_10TestModulesSS_y"] = function(boxPtr, file, line) { + const lower_closure_TestModule_10TestModulesSS_y = function(param0) { + const param0Bytes = textEncoder.encode(param0); + const param0Id = swift.memory.retain(param0Bytes); + instance.exports.invoke_swift_closure_TestModule_10TestModulesSS_y(boxPtr, param0Id, param0Bytes.length); + if (tmpRetException) { + const error = swift.memory.getObject(tmpRetException); + swift.memory.release(tmpRetException); + tmpRetException = undefined; + throw error; + } + }; + return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModulesSS_y); + } + bjs["invoke_js_callback_TestModule_10TestModulesSi_y"] = function(callbackId, param0) { + try { + const callback = swift.memory.getObject(callbackId); + callback(param0); + } catch (error) { + setException(error); + } + } + bjs["make_swift_closure_TestModule_10TestModulesSi_y"] = function(boxPtr, file, line) { + const lower_closure_TestModule_10TestModulesSi_y = function(param0) { + instance.exports.invoke_swift_closure_TestModule_10TestModulesSi_y(boxPtr, param0); + if (tmpRetException) { + const error = swift.memory.getObject(tmpRetException); + swift.memory.release(tmpRetException); + tmpRetException = undefined; + throw error; + } + }; + return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModulesSi_y); + } + bjs["invoke_js_callback_TestModule_10TestModuley_y"] = function(callbackId) { + try { + const callback = swift.memory.getObject(callbackId); + callback(); + } catch (error) { + setException(error); + } + } + bjs["make_swift_closure_TestModule_10TestModuley_y"] = function(boxPtr, file, line) { + const lower_closure_TestModule_10TestModuley_y = function() { + instance.exports.invoke_swift_closure_TestModule_10TestModuley_y(boxPtr); + if (tmpRetException) { + const error = swift.memory.getObject(tmpRetException); + swift.memory.release(tmpRetException); + tmpRetException = undefined; + throw error; + } + }; + return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuley_y); + } // Wrapper functions for module: TestModule if (!importObject["TestModule"]) { importObject["TestModule"] = {}; @@ -913,6 +1043,43 @@ export async function createInstantiator(options, swift) { setException(error); } } + TestModule["bjs_MyViewControllerDelegate_validate"] = function bjs_MyViewControllerDelegate_validate(self, value) { + try { + let ret = swift.memory.getObject(self).validate(value); + return ret; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_MyViewControllerDelegate_loadLabel"] = function bjs_MyViewControllerDelegate_loadLabel(resolveRef, rejectRef, self, keyWord0, keyWord1, keyWord2) { + const resolve = swift.memory.getObject(resolveRef); + const reject = swift.memory.getObject(rejectRef); + try { + const string = decodeString(keyWord0, keyWord1, keyWord2); + Promise.resolve(swift.memory.getObject(self).loadLabel(string)).then(resolve, reject); + } catch (error) { + reject(error); + } + } + TestModule["bjs_MyViewControllerDelegate_reset"] = function bjs_MyViewControllerDelegate_reset(resolveRef, rejectRef, self) { + const resolve = swift.memory.getObject(resolveRef); + const reject = swift.memory.getObject(rejectRef); + try { + Promise.resolve(swift.memory.getObject(self).reset()).then(resolve, reject); + } catch (error) { + reject(error); + } + } + TestModule["bjs_MyViewControllerDelegate_fetchCount"] = function bjs_MyViewControllerDelegate_fetchCount(resolveRef, rejectRef, self) { + const resolve = swift.memory.getObject(resolveRef); + const reject = swift.memory.getObject(rejectRef); + try { + Promise.resolve(swift.memory.getObject(self).fetchCount()).then(resolve, reject); + } catch (error) { + reject(error); + } + } }, setInstance: (i) => { if (memory !== i.exports.memory) immortalStrings.clear(); @@ -1182,6 +1349,30 @@ export async function createInstantiator(options, swift) { sendHelper(helper) { instance.exports.bjs_MyViewController_sendHelper(this.pointer, helper.pointer); } + validateValue(value) { + const ret = instance.exports.bjs_MyViewController_validateValue(this.pointer, value); + if (tmpRetException) { + const error = swift.memory.getObject(tmpRetException); + swift.memory.release(tmpRetException); + tmpRetException = undefined; + throw error; + } + return ret; + } + loadLabel(key) { + const keyBytes = textEncoder.encode(key); + const keyId = swift.memory.retain(keyBytes); + const ret = instance.exports.bjs_MyViewController_loadLabel(this.pointer, keyId, keyBytes.length); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + if (tmpRetException) { + const error = swift.memory.getObject(tmpRetException); + swift.memory.release(tmpRetException); + tmpRetException = undefined; + throw error; + } + return ret1; + } get delegate() { const ret = instance.exports.bjs_MyViewController_delegate_get(this.pointer); const ret1 = swift.memory.getObject(ret); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js index 928cc44a0..f56929d7c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js @@ -1380,9 +1380,13 @@ export async function createInstantiator(options, swift) { bjs["invoke_js_callback_TestModule_10TestModuleYaKSS_SS"] = function(resolveRef, rejectRef, callbackId, param0Word0, param0Word1, param0Word2) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Word0, param0Word1, param0Word2); - callback(string).then(resolve, reject); + try { + const callback = swift.memory.getObject(callbackId); + const string = decodeString(param0Word0, param0Word1, param0Word2); + Promise.resolve(callback(string)).then(resolve, reject); + } catch (error) { + reject(error); + } } bjs["make_swift_closure_TestModule_10TestModuleYaKSS_SS"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleYaKSS_SS = function(param0) { @@ -1404,8 +1408,12 @@ export async function createInstantiator(options, swift) { bjs["invoke_js_callback_TestModule_10TestModuleYaKSb_9APIResultO"] = function(resolveRef, rejectRef, callbackId, param0) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - const callback = swift.memory.getObject(callbackId); - callback(param0 !== 0).then(resolve, reject); + try { + const callback = swift.memory.getObject(callbackId); + Promise.resolve(callback(param0 !== 0)).then(resolve, reject); + } catch (error) { + reject(error); + } } bjs["make_swift_closure_TestModule_10TestModuleYaKSb_9APIResultO"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleYaKSb_9APIResultO = function(param0) { @@ -1425,9 +1433,13 @@ export async function createInstantiator(options, swift) { bjs["invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV"] = function(resolveRef, rejectRef, callbackId, param0Word0, param0Word1, param0Word2) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Word0, param0Word1, param0Word2); - callback(string).then(resolve, reject); + try { + const callback = swift.memory.getObject(callbackId); + const string = decodeString(param0Word0, param0Word1, param0Word2); + Promise.resolve(callback(string)).then(resolve, reject); + } catch (error) { + reject(error); + } } bjs["make_swift_closure_TestModule_10TestModuleYaSS_6AnimalV"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleYaSS_6AnimalV = function(param0) { @@ -1443,9 +1455,13 @@ export async function createInstantiator(options, swift) { bjs["invoke_js_callback_TestModule_10TestModuleYaSS_SS"] = function(resolveRef, rejectRef, callbackId, param0Word0, param0Word1, param0Word2) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Word0, param0Word1, param0Word2); - callback(string).then(resolve, reject); + try { + const callback = swift.memory.getObject(callbackId); + const string = decodeString(param0Word0, param0Word1, param0Word2); + Promise.resolve(callback(string)).then(resolve, reject); + } catch (error) { + reject(error); + } } bjs["make_swift_closure_TestModule_10TestModuleYaSS_SS"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleYaSS_SS = function(param0) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js index c47a65b6b..3ba6f5c0f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js @@ -393,9 +393,13 @@ export async function createInstantiator(options, swift) { bjs["invoke_js_callback_TestModule_10TestModuleYaKSS_SS"] = function(resolveRef, rejectRef, callbackId, param0Word0, param0Word1, param0Word2) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Word0, param0Word1, param0Word2); - callback(string).then(resolve, reject); + try { + const callback = swift.memory.getObject(callbackId); + const string = decodeString(param0Word0, param0Word1, param0Word2); + Promise.resolve(callback(string)).then(resolve, reject); + } catch (error) { + reject(error); + } } bjs["make_swift_closure_TestModule_10TestModuleYaKSS_SS"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleYaKSS_SS = function(param0) { diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Protocols.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Protocols.md index 4c91ba749..5bc907137 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Protocols.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Protocols.md @@ -217,6 +217,8 @@ struct AnyCounter: Counter, _BridgedSwiftProtocolWrapper { | Swift Feature | Status | |:--------------|:-------| | Method requirements: `func foo(_ param: String?) -> FooClass?` | ✅ | +| Throwing method requirements: `func foo() throws(JSException)` | ✅ | +| Async method requirements: `func foo() async throws(JSException) -> Bar` | ✅ | | Property requirements: `var property: Type { get }` / `var property: Type { get set }` | ✅ | | Optional parameters / return values in methods | ✅ | | Optional properties | ✅ | diff --git a/Tests/BridgeJSRuntimeTests/AsyncImportTests.swift b/Tests/BridgeJSRuntimeTests/AsyncImportTests.swift index a092d111c..17184c427 100644 --- a/Tests/BridgeJSRuntimeTests/AsyncImportTests.swift +++ b/Tests/BridgeJSRuntimeTests/AsyncImportTests.swift @@ -24,6 +24,8 @@ import JavaScriptKit @JSFunction static func jsAsyncRoundTripOptionalAssociatedValueEnum( _ v: AsyncImportedPayloadResult? ) async throws(JSException) -> AsyncImportedPayloadResult? + @JSFunction static func jsAsyncThrowSynchronously(_ message: String) async throws(JSException) -> Double + @JSFunction static func jsAsyncReturnNonPromise(_ v: Double) async throws(JSException) -> Double } @Suite struct AsyncImportTests { @@ -104,6 +106,19 @@ import JavaScriptKit } } + // MARK: - Non-Promise behavior + + @Test func asyncSynchronousThrowRejects() async throws { + let error = await #expect(throws: JSException.self) { + try await AsyncImportImports.jsAsyncThrowSynchronously("SyncFailure") + } + #expect(error?.thrownValue.object?["message"].string == "SyncFailure") + } + + @Test func asyncNonPromiseReturnResolves() async throws { + try #expect(await AsyncImportImports.jsAsyncReturnNonPromise(42) == 42) + } + // MARK: - Structured return type @Test func fetchWeatherData() async throws { diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 8778c2c8e..c09b84c95 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -961,6 +961,10 @@ enum GraphOperations { func createOptionalGreeter() -> Greeter? func handleAPIResult(_ result: APIResult?) func getAPIResult() -> APIResult? + func validate(_ value: Int) throws(JSException) -> Int + func loadLabel(_ key: String) async throws(JSException) -> String + func reset() async throws(JSException) + func fetchCount() async -> Int } @JS class DataProcessorManager { @@ -1053,6 +1057,22 @@ enum GraphOperations { @JS func setProcessorAPIResult(_ apiResult: APIResult?) { processor.handleAPIResult(apiResult) } + + @JS func validateProcessorValue(_ value: Int) throws(JSException) -> Int { + return try processor.validate(value) + } + + @JS func loadProcessorLabel(_ key: String) async throws(JSException) -> String { + return try await processor.loadLabel(key) + } + + @JS func resetProcessor() async throws(JSException) { + try await processor.reset() + } + + @JS func fetchProcessorCount() async -> Int { + return await processor.fetchCount() + } } @JS class SwiftDataProcessor: DataProcessor { @@ -1117,6 +1137,25 @@ enum GraphOperations { @JS func getAPIResult() -> APIResult? { return apiResult } + + @JS func validate(_ value: Int) throws(JSException) -> Int { + guard value >= 0 else { + throw JSException(JSError(message: "SwiftDataProcessor rejects negative values").jsValue) + } + return value + } + + @JS func loadLabel(_ key: String) async throws(JSException) -> String { + return "\(label):\(key)" + } + + @JS func reset() async throws(JSException) { + count = 0 + } + + @JS func fetchCount() async -> Int { + return count + } } @JS class ProtocolReturnTests { diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index 52799fa7c..b89bdb510 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -4366,6 +4366,39 @@ extension DataProcessor where Self: _BridgedSwiftProtocolWrapper { return Optional.bridgeJSLiftReturn(ret) } + func validate(_ value: Int) throws(JSException) -> Int { + let valueValue = value.bridgeJSLowerParameter() + let jsObjectValue = jsObject.bridgeJSLowerParameter() + let ret = bjs_DataProcessor_validate(jsObjectValue, valueValue) + if let error = _swift_js_take_exception() { throw error } + return Int.bridgeJSLiftReturn(ret) + } + + func loadLabel(_ key: String) async throws(JSException) -> String { + let resolved = try await _bjs_awaitPromise(makeResolveClosure: { JSTypedClosure<(sending String) -> Void>.sending($0) }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in + key.bridgeJSWithLoweredParameter { (keyWord0, keyWord1, keyWord2) in + let jsObjectValue = jsObject.bridgeJSLowerParameter() + bjs_DataProcessor_loadLabel(resolveRef, rejectRef, jsObjectValue, keyWord0, keyWord1, keyWord2) + } + } + return resolved + } + + func reset() async throws(JSException) -> Void { + try await _bjs_awaitPromise(makeResolveClosure: { JSTypedClosure<() -> Void>($0) }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in + let jsObjectValue = jsObject.bridgeJSLowerParameter() + bjs_DataProcessor_reset(resolveRef, rejectRef, jsObjectValue) + } + } + + func fetchCount() async -> Int { + let resolved = try! await _bjs_awaitPromise(makeResolveClosure: { JSTypedClosure<(sending Int) -> Void>.sending($0) }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in + let jsObjectValue = jsObject.bridgeJSLowerParameter() + bjs_DataProcessor_fetchCount(resolveRef, rejectRef, jsObjectValue) + } + return resolved + } + var count: Int { get { let jsObjectValue = jsObject.bridgeJSLowerParameter() @@ -4634,6 +4667,54 @@ fileprivate func bjs_DataProcessor_getAPIResult_extern(_ jsObject: Int32) -> Int return bjs_DataProcessor_getAPIResult_extern(jsObject) } +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_DataProcessor_validate") +fileprivate func bjs_DataProcessor_validate_extern(_ jsObject: Int32, _ value: Int32) -> Int32 +#else +fileprivate func bjs_DataProcessor_validate_extern(_ jsObject: Int32, _ value: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_DataProcessor_validate(_ jsObject: Int32, _ value: Int32) -> Int32 { + return bjs_DataProcessor_validate_extern(jsObject, value) +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_DataProcessor_loadLabel") +fileprivate func bjs_DataProcessor_loadLabel_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32, _ keyWord0: Int32, _ keyWord1: Int32, _ keyWord2: Int32) -> Void +#else +fileprivate func bjs_DataProcessor_loadLabel_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32, _ keyWord0: Int32, _ keyWord1: Int32, _ keyWord2: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_DataProcessor_loadLabel(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32, _ keyWord0: Int32, _ keyWord1: Int32, _ keyWord2: Int32) -> Void { + return bjs_DataProcessor_loadLabel_extern(resolveRef, rejectRef, jsObject, keyWord0, keyWord1, keyWord2) +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_DataProcessor_reset") +fileprivate func bjs_DataProcessor_reset_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void +#else +fileprivate func bjs_DataProcessor_reset_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_DataProcessor_reset(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void { + return bjs_DataProcessor_reset_extern(resolveRef, rejectRef, jsObject) +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_DataProcessor_fetchCount") +fileprivate func bjs_DataProcessor_fetchCount_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void +#else +fileprivate func bjs_DataProcessor_fetchCount_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_DataProcessor_fetchCount(_ resolveRef: Int32, _ rejectRef: Int32, _ jsObject: Int32) -> Void { + return bjs_DataProcessor_fetchCount_extern(resolveRef, rejectRef, jsObject) +} + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_DataProcessor_count_get") fileprivate func bjs_DataProcessor_count_get_extern(_ jsObject: Int32) -> Int32 @@ -14415,6 +14496,73 @@ public func _bjs_DataProcessorManager_setProcessorAPIResult(_ _self: UnsafeMutab #endif } +@_expose(wasm, "bjs_DataProcessorManager_validateProcessorValue") +@_cdecl("bjs_DataProcessorManager_validateProcessorValue") +public func _bjs_DataProcessorManager_validateProcessorValue(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Int32 { + #if arch(wasm32) + let value = Int.bridgeJSLiftParameter(value) + let _self = DataProcessorManager.bridgeJSLiftParameter(_self) + do { + let ret = try _self.validateProcessorValue(_: value) + return ret.bridgeJSLowerReturn() + } catch let error { + if let error = error.thrownValue.object { + withExtendedLifetime(error) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } else { + let jsError = JSError(message: error.description) + withExtendedLifetime(jsError.jsObject) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } + return 0 + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_DataProcessorManager_loadProcessorLabel") +@_cdecl("bjs_DataProcessorManager_loadProcessorLabel") +public func _bjs_DataProcessorManager_loadProcessorLabel(_ _self: UnsafeMutableRawPointer, _ keyBytes: Int32, _ keyLength: Int32) -> Int32 { + #if arch(wasm32) + let key = String.bridgeJSLiftParameter(keyBytes, keyLength) + let _self = DataProcessorManager.bridgeJSLiftParameter(_self) + return _bjs_makePromise(resolve: Promise_resolve_SS, reject: Promise_reject) { () async throws(JSException) -> String in + return try await _self.loadProcessorLabel(_: key) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_DataProcessorManager_resetProcessor") +@_cdecl("bjs_DataProcessorManager_resetProcessor") +public func _bjs_DataProcessorManager_resetProcessor(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let _self = DataProcessorManager.bridgeJSLiftParameter(_self) + return _bjs_makePromise(resolve: Promise_resolve_y, reject: Promise_reject) { () async throws(JSException) -> Void in + try await _self.resetProcessor() + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_DataProcessorManager_fetchProcessorCount") +@_cdecl("bjs_DataProcessorManager_fetchProcessorCount") +public func _bjs_DataProcessorManager_fetchProcessorCount(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let _self = DataProcessorManager.bridgeJSLiftParameter(_self) + return _bjs_makePromise(resolve: Promise_resolve_Si, reject: Promise_reject) { + return await _self.fetchProcessorCount() + } + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_DataProcessorManager_processor_get") @_cdecl("bjs_DataProcessorManager_processor_get") public func _bjs_DataProcessorManager_processor_get(_ _self: UnsafeMutableRawPointer) -> Int32 { @@ -14644,6 +14792,73 @@ public func _bjs_SwiftDataProcessor_getAPIResult(_ _self: UnsafeMutableRawPointe #endif } +@_expose(wasm, "bjs_SwiftDataProcessor_validate") +@_cdecl("bjs_SwiftDataProcessor_validate") +public func _bjs_SwiftDataProcessor_validate(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Int32 { + #if arch(wasm32) + let value = Int.bridgeJSLiftParameter(value) + let _self = SwiftDataProcessor.bridgeJSLiftParameter(_self) + do { + let ret = try _self.validate(_: value) + return ret.bridgeJSLowerReturn() + } catch let error { + if let error = error.thrownValue.object { + withExtendedLifetime(error) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } else { + let jsError = JSError(message: error.description) + withExtendedLifetime(jsError.jsObject) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } + return 0 + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftDataProcessor_loadLabel") +@_cdecl("bjs_SwiftDataProcessor_loadLabel") +public func _bjs_SwiftDataProcessor_loadLabel(_ _self: UnsafeMutableRawPointer, _ keyBytes: Int32, _ keyLength: Int32) -> Int32 { + #if arch(wasm32) + let key = String.bridgeJSLiftParameter(keyBytes, keyLength) + let _self = SwiftDataProcessor.bridgeJSLiftParameter(_self) + return _bjs_makePromise(resolve: Promise_resolve_SS, reject: Promise_reject) { () async throws(JSException) -> String in + return try await _self.loadLabel(_: key) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftDataProcessor_reset") +@_cdecl("bjs_SwiftDataProcessor_reset") +public func _bjs_SwiftDataProcessor_reset(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let _self = SwiftDataProcessor.bridgeJSLiftParameter(_self) + return _bjs_makePromise(resolve: Promise_resolve_y, reject: Promise_reject) { () async throws(JSException) -> Void in + try await _self.reset() + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SwiftDataProcessor_fetchCount") +@_cdecl("bjs_SwiftDataProcessor_fetchCount") +public func _bjs_SwiftDataProcessor_fetchCount(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let _self = SwiftDataProcessor.bridgeJSLiftParameter(_self) + return _bjs_makePromise(resolve: Promise_resolve_Si, reject: Promise_reject) { + return await _self.fetchCount() + } + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_SwiftDataProcessor_count_get") @_cdecl("bjs_SwiftDataProcessor_count_get") public func _bjs_SwiftDataProcessor_count_get(_ _self: UnsafeMutableRawPointer) -> Int32 { @@ -17729,6 +17944,30 @@ fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripOptionalAssociatedValueE return bjs_AsyncImportImports_jsAsyncRoundTripOptionalAssociatedValueEnum_static_extern(resolveRef, rejectRef, vIsSome, vCaseId) } +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_AsyncImportImports_jsAsyncThrowSynchronously_static") +fileprivate func bjs_AsyncImportImports_jsAsyncThrowSynchronously_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ messageWord0: Int32, _ messageWord1: Int32, _ messageWord2: Int32) -> Void +#else +fileprivate func bjs_AsyncImportImports_jsAsyncThrowSynchronously_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ messageWord0: Int32, _ messageWord1: Int32, _ messageWord2: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_AsyncImportImports_jsAsyncThrowSynchronously_static(_ resolveRef: Int32, _ rejectRef: Int32, _ messageWord0: Int32, _ messageWord1: Int32, _ messageWord2: Int32) -> Void { + return bjs_AsyncImportImports_jsAsyncThrowSynchronously_static_extern(resolveRef, rejectRef, messageWord0, messageWord1, messageWord2) +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_AsyncImportImports_jsAsyncReturnNonPromise_static") +fileprivate func bjs_AsyncImportImports_jsAsyncReturnNonPromise_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ v: Float64) -> Void +#else +fileprivate func bjs_AsyncImportImports_jsAsyncReturnNonPromise_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ v: Float64) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_AsyncImportImports_jsAsyncReturnNonPromise_static(_ resolveRef: Int32, _ rejectRef: Int32, _ v: Float64) -> Void { + return bjs_AsyncImportImports_jsAsyncReturnNonPromise_static_extern(resolveRef, rejectRef, v) +} + func _$AsyncImportImports_jsAsyncRoundTripVoid() async throws(JSException) -> Void { try await _bjs_awaitPromise(makeResolveClosure: { JSTypedClosure<() -> Void>($0) @@ -17874,6 +18113,31 @@ func _$AsyncImportImports_jsAsyncRoundTripOptionalAssociatedValueEnum(_ v: Optio return resolved } +func _$AsyncImportImports_jsAsyncThrowSynchronously(_ message: String) async throws(JSException) -> Double { + let resolved = try await _bjs_awaitPromise(makeResolveClosure: { + JSTypedClosure<(sending Double) -> Void>.sending($0) + }, makeRejectClosure: { + JSTypedClosure<(sending JSValue) -> Void>.sending($0) + }) { resolveRef, rejectRef in + message.bridgeJSWithLoweredParameter { (messageWord0, messageWord1, messageWord2) in + bjs_AsyncImportImports_jsAsyncThrowSynchronously_static(resolveRef, rejectRef, messageWord0, messageWord1, messageWord2) + } + } + return resolved +} + +func _$AsyncImportImports_jsAsyncReturnNonPromise(_ v: Double) async throws(JSException) -> Double { + let resolved = try await _bjs_awaitPromise(makeResolveClosure: { + JSTypedClosure<(sending Double) -> Void>.sending($0) + }, makeRejectClosure: { + JSTypedClosure<(sending JSValue) -> Void>.sending($0) + }) { resolveRef, rejectRef in + let vValue = v.bridgeJSLowerParameter() + bjs_AsyncImportImports_jsAsyncReturnNonPromise_static(resolveRef, rejectRef, vValue) + } + return resolved +} + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ClosureAsyncImports_runJsClosureAsyncTests_static") fileprivate func bjs_ClosureAsyncImports_runJsClosureAsyncTests_static_extern(_ resolveRef: Int32, _ rejectRef: Int32) -> Void diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index d649d4e76..9d5da0a7f 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -3019,6 +3019,99 @@ } } + }, + { + "abiName" : "bjs_DataProcessorManager_validateProcessorValue", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "name" : "validateProcessorValue", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_DataProcessorManager_loadProcessorLabel", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : true + }, + "name" : "loadProcessorLabel", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_DataProcessorManager_resetProcessor", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : true + }, + "name" : "resetProcessor", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_DataProcessorManager_fetchProcessorCount", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : false + }, + "name" : "fetchProcessorCount", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } } ], "name" : "DataProcessorManager", @@ -3325,6 +3418,99 @@ "_1" : "null" } } + }, + { + "abiName" : "bjs_SwiftDataProcessor_validate", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "name" : "validate", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_SwiftDataProcessor_loadLabel", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : true + }, + "name" : "loadLabel", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_SwiftDataProcessor_reset", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : true + }, + "name" : "reset", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_SwiftDataProcessor_fetchCount", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : false + }, + "name" : "fetchCount", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } } ], "name" : "SwiftDataProcessor", @@ -19336,6 +19522,99 @@ "_1" : "null" } } + }, + { + "abiName" : "bjs_DataProcessor_validate", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "name" : "validate", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_DataProcessor_loadLabel", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : true + }, + "name" : "loadLabel", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_DataProcessor_reset", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : true + }, + "name" : "reset", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_DataProcessor_fetchCount", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : false + }, + "name" : "fetchCount", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } } ], "name" : "DataProcessor", @@ -23286,6 +23565,54 @@ "_1" : "null" } } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : true + }, + "name" : "jsAsyncThrowSynchronously", + "parameters" : [ + { + "name" : "message", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : true, + "isStatic" : false, + "isThrows" : true + }, + "name" : "jsAsyncReturnNonPromise", + "parameters" : [ + { + "name" : "v", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } } ] } diff --git a/Tests/BridgeJSRuntimeTests/JavaScript/AsyncImportTests.mjs b/Tests/BridgeJSRuntimeTests/JavaScript/AsyncImportTests.mjs index 9be7af1be..a0f9a6ebd 100644 --- a/Tests/BridgeJSRuntimeTests/JavaScript/AsyncImportTests.mjs +++ b/Tests/BridgeJSRuntimeTests/JavaScript/AsyncImportTests.mjs @@ -44,6 +44,12 @@ export function getImports(importsContext) { jsAsyncRoundTripOptionalAssociatedValueEnum: (v) => { return Promise.resolve(v); }, + jsAsyncThrowSynchronously: (message) => { + throw new Error(message); + }, + jsAsyncReturnNonPromise: (v) => { + return v; + }, }; } diff --git a/Tests/BridgeJSRuntimeTests/JavaScript/ClosureAsyncTests.mjs b/Tests/BridgeJSRuntimeTests/JavaScript/ClosureAsyncTests.mjs index 3267d8fd6..07b1ec1ed 100644 --- a/Tests/BridgeJSRuntimeTests/JavaScript/ClosureAsyncTests.mjs +++ b/Tests/BridgeJSRuntimeTests/JavaScript/ClosureAsyncTests.mjs @@ -38,6 +38,14 @@ export async function runJsClosureAsyncTests(exports) { assert.notEqual(directionAReject, null); assert.equal(directionAReject.message, "CallbackRejected"); + await assert.rejects( + () => exports.awaitAsyncCallback(() => { + throw new Error("CallbackThrewSynchronously"); + }), + (error) => error instanceof Error && error.message === "CallbackThrewSynchronously" + ); + assert.equal(await exports.awaitAsyncCallback((req) => `plain-${req}`), "swift-saw:plain-request"); + const parser = exports.makeAsyncParser(); const parsed = parser("42"); diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 8a109bcb5..f54e3a149 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -145,6 +145,7 @@ export async function setupOptions(options, context) { throw new Error("No exports!?"); } await runAsyncWorksTests(exports); + await testAsyncProtocolSupport(exports); return; }, AsyncImportImports: getAsyncImportImports(importsContext), @@ -1311,6 +1312,102 @@ function setupTestGlobals(global) { }; } +/** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ +async function testAsyncProtocolSupport(exports) { + let processorValue = 3; + let shouldRejectReset = false; + const jsProcessor = { + count: 0, + name: "AsyncJSProcessor", + optionalTag: null, + direction: null, + optionalTheme: null, + httpStatus: null, + apiResult: null, + helper: new exports.Greeter("AsyncHelper"), + optionalHelper: null, + optionalCount: null, + increment(amount) { processorValue += amount; }, + getValue() { return processorValue; }, + setLabelElements(labelPrefix, labelSuffix) { }, + getLabel() { return "async"; }, + isEven() { return processorValue % 2 === 0; }, + processGreeter(greeter) { return ""; }, + createGreeter() { return new exports.Greeter("AsyncGreeter"); }, + processOptionalGreeter(greeter) { return ""; }, + createOptionalGreeter() { return null; }, + handleAPIResult(result) { }, + getAPIResult() { return { tag: exports.APIResult.Tag.Info }; }, + validate(value) { return value; }, + loadLabel(key) { + if (key === "invalid") { + throw new Error("Invalid key thrown synchronously"); + } + return (async () => { + await Promise.resolve(); + if (key === "missing") { + throw new Error(`No label for ${key}`); + } + return `async:${key}`; + })(); + }, + async reset() { + await Promise.resolve(); + if (shouldRejectReset) { + throw new Error("Reset rejected"); + } + processorValue = 0; + }, + fetchCount() { return Promise.resolve(processorValue); } + }; + + const jsManager = new exports.DataProcessorManager(jsProcessor); + + assert.equal(await jsManager.loadProcessorLabel("key"), "async:key"); + await assert.rejects( + () => jsManager.loadProcessorLabel("missing"), + (error) => error instanceof Error && error.message === "No label for missing" + ); + await assert.rejects( + () => jsManager.loadProcessorLabel("invalid"), + (error) => error instanceof Error && error.message === "Invalid key thrown synchronously" + ); + const [firstLabel, secondLabel] = await Promise.all([ + jsManager.loadProcessorLabel("first"), + jsManager.loadProcessorLabel("second"), + ]); + assert.equal(firstLabel, "async:first"); + assert.equal(secondLabel, "async:second"); + + assert.equal(await jsManager.fetchProcessorCount(), 3); + await jsManager.resetProcessor(); + assert.equal(await jsManager.fetchProcessorCount(), 0); + + shouldRejectReset = true; + jsManager.incrementByAmount(2); + await assert.rejects( + () => jsManager.resetProcessor(), + (error) => error instanceof Error && error.message === "Reset rejected" + ); + assert.equal(await jsManager.fetchProcessorCount(), 2); + + jsManager.release(); + jsProcessor.helper.release(); + + const swiftProcessor = new exports.SwiftDataProcessor(); + const swiftManager = new exports.DataProcessorManager(swiftProcessor); + + swiftManager.setProcessorLabel("Swift", "Label"); + swiftManager.incrementByAmount(4); + assert.equal(await swiftManager.loadProcessorLabel("key"), "SwiftLabel:key"); + assert.equal(await swiftManager.fetchProcessorCount(), 4); + await swiftManager.resetProcessor(); + assert.equal(await swiftManager.fetchProcessorCount(), 0); + + swiftManager.release(); + swiftProcessor.release(); +} + /** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ function testProtocolSupport(exports) { let processorValue = 0; @@ -1340,7 +1437,16 @@ function testProtocolSupport(exports) { }, createOptionalGreeter() { return new exports.Greeter("JSOptionalGreeter"); }, handleAPIResult(result) { lastAPIResult = result; }, - getAPIResult() { return lastAPIResult; } + getAPIResult() { return lastAPIResult; }, + validate(value) { + if (value < 0) { + throw new Error("JSProcessor rejects negative values"); + } + return value * 2; + }, + async loadLabel(key) { return `${processorLabel}:${key}`; }, + async reset() { processorValue = 0; }, + async fetchCount() { return processorValue; } }; const successResult = { tag: exports.Utilities.Result.Tag.Success, param0: "Operation completed" }; @@ -1444,6 +1550,13 @@ function testProtocolSupport(exports) { jsProcessor.optionalCount = null; assert.equal(jsProcessor.optionalCount, null); + assert.equal(jsManager.validateProcessorValue(21), 42); + assert.throws( + () => jsManager.validateProcessorValue(-1), + (error) => error instanceof Error && error.message === "JSProcessor rejects negative values" + ); + assert.equal(jsManager.validateProcessorValue(5), 10); + newHelper.release(); optHelper.release(); jsManager.release(); @@ -1497,6 +1610,9 @@ function testProtocolSupport(exports) { swiftManager.setProcessorAPIResult(successResult); assert.deepEqual(swiftProcessor.getAPIResult(), successResult); + assert.equal(swiftManager.validateProcessorValue(7), 7); + assert.throws(() => swiftManager.validateProcessorValue(-1), /SwiftDataProcessor rejects negative values/); + swiftManager.release(); swiftProcessor.release(); @@ -1522,7 +1638,11 @@ function testProtocolSupport(exports) { processOptionalGreeter(greeter) { return ""; }, createOptionalGreeter() { return null; }, handleAPIResult(result) { }, - getAPIResult() { return { tag: exports.APIResult.Tag.Info }; } + getAPIResult() { return { tag: exports.APIResult.Tag.Info }; }, + validate(value) { return value; }, + async loadLabel(key) { return ""; }, + async reset() { }, + async fetchCount() { return 0; } }; let mainValue = 0; @@ -1547,7 +1667,11 @@ function testProtocolSupport(exports) { processOptionalGreeter(greeter) { return ""; }, createOptionalGreeter() { return null; }, handleAPIResult(result) { }, - getAPIResult() { return { tag: exports.APIResult.Tag.Info }; } + getAPIResult() { return { tag: exports.APIResult.Tag.Info }; }, + validate(value) { return value; }, + async loadLabel(key) { return ""; }, + async reset() { }, + async fetchCount() { return 0; } }; const managerWithOptional = new exports.DataProcessorManager(mainProcessor); From 681bb0b231a2680a6b25256c54115f7313bcd386 Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Thu, 15 Oct 2026 11:34:25 +0200 Subject: [PATCH 2/2] [NFC] BridgeJS: Update renderAsyncFunction doc comment --- Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index c97bdf293..8e7c4939c 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -2897,9 +2897,8 @@ extension BridgeJSLink { /// Renders an async import function with resolve/reject closure refs. /// - /// The generated function takes `resolveRef` and `rejectRef` as the first parameters, - /// looks up the resolve/reject closures from memory, and executes the body which - /// chains `.then(resolve, reject)` on the import's returned Promise. + /// The generated function takes `resolveRef` and `rejectRef` as the first parameters + /// and settles them with the result of the import call. private func renderAsyncFunction(name: String?) -> [String] { let printer = CodeFragmentPrinter() let allParams = ["resolveRef", "rejectRef"] + parameterNames