Skip to content

Commit da199a5

Browse files
committed
BridgeJS: Swift struct support
1 parent fe5aa6c commit da199a5

File tree

86 files changed

+5360
-349
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+5360
-349
lines changed

Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,5 +726,8 @@
726726
"moduleName" : "Benchmarks",
727727
"protocols" : [
728728

729+
],
730+
"structs" : [
731+
729732
]
730733
}

Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,5 +142,8 @@
142142
"moduleName" : "PlayBridgeJS",
143143
"protocols" : [
144144

145+
],
146+
"structs" : [
147+
145148
]
146149
}

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 569 additions & 145 deletions
Large diffs are not rendered by default.

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,13 @@ extension BridgeType {
476476
case .exportSwift:
477477
return LoweringParameterInfo(loweredParameters: [("caseId", .i32)])
478478
}
479+
case .swiftStruct:
480+
switch context {
481+
case .importTS:
482+
throw BridgeJSCoreError("Swift structs are not yet supported in TypeScript imports")
483+
case .exportSwift:
484+
return LoweringParameterInfo(loweredParameters: [])
485+
}
479486
case .namespaceEnum:
480487
throw BridgeJSCoreError("Namespace enums cannot be used as parameters")
481488
case .optional(let wrappedType):
@@ -552,6 +559,13 @@ extension BridgeType {
552559
case .exportSwift:
553560
return LiftingReturnInfo(valueToLift: .i32)
554561
}
562+
case .swiftStruct:
563+
switch context {
564+
case .importTS:
565+
throw BridgeJSCoreError("Swift structs are not yet supported in TypeScript imports")
566+
case .exportSwift:
567+
return LiftingReturnInfo(valueToLift: nil)
568+
}
555569
case .namespaceEnum:
556570
throw BridgeJSCoreError("Namespace enums cannot be used as return values")
557571
case .optional(let wrappedType):

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 287 additions & 201 deletions
Large diffs are not rendered by default.

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 922 additions & 0 deletions
Large diffs are not rendered by default.

Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public struct ABINameGenerator {
2525
let contextPart: String?
2626
if let staticContext = staticContext {
2727
switch staticContext {
28-
case .className(let name), .enumName(let name):
28+
case .className(let name), .enumName(let name), .structName(let name):
2929
contextPart = name
3030
case .namespaceEnum:
3131
contextPart = namespacePart
@@ -115,6 +115,7 @@ public enum BridgeType: Codable, Equatable, Hashable, Sendable {
115115
case associatedValueEnum(String)
116116
case namespaceEnum(String)
117117
case swiftProtocol(String)
118+
case swiftStruct(String)
118119
indirect case closure(ClosureSignature)
119120
}
120121

@@ -205,10 +206,51 @@ public struct Effects: Codable, Equatable, Sendable {
205206

206207
public enum StaticContext: Codable, Equatable, Sendable {
207208
case className(String)
209+
case structName(String)
208210
case enumName(String)
209211
case namespaceEnum
210212
}
211213

214+
// MARK: - Struct Skeleton
215+
216+
public struct StructField: Codable, Equatable, Sendable {
217+
public let name: String
218+
public let type: BridgeType
219+
220+
public init(name: String, type: BridgeType) {
221+
self.name = name
222+
self.type = type
223+
}
224+
}
225+
226+
public struct ExportedStruct: Codable, Equatable, Sendable {
227+
public let name: String
228+
public let swiftCallName: String
229+
public let explicitAccessControl: String?
230+
public var properties: [ExportedProperty]
231+
public var constructor: ExportedConstructor?
232+
public var methods: [ExportedFunction]
233+
public let namespace: [String]?
234+
235+
public init(
236+
name: String,
237+
swiftCallName: String,
238+
explicitAccessControl: String?,
239+
properties: [ExportedProperty] = [],
240+
constructor: ExportedConstructor? = nil,
241+
methods: [ExportedFunction] = [],
242+
namespace: [String]?
243+
) {
244+
self.name = name
245+
self.swiftCallName = swiftCallName
246+
self.explicitAccessControl = explicitAccessControl
247+
self.properties = properties
248+
self.constructor = constructor
249+
self.methods = methods
250+
self.namespace = namespace
251+
}
252+
}
253+
212254
// MARK: - Enum Skeleton
213255

214256
public struct AssociatedValue: Codable, Equatable, Sendable {
@@ -416,7 +458,7 @@ public struct ExportedClass: Codable {
416458
}
417459
}
418460

419-
public struct ExportedConstructor: Codable {
461+
public struct ExportedConstructor: Codable, Equatable, Sendable {
420462
public var abiName: String
421463
public var parameters: [Parameter]
422464
public var effects: Effects
@@ -457,7 +499,7 @@ public struct ExportedProperty: Codable, Equatable, Sendable {
457499
public func callName(prefix: String? = nil) -> String {
458500
if let staticContext = staticContext {
459501
switch staticContext {
460-
case .className(let baseName), .enumName(let baseName):
502+
case .className(let baseName), .enumName(let baseName), .structName(let baseName):
461503
return "\(baseName).\(name)"
462504
case .namespaceEnum:
463505
if let namespace = namespace, !namespace.isEmpty {
@@ -498,6 +540,7 @@ public struct ExportedSkeleton: Codable {
498540
public let functions: [ExportedFunction]
499541
public let classes: [ExportedClass]
500542
public let enums: [ExportedEnum]
543+
public let structs: [ExportedStruct]
501544
public let protocols: [ExportedProtocol]
502545
/// Whether to expose exported APIs to the global namespace.
503546
///
@@ -511,13 +554,15 @@ public struct ExportedSkeleton: Codable {
511554
functions: [ExportedFunction],
512555
classes: [ExportedClass],
513556
enums: [ExportedEnum],
557+
structs: [ExportedStruct] = [],
514558
protocols: [ExportedProtocol] = [],
515559
exposeToGlobal: Bool
516560
) {
517561
self.moduleName = moduleName
518562
self.functions = functions
519563
self.classes = classes
520564
self.enums = enums
565+
self.structs = structs
521566
self.protocols = protocols
522567
self.exposeToGlobal = exposeToGlobal
523568
}
@@ -624,6 +669,9 @@ extension BridgeType {
624669
case .swiftProtocol:
625670
// Protocols pass JSObject IDs as Int32
626671
return .i32
672+
case .swiftStruct:
673+
// Structs use stack-based return (no direct WASM return type)
674+
return nil
627675
case .closure:
628676
// Closures pass callback ID as Int32
629677
return .i32
@@ -660,6 +708,8 @@ extension BridgeType {
660708
return "\(name.count)\(name)O"
661709
case .swiftProtocol(let name):
662710
return "\(name.count)\(name)P"
711+
case .swiftStruct(let name):
712+
return "\(name.count)\(name)V"
663713
case .closure(let signature):
664714
let params =
665715
signature.parameters.isEmpty
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
@JS struct DataPoint {
2+
let x: Double
3+
let y: Double
4+
var label: String
5+
var optCount: Int?
6+
var optFlag: Bool?
7+
8+
@JS init(x: Double, y: Double, label: String, optCount: Int?, optFlag: Bool?)
9+
}
10+
11+
@JS struct Address {
12+
var street: String
13+
var city: String
14+
var zipCode: Int?
15+
}
16+
17+
@JS struct Person {
18+
var name: String
19+
var age: Int
20+
var address: Address
21+
var email: String?
22+
}
23+
24+
@JS class Greeter {
25+
@JS var name: String
26+
27+
@JS init(name: String)
28+
@JS func greet() -> String
29+
}
30+
31+
@JS struct Session {
32+
var id: Int
33+
var owner: Greeter
34+
}
35+
36+
@JS func roundtrip(_ session: Person) -> Person
37+
38+
@JS struct ConfigStruct {
39+
@JS static let maxRetries: Int = 3
40+
@JS nonisolated(unsafe) static var defaultConfig: String = "production"
41+
@JS nonisolated(unsafe) static var timeout: Double = 30.0
42+
@JS static var computedSetting: String { "Config: \(defaultConfig)" }
43+
@JS static func update(_ timeout: Double) -> Double
44+
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) {
2626
let tmpParamInts = [];
2727
let tmpParamF32s = [];
2828
let tmpParamF64s = [];
29+
let tmpRetPointers = [];
30+
let tmpParamPointers = [];
2931

3032
let _exports = null;
3133
let bjs = null;
@@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) {
9193
bjs["swift_js_pop_param_f64"] = function() {
9294
return tmpParamF64s.pop();
9395
}
96+
bjs["swift_js_push_pointer"] = function(pointer) {
97+
tmpRetPointers.push(pointer);
98+
}
99+
bjs["swift_js_pop_param_pointer"] = function() {
100+
return tmpParamPointers.pop();
101+
}
94102
bjs["swift_js_return_optional_bool"] = function(isSome, value) {
95103
if (isSome === 0) {
96104
tmpRetOptionalBool = null;

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) {
2626
let tmpParamInts = [];
2727
let tmpParamF32s = [];
2828
let tmpParamF64s = [];
29+
let tmpRetPointers = [];
30+
let tmpParamPointers = [];
2931

3032
let _exports = null;
3133
let bjs = null;
@@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) {
9193
bjs["swift_js_pop_param_f64"] = function() {
9294
return tmpParamF64s.pop();
9395
}
96+
bjs["swift_js_push_pointer"] = function(pointer) {
97+
tmpRetPointers.push(pointer);
98+
}
99+
bjs["swift_js_pop_param_pointer"] = function() {
100+
return tmpParamPointers.pop();
101+
}
94102
bjs["swift_js_return_optional_bool"] = function(isSome, value) {
95103
if (isSome === 0) {
96104
tmpRetOptionalBool = null;

0 commit comments

Comments
 (0)