diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index d4ea770a..ebcf7d4c 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -1667,6 +1667,7 @@ public class ExportSwift { } func call(name: String, returnType: BridgeType) { + generateParameterLifting() let item = renderCallStatement(callee: "\(raw: name)", returnType: returnType) append(item) } @@ -1694,6 +1695,7 @@ public class ExportSwift { func callMethod(klassName: String, methodName: String, returnType: BridgeType) { let (_, selfExpr) = removeFirstLiftedParameter() + generateParameterLifting() let item = renderCallStatement( callee: "\(raw: selfExpr).\(raw: methodName)", returnType: returnType @@ -1827,6 +1829,29 @@ public class ExportSwift { func returnSignature() -> String { return abiReturnType?.swiftType ?? "Void" } + + /// Generates intermediate variables for stack-using parameters if needed for LIFO compatibility + private func generateParameterLifting() { + let stackParamIndices = parameters.enumerated().compactMap { index, param -> Int? in + switch param.type { + case .optional(.associatedValueEnum): + return index + default: + return nil + } + } + + guard stackParamIndices.count > 1 else { return } + + for index in stackParamIndices.reversed() { + let param = parameters[index] + let expr = liftedParameterExprs[index] + let varName = "_tmp_\(param.name)" + + append("let \(raw: varName) = \(expr)") + liftedParameterExprs[index] = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(varName))) + } + } } private struct ClosureCodegen { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumAssociatedValue.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumAssociatedValue.swift index e099ba85..efb6cd1b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumAssociatedValue.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumAssociatedValue.swift @@ -10,12 +10,8 @@ enum APIResult { @JS func handle(result: APIResult) @JS func getResult() -> APIResult -@JS func roundtripAPIResult(result: APIResult) -> APIResult { - return result -} -@JS func roundTripOptionalAPIResult(result: APIResult?) -> APIResult? { - return result -} +@JS func roundtripAPIResult(result: APIResult) -> APIResult +@JS func roundTripOptionalAPIResult(result: APIResult?) -> APIResult? @JS enum ComplexResult { @@ -29,12 +25,8 @@ enum ComplexResult { @JS func handleComplex(result: ComplexResult) @JS func getComplexResult() -> ComplexResult -@JS func roundtripComplexResult(_ result: ComplexResult) -> ComplexResult { - return result -} -@JS func roundTripOptionalComplexResult(result: ComplexResult?) -> ComplexResult? { - return result -} +@JS func roundtripComplexResult(_ result: ComplexResult) -> ComplexResult +@JS func roundTripOptionalComplexResult(result: ComplexResult?) -> ComplexResult? @JS enum Utilities { @@ -45,9 +37,7 @@ enum Utilities { } } -@JS func roundTripOptionalUtilitiesResult(result: Utilities.Result?) -> Utilities.Result? { - return result -} +@JS func roundTripOptionalUtilitiesResult(result: Utilities.Result?) -> Utilities.Result? @JS(namespace: "API") @JS enum NetworkingResult { @@ -55,9 +45,7 @@ enum Utilities { case failure(String, Int) } -@JS func roundTripOptionalNetworkingResult(result: NetworkingResult?) -> NetworkingResult? { - return result -} +@JS func roundTripOptionalNetworkingResult(result: NetworkingResult?) -> NetworkingResult? @JS enum APIOptionalResult { @@ -65,6 +53,5 @@ enum APIOptionalResult { case failure(Int?, Bool?) case status(Bool?, Int?, String?) } -@JS func roundTripOptionalAPIOptionalResult(result: APIOptionalResult?) -> APIOptionalResult? { - return result -} +@JS func roundTripOptionalAPIOptionalResult(result: APIOptionalResult?) -> APIOptionalResult? +@JS func compareAPIResults(result1: APIOptionalResult?, result2: APIOptionalResult?) -> APIOptionalResult? diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.d.ts index 9df9bbea..20962c38 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.d.ts @@ -86,6 +86,7 @@ export type Exports = { roundTripOptionalUtilitiesResult(result: Utilities.ResultTag | null): Utilities.ResultTag | null; roundTripOptionalNetworkingResult(result: API.NetworkingResultTag | null): API.NetworkingResultTag | null; roundTripOptionalAPIOptionalResult(result: APIOptionalResultTag | null): APIOptionalResultTag | null; + compareAPIResults(result1: APIOptionalResultTag | null, result2: APIOptionalResultTag | null): APIOptionalResultTag | null; APIResult: APIResultObject ComplexResult: ComplexResultObject APIOptionalResult: APIOptionalResultObject diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js index c332a403..e484b72c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js @@ -814,6 +814,33 @@ export async function createInstantiator(options, swift) { if (resultCleanup) { resultCleanup(); } return optResult; }, + compareAPIResults: function bjs_compareAPIResults(result1, result2) { + const isSome = result1 != null; + let result1CaseId, result1Cleanup; + if (isSome) { + const enumResult = enumHelpers.APIOptionalResult.lower(result1); + result1CaseId = enumResult.caseId; + result1Cleanup = enumResult.cleanup; + } + const isSome1 = result2 != null; + let result2CaseId, result2Cleanup; + if (isSome1) { + const enumResult1 = enumHelpers.APIOptionalResult.lower(result2); + result2CaseId = enumResult1.caseId; + result2Cleanup = enumResult1.cleanup; + } + instance.exports.bjs_compareAPIResults(+isSome, isSome ? result1CaseId : 0, +isSome1, isSome1 ? result2CaseId : 0); + const isNull = (tmpRetTag === -1); + let optResult; + if (isNull) { + optResult = null; + } else { + optResult = enumHelpers.APIOptionalResult.raise(tmpRetTag, tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s); + } + if (result1Cleanup) { result1Cleanup(); } + if (result2Cleanup) { result2Cleanup(); } + return optResult; + }, APIResult: APIResultValues, ComplexResult: ComplexResultValues, APIOptionalResult: APIOptionalResultValues, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json index 52279ce0..fb7271b9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json @@ -787,6 +787,52 @@ } } } + }, + { + "abiName" : "bjs_compareAPIResults", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "compareAPIResults", + "parameters" : [ + { + "label" : "result1", + "name" : "result1", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIOptionalResult" + } + } + } + } + }, + { + "label" : "result2", + "name" : "result2", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIOptionalResult" + } + } + } + } + } + ], + "returnType" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIOptionalResult" + } + } + } + } } ], "moduleName" : "TestModule", diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.swift index 0c70fe5c..bcc95ef4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.swift @@ -606,4 +606,17 @@ public func _bjs_roundTripOptionalAPIOptionalResult(resultIsSome: Int32, resultC #else fatalError("Only available on WebAssembly") #endif +} + +@_expose(wasm, "bjs_compareAPIResults") +@_cdecl("bjs_compareAPIResults") +public func _bjs_compareAPIResults(result1IsSome: Int32, result1CaseId: Int32, result2IsSome: Int32, result2CaseId: Int32) -> Void { + #if arch(wasm32) + let _tmp_result2 = Optional.bridgeJSLiftParameter(result2IsSome, result2CaseId) + let _tmp_result1 = Optional.bridgeJSLiftParameter(result1IsSome, result1CaseId) + let ret = compareAPIResults(result1: _tmp_result1, result2: _tmp_result2) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index ff3d9dbe..682e1185 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -528,6 +528,32 @@ typealias OptionalAge = Int? return value } +@JS func compareAPIResults(_ r1: APIResult?, _ r2: APIResult?) -> String { + let r1Str: String + switch r1 { + case .none: r1Str = "nil" + case .some(.success(let msg)): r1Str = "success:\(msg)" + case .some(.failure(let code)): r1Str = "failure:\(code)" + case .some(.info): r1Str = "info" + case .some(.flag(let b)): r1Str = "flag:\(b)" + case .some(.rate(let r)): r1Str = "rate:\(r)" + case .some(.precise(let p)): r1Str = "precise:\(p)" + } + + let r2Str: String + switch r2 { + case .none: r2Str = "nil" + case .some(.success(let msg)): r2Str = "success:\(msg)" + case .some(.failure(let code)): r2Str = "failure:\(code)" + case .some(.info): r2Str = "info" + case .some(.flag(let b)): r2Str = "flag:\(b)" + case .some(.rate(let r)): r2Str = "rate:\(r)" + case .some(.precise(let p)): r2Str = "precise:\(p)" + } + + return "r1:\(r1Str),r2:\(r2Str)" +} + @JS func roundTripOptionalComplexResult(_ result: ComplexResult?) -> ComplexResult? { return result } diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 9923eedd..bfb10219 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -3079,6 +3079,19 @@ public func _bjs_roundTripOptionalAPIResult(valueIsSome: Int32, valueCaseId: Int #endif } +@_expose(wasm, "bjs_compareAPIResults") +@_cdecl("bjs_compareAPIResults") +public func _bjs_compareAPIResults(r1IsSome: Int32, r1CaseId: Int32, r2IsSome: Int32, r2CaseId: Int32) -> Void { + #if arch(wasm32) + let _tmp_r2 = Optional.bridgeJSLiftParameter(r2IsSome, r2CaseId) + let _tmp_r1 = Optional.bridgeJSLiftParameter(r1IsSome, r1CaseId) + let ret = compareAPIResults(_: _tmp_r1, _: _tmp_r2) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_roundTripOptionalComplexResult") @_cdecl("bjs_roundTripOptionalComplexResult") public func _bjs_roundTripOptionalComplexResult(resultIsSome: Int32, resultCaseId: Int32) -> Void { diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 75964deb..50a9236e 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -6826,6 +6826,48 @@ } } }, + { + "abiName" : "bjs_compareAPIResults", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "compareAPIResults", + "parameters" : [ + { + "label" : "_", + "name" : "r1", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIResult" + } + } + } + } + }, + { + "label" : "_", + "name" : "r2", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIResult" + } + } + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, { "abiName" : "bjs_roundTripOptionalComplexResult", "effects" : { diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index cfd898b3..04fa3abd 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -571,6 +571,15 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { assert.deepEqual(exports.roundTripOptionalAPIResult(p1), p1); assert.deepEqual(exports.roundTripOptionalComplexResult(cl1), cl1); + const apiSuccess = { tag: exports.APIResult.Tag.Success, param0: "test success" }; + const apiFailure = { tag: exports.APIResult.Tag.Failure, param0: 404 }; + const apiInfo = { tag: exports.APIResult.Tag.Info }; + + assert.equal(exports.compareAPIResults(apiSuccess, apiFailure), "r1:success:test success,r2:failure:404"); + assert.equal(exports.compareAPIResults(null, apiInfo), "r1:nil,r2:info"); + assert.equal(exports.compareAPIResults(apiFailure, null), "r1:failure:404,r2:nil"); + assert.equal(exports.compareAPIResults(null, null), "r1:nil,r2:nil"); + const optionalGreeter = new exports.Greeter("Schrödinger"); const optionalGreeter2 = exports.roundTripOptionalClass(optionalGreeter); assert.equal(optionalGreeter2?.greet() ?? "", "Hello, Schrödinger!");