From 5ceeb0802d71c59e126d58ea8cea7f97c6c61e35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 08:56:02 +0000 Subject: [PATCH 1/6] Bump vite from 7.3.1 to 7.3.2 (#712) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.1 to 7.3.2. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 7.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5eab62401..1c95a1ce2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1582,9 +1582,9 @@ "dev": true }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", "dependencies": { From 76543856201aca11bec46c3cb1cbd9c619ac3d4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:19:19 +0100 Subject: [PATCH 2/6] Bump actions/deploy-pages from 4 to 5 (#710) Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 4 to 5. - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](https://github.com/actions/deploy-pages/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/deploy-pages dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 419c772a0..e8118a481 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -199,4 +199,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 From af88676816797ac9e4ab68870813999a8267ce28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:19:55 +0100 Subject: [PATCH 3/6] Bump picomatch from 4.0.3 to 4.0.4 (#713) Bumps [picomatch](https://github.com/micromatch/picomatch) from 4.0.3 to 4.0.4. - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4) --- updated-dependencies: - dependency-name: picomatch dependency-version: 4.0.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1c95a1ce2..ea24d1645 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1289,9 +1289,9 @@ "dev": true }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { From 9f149ccbae947e79c1d4607a941dba06b5c55947 Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Thu, 9 Apr 2026 12:35:21 +0200 Subject: [PATCH 4/6] BridgeJS: Fix for-loop emission in stack codegen Three `StackCodegen` helpers built their `for`-loops from separate `CodeBlockItemSyntax` fragments ("for ... {", body, "}"). swift-syntax 603's formatter then renders such partial statements with the closing brace glued to the previous line. Combine each for-loop into a single multi-line `CodeBlockItemSyntax` so the formatter produces consistent output across swift-syntax 600-603. Also fix a latent bug in `StructCodegen.generateStructLowerCode` and `EnumCodegen.generatePayloadPushingCode`: they concatenated lowering statements via `CodeBlockItemListSyntax(statements).description` which does not insert separators between items that lack leading trivia. The thunk builder path was fine because `append` explicitly adds `.newline` trivia, but any multi-statement lowering routed through a struct field or enum payload was silently glued onto a single line. Iterate over the statements and write each one individually through the printer. Add regression coverage for the previously untested codepaths: - `[String: MyProtocol]` as a function return, class property getter, and parameter (exercises `lowerProtocolDictionaryStatements`). - A `@JS struct` field of type `[String: Int?]` (exercises `lowerDictionaryStatementsInline` via `generateStructLowerCode`). --- .../Sources/BridgeJSCore/ExportSwift.swift | 97 +++++++++++-------- .../Inputs/MacroSwift/DictionaryTypes.swift | 6 ++ .../Inputs/MacroSwift/Protocol.swift | 8 ++ .../BridgeJSCodegenTests/DictionaryTypes.json | 65 +++++++++++++ .../DictionaryTypes.swift | 65 +++++++++++++ .../BridgeJSCodegenTests/Protocol.json | 47 +++++++++ .../BridgeJSCodegenTests/Protocol.swift | 46 ++++++++- .../BridgeJSLinkTests/DictionaryTypes.d.ts | 5 + .../BridgeJSLinkTests/DictionaryTypes.js | 56 +++++++++++ .../BridgeJSLinkTests/Protocol.d.ts | 2 + .../BridgeJSLinkTests/Protocol.js | 51 ++++++++++ .../Generated/BridgeJS.swift | 11 ++- 12 files changed, 411 insertions(+), 48 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index ca7bdc3c2..f7824711f 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -827,15 +827,15 @@ struct StackCodegen { accessor: String, varPrefix: String ) -> [CodeBlockItemSyntax] { - var statements: [CodeBlockItemSyntax] = [] let elemVar = "__bjs_elem_\(varPrefix)" - statements.append("for \(raw: elemVar) in \(raw: accessor) {") - statements.append( - " _swift_js_push_i32((\(raw: elemVar) as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())" - ) - statements.append("}") - statements.append("_swift_js_push_i32(Int32(\(raw: accessor).count))") - return statements + return [ + """ + for \(raw: elemVar) in \(raw: accessor) { + _swift_js_push_i32((\(raw: elemVar) as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn()) + } + """, + "_swift_js_push_i32(Int32(\(raw: accessor).count))", + ] } private func lowerDictionaryStatements( @@ -866,49 +866,58 @@ struct StackCodegen { accessor: String, varPrefix: String ) -> [CodeBlockItemSyntax] { - var statements: [CodeBlockItemSyntax] = [] let pairVarName = "__bjs_kv_\(varPrefix)" - statements.append("for \(raw: pairVarName) in \(raw: accessor) {") - statements.append("let __bjs_key_\(raw: varPrefix) = \(raw: pairVarName).key") - statements.append("let __bjs_value_\(raw: varPrefix) = \(raw: pairVarName).value") - - let keyStatements = lowerStatements( - for: .string, - accessor: "__bjs_key_\(varPrefix)", - varPrefix: "\(varPrefix)_key" + let keyVarName = "__bjs_key_\(varPrefix)" + let valueVarName = "__bjs_value_\(varPrefix)" + + // The dispatch in `lowerDictionaryStatements` routes only .nullable and .closure value + // types into this helper, both of which produce single-line lowering statements, so we can + // join their descriptions into the for-body as plain lines without worrying about nested + // multi-statement lowering. + var bodyLines: [String] = [ + "let \(keyVarName) = \(pairVarName).key", + "let \(valueVarName) = \(pairVarName).value", + ] + bodyLines.append( + contentsOf: lowerStatements( + for: .string, + accessor: keyVarName, + varPrefix: "\(varPrefix)_key" + ).map { $0.description } ) - for stmt in keyStatements { - statements.append(stmt) - } - - let valueStatements = lowerStatements( - for: valueType, - accessor: "__bjs_value_\(varPrefix)", - varPrefix: "\(varPrefix)_value" + bodyLines.append( + contentsOf: lowerStatements( + for: valueType, + accessor: valueVarName, + varPrefix: "\(varPrefix)_value" + ).map { $0.description } ) - for stmt in valueStatements { - statements.append(stmt) - } + let body = bodyLines.joined(separator: "\n ") - statements.append("}") - statements.append("_swift_js_push_i32(Int32(\(raw: accessor).count))") - return statements + return [ + """ + for \(raw: pairVarName) in \(raw: accessor) { + \(raw: body) + } + """, + "_swift_js_push_i32(Int32(\(raw: accessor).count))", + ] } private func lowerProtocolDictionaryStatements( accessor: String, varPrefix: String ) -> [CodeBlockItemSyntax] { - var statements: [CodeBlockItemSyntax] = [] let pairVar = "__bjs_kv_\(varPrefix)" - statements.append("for \(raw: pairVar) in \(raw: accessor) {") - statements.append(" \(raw: pairVar).key.bridgeJSStackPush()") - statements.append( - " _swift_js_push_i32((\(raw: pairVar).value as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())" - ) - statements.append("}") - statements.append("_swift_js_push_i32(Int32(\(raw: accessor).count))") - return statements + return [ + """ + for \(raw: pairVar) in \(raw: accessor) { + \(raw: pairVar).key.bridgeJSStackPush() + _swift_js_push_i32((\(raw: pairVar).value as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn()) + } + """, + "_swift_js_push_i32(Int32(\(raw: accessor).count))", + ] } } @@ -1073,7 +1082,9 @@ struct EnumCodegen { accessor: paramName, varPrefix: paramName ) - printer.write(multilineString: CodeBlockItemListSyntax(statements).description) + for statement in statements { + printer.write(multilineString: statement.description) + } } } @@ -1207,7 +1218,9 @@ struct StructCodegen { accessor: accessor, varPrefix: property.name ) - printer.write(multilineString: CodeBlockItemListSyntax(statements).description) + for statement in statements { + printer.write(multilineString: statement.description) + } } return printer.lines diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/DictionaryTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/DictionaryTypes.swift index c699ea79b..a45bf1dd8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/DictionaryTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/DictionaryTypes.swift @@ -6,10 +6,16 @@ } } +@JS struct Counters { + var name: String + var counts: [String: Int?] +} + @JS func mirrorDictionary(_ values: [String: Int]) -> [String: Int] @JS func optionalDictionary(_ values: [String: String]?) -> [String: String]? @JS func nestedDictionary(_ values: [String: [Int]]) -> [String: [Int]] @JS func boxDictionary(_ boxes: [String: Box]) -> [String: Box] @JS func optionalBoxDictionary(_ boxes: [String: Box?]) -> [String: Box?] +@JS func roundtripCounters(_ counters: Counters) -> Counters @JSFunction func importMirrorDictionary(_ values: [String: Double]) throws(JSException) -> [String: Double] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Protocol.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Protocol.swift index fbbad0615..bdb700d69 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Protocol.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Protocol.swift @@ -102,8 +102,12 @@ import JavaScriptKit @JS var delegates: [MyViewControllerDelegate] + @JS + var delegatesByName: [String: MyViewControllerDelegate] + @JS init(delegates: [MyViewControllerDelegate]) { self.delegates = delegates + self.delegatesByName = [:] } @JS func notifyAll() { @@ -114,3 +118,7 @@ import JavaScriptKit } @JS func processDelegates(_ delegates: [MyViewControllerDelegate]) -> [MyViewControllerDelegate] + +@JS func processDelegatesByName( + _ delegates: [String: MyViewControllerDelegate] +) -> [String: MyViewControllerDelegate] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.json index ea707098d..e18586e1c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.json @@ -221,13 +221,78 @@ } } } + }, + { + "abiName" : "bjs_roundtripCounters", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripCounters", + "parameters" : [ + { + "label" : "_", + "name" : "counters", + "type" : { + "swiftStruct" : { + "_0" : "Counters" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Counters" + } + } } ], "protocols" : [ ], "structs" : [ + { + "methods" : [ + + ], + "name" : "Counters", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "counts", + "type" : { + "dictionary" : { + "_0" : { + "nullable" : { + "_0" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + }, + "_1" : "null" + } + } + } + } + } + ], + "swiftCallName" : "Counters" + } ] }, "imported" : { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.swift index dc0a54bcf..26a4c087e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.swift @@ -1,3 +1,57 @@ +extension Counters: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> Counters { + let counts = [String: Optional].bridgeJSStackPop() + let name = String.bridgeJSStackPop() + return Counters(name: name, counts: counts) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.name.bridgeJSStackPush() + for __bjs_kv_counts in self.counts { + let __bjs_key_counts = __bjs_kv_counts.key + let __bjs_value_counts = __bjs_kv_counts.value + __bjs_key_counts.bridgeJSStackPush() + __bjs_value_counts.bridgeJSStackPush() + } + _swift_js_push_i32(Int32(self.counts.count)) + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_Counters(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_Counters())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_Counters") +fileprivate func _bjs_struct_lower_Counters_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_Counters_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_Counters(_ objectId: Int32) -> Void { + return _bjs_struct_lower_Counters_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_Counters") +fileprivate func _bjs_struct_lift_Counters_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_Counters_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_Counters() -> Int32 { + return _bjs_struct_lift_Counters_extern() +} + @_expose(wasm, "bjs_mirrorDictionary") @_cdecl("bjs_mirrorDictionary") public func _bjs_mirrorDictionary() -> Void { @@ -53,6 +107,17 @@ public func _bjs_optionalBoxDictionary() -> Void { #endif } +@_expose(wasm, "bjs_roundtripCounters") +@_cdecl("bjs_roundtripCounters") +public func _bjs_roundtripCounters() -> Void { + #if arch(wasm32) + let ret = roundtripCounters(_: Counters.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_Box_deinit") @_cdecl("bjs_Box_deinit") public func _bjs_Box_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.json index 757115c59..b46d1125e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.json @@ -317,6 +317,20 @@ } } } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "delegatesByName", + "type" : { + "dictionary" : { + "_0" : { + "swiftProtocol" : { + "_0" : "MyViewControllerDelegate" + } + } + } + } } ], "swiftCallName" : "DelegateManager" @@ -502,6 +516,39 @@ } } } + }, + { + "abiName" : "bjs_processDelegatesByName", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "processDelegatesByName", + "parameters" : [ + { + "label" : "_", + "name" : "delegates", + "type" : { + "dictionary" : { + "_0" : { + "swiftProtocol" : { + "_0" : "MyViewControllerDelegate" + } + } + } + } + } + ], + "returnType" : { + "dictionary" : { + "_0" : { + "swiftProtocol" : { + "_0" : "MyViewControllerDelegate" + } + } + } + } } ], "protocols" : [ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift index cf7464cc3..745ec6cc9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift @@ -698,7 +698,23 @@ public func _bjs_processDelegates() -> Void { #if arch(wasm32) let ret = processDelegates(_: [AnyMyViewControllerDelegate].bridgeJSStackPop()) for __bjs_elem_ret in ret { - _swift_js_push_i32((__bjs_elem_ret as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())} + _swift_js_push_i32((__bjs_elem_ret as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn()) + } + _swift_js_push_i32(Int32(ret.count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processDelegatesByName") +@_cdecl("bjs_processDelegatesByName") +public func _bjs_processDelegatesByName() -> Void { + #if arch(wasm32) + let ret = processDelegatesByName(_: [String: AnyMyViewControllerDelegate].bridgeJSLiftParameter()) + for __bjs_kv_ret in ret { + __bjs_kv_ret.key.bridgeJSStackPush() + _swift_js_push_i32((__bjs_kv_ret.value as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn()) + } _swift_js_push_i32(Int32(ret.count)) #else fatalError("Only available on WebAssembly") @@ -955,7 +971,8 @@ public func _bjs_DelegateManager_delegates_get(_ _self: UnsafeMutableRawPointer) #if arch(wasm32) let ret = DelegateManager.bridgeJSLiftParameter(_self).delegates for __bjs_elem_ret in ret { - _swift_js_push_i32((__bjs_elem_ret as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())} + _swift_js_push_i32((__bjs_elem_ret as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn()) + } _swift_js_push_i32(Int32(ret.count)) #else fatalError("Only available on WebAssembly") @@ -972,6 +989,31 @@ public func _bjs_DelegateManager_delegates_set(_ _self: UnsafeMutableRawPointer) #endif } +@_expose(wasm, "bjs_DelegateManager_delegatesByName_get") +@_cdecl("bjs_DelegateManager_delegatesByName_get") +public func _bjs_DelegateManager_delegatesByName_get(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = DelegateManager.bridgeJSLiftParameter(_self).delegatesByName + for __bjs_kv_ret in ret { + __bjs_kv_ret.key.bridgeJSStackPush() + _swift_js_push_i32((__bjs_kv_ret.value as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn()) + } + _swift_js_push_i32(Int32(ret.count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_DelegateManager_delegatesByName_set") +@_cdecl("bjs_DelegateManager_delegatesByName_set") +public func _bjs_DelegateManager_delegatesByName_set(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + DelegateManager.bridgeJSLiftParameter(_self).delegatesByName = [String: AnyMyViewControllerDelegate].bridgeJSLiftParameter() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_DelegateManager_deinit") @_cdecl("bjs_DelegateManager_deinit") public func _bjs_DelegateManager_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.d.ts index dadcc74ba..f14b29aa4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.d.ts @@ -4,6 +4,10 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. +export interface Counters { + name: string; + counts: Record; +} /// Represents a Swift heap object like a class instance or an actor instance. export interface SwiftHeapObject { /// Release the heap object. @@ -21,6 +25,7 @@ export type Exports = { nestedDictionary(values: Record): Record; boxDictionary(boxes: Record): Record; optionalBoxDictionary(boxes: Record): Record; + roundtripCounters(counters: Counters): Counters; } export type Imports = { importMirrorDictionary(values: Record): Record; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js index 1e9059e34..b6cf2c253 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js @@ -30,6 +30,46 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; + const __bjs_createCountersHelpers = () => ({ + lower: (value) => { + const bytes = textEncoder.encode(value.name); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + const entries = Object.entries(value.counts); + for (const entry of entries) { + const [key, value] = entry; + const bytes1 = textEncoder.encode(key); + const id1 = swift.memory.retain(bytes1); + i32Stack.push(bytes1.length); + i32Stack.push(id1); + const isSome = value != null ? 1 : 0; + if (isSome) { + i32Stack.push((value | 0)); + } + i32Stack.push(isSome); + } + i32Stack.push(entries.length); + }, + lift: () => { + const dictLen = i32Stack.pop(); + const dictResult = {}; + for (let i = 0; i < dictLen; i++) { + const isSome = i32Stack.pop(); + let optValue; + if (isSome === 0) { + optValue = null; + } else { + const int = i32Stack.pop(); + optValue = int; + } + const string = strStack.pop(); + dictResult[string] = optValue; + } + const string1 = strStack.pop(); + return { name: string1, counts: dictResult }; + } + }); return { /** @@ -99,6 +139,13 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_i64"] = function() { return i64Stack.pop(); } + bjs["swift_js_struct_lower_Counters"] = function(objectId) { + structHelpers.Counters.lower(swift.memory.getObject(objectId)); + } + bjs["swift_js_struct_lift_Counters"] = function() { + const value = structHelpers.Counters.lift(); + return swift.memory.retain(value); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; @@ -271,6 +318,9 @@ export async function createInstantiator(options, swift) { } } + const CountersHelpers = __bjs_createCountersHelpers(); + structHelpers.Counters = CountersHelpers; + const exports = { Box, mirrorDictionary: function bjs_mirrorDictionary(values) { @@ -414,6 +464,12 @@ export async function createInstantiator(options, swift) { } return dictResult; }, + roundtripCounters: function bjs_roundtripCounters(counters) { + structHelpers.Counters.lower(counters); + instance.exports.bjs_roundtripCounters(); + const structValue = structHelpers.Counters.lift(); + return structValue; + }, }; _exports = exports; return exports; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts index 4c09ad85f..27cd9212b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts @@ -93,6 +93,7 @@ export interface MyViewController extends SwiftHeapObject { export interface DelegateManager extends SwiftHeapObject { notifyAll(): void; delegates: MyViewControllerDelegate[]; + delegatesByName: Record; } export type Exports = { Helper: { @@ -105,6 +106,7 @@ export type Exports = { new(delegates: MyViewControllerDelegate[]): DelegateManager; } processDelegates(delegates: MyViewControllerDelegate[]): MyViewControllerDelegate[]; + processDelegatesByName(delegates: Record): Record; Direction: DirectionObject ExampleEnum: ExampleEnumObject Result: ResultObject diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js index e606c3a77..f82e41703 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js @@ -718,6 +718,33 @@ export async function createInstantiator(options, swift) { i32Stack.push(value.length); instance.exports.bjs_DelegateManager_delegates_set(this.pointer); } + get delegatesByName() { + instance.exports.bjs_DelegateManager_delegatesByName_get(this.pointer); + const dictLen = i32Stack.pop(); + const dictResult = {}; + for (let i = 0; i < dictLen; i++) { + const objId = i32Stack.pop(); + const obj = swift.memory.getObject(objId); + swift.memory.release(objId); + const string = strStack.pop(); + dictResult[string] = obj; + } + return dictResult; + } + set delegatesByName(value) { + const entries = Object.entries(value); + for (const entry of entries) { + const [key, value1] = entry; + const bytes = textEncoder.encode(key); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + const objId = swift.memory.retain(value1); + i32Stack.push(objId); + } + i32Stack.push(entries.length); + instance.exports.bjs_DelegateManager_delegatesByName_set(this.pointer); + } } const ResultHelpers = __bjs_createResultValuesHelpers(); enumHelpers.Result = ResultHelpers; @@ -744,6 +771,30 @@ export async function createInstantiator(options, swift) { arrayResult.reverse(); return arrayResult; }, + processDelegatesByName: function bjs_processDelegatesByName(delegates) { + const entries = Object.entries(delegates); + for (const entry of entries) { + const [key, value] = entry; + const bytes = textEncoder.encode(key); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + const objId = swift.memory.retain(value); + i32Stack.push(objId); + } + i32Stack.push(entries.length); + instance.exports.bjs_processDelegatesByName(); + const dictLen = i32Stack.pop(); + const dictResult = {}; + for (let i = 0; i < dictLen; i++) { + const objId1 = i32Stack.pop(); + const obj = swift.memory.getObject(objId1); + swift.memory.release(objId1); + const string = strStack.pop(); + dictResult[string] = obj; + } + return dictResult; + }, Direction: DirectionValues, ExampleEnum: ExampleEnumValues, Result: ResultValues, diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index 71e5ef537..4f5b6e477 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -3420,7 +3420,8 @@ public func _bjs_ArraySupportExports_static_roundTripProtocolArray() -> Void { #if arch(wasm32) let ret = ArraySupportExports.roundTripProtocolArray(_: [AnyArrayElementProtocol].bridgeJSStackPop()) for __bjs_elem_ret in ret { - _swift_js_push_i32((__bjs_elem_ret as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())} + _swift_js_push_i32((__bjs_elem_ret as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn()) + } _swift_js_push_i32(Int32(ret.count)) #else fatalError("Only available on WebAssembly") @@ -10089,7 +10090,8 @@ public func _bjs_ProtocolReturnTests_static_createNativeProcessorArray() -> Void #if arch(wasm32) let ret = ProtocolReturnTests.createNativeProcessorArray() for __bjs_elem_ret in ret { - _swift_js_push_i32((__bjs_elem_ret as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())} + _swift_js_push_i32((__bjs_elem_ret as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn()) + } _swift_js_push_i32(Int32(ret.count)) #else fatalError("Only available on WebAssembly") @@ -10102,8 +10104,9 @@ public func _bjs_ProtocolReturnTests_static_createNativeProcessorDictionary() -> #if arch(wasm32) let ret = ProtocolReturnTests.createNativeProcessorDictionary() for __bjs_kv_ret in ret { - __bjs_kv_ret.key.bridgeJSStackPush() - _swift_js_push_i32((__bjs_kv_ret.value as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())} + __bjs_kv_ret.key.bridgeJSStackPush() + _swift_js_push_i32((__bjs_kv_ret.value as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn()) + } _swift_js_push_i32(Int32(ret.count)) #else fatalError("Only available on WebAssembly") From 9f2b43213a5a1b33489a476a01555903f50a32a4 Mon Sep 17 00:00:00 2001 From: Stephan Diederich Date: Thu, 9 Apr 2026 13:32:20 +0200 Subject: [PATCH 5/6] flip `Package.swift` file versioning (#715) makes `Package.swift` be the newest, while supporting older versions with concrete versions baked in as mentioned in https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/swiftversionspecificpackaging/#Version-specific-Manifest-Selection --- Package.swift | 19 +++++++++++++++---- ...swift-6.2.swift => Package@swift-6.1.swift | 19 ++++--------------- 2 files changed, 19 insertions(+), 19 deletions(-) rename Package@swift-6.2.swift => Package@swift-6.1.swift (91%) diff --git a/Package.swift b/Package.swift index 60adb7a5f..20583865b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:6.1 +// swift-tools-version:6.2 import CompilerPluginSupport import PackageDescription @@ -7,6 +7,13 @@ import PackageDescription let shouldBuildForEmbedded = Context.environment["JAVASCRIPTKIT_EXPERIMENTAL_EMBEDDED_WASM"].flatMap(Bool.init) ?? false let useLegacyResourceBundling = Context.environment["JAVASCRIPTKIT_USE_LEGACY_RESOURCE_BUNDLING"].flatMap(Bool.init) ?? false +let enableTracingByEnv = Context.environment["JAVASCRIPTKIT_ENABLE_TRACING"].flatMap(Bool.init) ?? false + +let tracingTrait = Trait( + name: "Tracing", + description: "Enable opt-in Swift <-> JavaScript bridge tracing hooks.", + enabledTraits: [] +) let testingLinkerFlags: [LinkerSetting] = [ .unsafeFlags( @@ -39,6 +46,7 @@ let package = Package( .plugin(name: "BridgeJS", targets: ["BridgeJS"]), .plugin(name: "BridgeJSCommandPlugin", targets: ["BridgeJSCommandPlugin"]), ], + traits: [tracingTrait], dependencies: [ .package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0"..<"603.0.0") ], @@ -53,8 +61,10 @@ let package = Package( .unsafeFlags(["-fdeclspec"]) ] : nil, swiftSettings: [ - .enableExperimentalFeature("Extern") + .enableExperimentalFeature("Extern"), + .define("Tracing", .when(traits: ["Tracing"])), ] + + (enableTracingByEnv ? [.define("Tracing")] : []) + (shouldBuildForEmbedded ? [ .enableExperimentalFeature("Embedded"), @@ -74,8 +84,9 @@ let package = Package( name: "JavaScriptKitTests", dependencies: ["JavaScriptKit"], swiftSettings: [ - .enableExperimentalFeature("Extern") - ], + .enableExperimentalFeature("Extern"), + .define("Tracing", .when(traits: ["Tracing"])), + ] + (enableTracingByEnv ? [.define("Tracing")] : []), linkerSettings: testingLinkerFlags ), diff --git a/Package@swift-6.2.swift b/Package@swift-6.1.swift similarity index 91% rename from Package@swift-6.2.swift rename to Package@swift-6.1.swift index 20583865b..60adb7a5f 100644 --- a/Package@swift-6.2.swift +++ b/Package@swift-6.1.swift @@ -1,4 +1,4 @@ -// swift-tools-version:6.2 +// swift-tools-version:6.1 import CompilerPluginSupport import PackageDescription @@ -7,13 +7,6 @@ import PackageDescription let shouldBuildForEmbedded = Context.environment["JAVASCRIPTKIT_EXPERIMENTAL_EMBEDDED_WASM"].flatMap(Bool.init) ?? false let useLegacyResourceBundling = Context.environment["JAVASCRIPTKIT_USE_LEGACY_RESOURCE_BUNDLING"].flatMap(Bool.init) ?? false -let enableTracingByEnv = Context.environment["JAVASCRIPTKIT_ENABLE_TRACING"].flatMap(Bool.init) ?? false - -let tracingTrait = Trait( - name: "Tracing", - description: "Enable opt-in Swift <-> JavaScript bridge tracing hooks.", - enabledTraits: [] -) let testingLinkerFlags: [LinkerSetting] = [ .unsafeFlags( @@ -46,7 +39,6 @@ let package = Package( .plugin(name: "BridgeJS", targets: ["BridgeJS"]), .plugin(name: "BridgeJSCommandPlugin", targets: ["BridgeJSCommandPlugin"]), ], - traits: [tracingTrait], dependencies: [ .package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0"..<"603.0.0") ], @@ -61,10 +53,8 @@ let package = Package( .unsafeFlags(["-fdeclspec"]) ] : nil, swiftSettings: [ - .enableExperimentalFeature("Extern"), - .define("Tracing", .when(traits: ["Tracing"])), + .enableExperimentalFeature("Extern") ] - + (enableTracingByEnv ? [.define("Tracing")] : []) + (shouldBuildForEmbedded ? [ .enableExperimentalFeature("Embedded"), @@ -84,9 +74,8 @@ let package = Package( name: "JavaScriptKitTests", dependencies: ["JavaScriptKit"], swiftSettings: [ - .enableExperimentalFeature("Extern"), - .define("Tracing", .when(traits: ["Tracing"])), - ] + (enableTracingByEnv ? [.define("Tracing")] : []), + .enableExperimentalFeature("Extern") + ], linkerSettings: testingLinkerFlags ), From 0d6544f95427d6c1689b4908f961c0d5f566d5aa Mon Sep 17 00:00:00 2001 From: Stephan Diederich Date: Thu, 9 Apr 2026 14:46:49 +0200 Subject: [PATCH 6/6] relax swift-syntax version constraint to allow 603 (#714) * relax swift-syntax version constraint to allow 603 Packages are updating to 603 and this can fail the dependency solver. * .. and also test against it --- .github/workflows/test.yml | 2 ++ Package.swift | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e8118a481..c3c19b1c4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -81,6 +81,8 @@ jobs: swift-syntax-version: "601.0.0" - image: "swift:6.2" swift-syntax-version: "602.0.0" + - image: "swift:6.3" + swift-syntax-version: "603.0.0" runs-on: ubuntu-latest container: image: ${{ matrix.entry.image }} diff --git a/Package.swift b/Package.swift index 20583865b..820524177 100644 --- a/Package.swift +++ b/Package.swift @@ -48,7 +48,7 @@ let package = Package( ], traits: [tracingTrait], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0"..<"603.0.0") + .package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0"..<"604.0.0") ], targets: [ .target(