From 2e4de4fe08d5be9a1b7617de6c9287f3c6f76255 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 9 Feb 2026 12:48:00 +0900 Subject: [PATCH 1/3] BridgeJS: Explicitly reject unsupported optional wrapped types --- Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index 9402cdd9d..3205388e4 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -1230,7 +1230,7 @@ struct IntrinsicJSFragment: Sendable { printer.write("}") scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) default: - () + throw BridgeJSLinkError(message: "Unsupported wrapped type for returning from JS function: \(wrappedType)") } return [] From 8f77619d802c83abc0a0531ddaab705c39ebdd71 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 9 Feb 2026 12:56:35 +0900 Subject: [PATCH 2/3] BridgeJS: Fix handling of optional Swift heap objects in JS glue generation --- .../Sources/BridgeJSLink/JSGlueGen.swift | 2 ++ .../Inputs/MacroSwift/SwiftClass.swift | 1 + .../BridgeJSCodegenTests/SwiftClass.json | 28 +++++++++++++++++++ .../BridgeJSCodegenTests/SwiftClass.swift | 18 ++++++++++++ .../BridgeJSLinkTests/Protocol.js | 1 + .../BridgeJSLinkTests/SwiftClass.d.ts | 1 + .../BridgeJSLinkTests/SwiftClass.js | 9 ++++++ .../Generated/BridgeJS.swift | 18 ++++++++++++ .../Generated/JavaScript/BridgeJS.json | 28 +++++++++++++++++++ .../JavaScript/SwiftClassSupportTests.mjs | 3 ++ .../SwiftClassSupportTests.swift | 9 ++++++ 11 files changed, 118 insertions(+) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index 3205388e4..c502513d4 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -1120,6 +1120,8 @@ struct IntrinsicJSFragment: Sendable { scope.emitPushF64Parameter(payload2Var, printer: printer) } scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) + case .swiftHeapObject: + printer.write("return \(isSomeVar) ? \(value).pointer : 0;") case .array(let elementType): printer.write("if (\(isSomeVar)) {") try printer.indent { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftClass.swift index aa520783a..2b41d91d6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftClass.swift @@ -20,3 +20,4 @@ @JS package class PackageGreeter {} @JSFunction func jsRoundTripGreeter(greeter: Greeter) throws(JSException) -> Greeter +@JSFunction func jsRoundTripOptionalGreeter(greeter: Greeter?) throws(JSException) -> Greeter? \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClass.json index d1bf42cff..7cebdd5e6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClass.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClass.json @@ -162,6 +162,34 @@ "_0" : "Greeter" } } + }, + { + "name" : "jsRoundTripOptionalGreeter", + "parameters" : [ + { + "name" : "greeter", + "type" : { + "nullable" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + }, + "_1" : "null" + } + } } ], "types" : [ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClass.swift index 581d6f1c0..ca82b87c4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClass.swift @@ -152,4 +152,22 @@ func _$jsRoundTripGreeter(_ greeter: Greeter) throws(JSException) -> Greeter { throw error } return Greeter.bridgeJSLiftReturn(ret) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_jsRoundTripOptionalGreeter") +fileprivate func bjs_jsRoundTripOptionalGreeter(_ greeterIsSome: Int32, _ greeterPointer: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer +#else +fileprivate func bjs_jsRoundTripOptionalGreeter(_ greeterIsSome: Int32, _ greeterPointer: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + fatalError("Only available on WebAssembly") +} +#endif + +func _$jsRoundTripOptionalGreeter(_ greeter: Optional) throws(JSException) -> Optional { + let (greeterIsSome, greeterPointer) = greeter.bridgeJSLowerParameter() + let ret = bjs_jsRoundTripOptionalGreeter(greeterIsSome, greeterPointer) + if let error = _swift_js_take_exception() { + throw error + } + return Optional.bridgeJSLiftReturn(ret) } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js index 00b9626e3..4d9f3e0bb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js @@ -538,6 +538,7 @@ export async function createInstantiator(options, swift) { try { let ret = swift.memory.getObject(self).createOptionalHelper(); const isSome = ret != null; + return isSome ? ret.pointer : 0; } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.d.ts index d3461f8c1..05fc97fee 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.d.ts @@ -32,6 +32,7 @@ export type Exports = { } export type Imports = { jsRoundTripGreeter(greeter: Greeter): Greeter; + jsRoundTripOptionalGreeter(greeter: Greeter | null): Greeter | null; } export function createInstantiator(options: { imports: Imports; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js index eaa5d7720..85d423f8a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js @@ -225,6 +225,15 @@ export async function createInstantiator(options, swift) { return 0 } } + TestModule["bjs_jsRoundTripOptionalGreeter"] = function bjs_jsRoundTripOptionalGreeter(greeterIsSome, greeterWrappedValue) { + try { + let ret = imports.jsRoundTripOptionalGreeter(greeterIsSome ? Greeter.__construct(greeterWrappedValue) : null); + const isSome = ret != null; + return isSome ? ret.pointer : 0; + } catch (error) { + setException(error); + } + } }, setInstance: (i) => { instance = i; diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index 1feb3eed6..b10a82fdf 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -10677,6 +10677,15 @@ fileprivate func bjs_SwiftClassSupportImports_jsRoundTripGreeter_static(_ greete } #endif +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_SwiftClassSupportImports_jsRoundTripOptionalGreeter_static") +fileprivate func bjs_SwiftClassSupportImports_jsRoundTripOptionalGreeter_static(_ greeterIsSome: Int32, _ greeterPointer: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer +#else +fileprivate func bjs_SwiftClassSupportImports_jsRoundTripOptionalGreeter_static(_ greeterIsSome: Int32, _ greeterPointer: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + fatalError("Only available on WebAssembly") +} +#endif + func _$SwiftClassSupportImports_jsRoundTripGreeter(_ greeter: Greeter) throws(JSException) -> Greeter { let greeterPointer = greeter.bridgeJSLowerParameter() let ret = bjs_SwiftClassSupportImports_jsRoundTripGreeter_static(greeterPointer) @@ -10684,4 +10693,13 @@ func _$SwiftClassSupportImports_jsRoundTripGreeter(_ greeter: Greeter) throws(JS throw error } return Greeter.bridgeJSLiftReturn(ret) +} + +func _$SwiftClassSupportImports_jsRoundTripOptionalGreeter(_ greeter: Optional) throws(JSException) -> Optional { + let (greeterIsSome, greeterPointer) = greeter.bridgeJSLowerParameter() + let ret = bjs_SwiftClassSupportImports_jsRoundTripOptionalGreeter_static(greeterIsSome, greeterPointer) + if let error = _swift_js_take_exception() { + throw error + } + return Optional.bridgeJSLiftReturn(ret) } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index de7ab4a5d..7e2d4d855 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -15345,6 +15345,34 @@ "_0" : "Greeter" } } + }, + { + "name" : "jsRoundTripOptionalGreeter", + "parameters" : [ + { + "name" : "greeter", + "type" : { + "nullable" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + }, + "_1" : "null" + } + } } ] } diff --git a/Tests/BridgeJSRuntimeTests/JavaScript/SwiftClassSupportTests.mjs b/Tests/BridgeJSRuntimeTests/JavaScript/SwiftClassSupportTests.mjs index fdb5d498d..0368cb74a 100644 --- a/Tests/BridgeJSRuntimeTests/JavaScript/SwiftClassSupportTests.mjs +++ b/Tests/BridgeJSRuntimeTests/JavaScript/SwiftClassSupportTests.mjs @@ -6,5 +6,8 @@ export function getImports(importsContext) { jsRoundTripGreeter: (greeter) => { return greeter; }, + jsRoundTripOptionalGreeter: (greeter) => { + return greeter; + }, }; } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/SwiftClassSupportTests.swift b/Tests/BridgeJSRuntimeTests/SwiftClassSupportTests.swift index 3cee17554..1a3dd0a4f 100644 --- a/Tests/BridgeJSRuntimeTests/SwiftClassSupportTests.swift +++ b/Tests/BridgeJSRuntimeTests/SwiftClassSupportTests.swift @@ -3,6 +3,7 @@ import XCTest @JSClass struct SwiftClassSupportImports { @JSFunction static func jsRoundTripGreeter(_ greeter: Greeter) throws(JSException) -> Greeter + @JSFunction static func jsRoundTripOptionalGreeter(_ greeter: Greeter?) throws(JSException) -> Greeter? } final class SwiftClassSupportTests: XCTestCase { @@ -10,4 +11,12 @@ final class SwiftClassSupportTests: XCTestCase { let greeter = try SwiftClassSupportImports.jsRoundTripGreeter(Greeter(name: "Hello")) XCTAssertEqual(greeter.name, "Hello") } + + func testRoundTripOptionalGreeter() throws { + let greeter1 = try SwiftClassSupportImports.jsRoundTripOptionalGreeter(nil) + XCTAssertNil(greeter1) + + let greeter2 = try SwiftClassSupportImports.jsRoundTripOptionalGreeter(Greeter(name: "Hello")) + XCTAssertEqual(greeter2?.name, "Hello") + } } From 4c0a345b09923faa97ae280a2140830a01cbd1ad Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 9 Feb 2026 12:58:39 +0900 Subject: [PATCH 3/3] BridgeJS: Fix optional Swift heap object for parameter lifting in JS glue --- Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift | 6 ++++-- .../BridgeJSToolTests/Inputs/MacroSwift/SwiftClass.swift | 2 +- .../__Snapshots__/BridgeJSLinkTests/Protocol.js | 2 +- .../__Snapshots__/BridgeJSLinkTests/SwiftClass.js | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index c502513d4..db900cf99 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -668,7 +668,7 @@ struct IntrinsicJSFragment: Sendable { resultExpr = "\(isSome) ? \(JSGlueVariableScope.reservedSwift).memory.getObject(\(wrappedValue)) : \(absenceLiteral)" case .swiftHeapObject(let name): - resultExpr = "\(isSome) ? \(name).__construct(\(wrappedValue)) : \(absenceLiteral)" + resultExpr = "\(isSome) ? _exports['\(name)'].__construct(\(wrappedValue)) : \(absenceLiteral)" case .jsObject: resultExpr = "\(isSome) ? \(JSGlueVariableScope.reservedSwift).memory.getObject(\(wrappedValue)) : \(absenceLiteral)" @@ -1232,7 +1232,9 @@ struct IntrinsicJSFragment: Sendable { printer.write("}") scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) default: - throw BridgeJSLinkError(message: "Unsupported wrapped type for returning from JS function: \(wrappedType)") + throw BridgeJSLinkError( + message: "Unsupported wrapped type for returning from JS function: \(wrappedType)" + ) } return [] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftClass.swift index 2b41d91d6..d7b5a5b8e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftClass.swift @@ -20,4 +20,4 @@ @JS package class PackageGreeter {} @JSFunction func jsRoundTripGreeter(greeter: Greeter) throws(JSException) -> Greeter -@JSFunction func jsRoundTripOptionalGreeter(greeter: Greeter?) throws(JSException) -> Greeter? \ No newline at end of file +@JSFunction func jsRoundTripOptionalGreeter(greeter: Greeter?) throws(JSException) -> Greeter? diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js index 4d9f3e0bb..fd37c7178 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js @@ -529,7 +529,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_onOptionalHelperUpdated"] = function bjs_MyViewControllerDelegate_onOptionalHelperUpdated(self, helperIsSome, helperWrappedValue) { try { - swift.memory.getObject(self).onOptionalHelperUpdated(helperIsSome ? Helper.__construct(helperWrappedValue) : null); + swift.memory.getObject(self).onOptionalHelperUpdated(helperIsSome ? _exports['Helper'].__construct(helperWrappedValue) : null); } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js index 85d423f8a..13ea6ba75 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js @@ -227,7 +227,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_jsRoundTripOptionalGreeter"] = function bjs_jsRoundTripOptionalGreeter(greeterIsSome, greeterWrappedValue) { try { - let ret = imports.jsRoundTripOptionalGreeter(greeterIsSome ? Greeter.__construct(greeterWrappedValue) : null); + let ret = imports.jsRoundTripOptionalGreeter(greeterIsSome ? _exports['Greeter'].__construct(greeterWrappedValue) : null); const isSome = ret != null; return isSome ? ret.pointer : 0; } catch (error) {