From 9339c1cc7f93d2c3405f809e1833bde4928635ef Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Sun, 22 Feb 2026 00:53:27 +0100 Subject: [PATCH] BridgeJS: Fix stack ordering for multiple stack-based parameters --- .../Sources/BridgeJSCore/ExportSwift.swift | 20 +- .../Inputs/MacroSwift/ArrayTypes.swift | 16 ++ .../BridgeJSCodegenTests/ArrayTypes.json | 167 ++++++++++++++ .../BridgeJSCodegenTests/ArrayTypes.swift | 89 ++++++++ .../BridgeJSLinkTests/ArrayTypes.d.ts | 9 + .../BridgeJSLinkTests/ArrayTypes.js | 86 +++++++ .../BridgeJSRuntimeTests/ExportAPITests.swift | 18 ++ .../Generated/BridgeJS.swift | 52 +++++ .../Generated/JavaScript/BridgeJS.json | 214 ++++++++++++++++++ Tests/prelude.mjs | 11 + 10 files changed, 674 insertions(+), 8 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 2332918c1..b7c66dcd2 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -246,14 +246,7 @@ public class ExportSwift { /// 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 .swiftStruct, .nullable(.swiftStruct, _), - .associatedValueEnum, .nullable(.associatedValueEnum, _), - .array: - return index - default: - return nil - } + param.type.isStackUsingParameter ? index : nil } guard stackParamIndices.count > 1 else { return } @@ -1547,6 +1540,17 @@ extension BridgeType { return false } + var isStackUsingParameter: Bool { + switch self { + case .swiftStruct, .array, .dictionary, .associatedValueEnum: + return true + case .nullable(let wrapped, _): + return wrapped.isStackUsingParameter + default: + return false + } + } + struct LiftingIntrinsicInfo: Sendable { let parameters: [(name: String, type: WasmCoreType)] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ArrayTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ArrayTypes.swift index 0cea90512..596feeae7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ArrayTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ArrayTypes.swift @@ -67,3 +67,19 @@ @JSFunction func importTransformNumbers(_ values: [Double]) throws(JSException) -> [Double] @JSFunction func importProcessStrings(_ values: [String]) throws(JSException) -> [String] @JSFunction func importProcessBooleans(_ values: [Bool]) throws(JSException) -> [Bool] + +@JS func multiArrayParams(nums: [Int], strs: [String]) -> Int +@JS func multiOptionalArrayParams(a: [Int]?, b: [String]?) -> Int + +@JS class MultiArrayContainer { + var nums: [Int] + var strs: [String] + + @JS init(nums: [Int], strs: [String]) { + self.nums = nums + self.strs = strs + } + + @JS var numbers: [Int] { nums } + @JS var strings: [String] { strs } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.json index 8eab0c059..32bdb3374 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.json @@ -10,6 +10,79 @@ ], "swiftCallName" : "Item" + }, + { + "constructor" : { + "abiName" : "bjs_MultiArrayContainer_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "nums", + "name" : "nums", + "type" : { + "array" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "label" : "strs", + "name" : "strs", + "type" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "MultiArrayContainer", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "numbers", + "type" : { + "array" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "strings", + "type" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + } + } + ], + "swiftCallName" : "MultiArrayContainer" } ], "enums" : [ @@ -1074,6 +1147,100 @@ } } } + }, + { + "abiName" : "bjs_multiArrayParams", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "multiArrayParams", + "parameters" : [ + { + "label" : "nums", + "name" : "nums", + "type" : { + "array" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "label" : "strs", + "name" : "strs", + "type" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + } + } + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_multiOptionalArrayParams", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "multiOptionalArrayParams", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "int" : { + + } + } + } + }, + "_1" : "null" + } + } + }, + { + "label" : "b", + "name" : "b", + "type" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "int" : { + + } + } } ], "protocols" : [ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift index be884310b..6b6b59fce 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift @@ -470,6 +470,32 @@ public func _bjs_processNestedJSObjectArray() -> Void { #endif } +@_expose(wasm, "bjs_multiArrayParams") +@_cdecl("bjs_multiArrayParams") +public func _bjs_multiArrayParams() -> Int32 { + #if arch(wasm32) + let _tmp_strs = [String].bridgeJSStackPop() + let _tmp_nums = [Int].bridgeJSStackPop() + let ret = multiArrayParams(nums: _tmp_nums, strs: _tmp_strs) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_multiOptionalArrayParams") +@_cdecl("bjs_multiOptionalArrayParams") +public func _bjs_multiOptionalArrayParams() -> Int32 { + #if arch(wasm32) + let _tmp_b = Optional<[String]>.bridgeJSLiftParameter() + let _tmp_a = Optional<[Int]>.bridgeJSLiftParameter() + let ret = multiOptionalArrayParams(a: _tmp_a, b: _tmp_b) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_Item_deinit") @_cdecl("bjs_Item_deinit") public func _bjs_Item_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { @@ -498,6 +524,69 @@ fileprivate func _bjs_Item_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> In return _bjs_Item_wrap_extern(pointer) } +@_expose(wasm, "bjs_MultiArrayContainer_init") +@_cdecl("bjs_MultiArrayContainer_init") +public func _bjs_MultiArrayContainer_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let _tmp_strs = [String].bridgeJSStackPop() + let _tmp_nums = [Int].bridgeJSStackPop() + let ret = MultiArrayContainer(nums: _tmp_nums, strs: _tmp_strs) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MultiArrayContainer_numbers_get") +@_cdecl("bjs_MultiArrayContainer_numbers_get") +public func _bjs_MultiArrayContainer_numbers_get(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = MultiArrayContainer.bridgeJSLiftParameter(_self).numbers + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MultiArrayContainer_strings_get") +@_cdecl("bjs_MultiArrayContainer_strings_get") +public func _bjs_MultiArrayContainer_strings_get(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = MultiArrayContainer.bridgeJSLiftParameter(_self).strings + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MultiArrayContainer_deinit") +@_cdecl("bjs_MultiArrayContainer_deinit") +public func _bjs_MultiArrayContainer_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension MultiArrayContainer: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_MultiArrayContainer_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_MultiArrayContainer_wrap") +fileprivate func _bjs_MultiArrayContainer_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_MultiArrayContainer_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_MultiArrayContainer_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_MultiArrayContainer_wrap_extern(pointer) +} + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_checkArray") fileprivate func bjs_checkArray_extern(_ a: Int32) -> Void diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts index 8dc76156b..255249eef 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts @@ -36,9 +36,16 @@ export interface SwiftHeapObject { } export interface Item extends SwiftHeapObject { } +export interface MultiArrayContainer extends SwiftHeapObject { + readonly numbers: number[]; + readonly strings: string[]; +} export type Exports = { Item: { } + MultiArrayContainer: { + new(nums: number[], strs: string[]): MultiArrayContainer; + } processIntArray(values: number[]): number[]; processStringArray(values: string[]): string[]; processDoubleArray(values: number[]): number[]; @@ -65,6 +72,8 @@ export type Exports = { processJSObjectArray(objects: any[]): any[]; processOptionalJSObjectArray(objects: (any | null)[]): (any | null)[]; processNestedJSObjectArray(objects: any[][]): any[][]; + multiArrayParams(nums: number[], strs: string[]): number; + multiOptionalArrayParams(a: number[] | null, b: string[] | null): number; Direction: DirectionObject Status: StatusObject } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js index 47db4d410..d0665da73 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js @@ -224,6 +224,10 @@ export async function createInstantiator(options, swift) { const obj = _exports['Item'].__construct(pointer); return swift.memory.retain(obj); }; + importObject["TestModule"]["bjs_MultiArrayContainer_wrap"] = function(pointer) { + const obj = _exports['MultiArrayContainer'].__construct(pointer); + return swift.memory.retain(obj); + }; const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkArray"] = function bjs_checkArray(a) { try { @@ -368,11 +372,55 @@ export async function createInstantiator(options, swift) { } } + class MultiArrayContainer extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_MultiArrayContainer_deinit, MultiArrayContainer.prototype); + } + + constructor(nums, strs) { + for (const elem of nums) { + i32Stack.push((elem | 0)); + } + i32Stack.push(nums.length); + for (const elem1 of strs) { + const bytes = textEncoder.encode(elem1); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + } + i32Stack.push(strs.length); + const ret = instance.exports.bjs_MultiArrayContainer_init(); + return MultiArrayContainer.__construct(ret); + } + get numbers() { + instance.exports.bjs_MultiArrayContainer_numbers_get(this.pointer); + const arrayLen = i32Stack.pop(); + const arrayResult = []; + for (let i = 0; i < arrayLen; i++) { + const int = i32Stack.pop(); + arrayResult.push(int); + } + arrayResult.reverse(); + return arrayResult; + } + get strings() { + instance.exports.bjs_MultiArrayContainer_strings_get(this.pointer); + const arrayLen = i32Stack.pop(); + const arrayResult = []; + for (let i = 0; i < arrayLen; i++) { + const string = strStack.pop(); + arrayResult.push(string); + } + arrayResult.reverse(); + return arrayResult; + } + } const PointHelpers = __bjs_createPointHelpers(); structHelpers.Point = PointHelpers; const exports = { Item, + MultiArrayContainer, processIntArray: function bjs_processIntArray(values) { for (const elem of values) { i32Stack.push((elem | 0)); @@ -905,6 +953,44 @@ export async function createInstantiator(options, swift) { arrayResult.reverse(); return arrayResult; }, + multiArrayParams: function bjs_multiArrayParams(nums, strs) { + for (const elem of nums) { + i32Stack.push((elem | 0)); + } + i32Stack.push(nums.length); + for (const elem1 of strs) { + const bytes = textEncoder.encode(elem1); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + } + i32Stack.push(strs.length); + const ret = instance.exports.bjs_multiArrayParams(); + return ret; + }, + multiOptionalArrayParams: function bjs_multiOptionalArrayParams(a, b) { + const isSome = a != null; + if (isSome) { + for (const elem of a) { + i32Stack.push((elem | 0)); + } + i32Stack.push(a.length); + } + i32Stack.push(+isSome); + const isSome1 = b != null; + if (isSome1) { + for (const elem1 of b) { + const bytes = textEncoder.encode(elem1); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + } + i32Stack.push(b.length); + } + i32Stack.push(+isSome1); + const ret = instance.exports.bjs_multiOptionalArrayParams(); + return ret; + }, Direction: DirectionValues, Status: StatusValues, }; diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index fe24ef42d..26f587fa3 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -1423,6 +1423,24 @@ enum GraphOperations { return foos } +// MARK: - Multiple stack-based parameters (regression test for LIFO ordering) + +@JS func multiArrayFirstNums(nums: [Int], strs: [String]) -> [Int] { + return nums +} + +@JS func multiArrayFirstStrs(nums: [Int], strs: [String]) -> [String] { + return strs +} + +@JS func multiOptionalArrayFirstA(a: [Int]?, b: [String]?) -> [Int]? { + return a +} + +@JS func multiOptionalArrayFirstB(a: [Int]?, b: [String]?) -> [String]? { + return b +} + class ExportAPITests: XCTestCase { func testAll() { var hasDeinitGreeter = false diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index 63d55c265..8acb0a347 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -6194,6 +6194,58 @@ public func _bjs_roundTripOptionalFooArray() -> Void { #endif } +@_expose(wasm, "bjs_multiArrayFirstNums") +@_cdecl("bjs_multiArrayFirstNums") +public func _bjs_multiArrayFirstNums() -> Void { + #if arch(wasm32) + let _tmp_strs = [String].bridgeJSStackPop() + let _tmp_nums = [Int].bridgeJSStackPop() + let ret = multiArrayFirstNums(nums: _tmp_nums, strs: _tmp_strs) + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_multiArrayFirstStrs") +@_cdecl("bjs_multiArrayFirstStrs") +public func _bjs_multiArrayFirstStrs() -> Void { + #if arch(wasm32) + let _tmp_strs = [String].bridgeJSStackPop() + let _tmp_nums = [Int].bridgeJSStackPop() + let ret = multiArrayFirstStrs(nums: _tmp_nums, strs: _tmp_strs) + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_multiOptionalArrayFirstA") +@_cdecl("bjs_multiOptionalArrayFirstA") +public func _bjs_multiOptionalArrayFirstA() -> Void { + #if arch(wasm32) + let _tmp_b = Optional<[String]>.bridgeJSLiftParameter() + let _tmp_a = Optional<[Int]>.bridgeJSLiftParameter() + let ret = multiOptionalArrayFirstA(a: _tmp_a, b: _tmp_b) + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_multiOptionalArrayFirstB") +@_cdecl("bjs_multiOptionalArrayFirstB") +public func _bjs_multiOptionalArrayFirstB() -> Void { + #if arch(wasm32) + let _tmp_b = Optional<[String]>.bridgeJSLiftParameter() + let _tmp_a = Optional<[Int]>.bridgeJSLiftParameter() + let ret = multiOptionalArrayFirstB(a: _tmp_a, b: _tmp_b) + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_roundTripOptionalString") @_cdecl("bjs_roundTripOptionalString") public func _bjs_roundTripOptionalString(_ nameIsSome: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void { diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index 8a66705fc..42b6abfbb 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -10150,6 +10150,220 @@ } } }, + { + "abiName" : "bjs_multiArrayFirstNums", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "multiArrayFirstNums", + "parameters" : [ + { + "label" : "nums", + "name" : "nums", + "type" : { + "array" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "label" : "strs", + "name" : "strs", + "type" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "abiName" : "bjs_multiArrayFirstStrs", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "multiArrayFirstStrs", + "parameters" : [ + { + "label" : "nums", + "name" : "nums", + "type" : { + "array" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "label" : "strs", + "name" : "strs", + "type" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + } + }, + { + "abiName" : "bjs_multiOptionalArrayFirstA", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "multiOptionalArrayFirstA", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "int" : { + + } + } + } + }, + "_1" : "null" + } + } + }, + { + "label" : "b", + "name" : "b", + "type" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "int" : { + + } + } + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_multiOptionalArrayFirstB", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "multiOptionalArrayFirstB", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "int" : { + + } + } + } + }, + "_1" : "null" + } + } + }, + { + "label" : "b", + "name" : "b", + "type" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + }, + "_1" : "null" + } + } + }, { "abiName" : "bjs_roundTripOptionalString", "effects" : { diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 9e32f1d26..4242d8bdb 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -1373,6 +1373,17 @@ function testArraySupport(exports) { assert.equal(optFooResult[0].value, "first"); assert.equal(optFooResult[1], null); assert.equal(optFooResult[2].value, "second"); + + // Multiple stack-based parameters (regression test for LIFO ordering) + assert.deepEqual(exports.multiArrayFirstNums([1, 2, 3], ["a", "b"]), [1, 2, 3]); + assert.deepEqual(exports.multiArrayFirstStrs([1, 2, 3], ["a", "b"]), ["a", "b"]); + + assert.deepEqual(exports.multiOptionalArrayFirstA([10, 20], ["x"]), [10, 20]); + assert.deepEqual(exports.multiOptionalArrayFirstB([10, 20], ["x"]), ["x"]); + assert.equal(exports.multiOptionalArrayFirstA(null, ["y"]), null); + assert.deepEqual(exports.multiOptionalArrayFirstB(null, ["y"]), ["y"]); + assert.deepEqual(exports.multiOptionalArrayFirstA([5], null), [5]); + assert.equal(exports.multiOptionalArrayFirstB([5], null), null); } /** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */