Skip to content

Commit 148e65b

Browse files
committed
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`).
1 parent af88676 commit 148e65b

File tree

11 files changed

+404
-44
lines changed

11 files changed

+404
-44
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -827,15 +827,15 @@ struct StackCodegen {
827827
accessor: String,
828828
varPrefix: String
829829
) -> [CodeBlockItemSyntax] {
830-
var statements: [CodeBlockItemSyntax] = []
831830
let elemVar = "__bjs_elem_\(varPrefix)"
832-
statements.append("for \(raw: elemVar) in \(raw: accessor) {")
833-
statements.append(
834-
" _swift_js_push_i32((\(raw: elemVar) as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())"
835-
)
836-
statements.append("}")
837-
statements.append("_swift_js_push_i32(Int32(\(raw: accessor).count))")
838-
return statements
831+
return [
832+
"""
833+
for \(raw: elemVar) in \(raw: accessor) {
834+
_swift_js_push_i32((\(raw: elemVar) as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())
835+
}
836+
""",
837+
"_swift_js_push_i32(Int32(\(raw: accessor).count))",
838+
]
839839
}
840840

841841
private func lowerDictionaryStatements(
@@ -866,49 +866,58 @@ struct StackCodegen {
866866
accessor: String,
867867
varPrefix: String
868868
) -> [CodeBlockItemSyntax] {
869-
var statements: [CodeBlockItemSyntax] = []
870869
let pairVarName = "__bjs_kv_\(varPrefix)"
871-
statements.append("for \(raw: pairVarName) in \(raw: accessor) {")
872-
statements.append("let __bjs_key_\(raw: varPrefix) = \(raw: pairVarName).key")
873-
statements.append("let __bjs_value_\(raw: varPrefix) = \(raw: pairVarName).value")
874-
875-
let keyStatements = lowerStatements(
876-
for: .string,
877-
accessor: "__bjs_key_\(varPrefix)",
878-
varPrefix: "\(varPrefix)_key"
870+
let keyVarName = "__bjs_key_\(varPrefix)"
871+
let valueVarName = "__bjs_value_\(varPrefix)"
872+
873+
// The dispatch in `lowerDictionaryStatements` routes only .nullable and .closure value
874+
// types into this helper, both of which produce single-line lowering statements, so we can
875+
// join their descriptions into the for-body as plain lines without worrying about nested
876+
// multi-statement lowering.
877+
var bodyLines: [String] = [
878+
"let \(keyVarName) = \(pairVarName).key",
879+
"let \(valueVarName) = \(pairVarName).value",
880+
]
881+
bodyLines.append(
882+
contentsOf: lowerStatements(
883+
for: .string,
884+
accessor: keyVarName,
885+
varPrefix: "\(varPrefix)_key"
886+
).map { $0.description }
879887
)
880-
for stmt in keyStatements {
881-
statements.append(stmt)
882-
}
883-
884-
let valueStatements = lowerStatements(
885-
for: valueType,
886-
accessor: "__bjs_value_\(varPrefix)",
887-
varPrefix: "\(varPrefix)_value"
888+
bodyLines.append(
889+
contentsOf: lowerStatements(
890+
for: valueType,
891+
accessor: valueVarName,
892+
varPrefix: "\(varPrefix)_value"
893+
).map { $0.description }
888894
)
889-
for stmt in valueStatements {
890-
statements.append(stmt)
891-
}
895+
let body = bodyLines.joined(separator: "\n ")
892896

893-
statements.append("}")
894-
statements.append("_swift_js_push_i32(Int32(\(raw: accessor).count))")
895-
return statements
897+
return [
898+
"""
899+
for \(raw: pairVarName) in \(raw: accessor) {
900+
\(raw: body)
901+
}
902+
""",
903+
"_swift_js_push_i32(Int32(\(raw: accessor).count))",
904+
]
896905
}
897906

898907
private func lowerProtocolDictionaryStatements(
899908
accessor: String,
900909
varPrefix: String
901910
) -> [CodeBlockItemSyntax] {
902-
var statements: [CodeBlockItemSyntax] = []
903911
let pairVar = "__bjs_kv_\(varPrefix)"
904-
statements.append("for \(raw: pairVar) in \(raw: accessor) {")
905-
statements.append(" \(raw: pairVar).key.bridgeJSStackPush()")
906-
statements.append(
907-
" _swift_js_push_i32((\(raw: pairVar).value as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())"
908-
)
909-
statements.append("}")
910-
statements.append("_swift_js_push_i32(Int32(\(raw: accessor).count))")
911-
return statements
912+
return [
913+
"""
914+
for \(raw: pairVar) in \(raw: accessor) {
915+
\(raw: pairVar).key.bridgeJSStackPush()
916+
_swift_js_push_i32((\(raw: pairVar).value as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())
917+
}
918+
""",
919+
"_swift_js_push_i32(Int32(\(raw: accessor).count))",
920+
]
912921
}
913922
}
914923

@@ -1073,7 +1082,9 @@ struct EnumCodegen {
10731082
accessor: paramName,
10741083
varPrefix: paramName
10751084
)
1076-
printer.write(multilineString: CodeBlockItemListSyntax(statements).description)
1085+
for statement in statements {
1086+
printer.write(multilineString: statement.description)
1087+
}
10771088
}
10781089
}
10791090

@@ -1207,7 +1218,9 @@ struct StructCodegen {
12071218
accessor: accessor,
12081219
varPrefix: property.name
12091220
)
1210-
printer.write(multilineString: CodeBlockItemListSyntax(statements).description)
1221+
for statement in statements {
1222+
printer.write(multilineString: statement.description)
1223+
}
12111224
}
12121225

12131226
return printer.lines

Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/DictionaryTypes.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@
66
}
77
}
88

9+
@JS struct Counters {
10+
var name: String
11+
var counts: [String: Int?]
12+
}
13+
914
@JS func mirrorDictionary(_ values: [String: Int]) -> [String: Int]
1015
@JS func optionalDictionary(_ values: [String: String]?) -> [String: String]?
1116
@JS func nestedDictionary(_ values: [String: [Int]]) -> [String: [Int]]
1217
@JS func boxDictionary(_ boxes: [String: Box]) -> [String: Box]
1318
@JS func optionalBoxDictionary(_ boxes: [String: Box?]) -> [String: Box?]
19+
@JS func roundtripCounters(_ counters: Counters) -> Counters
1420

1521
@JSFunction func importMirrorDictionary(_ values: [String: Double]) throws(JSException) -> [String: Double]

Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Protocol.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,12 @@ import JavaScriptKit
102102
@JS
103103
var delegates: [MyViewControllerDelegate]
104104

105+
@JS
106+
var delegatesByName: [String: MyViewControllerDelegate]
107+
105108
@JS init(delegates: [MyViewControllerDelegate]) {
106109
self.delegates = delegates
110+
self.delegatesByName = [:]
107111
}
108112

109113
@JS func notifyAll() {
@@ -114,3 +118,7 @@ import JavaScriptKit
114118
}
115119

116120
@JS func processDelegates(_ delegates: [MyViewControllerDelegate]) -> [MyViewControllerDelegate]
121+
122+
@JS func processDelegatesByName(
123+
_ delegates: [String: MyViewControllerDelegate]
124+
) -> [String: MyViewControllerDelegate]

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,78 @@
221221
}
222222
}
223223
}
224+
},
225+
{
226+
"abiName" : "bjs_roundtripCounters",
227+
"effects" : {
228+
"isAsync" : false,
229+
"isStatic" : false,
230+
"isThrows" : false
231+
},
232+
"name" : "roundtripCounters",
233+
"parameters" : [
234+
{
235+
"label" : "_",
236+
"name" : "counters",
237+
"type" : {
238+
"swiftStruct" : {
239+
"_0" : "Counters"
240+
}
241+
}
242+
}
243+
],
244+
"returnType" : {
245+
"swiftStruct" : {
246+
"_0" : "Counters"
247+
}
248+
}
224249
}
225250
],
226251
"protocols" : [
227252

228253
],
229254
"structs" : [
255+
{
256+
"methods" : [
257+
258+
],
259+
"name" : "Counters",
260+
"properties" : [
261+
{
262+
"isReadonly" : true,
263+
"isStatic" : false,
264+
"name" : "name",
265+
"type" : {
266+
"string" : {
230267

268+
}
269+
}
270+
},
271+
{
272+
"isReadonly" : true,
273+
"isStatic" : false,
274+
"name" : "counts",
275+
"type" : {
276+
"dictionary" : {
277+
"_0" : {
278+
"nullable" : {
279+
"_0" : {
280+
"integer" : {
281+
"_0" : {
282+
"isSigned" : true,
283+
"width" : "word"
284+
}
285+
}
286+
},
287+
"_1" : "null"
288+
}
289+
}
290+
}
291+
}
292+
}
293+
],
294+
"swiftCallName" : "Counters"
295+
}
231296
]
232297
},
233298
"imported" : {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.swift

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,57 @@
1+
extension Counters: _BridgedSwiftStruct {
2+
@_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> Counters {
3+
let counts = [String: Optional<Int>].bridgeJSStackPop()
4+
let name = String.bridgeJSStackPop()
5+
return Counters(name: name, counts: counts)
6+
}
7+
8+
@_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() {
9+
self.name.bridgeJSStackPush()
10+
for __bjs_kv_counts in self.counts {
11+
let __bjs_key_counts = __bjs_kv_counts.key
12+
let __bjs_value_counts = __bjs_kv_counts.value
13+
__bjs_key_counts.bridgeJSStackPush()
14+
__bjs_value_counts.bridgeJSStackPush()
15+
}
16+
_swift_js_push_i32(Int32(self.counts.count))
17+
}
18+
19+
init(unsafelyCopying jsObject: JSObject) {
20+
_bjs_struct_lower_Counters(jsObject.bridgeJSLowerParameter())
21+
self = Self.bridgeJSStackPop()
22+
}
23+
24+
func toJSObject() -> JSObject {
25+
let __bjs_self = self
26+
__bjs_self.bridgeJSStackPush()
27+
return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_Counters()))
28+
}
29+
}
30+
31+
#if arch(wasm32)
32+
@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_Counters")
33+
fileprivate func _bjs_struct_lower_Counters_extern(_ objectId: Int32) -> Void
34+
#else
35+
fileprivate func _bjs_struct_lower_Counters_extern(_ objectId: Int32) -> Void {
36+
fatalError("Only available on WebAssembly")
37+
}
38+
#endif
39+
@inline(never) fileprivate func _bjs_struct_lower_Counters(_ objectId: Int32) -> Void {
40+
return _bjs_struct_lower_Counters_extern(objectId)
41+
}
42+
43+
#if arch(wasm32)
44+
@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_Counters")
45+
fileprivate func _bjs_struct_lift_Counters_extern() -> Int32
46+
#else
47+
fileprivate func _bjs_struct_lift_Counters_extern() -> Int32 {
48+
fatalError("Only available on WebAssembly")
49+
}
50+
#endif
51+
@inline(never) fileprivate func _bjs_struct_lift_Counters() -> Int32 {
52+
return _bjs_struct_lift_Counters_extern()
53+
}
54+
155
@_expose(wasm, "bjs_mirrorDictionary")
256
@_cdecl("bjs_mirrorDictionary")
357
public func _bjs_mirrorDictionary() -> Void {
@@ -53,6 +107,17 @@ public func _bjs_optionalBoxDictionary() -> Void {
53107
#endif
54108
}
55109

110+
@_expose(wasm, "bjs_roundtripCounters")
111+
@_cdecl("bjs_roundtripCounters")
112+
public func _bjs_roundtripCounters() -> Void {
113+
#if arch(wasm32)
114+
let ret = roundtripCounters(_: Counters.bridgeJSLiftParameter())
115+
return ret.bridgeJSLowerReturn()
116+
#else
117+
fatalError("Only available on WebAssembly")
118+
#endif
119+
}
120+
56121
@_expose(wasm, "bjs_Box_deinit")
57122
@_cdecl("bjs_Box_deinit")
58123
public func _bjs_Box_deinit(_ pointer: UnsafeMutableRawPointer) -> Void {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,20 @@
317317
}
318318
}
319319
}
320+
},
321+
{
322+
"isReadonly" : false,
323+
"isStatic" : false,
324+
"name" : "delegatesByName",
325+
"type" : {
326+
"dictionary" : {
327+
"_0" : {
328+
"swiftProtocol" : {
329+
"_0" : "MyViewControllerDelegate"
330+
}
331+
}
332+
}
333+
}
320334
}
321335
],
322336
"swiftCallName" : "DelegateManager"
@@ -502,6 +516,39 @@
502516
}
503517
}
504518
}
519+
},
520+
{
521+
"abiName" : "bjs_processDelegatesByName",
522+
"effects" : {
523+
"isAsync" : false,
524+
"isStatic" : false,
525+
"isThrows" : false
526+
},
527+
"name" : "processDelegatesByName",
528+
"parameters" : [
529+
{
530+
"label" : "_",
531+
"name" : "delegates",
532+
"type" : {
533+
"dictionary" : {
534+
"_0" : {
535+
"swiftProtocol" : {
536+
"_0" : "MyViewControllerDelegate"
537+
}
538+
}
539+
}
540+
}
541+
}
542+
],
543+
"returnType" : {
544+
"dictionary" : {
545+
"_0" : {
546+
"swiftProtocol" : {
547+
"_0" : "MyViewControllerDelegate"
548+
}
549+
}
550+
}
551+
}
505552
}
506553
],
507554
"protocols" : [

0 commit comments

Comments
 (0)