From ffa6c066fb561011b2d0f362f75170b2caf00f20 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 5 Feb 2026 10:12:33 +0900 Subject: [PATCH 1/2] - Added JSValue-aware stack lift/lower fragments so arrays push/pop JSValue payloads using the shared helpers, enabling `[JSValue]` round-trips in both directions (`Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift`). - Extended JSValue test inputs with array and optional-array cases plus a JS-imported array echo to exercise the new path (`Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSValue.swift`). - Updated codegen/link snapshots to reflect the new JSValue array glue (`Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSValue.swift`, `Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSValue.json`, `Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js`, `Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.d.ts`). Tests: `UPDATE_SNAPSHOTS=1 swift test --package-path ./Plugins/BridgeJS`. Next step: run `make unittest SWIFT_SDK_ID=DEVELOPMENT-SNAPSHOT-2025-11-03-a-wasm32-unknown-wasip1` for the full runtime suite if you want end-to-end validation. --- .../Sources/BridgeJSLink/JSGlueGen.swift | 33 +++++- .../Inputs/MacroSwift/JSValue.swift | 9 ++ .../BridgeJSCodegenTests/JSValue.json | 102 ++++++++++++++++++ .../BridgeJSCodegenTests/JSValue.swift | 49 +++++++++ .../BridgeJSLinkTests/JSValue.d.ts | 3 + .../BridgeJSLinkTests/JSValue.js | 83 ++++++++++++++ 6 files changed, 277 insertions(+), 2 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index 410bce67..cdb23f60 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -2613,7 +2613,23 @@ struct IntrinsicJSFragment: Sendable { private static func stackLiftFragment(elementType: BridgeType) throws -> IntrinsicJSFragment { switch elementType { case .jsValue: - throw BridgeJSLinkError(message: "Array of JSValue is not supported yet") + return IntrinsicJSFragment( + parameters: [], + printCode: { _, scope, printer, cleanup in + registerJSValueHelpers(scope: scope) + let payload2Var = scope.variable("jsValuePayload2") + let payload1Var = scope.variable("jsValuePayload1") + let kindVar = scope.variable("jsValueKind") + printer.write("const \(payload2Var) = \(JSGlueVariableScope.reservedTmpRetF64s).pop();") + printer.write("const \(payload1Var) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + printer.write("const \(kindVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + let resultVar = scope.variable("jsValue") + printer.write( + "const \(resultVar) = \(jsValueLiftHelperName)(\(kindVar), \(payload1Var), \(payload2Var));" + ) + return [resultVar] + } + ) case .string: return IntrinsicJSFragment( parameters: [], @@ -2775,7 +2791,20 @@ struct IntrinsicJSFragment: Sendable { private static func stackLowerFragment(elementType: BridgeType) throws -> IntrinsicJSFragment { switch elementType { case .jsValue: - throw BridgeJSLinkError(message: "Array of JSValue is not supported yet") + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + registerJSValueHelpers(scope: scope) + let lowered = jsValueLower.printCode([arguments[0]], scope, printer, cleanup) + let kindVar = lowered[0] + let payload1Var = lowered[1] + let payload2Var = lowered[2] + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(kindVar));") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(payload1Var));") + printer.write("\(JSGlueVariableScope.reservedTmpParamF64s).push(\(payload2Var));") + return [] + } + ) case .string: return IntrinsicJSFragment( parameters: ["value"], diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSValue.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSValue.swift index 84251f6b..2a34f5f2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSValue.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSValue.swift @@ -6,6 +6,14 @@ return value } +@JS func roundTripJSValueArray(_ values: [JSValue]) -> [JSValue] { + return values +} + +@JS func roundTripOptionalJSValueArray(_ values: [JSValue]?) -> [JSValue]? { + return values +} + @JS class JSValueHolder { @JS var value: JSValue @JS var optionalValue: JSValue? @@ -30,3 +38,4 @@ } @JSFunction func jsEchoJSValue(_ value: JSValue) throws(JSException) -> JSValue +@JSFunction func jsEchoJSValueArray(_ values: [JSValue]) throws(JSException) -> [JSValue] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSValue.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSValue.json index d10d673d..5bd83be2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSValue.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSValue.json @@ -231,6 +231,82 @@ "_1" : "null" } } + }, + { + "abiName" : "bjs_roundTripJSValueArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripJSValueArray", + "parameters" : [ + { + "label" : "_", + "name" : "values", + "type" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + } + }, + { + "abiName" : "bjs_roundTripOptionalJSValueArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripOptionalJSValueArray", + "parameters" : [ + { + "label" : "_", + "name" : "values", + "type" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + }, + "_1" : "null" + } + } } ], "protocols" : [ @@ -261,6 +337,32 @@ } } + }, + { + "name" : "jsEchoJSValueArray", + "parameters" : [ + { + "name" : "values", + "type" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + } } ], "types" : [ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSValue.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSValue.swift index 3a3be507..f3550129 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSValue.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSValue.swift @@ -20,6 +20,37 @@ public func _bjs_roundTripOptionalJSValue(_ valueIsSome: Int32, _ valueKind: Int #endif } +@_expose(wasm, "bjs_roundTripJSValueArray") +@_cdecl("bjs_roundTripJSValueArray") +public func _bjs_roundTripJSValueArray() -> Void { + #if arch(wasm32) + let ret = roundTripJSValueArray(_: [JSValue].bridgeJSLiftParameter()) + ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripOptionalJSValueArray") +@_cdecl("bjs_roundTripOptionalJSValueArray") +public func _bjs_roundTripOptionalJSValueArray(_ values: Int32) -> Void { + #if arch(wasm32) + let ret = roundTripOptionalJSValueArray(_: { + if values == 0 { + return Optional<[JSValue]>.none + } else { + return [JSValue].bridgeJSLiftParameter() + } + }()) + let __bjs_isSome_ret = ret != nil + if let __bjs_unwrapped_ret = ret { + __bjs_unwrapped_ret.bridgeJSLowerReturn()} + _swift_js_push_i32(__bjs_isSome_ret ? 1 : 0) + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_JSValueHolder_init") @_cdecl("bjs_JSValueHolder_init") public func _bjs_JSValueHolder_init(_ valueKind: Int32, _ valuePayload1: Int32, _ valuePayload2: Float64, _ optionalValueIsSome: Int32, _ optionalValueKind: Int32, _ optionalValuePayload1: Int32, _ optionalValuePayload2: Float64) -> UnsafeMutableRawPointer { @@ -146,4 +177,22 @@ func _$jsEchoJSValue(_ value: JSValue) throws(JSException) -> JSValue { throw error } return JSValue.bridgeJSLiftReturn() +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_jsEchoJSValueArray") +fileprivate func bjs_jsEchoJSValueArray() -> Void +#else +fileprivate func bjs_jsEchoJSValueArray() -> Void { + fatalError("Only available on WebAssembly") +} +#endif + +func _$jsEchoJSValueArray(_ values: [JSValue]) throws(JSException) -> [JSValue] { + let _ = values.bridgeJSLowerParameter() + bjs_jsEchoJSValueArray() + if let error = _swift_js_take_exception() { + throw error + } + return [JSValue].bridgeJSLiftReturn() } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.d.ts index 9e2a85ac..f4c13c61 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.d.ts @@ -24,9 +24,12 @@ export type Exports = { } roundTripJSValue(value: any): any; roundTripOptionalJSValue(value: any | null): any | null; + roundTripJSValueArray(values: any[]): any[]; + roundTripOptionalJSValueArray(values: any[] | null): any[] | null; } export type Imports = { jsEchoJSValue(value: any): any; + jsEchoJSValueArray(values: any[]): any[]; } export function createInstantiator(options: { imports: Imports; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js index 2bd696c3..f5c360bb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js @@ -312,6 +312,31 @@ export async function createInstantiator(options, swift) { setException(error); } } + TestModule["bjs_jsEchoJSValueArray"] = function bjs_jsEchoJSValueArray() { + try { + const arrayLen = tmpRetInts.pop(); + const arrayResult = []; + for (let i = 0; i < arrayLen; i++) { + const jsValuePayload2 = tmpRetF64s.pop(); + const jsValuePayload1 = tmpRetInts.pop(); + const jsValueKind = tmpRetInts.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + arrayResult.push(jsValue); + } + arrayResult.reverse(); + let ret = imports.jsEchoJSValueArray(arrayResult); + const arrayCleanups = []; + for (const elem of ret) { + const [elemKind, elemPayload1, elemPayload2] = __bjs_jsValueLower(elem); + tmpParamInts.push(elemKind); + tmpParamInts.push(elemPayload1); + tmpParamF64s.push(elemPayload2); + } + tmpParamInts.push(ret.length); + } catch (error) { + setException(error); + } + } }, setInstance: (i) => { instance = i; @@ -448,6 +473,64 @@ export async function createInstantiator(options, swift) { } return optResult; }, + roundTripJSValueArray: function bjs_roundTripJSValueArray(values) { + const arrayCleanups = []; + for (const elem of values) { + const [elemKind, elemPayload1, elemPayload2] = __bjs_jsValueLower(elem); + tmpParamInts.push(elemKind); + tmpParamInts.push(elemPayload1); + tmpParamF64s.push(elemPayload2); + } + tmpParamInts.push(values.length); + instance.exports.bjs_roundTripJSValueArray(); + const arrayLen = tmpRetInts.pop(); + const arrayResult = []; + for (let i = 0; i < arrayLen; i++) { + const jsValuePayload2 = tmpRetF64s.pop(); + const jsValuePayload1 = tmpRetInts.pop(); + const jsValueKind = tmpRetInts.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + arrayResult.push(jsValue); + } + arrayResult.reverse(); + for (const cleanup of arrayCleanups) { cleanup(); } + return arrayResult; + }, + roundTripOptionalJSValueArray: function bjs_roundTripOptionalJSValueArray(values) { + const isSome = values != null; + const valuesCleanups = []; + if (isSome) { + const arrayCleanups = []; + for (const elem of values) { + const [elemKind, elemPayload1, elemPayload2] = __bjs_jsValueLower(elem); + tmpParamInts.push(elemKind); + tmpParamInts.push(elemPayload1); + tmpParamF64s.push(elemPayload2); + } + tmpParamInts.push(values.length); + valuesCleanups.push(() => { for (const cleanup of arrayCleanups) { cleanup(); } }); + } + instance.exports.bjs_roundTripOptionalJSValueArray(+isSome); + const isSome1 = tmpRetInts.pop(); + let optResult; + if (isSome1) { + const arrayLen = tmpRetInts.pop(); + const arrayResult = []; + for (let i = 0; i < arrayLen; i++) { + const jsValuePayload2 = tmpRetF64s.pop(); + const jsValuePayload1 = tmpRetInts.pop(); + const jsValueKind = tmpRetInts.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + arrayResult.push(jsValue); + } + arrayResult.reverse(); + optResult = arrayResult; + } else { + optResult = null; + } + for (const cleanup of valuesCleanups) { cleanup(); } + return optResult; + }, }; _exports = exports; return exports; From 3dba5dfe81e9d17f64b62c4f6f1cebfd9dea5254 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 5 Feb 2026 10:27:11 +0900 Subject: [PATCH 2/2] - Added JSValue array support end-to-end: JS glue now lowers/lifts optional JSValue arrays, using tmp stacks and JSValue helpers, and Swift intrinsics gained Optional<[JSValue]> bridging (`Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift`, `Sources/JavaScriptKit/BridgeJSIntrinsics.swift`). - Expanded runtime coverage with JSValue array round-trips (including optionals) on both import and export paths plus JS prelude wiring and d.ts definitions (`Tests/BridgeJSRuntimeTests/ExportAPITests.swift`, `Tests/BridgeJSRuntimeTests/ImportAPITests.swift`, `Tests/BridgeJSRuntimeTests/bridge-js.d.ts`, `Tests/prelude.mjs`), and regenerated BridgeJS outputs (`Tests/BridgeJSRuntimeTests/Generated/*`, `Tests/BridgeJSGlobalTests/Generated/*`, plugin outputs). - Runtime tests now include JSValue array cases and all suites pass with the prescribed SDK. Tests: `make unittest SWIFT_SDK_ID=DEVELOPMENT-SNAPSHOT-2025-11-03-a-wasm32-unknown-wasip1`. --- .../Sources/BridgeJSLink/JSGlueGen.swift | 14 ++ .../JavaScriptKit/BridgeJSIntrinsics.swift | 44 ++++++ .../BridgeJSRuntimeTests/ExportAPITests.swift | 8 + .../Generated/BridgeJS.Macros.swift | 4 + .../Generated/BridgeJS.swift | 67 +++++++++ .../Generated/JavaScript/BridgeJS.json | 138 ++++++++++++++++++ .../BridgeJSRuntimeTests/ImportAPITests.swift | 26 ++++ Tests/BridgeJSRuntimeTests/bridge-js.d.ts | 2 + Tests/prelude.mjs | 10 ++ 9 files changed, 313 insertions(+) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index cdb23f60..60a6bd33 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -1029,6 +1029,20 @@ struct IntrinsicJSFragment: Sendable { printer.write("\(JSGlueVariableScope.reservedTmpRetF64s).push(\(payload2Var));") } printer.write("\(JSGlueVariableScope.reservedTmpRetInts).push(\(isSomeVar) ? 1 : 0);") + case .array(let elementType): + printer.write("if (\(isSomeVar)) {") + printer.indent { + let arrayLowerFragment = try! arrayLower(elementType: elementType) + let arrayCleanup = CodeFragmentPrinter() + let _ = arrayLowerFragment.printCode([value], scope, printer, arrayCleanup) + if !arrayCleanup.lines.isEmpty { + for line in arrayCleanup.lines { + printer.write(line) + } + } + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") case .rawValueEnum(_, let rawType): switch rawType { case .string: diff --git a/Sources/JavaScriptKit/BridgeJSIntrinsics.swift b/Sources/JavaScriptKit/BridgeJSIntrinsics.swift index 349cecb9..1c859272 100644 --- a/Sources/JavaScriptKit/BridgeJSIntrinsics.swift +++ b/Sources/JavaScriptKit/BridgeJSIntrinsics.swift @@ -1394,6 +1394,50 @@ extension Optional where Wrapped == JSValue { } } +extension Optional where Wrapped == [JSValue] { + // MARK: ExportSwift + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + switch consume self { + case .none: + return 0 + case .some(let array): + array.bridgeJSLowerReturn() + return 1 + } + } + + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ isSome: Int32) -> [JSValue]? { + if isSome == 0 { + return nil + } + return [JSValue].bridgeJSLiftParameter() + } + + @_spi(BridgeJS) public static func bridgeJSLiftParameter() -> [JSValue]? { + let isSome = _swift_js_pop_i32() + return bridgeJSLiftParameter(isSome) + } + + @_spi(BridgeJS) public static func bridgeJSLiftReturn() -> [JSValue]? { + let isSome = _swift_js_pop_i32() + if isSome == 0 { + return nil + } + return [JSValue].bridgeJSLiftReturn() + } + + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void { + switch consume self { + case .none: + _swift_js_push_i32(0) + case .some(let array): + array.bridgeJSLowerReturn() + _swift_js_push_i32(1) + } + } +} + extension Optional where Wrapped: _BridgedSwiftProtocolWrapper { // MARK: ExportSwift diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index fe73ea6f..05fcf146 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -60,6 +60,14 @@ func runJsWorks() -> Void return v } +@JS func roundTripJSValueArray(v: [JSValue]) -> [JSValue] { + return v +} + +@JS func roundTripOptionalJSValueArray(v: [JSValue]?) -> [JSValue]? { + return v +} + @JSClass struct Foo { @JSGetter var value: String @JSFunction init(_ value: String) throws(JSException) diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift index 9e05b3ae..675bf9df 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift @@ -20,6 +20,10 @@ @JSFunction func jsRoundTripJSValue(_ v: JSValue) throws(JSException) -> JSValue +@JSFunction func jsRoundTripJSValueArray(_ v: [JSValue]) throws(JSException) -> [JSValue] + +@JSFunction func jsRoundTripOptionalJSValueArray(_ v: Optional<[JSValue]>) throws(JSException) -> Optional<[JSValue]> + @JSFunction func jsThrowOrVoid(_ shouldThrow: Bool) throws(JSException) -> Void @JSFunction func jsThrowOrNumber(_ shouldThrow: Bool) throws(JSException) -> Double diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index 5638d044..40f1a569 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -3832,6 +3832,37 @@ public func _bjs_roundTripOptionalJSValue(_ vIsSome: Int32, _ vKind: Int32, _ vP #endif } +@_expose(wasm, "bjs_roundTripJSValueArray") +@_cdecl("bjs_roundTripJSValueArray") +public func _bjs_roundTripJSValueArray() -> Void { + #if arch(wasm32) + let ret = roundTripJSValueArray(v: [JSValue].bridgeJSLiftParameter()) + ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripOptionalJSValueArray") +@_cdecl("bjs_roundTripOptionalJSValueArray") +public func _bjs_roundTripOptionalJSValueArray(_ v: Int32) -> Void { + #if arch(wasm32) + let ret = roundTripOptionalJSValueArray(v: { + if v == 0 { + return Optional<[JSValue]>.none + } else { + return [JSValue].bridgeJSLiftParameter() + } + }()) + let __bjs_isSome_ret = ret != nil + if let __bjs_unwrapped_ret = ret { + __bjs_unwrapped_ret.bridgeJSLowerReturn()} + _swift_js_push_i32(__bjs_isSome_ret ? 1 : 0) + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_makeImportedFoo") @_cdecl("bjs_makeImportedFoo") public func _bjs_makeImportedFoo(_ valueBytes: Int32, _ valueLength: Int32) -> Int32 { @@ -8583,6 +8614,42 @@ func _$jsRoundTripJSValue(_ v: JSValue) throws(JSException) -> JSValue { return JSValue.bridgeJSLiftReturn() } +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripJSValueArray") +fileprivate func bjs_jsRoundTripJSValueArray() -> Void +#else +fileprivate func bjs_jsRoundTripJSValueArray() -> Void { + fatalError("Only available on WebAssembly") +} +#endif + +func _$jsRoundTripJSValueArray(_ v: [JSValue]) throws(JSException) -> [JSValue] { + let _ = v.bridgeJSLowerParameter() + bjs_jsRoundTripJSValueArray() + if let error = _swift_js_take_exception() { + throw error + } + return [JSValue].bridgeJSLiftReturn() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripOptionalJSValueArray") +fileprivate func bjs_jsRoundTripOptionalJSValueArray(_ v: Int32) -> Void +#else +fileprivate func bjs_jsRoundTripOptionalJSValueArray(_ v: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif + +func _$jsRoundTripOptionalJSValueArray(_ v: Optional<[JSValue]>) throws(JSException) -> Optional<[JSValue]> { + let vIsSome = v.bridgeJSLowerParameter() + bjs_jsRoundTripOptionalJSValueArray(vIsSome) + if let error = _swift_js_take_exception() { + throw error + } + return Optional<[JSValue]>.bridgeJSLiftReturn() +} + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsThrowOrVoid") fileprivate func bjs_jsThrowOrVoid(_ shouldThrow: Int32) -> Void diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index 36611579..e9591185 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -5637,6 +5637,82 @@ } } }, + { + "abiName" : "bjs_roundTripJSValueArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripJSValueArray", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + } + }, + { + "abiName" : "bjs_roundTripOptionalJSValueArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripOptionalJSValueArray", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + }, + "_1" : "null" + } + } + }, { "abiName" : "bjs_makeImportedFoo", "effects" : { @@ -12944,6 +13020,68 @@ } } }, + { + "name" : "jsRoundTripJSValueArray", + "parameters" : [ + { + "name" : "v", + "type" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + } + }, + { + "name" : "jsRoundTripOptionalJSValueArray", + "parameters" : [ + { + "name" : "v", + "type" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "jsValue" : { + + } + } + } + }, + "_1" : "null" + } + } + }, { "name" : "jsThrowOrVoid", "parameters" : [ diff --git a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift index a465c57b..43aa6d56 100644 --- a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift @@ -73,6 +73,32 @@ class ImportAPITests: XCTestCase { } } + func testRoundTripJSValueArray() throws { + let object = JSObject.global + let symbol = JSSymbol("array") + let bigInt = JSBigInt(_slowBridge: Int64(42)) + let values: [JSValue] = [ + .boolean(false), + .number(123.5), + .string(JSString("hello")), + .object(object), + .null, + .undefined, + .symbol(symbol), + .bigInt(bigInt), + ] + let roundTripped = try jsRoundTripJSValueArray(values) + XCTAssertEqual(roundTripped, values) + XCTAssertEqual(try jsRoundTripJSValueArray([]), []) + } + + func testRoundTripOptionalJSValueArray() throws { + XCTAssertNil(try jsRoundTripOptionalJSValueArray(nil)) + let values: [JSValue] = [.number(1), .undefined, .null] + let result = try jsRoundTripOptionalJSValueArray(values) + XCTAssertEqual(result, values) + } + func testRoundTripFeatureFlag() throws { for v in [FeatureFlag.foo, .bar] { try XCTAssertEqual(jsRoundTripFeatureFlag(v), v) diff --git a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts index b7dc6c48..2f01a5a6 100644 --- a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts +++ b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts @@ -6,6 +6,8 @@ export function jsRoundTripOptionalNumberNull(v: number | null): number | null export function jsRoundTripOptionalNumberUndefined(v: number | undefined): number | undefined export type JSValue = any export function jsRoundTripJSValue(v: JSValue): JSValue +export function jsRoundTripJSValueArray(v: JSValue[]): JSValue[] +export function jsRoundTripOptionalJSValueArray(v: JSValue[] | null): JSValue[] | null export function jsThrowOrVoid(shouldThrow: boolean): void export function jsThrowOrNumber(shouldThrow: boolean): number export function jsThrowOrBool(shouldThrow: boolean): boolean diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index e8f0b081..2809a96e 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -71,6 +71,12 @@ export async function setupOptions(options, context) { "jsRoundTripJSValue": (v) => { return v; }, + "jsRoundTripJSValueArray": (values) => { + return values; + }, + "jsRoundTripOptionalJSValueArray": (values) => { + return values ?? null; + }, "jsRoundTripIntArray": (items) => { return items; }, @@ -273,6 +279,10 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { assert.deepEqual(arrayStructRoundTrip.optStrings, ["a", "b"]); assert.equal(exports.arrayMembersSum(arrayStruct, [10, 20]), 30); assert.equal(exports.arrayMembersFirst(arrayStruct, ["x", "y"]), "x"); + const jsValueArray = [true, 42, "ok", { nested: 1 }, null, undefined]; + assert.deepEqual(exports.roundTripJSValueArray(jsValueArray), jsValueArray); + assert.deepEqual(exports.roundTripOptionalJSValueArray(jsValueArray), jsValueArray); + assert.equal(exports.roundTripOptionalJSValueArray(null), null); for (const p of [1, 4, 1024, 65536, 2147483647]) { assert.equal(exports.roundTripUnsafeRawPointer(p), p);