diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 487b008e2..b79d911ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,14 +19,19 @@ jobs: download-url: https://download.swift.org/swift-6.1-release/ubuntu2204/swift-6.1-RELEASE/swift-6.1-RELEASE-ubuntu22.04.tar.gz wasi-backend: Node target: "wasm32-unknown-wasi" - - os: ubuntu-22.04 + - os: ubuntu-24.04 + toolchain: + download-url: https://download.swift.org/development/ubuntu2404/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a-ubuntu24.04.tar.gz + wasi-backend: Node + target: "wasm32-unknown-wasip1" + - os: ubuntu-24.04 toolchain: - download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-09-14-a/swift-DEVELOPMENT-SNAPSHOT-2025-09-14-a-ubuntu22.04.tar.gz + download-url: https://download.swift.org/swift-6.3-branch/ubuntu2404/swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-05-a/swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-05-a-ubuntu24.04.tar.gz wasi-backend: Node target: "wasm32-unknown-wasip1" - os: ubuntu-22.04 toolchain: - download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-09-14-a/swift-DEVELOPMENT-SNAPSHOT-2025-09-14-a-ubuntu22.04.tar.gz + download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a/swift-DEVELOPMENT-SNAPSHOT-2025-12-01-a-ubuntu22.04.tar.gz wasi-backend: Node target: "wasm32-unknown-wasip1-threads" diff --git a/Benchmarks/Sources/Benchmarks.swift b/Benchmarks/Sources/Benchmarks.swift index b05075eb3..661aba63e 100644 --- a/Benchmarks/Sources/Benchmarks.swift +++ b/Benchmarks/Sources/Benchmarks.swift @@ -111,6 +111,136 @@ enum ComplexResult { } } +// MARK: - Struct Performance Tests + +@JS struct SimpleStruct { + var name: String + var count: Int + var flag: Bool + var rate: Float + var precise: Double +} + +@JS struct Address { + var street: String + var city: String + var zipCode: Int +} + +@JS struct Person { + var name: String + var age: Int + var address: Address + var email: String? +} + +@JS struct ComplexStruct { + var id: Int + var title: String + var active: Bool + var score: Double + var tags: String + var metadata: String +} + +@JS class StructRoundtrip { + @JS init() {} + + @JS func takeSimple(_ value: SimpleStruct) {} + @JS func makeSimple() -> SimpleStruct { + return SimpleStruct(name: "Hello", count: 42, flag: true, rate: 0.5, precise: 3.14159) + } + @JS func roundtripSimple(_ value: SimpleStruct) -> SimpleStruct { + return value + } + + @JS func takeAddress(_ value: Address) {} + @JS func makeAddress() -> Address { + return Address(street: "123 Main St", city: "San Francisco", zipCode: 94102) + } + @JS func roundtripAddress(_ value: Address) -> Address { + return value + } + + @JS func takePerson(_ value: Person) {} + @JS func makePerson() -> Person { + return Person( + name: "John Doe", + age: 30, + address: Address(street: "456 Oak Ave", city: "New York", zipCode: 10001), + email: "john@example.com" + ) + } + @JS func roundtripPerson(_ value: Person) -> Person { + return value + } + + @JS func takeComplex(_ value: ComplexStruct) {} + @JS func makeComplex() -> ComplexStruct { + return ComplexStruct( + id: 12345, + title: "Test Item", + active: true, + score: 98.6, + tags: "swift,wasm,benchmark", + metadata: "{\"version\":1}" + ) + } + @JS func roundtripComplex(_ value: ComplexStruct) -> ComplexStruct { + return value + } +} + +// MARK: - Class vs Struct Comparison Tests + +@JS class SimpleClass { + @JS var name: String + @JS var count: Int + @JS var flag: Bool + @JS var rate: Float + @JS var precise: Double + + @JS init(name: String, count: Int, flag: Bool, rate: Float, precise: Double) { + self.name = name + self.count = count + self.flag = flag + self.rate = rate + self.precise = precise + } +} + +@JS class AddressClass { + @JS var street: String + @JS var city: String + @JS var zipCode: Int + + @JS init(street: String, city: String, zipCode: Int) { + self.street = street + self.city = city + self.zipCode = zipCode + } +} + +@JS class ClassRoundtrip { + @JS init() {} + + @JS func takeSimpleClass(_ value: SimpleClass) {} + @JS func makeSimpleClass() -> SimpleClass { + return SimpleClass(name: "Hello", count: 42, flag: true, rate: 0.5, precise: 3.14159) + } + @JS func roundtripSimpleClass(_ value: SimpleClass) -> SimpleClass { + return value + } + + @JS func takeAddressClass(_ value: AddressClass) {} + @JS func makeAddressClass() -> AddressClass { + return AddressClass(street: "123 Main St", city: "San Francisco", zipCode: 94102) + } + @JS func roundtripAddressClass(_ value: AddressClass) -> AddressClass { + return value + } +} + @JS func run() { let call = Benchmark("Call") diff --git a/Benchmarks/Sources/Generated/BridgeJS.ExportSwift.swift b/Benchmarks/Sources/Generated/BridgeJS.ExportSwift.swift index 8fb5c97d7..fcec5e675 100644 --- a/Benchmarks/Sources/Generated/BridgeJS.ExportSwift.swift +++ b/Benchmarks/Sources/Generated/BridgeJS.ExportSwift.swift @@ -246,6 +246,106 @@ extension ComplexResult: _BridgedSwiftAssociatedValueEnum { } } +extension SimpleStruct: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> SimpleStruct { + let precise = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + let rate = Float.bridgeJSLiftParameter(_swift_js_pop_param_f32()) + let flag = Bool.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let count = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let name = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return SimpleStruct(name: name, count: count, flag: flag, rate: rate, precise: precise) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_name = self.name + __bjs_name.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + _swift_js_push_int(Int32(self.count)) + _swift_js_push_int(self.flag ? 1 : 0) + _swift_js_push_f32(self.rate) + _swift_js_push_f64(self.precise) + } +} + +extension Address: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Address { + let zipCode = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let city = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let street = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Address(street: street, city: city, zipCode: zipCode) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_street = self.street + __bjs_street.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + var __bjs_city = self.city + __bjs_city.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + _swift_js_push_int(Int32(self.zipCode)) + } +} + +extension Person: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Person { + let email = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let address = Address.bridgeJSLiftParameter() + let age = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let name = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Person(name: name, age: age, address: address, email: email) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_name = self.name + __bjs_name.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + _swift_js_push_int(Int32(self.age)) + self.address.bridgeJSLowerReturn() + let __bjs_isSome_email = self.email != nil + if let __bjs_unwrapped_email = self.email { + var __bjs_str_email = __bjs_unwrapped_email + __bjs_str_email.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + } + _swift_js_push_int(__bjs_isSome_email ? 1 : 0) + } +} + +extension ComplexStruct: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> ComplexStruct { + let metadata = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let tags = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let score = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + let active = Bool.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let title = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let id = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + return ComplexStruct(id: id, title: title, active: active, score: score, tags: tags, metadata: metadata) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_int(Int32(self.id)) + var __bjs_title = self.title + __bjs_title.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + _swift_js_push_int(self.active ? 1 : 0) + _swift_js_push_f64(self.score) + var __bjs_tags = self.tags + __bjs_tags.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + var __bjs_metadata = self.metadata + __bjs_metadata.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + } +} + @_expose(wasm, "bjs_run") @_cdecl("bjs_run") public func _bjs_run() -> Void { @@ -556,4 +656,492 @@ fileprivate func _bjs_StringRoundtrip_wrap(_: UnsafeMutableRawPointer) -> Int32 fileprivate func _bjs_StringRoundtrip_wrap(_: UnsafeMutableRawPointer) -> Int32 { fatalError("Only available on WebAssembly") } +#endif + +@_expose(wasm, "bjs_StructRoundtrip_init") +@_cdecl("bjs_StructRoundtrip_init") +public func _bjs_StructRoundtrip_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = StructRoundtrip() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_takeSimple") +@_cdecl("bjs_StructRoundtrip_takeSimple") +public func _bjs_StructRoundtrip_takeSimple(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + StructRoundtrip.bridgeJSLiftParameter(_self).takeSimple(_: SimpleStruct.bridgeJSLiftParameter()) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_makeSimple") +@_cdecl("bjs_StructRoundtrip_makeSimple") +public func _bjs_StructRoundtrip_makeSimple(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = StructRoundtrip.bridgeJSLiftParameter(_self).makeSimple() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_roundtripSimple") +@_cdecl("bjs_StructRoundtrip_roundtripSimple") +public func _bjs_StructRoundtrip_roundtripSimple(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = StructRoundtrip.bridgeJSLiftParameter(_self).roundtripSimple(_: SimpleStruct.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_takeAddress") +@_cdecl("bjs_StructRoundtrip_takeAddress") +public func _bjs_StructRoundtrip_takeAddress(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + StructRoundtrip.bridgeJSLiftParameter(_self).takeAddress(_: Address.bridgeJSLiftParameter()) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_makeAddress") +@_cdecl("bjs_StructRoundtrip_makeAddress") +public func _bjs_StructRoundtrip_makeAddress(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = StructRoundtrip.bridgeJSLiftParameter(_self).makeAddress() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_roundtripAddress") +@_cdecl("bjs_StructRoundtrip_roundtripAddress") +public func _bjs_StructRoundtrip_roundtripAddress(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = StructRoundtrip.bridgeJSLiftParameter(_self).roundtripAddress(_: Address.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_takePerson") +@_cdecl("bjs_StructRoundtrip_takePerson") +public func _bjs_StructRoundtrip_takePerson(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + StructRoundtrip.bridgeJSLiftParameter(_self).takePerson(_: Person.bridgeJSLiftParameter()) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_makePerson") +@_cdecl("bjs_StructRoundtrip_makePerson") +public func _bjs_StructRoundtrip_makePerson(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = StructRoundtrip.bridgeJSLiftParameter(_self).makePerson() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_roundtripPerson") +@_cdecl("bjs_StructRoundtrip_roundtripPerson") +public func _bjs_StructRoundtrip_roundtripPerson(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = StructRoundtrip.bridgeJSLiftParameter(_self).roundtripPerson(_: Person.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_takeComplex") +@_cdecl("bjs_StructRoundtrip_takeComplex") +public func _bjs_StructRoundtrip_takeComplex(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + StructRoundtrip.bridgeJSLiftParameter(_self).takeComplex(_: ComplexStruct.bridgeJSLiftParameter()) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_makeComplex") +@_cdecl("bjs_StructRoundtrip_makeComplex") +public func _bjs_StructRoundtrip_makeComplex(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = StructRoundtrip.bridgeJSLiftParameter(_self).makeComplex() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_roundtripComplex") +@_cdecl("bjs_StructRoundtrip_roundtripComplex") +public func _bjs_StructRoundtrip_roundtripComplex(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = StructRoundtrip.bridgeJSLiftParameter(_self).roundtripComplex(_: ComplexStruct.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StructRoundtrip_deinit") +@_cdecl("bjs_StructRoundtrip_deinit") +public func _bjs_StructRoundtrip_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension StructRoundtrip: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_StructRoundtrip_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_StructRoundtrip_wrap") +fileprivate func _bjs_StructRoundtrip_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_StructRoundtrip_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +@_expose(wasm, "bjs_SimpleClass_init") +@_cdecl("bjs_SimpleClass_init") +public func _bjs_SimpleClass_init(nameBytes: Int32, nameLength: Int32, count: Int32, flag: Int32, rate: Float32, precise: Float64) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = SimpleClass(name: String.bridgeJSLiftParameter(nameBytes, nameLength), count: Int.bridgeJSLiftParameter(count), flag: Bool.bridgeJSLiftParameter(flag), rate: Float.bridgeJSLiftParameter(rate), precise: Double.bridgeJSLiftParameter(precise)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClass_name_get") +@_cdecl("bjs_SimpleClass_name_get") +public func _bjs_SimpleClass_name_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = SimpleClass.bridgeJSLiftParameter(_self).name + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClass_name_set") +@_cdecl("bjs_SimpleClass_name_set") +public func _bjs_SimpleClass_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + SimpleClass.bridgeJSLiftParameter(_self).name = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClass_count_get") +@_cdecl("bjs_SimpleClass_count_get") +public func _bjs_SimpleClass_count_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = SimpleClass.bridgeJSLiftParameter(_self).count + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClass_count_set") +@_cdecl("bjs_SimpleClass_count_set") +public func _bjs_SimpleClass_count_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + SimpleClass.bridgeJSLiftParameter(_self).count = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClass_flag_get") +@_cdecl("bjs_SimpleClass_flag_get") +public func _bjs_SimpleClass_flag_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = SimpleClass.bridgeJSLiftParameter(_self).flag + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClass_flag_set") +@_cdecl("bjs_SimpleClass_flag_set") +public func _bjs_SimpleClass_flag_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + SimpleClass.bridgeJSLiftParameter(_self).flag = Bool.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClass_rate_get") +@_cdecl("bjs_SimpleClass_rate_get") +public func _bjs_SimpleClass_rate_get(_self: UnsafeMutableRawPointer) -> Float32 { + #if arch(wasm32) + let ret = SimpleClass.bridgeJSLiftParameter(_self).rate + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClass_rate_set") +@_cdecl("bjs_SimpleClass_rate_set") +public func _bjs_SimpleClass_rate_set(_self: UnsafeMutableRawPointer, value: Float32) -> Void { + #if arch(wasm32) + SimpleClass.bridgeJSLiftParameter(_self).rate = Float.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClass_precise_get") +@_cdecl("bjs_SimpleClass_precise_get") +public func _bjs_SimpleClass_precise_get(_self: UnsafeMutableRawPointer) -> Float64 { + #if arch(wasm32) + let ret = SimpleClass.bridgeJSLiftParameter(_self).precise + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClass_precise_set") +@_cdecl("bjs_SimpleClass_precise_set") +public func _bjs_SimpleClass_precise_set(_self: UnsafeMutableRawPointer, value: Float64) -> Void { + #if arch(wasm32) + SimpleClass.bridgeJSLiftParameter(_self).precise = Double.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimpleClass_deinit") +@_cdecl("bjs_SimpleClass_deinit") +public func _bjs_SimpleClass_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension SimpleClass: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_SimpleClass_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_SimpleClass_wrap") +fileprivate func _bjs_SimpleClass_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_SimpleClass_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +@_expose(wasm, "bjs_AddressClass_init") +@_cdecl("bjs_AddressClass_init") +public func _bjs_AddressClass_init(streetBytes: Int32, streetLength: Int32, cityBytes: Int32, cityLength: Int32, zipCode: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = AddressClass(street: String.bridgeJSLiftParameter(streetBytes, streetLength), city: String.bridgeJSLiftParameter(cityBytes, cityLength), zipCode: Int.bridgeJSLiftParameter(zipCode)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_AddressClass_street_get") +@_cdecl("bjs_AddressClass_street_get") +public func _bjs_AddressClass_street_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = AddressClass.bridgeJSLiftParameter(_self).street + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_AddressClass_street_set") +@_cdecl("bjs_AddressClass_street_set") +public func _bjs_AddressClass_street_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + AddressClass.bridgeJSLiftParameter(_self).street = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_AddressClass_city_get") +@_cdecl("bjs_AddressClass_city_get") +public func _bjs_AddressClass_city_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = AddressClass.bridgeJSLiftParameter(_self).city + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_AddressClass_city_set") +@_cdecl("bjs_AddressClass_city_set") +public func _bjs_AddressClass_city_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + AddressClass.bridgeJSLiftParameter(_self).city = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_AddressClass_zipCode_get") +@_cdecl("bjs_AddressClass_zipCode_get") +public func _bjs_AddressClass_zipCode_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = AddressClass.bridgeJSLiftParameter(_self).zipCode + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_AddressClass_zipCode_set") +@_cdecl("bjs_AddressClass_zipCode_set") +public func _bjs_AddressClass_zipCode_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + AddressClass.bridgeJSLiftParameter(_self).zipCode = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_AddressClass_deinit") +@_cdecl("bjs_AddressClass_deinit") +public func _bjs_AddressClass_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension AddressClass: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_AddressClass_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_AddressClass_wrap") +fileprivate func _bjs_AddressClass_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_AddressClass_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +@_expose(wasm, "bjs_ClassRoundtrip_init") +@_cdecl("bjs_ClassRoundtrip_init") +public func _bjs_ClassRoundtrip_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassRoundtrip() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtrip_takeSimpleClass") +@_cdecl("bjs_ClassRoundtrip_takeSimpleClass") +public func _bjs_ClassRoundtrip_takeSimpleClass(_self: UnsafeMutableRawPointer, value: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + ClassRoundtrip.bridgeJSLiftParameter(_self).takeSimpleClass(_: SimpleClass.bridgeJSLiftParameter(value)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtrip_makeSimpleClass") +@_cdecl("bjs_ClassRoundtrip_makeSimpleClass") +public func _bjs_ClassRoundtrip_makeSimpleClass(_self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassRoundtrip.bridgeJSLiftParameter(_self).makeSimpleClass() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtrip_roundtripSimpleClass") +@_cdecl("bjs_ClassRoundtrip_roundtripSimpleClass") +public func _bjs_ClassRoundtrip_roundtripSimpleClass(_self: UnsafeMutableRawPointer, value: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassRoundtrip.bridgeJSLiftParameter(_self).roundtripSimpleClass(_: SimpleClass.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtrip_takeAddressClass") +@_cdecl("bjs_ClassRoundtrip_takeAddressClass") +public func _bjs_ClassRoundtrip_takeAddressClass(_self: UnsafeMutableRawPointer, value: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + ClassRoundtrip.bridgeJSLiftParameter(_self).takeAddressClass(_: AddressClass.bridgeJSLiftParameter(value)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtrip_makeAddressClass") +@_cdecl("bjs_ClassRoundtrip_makeAddressClass") +public func _bjs_ClassRoundtrip_makeAddressClass(_self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassRoundtrip.bridgeJSLiftParameter(_self).makeAddressClass() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtrip_roundtripAddressClass") +@_cdecl("bjs_ClassRoundtrip_roundtripAddressClass") +public func _bjs_ClassRoundtrip_roundtripAddressClass(_self: UnsafeMutableRawPointer, value: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassRoundtrip.bridgeJSLiftParameter(_self).roundtripAddressClass(_: AddressClass.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassRoundtrip_deinit") +@_cdecl("bjs_ClassRoundtrip_deinit") +public func _bjs_ClassRoundtrip_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension ClassRoundtrip: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ClassRoundtrip_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Benchmarks", name: "bjs_ClassRoundtrip_wrap") +fileprivate func _bjs_ClassRoundtrip_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ClassRoundtrip_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} #endif \ No newline at end of file diff --git a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json index 9094df267..e3532d33c 100644 --- a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -422,6 +422,638 @@ ], "swiftCallName" : "StringRoundtrip" + }, + { + "constructor" : { + "abiName" : "bjs_StructRoundtrip_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_StructRoundtrip_takeSimple", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "takeSimple", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftStruct" : { + "_0" : "SimpleStruct" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_StructRoundtrip_makeSimple", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "makeSimple", + "parameters" : [ + + ], + "returnType" : { + "swiftStruct" : { + "_0" : "SimpleStruct" + } + } + }, + { + "abiName" : "bjs_StructRoundtrip_roundtripSimple", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripSimple", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftStruct" : { + "_0" : "SimpleStruct" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "SimpleStruct" + } + } + }, + { + "abiName" : "bjs_StructRoundtrip_takeAddress", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "takeAddress", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftStruct" : { + "_0" : "Address" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_StructRoundtrip_makeAddress", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "makeAddress", + "parameters" : [ + + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Address" + } + } + }, + { + "abiName" : "bjs_StructRoundtrip_roundtripAddress", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripAddress", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftStruct" : { + "_0" : "Address" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Address" + } + } + }, + { + "abiName" : "bjs_StructRoundtrip_takePerson", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "takePerson", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftStruct" : { + "_0" : "Person" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_StructRoundtrip_makePerson", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "makePerson", + "parameters" : [ + + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Person" + } + } + }, + { + "abiName" : "bjs_StructRoundtrip_roundtripPerson", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripPerson", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftStruct" : { + "_0" : "Person" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Person" + } + } + }, + { + "abiName" : "bjs_StructRoundtrip_takeComplex", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "takeComplex", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftStruct" : { + "_0" : "ComplexStruct" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_StructRoundtrip_makeComplex", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "makeComplex", + "parameters" : [ + + ], + "returnType" : { + "swiftStruct" : { + "_0" : "ComplexStruct" + } + } + }, + { + "abiName" : "bjs_StructRoundtrip_roundtripComplex", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripComplex", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftStruct" : { + "_0" : "ComplexStruct" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "ComplexStruct" + } + } + } + ], + "name" : "StructRoundtrip", + "properties" : [ + + ], + "swiftCallName" : "StructRoundtrip" + }, + { + "constructor" : { + "abiName" : "bjs_SimpleClass_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "name", + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "label" : "count", + "name" : "count", + "type" : { + "int" : { + + } + } + }, + { + "label" : "flag", + "name" : "flag", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "rate", + "name" : "rate", + "type" : { + "float" : { + + } + } + }, + { + "label" : "precise", + "name" : "precise", + "type" : { + "double" : { + + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "SimpleClass", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "count", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "flag", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "rate", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "precise", + "type" : { + "double" : { + + } + } + } + ], + "swiftCallName" : "SimpleClass" + }, + { + "constructor" : { + "abiName" : "bjs_AddressClass_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "street", + "name" : "street", + "type" : { + "string" : { + + } + } + }, + { + "label" : "city", + "name" : "city", + "type" : { + "string" : { + + } + } + }, + { + "label" : "zipCode", + "name" : "zipCode", + "type" : { + "int" : { + + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "AddressClass", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "street", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "city", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "zipCode", + "type" : { + "int" : { + + } + } + } + ], + "swiftCallName" : "AddressClass" + }, + { + "constructor" : { + "abiName" : "bjs_ClassRoundtrip_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_ClassRoundtrip_takeSimpleClass", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "takeSimpleClass", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftHeapObject" : { + "_0" : "SimpleClass" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_ClassRoundtrip_makeSimpleClass", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "makeSimpleClass", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "SimpleClass" + } + } + }, + { + "abiName" : "bjs_ClassRoundtrip_roundtripSimpleClass", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripSimpleClass", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftHeapObject" : { + "_0" : "SimpleClass" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "SimpleClass" + } + } + }, + { + "abiName" : "bjs_ClassRoundtrip_takeAddressClass", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "takeAddressClass", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftHeapObject" : { + "_0" : "AddressClass" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_ClassRoundtrip_makeAddressClass", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "makeAddressClass", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "AddressClass" + } + } + }, + { + "abiName" : "bjs_ClassRoundtrip_roundtripAddressClass", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripAddressClass", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftHeapObject" : { + "_0" : "AddressClass" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "AddressClass" + } + } + } + ], + "name" : "ClassRoundtrip", + "properties" : [ + + ], + "swiftCallName" : "ClassRoundtrip" } ], "enums" : [ @@ -726,5 +1358,227 @@ "moduleName" : "Benchmarks", "protocols" : [ + ], + "structs" : [ + { + "methods" : [ + + ], + "name" : "SimpleStruct", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "count", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "flag", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "rate", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "precise", + "type" : { + "double" : { + + } + } + } + ], + "swiftCallName" : "SimpleStruct" + }, + { + "methods" : [ + + ], + "name" : "Address", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "street", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "city", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "zipCode", + "type" : { + "int" : { + + } + } + } + ], + "swiftCallName" : "Address" + }, + { + "methods" : [ + + ], + "name" : "Person", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "age", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "address", + "type" : { + "swiftStruct" : { + "_0" : "Address" + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "email", + "type" : { + "optional" : { + "_0" : { + "string" : { + + } + } + } + } + } + ], + "swiftCallName" : "Person" + }, + { + "methods" : [ + + ], + "name" : "ComplexStruct", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "id", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "title", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "active", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "score", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "tags", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "metadata", + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "ComplexStruct" + } ] } \ No newline at end of file diff --git a/Benchmarks/run.js b/Benchmarks/run.js index d47473115..5c27e6fff 100644 --- a/Benchmarks/run.js +++ b/Benchmarks/run.js @@ -3,7 +3,7 @@ import { defaultNodeSetup } from "./.build/plugins/PackageToJS/outputs/Package/p import fs from 'fs'; import path from 'path'; import { parseArgs } from "util"; -import { APIResult, ComplexResult } from "./.build/plugins/PackageToJS/outputs/Package/bridge-js.js"; +import { APIResultValues as APIResult, ComplexResultValues as ComplexResult } from "./.build/plugins/PackageToJS/outputs/Package/bridge-js.js"; /** * Update progress bar on the current line @@ -454,6 +454,136 @@ async function singleRun(results, nameFilter) { stringRoundtrip.make() } }) + + // Struct performance tests + const structRoundtrip = new exports.StructRoundtrip(); + + benchmarkRunner("StructRoundtrip/takeSimple", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.takeSimple({ name: "Hello", count: 42, flag: true, rate: 0.5, precise: 3.14159 }) + } + }) + benchmarkRunner("StructRoundtrip/makeSimple", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.makeSimple() + } + }) + benchmarkRunner("StructRoundtrip/roundtripSimple", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.roundtripSimple({ name: "Hello", count: 42, flag: true, rate: 0.5, precise: 3.14159 }) + } + }) + + benchmarkRunner("StructRoundtrip/takeAddress", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.takeAddress({ street: "123 Main St", city: "San Francisco", zipCode: 94102 }) + } + }) + benchmarkRunner("StructRoundtrip/makeAddress", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.makeAddress() + } + }) + benchmarkRunner("StructRoundtrip/roundtripAddress", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.roundtripAddress({ street: "123 Main St", city: "San Francisco", zipCode: 94102 }) + } + }) + + benchmarkRunner("StructRoundtrip/takePerson", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.takePerson({ + name: "John Doe", + age: 30, + address: { street: "456 Oak Ave", city: "New York", zipCode: 10001 }, + email: "john@example.com" + }) + } + }) + benchmarkRunner("StructRoundtrip/makePerson", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.makePerson() + } + }) + benchmarkRunner("StructRoundtrip/roundtripPerson", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.roundtripPerson({ + name: "John Doe", + age: 30, + address: { street: "456 Oak Ave", city: "New York", zipCode: 10001 }, + email: "john@example.com" + }) + } + }) + + benchmarkRunner("StructRoundtrip/takeComplex", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.takeComplex({ + id: 12345, + title: "Test Item", + active: true, + score: 98.6, + tags: "swift,wasm,benchmark", + metadata: "{\"version\":1}" + }) + } + }) + benchmarkRunner("StructRoundtrip/makeComplex", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.makeComplex() + } + }) + benchmarkRunner("StructRoundtrip/roundtripComplex", () => { + for (let i = 0; i < iterations; i++) { + structRoundtrip.roundtripComplex({ + id: 12345, + title: "Test Item", + active: true, + score: 98.6, + tags: "swift,wasm,benchmark", + metadata: "{\"version\":1}" + }) + } + }) + + // Class vs Struct comparison tests + const classRoundtrip = new exports.ClassRoundtrip(); + + benchmarkRunner("ClassRoundtrip/takeSimpleClass", () => { + const simple = new exports.SimpleClass("Hello", 42, true, 0.5, 3.14159) + for (let i = 0; i < iterations; i++) { + classRoundtrip.takeSimpleClass(simple) + } + }) + benchmarkRunner("ClassRoundtrip/makeSimpleClass", () => { + for (let i = 0; i < iterations; i++) { + classRoundtrip.makeSimpleClass() + } + }) + benchmarkRunner("ClassRoundtrip/roundtripSimpleClass", () => { + const simple = new exports.SimpleClass("Hello", 42, true, 0.5, 3.14159) + for (let i = 0; i < iterations; i++) { + classRoundtrip.roundtripSimpleClass(simple) + } + }) + + benchmarkRunner("ClassRoundtrip/takeAddressClass", () => { + const address = new exports.AddressClass("123 Main St", "San Francisco", 94102) + for (let i = 0; i < iterations; i++) { + classRoundtrip.takeAddressClass(address) + } + }) + benchmarkRunner("ClassRoundtrip/makeAddressClass", () => { + for (let i = 0; i < iterations; i++) { + classRoundtrip.makeAddressClass() + } + }) + benchmarkRunner("ClassRoundtrip/roundtripAddressClass", () => { + const address = new exports.AddressClass("123 Main St", "San Francisco", 94102) + for (let i = 0; i < iterations; i++) { + classRoundtrip.roundtripAddressClass(address) + } + }) } /** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ca71ef13..712aeffb7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,7 +114,7 @@ These changes require updating the pre-generated Swift bindings committed to the **Adding new BridgeJS intrinsics:** -If you add new `@_extern(wasm, module: "bjs")` functions to [`BridgeJSInstrincics.swift`](Sources/JavaScriptKit/BridgeJSInstrincics.swift), also add corresponding stub entries to [`Plugins/PackageToJS/Templates/instantiate.js`](Plugins/PackageToJS/Templates/instantiate.js) in the `importObject["bjs"]` object. This allows packages without BridgeJS-generated code to instantiate successfully. +If you add new `@_extern(wasm, module: "bjs")` functions to [`BridgeJSIntrinsics.swift`](Sources/JavaScriptKit/BridgeJSIntrinsics.swift), also add corresponding stub entries to [`Plugins/PackageToJS/Templates/instantiate.js`](Plugins/PackageToJS/Templates/instantiate.js) in the `importObject["bjs"]` object. This allows packages without BridgeJS-generated code to instantiate successfully. ## Support If you have any questions or need assistance, feel free to reach out via [GitHub Issues](https://github.com/swiftwasm/JavaScriptKit/issues) or [Discord](https://discord.gg/ashJW8T8yp). diff --git a/Examples/Embedded/Package.swift b/Examples/Embedded/Package.swift index 5ae19adc6..42702394a 100644 --- a/Examples/Embedded/Package.swift +++ b/Examples/Embedded/Package.swift @@ -5,32 +5,17 @@ import PackageDescription let package = Package( name: "Embedded", dependencies: [ - .package(name: "JavaScriptKit", path: "../../"), - .package(url: "https://github.com/swiftwasm/swift-dlmalloc", branch: "0.1.0"), + .package(name: "JavaScriptKit", path: "../../") ], targets: [ .executableTarget( name: "EmbeddedApp", dependencies: [ - "JavaScriptKit", - .product(name: "dlmalloc", package: "swift-dlmalloc"), + "JavaScriptKit" ], - cSettings: [.unsafeFlags(["-fdeclspec"])], swiftSettings: [ - .enableExperimentalFeature("Embedded"), - .enableExperimentalFeature("Extern"), - .unsafeFlags([ - "-Xfrontend", "-gnone", - "-Xfrontend", "-disable-stack-protector", - ]), + .enableExperimentalFeature("Extern") ], - linkerSettings: [ - .unsafeFlags([ - "-Xclang-linker", "-nostdlib", - "-Xlinker", "--no-entry", - "-Xlinker", "--export-if-defined=__main_argc_argv", - ]) - ] ) ], swiftLanguageModes: [.v5] diff --git a/Examples/Embedded/Sources/EmbeddedApp/_thingsThatShouldNotBeNeeded.swift b/Examples/Embedded/Sources/EmbeddedApp/_thingsThatShouldNotBeNeeded.swift deleted file mode 100644 index 8f45ccee9..000000000 --- a/Examples/Embedded/Sources/EmbeddedApp/_thingsThatShouldNotBeNeeded.swift +++ /dev/null @@ -1,36 +0,0 @@ -import JavaScriptKit - -// NOTE: it seems the embedded tree shaker gets rid of these exports if they are not used somewhere -func _i_need_to_be_here_for_wasm_exports_to_work() { - _ = _swjs_library_features - _ = _swjs_call_host_function - _ = _swjs_free_host_function -} - -// TODO: why do I need this? and surely this is not ideal... figure this out, or at least have this come from a C lib -@_cdecl("strlen") -func strlen(_ s: UnsafePointer) -> Int { - var p = s - while p.pointee != 0 { - p += 1 - } - return p - s -} - -enum LCG { - static var x: UInt8 = 0 - static let a: UInt8 = 0x05 - static let c: UInt8 = 0x0b - - static func next() -> UInt8 { - x = a &* x &+ c - return x - } -} - -@_cdecl("arc4random_buf") -public func arc4random_buf(_ buffer: UnsafeMutableRawPointer, _ size: Int) { - for i in 0.. 0 { - throw BridgeJSCoreError( - errors.map { $0.formattedDescription(fileName: inputFilePath) } - .joined(separator: "\n") - ) - } + sourceFiles.append((sourceFile, inputFilePath)) } /// Finalizes the export process and generates the bridge code @@ -61,6 +54,27 @@ public class ExportSwift { /// - Returns: A tuple containing the generated Swift code and a skeleton /// describing the exported APIs public func finalize() throws -> (outputSwift: String, outputSkeleton: ExportedSkeleton)? { + // Walk through each source file and collect exported APIs + var perSourceErrors: [(inputFilePath: String, errors: [DiagnosticError])] = [] + for (sourceFile, inputFilePath) in sourceFiles { + progress.print("Processing \(inputFilePath)") + let errors = try parseSingleFile(sourceFile) + if errors.count > 0 { + perSourceErrors.append((inputFilePath: inputFilePath, errors: errors)) + } + } + + if !perSourceErrors.isEmpty { + // Aggregate and throw all errors + var allErrors: [String] = [] + for (inputFilePath, errors) in perSourceErrors { + for error in errors { + allErrors.append(error.formattedDescription(fileName: inputFilePath)) + } + } + throw BridgeJSCoreError(allErrors.joined(separator: "\n")) + } + guard let outputSwift = try renderSwiftGlue() else { return nil } @@ -71,6 +85,7 @@ public class ExportSwift { functions: exportedFunctions, classes: exportedClasses, enums: exportedEnums, + structs: exportedStructs, protocols: exportedProtocols, exposeToGlobal: exposeToGlobal ) @@ -88,6 +103,9 @@ public class ExportSwift { /// The names of the exported protocols, in the order they were written in the source file var exportedProtocolNames: [String] = [] var exportedProtocolByName: [String: ExportedProtocol] = [:] + /// The names of the exported structs, in the order they were written in the source file + var exportedStructNames: [String] = [] + var exportedStructByName: [String: ExportedStruct] = [:] var errors: [DiagnosticError] = [] /// Creates a unique key by combining name and namespace @@ -132,6 +150,7 @@ public class ExportSwift { case classBody(name: String, key: String) case enumBody(name: String, key: String) case protocolBody(name: String, key: String) + case structBody(name: String, key: String) } struct StateStack { @@ -310,50 +329,77 @@ public class ExportSwift { return nil } - let className = calledExpr.baseName.text - let expectedClassName: String? + let typeName = calledExpr.baseName.text + + let isStructType: Bool + let expectedTypeName: String? switch type { - case .swiftHeapObject(let name): - expectedClassName = name.split(separator: ".").last.map(String.init) - case .optional(.swiftHeapObject(let name)): - expectedClassName = name.split(separator: ".").last.map(String.init) + case .swiftStruct(let name), .optional(.swiftStruct(let name)): + isStructType = true + expectedTypeName = name.split(separator: ".").last.map(String.init) + case .swiftHeapObject(let name), .optional(.swiftHeapObject(let name)): + isStructType = false + expectedTypeName = name.split(separator: ".").last.map(String.init) default: diagnose( node: funcCall, - message: "Constructor calls are only supported for class types", - hint: "Parameter type should be a Swift class" + message: "Constructor calls are only supported for class and struct types", + hint: "Parameter type should be a Swift class or struct" ) return nil } - guard let expectedClassName = expectedClassName, className == expectedClassName else { + guard let expectedTypeName = expectedTypeName, typeName == expectedTypeName else { diagnose( node: funcCall, - message: "Constructor class name '\(className)' doesn't match parameter type", + message: "Constructor type name '\(typeName)' doesn't match parameter type", hint: "Ensure the constructor matches the parameter type" ) return nil } - if funcCall.arguments.isEmpty { - return .object(className) - } - - var constructorArgs: [DefaultValue] = [] - for argument in funcCall.arguments { - guard let argValue = extractLiteralValue(from: argument.expression) else { - diagnose( - node: argument.expression, - message: "Constructor argument must be a literal value", - hint: "Use simple literals like \"text\", 42, true, false in constructor arguments" - ) - return nil + if isStructType { + // For structs, extract field name/value pairs + var fields: [DefaultValueField] = [] + for argument in funcCall.arguments { + guard let fieldName = argument.label?.text else { + diagnose( + node: argument, + message: "Struct initializer arguments must have labels", + hint: "Use labeled arguments like MyStruct(x: 1, y: 2)" + ) + return nil + } + guard let fieldValue = extractLiteralValue(from: argument.expression) else { + diagnose( + node: argument.expression, + message: "Struct field value must be a literal", + hint: "Use simple literals like \"text\", 42, true, false in struct fields" + ) + return nil + } + fields.append(DefaultValueField(name: fieldName, value: fieldValue)) + } + return .structLiteral(typeName, fields) + } else { + if funcCall.arguments.isEmpty { + return .object(typeName) } - constructorArgs.append(argValue) + var constructorArgs: [DefaultValue] = [] + for argument in funcCall.arguments { + guard let argValue = extractLiteralValue(from: argument.expression) else { + diagnose( + node: argument.expression, + message: "Constructor argument must be a literal value", + hint: "Use simple literals like \"text\", 42, true, false in constructor arguments" + ) + return nil + } + constructorArgs.append(argValue) + } + return .objectWithArguments(typeName, constructorArgs) } - - return .objectWithArguments(className, constructorArgs) } /// Extracts a literal value from an expression with optional type checking @@ -516,6 +562,14 @@ public class ExportSwift { case .protocolBody(_, _): // Protocol methods are handled in visitProtocolMethod during protocol parsing return .skipChildren + case .structBody(let structName, let structKey): + if let exportedFunction = visitFunction(node: node, isStatic: isStatic, structName: structName) { + if var currentStruct = exportedStructByName[structKey] { + currentStruct.methods.append(exportedFunction) + exportedStructByName[structKey] = currentStruct + } + } + return .skipChildren } } @@ -524,7 +578,8 @@ public class ExportSwift { isStatic: Bool, className: String? = nil, classKey: String? = nil, - enumName: String? = nil + enumName: String? = nil, + structName: String? = nil ) -> ExportedFunction? { guard let jsAttribute = node.attributes.firstJSAttribute else { return nil @@ -600,12 +655,21 @@ public class ExportSwift { staticContext = isNamespaceEnum ? .namespaceEnum : .enumName(enumName) case .protocolBody(_, _): return nil + case .structBody(let structName, _): + if isStatic { + staticContext = .structName(structName) + } else { + staticContext = nil + } } let classNameForABI: String? - if case .classBody(let className, _) = state { + switch state { + case .classBody(let className, _): classNameForABI = className - } else { + case .structBody(let structName, _): + classNameForABI = structName + default: classNameForABI = nil } abiName = ABINameGenerator.generateABIName( @@ -691,37 +755,59 @@ public class ExportSwift { override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { guard let jsAttribute = node.attributes.firstJSAttribute else { return .skipChildren } - guard case .classBody(let className, _) = state else { - if case .enumBody(_, _) = state { - diagnose(node: node, message: "Initializers are not supported inside enums") - } else { - diagnose(node: node, message: "@JS init must be inside a @JS class") + + switch state { + case .classBody(let className, let classKey): + if extractNamespace(from: jsAttribute) != nil { + diagnose( + node: jsAttribute, + message: "Namespace is not supported for initializer declarations", + hint: "Remove the namespace from @JS attribute" + ) } - return .skipChildren - } - if extractNamespace(from: jsAttribute) != nil { - diagnose( - node: jsAttribute, - message: "Namespace is not supported for initializer declarations", - hint: "Remove the namespace from @JS attribute" + let parameters = parseParameters(from: node.signature.parameterClause, allowDefaults: true) + + guard let effects = collectEffects(signature: node.signature) else { + return .skipChildren + } + + let constructor = ExportedConstructor( + abiName: "bjs_\(className)_init", + parameters: parameters, + effects: effects ) - } + exportedClassByName[classKey]?.constructor = constructor - let parameters = parseParameters(from: node.signature.parameterClause, allowDefaults: true) + case .structBody(let structName, let structKey): + if extractNamespace(from: jsAttribute) != nil { + diagnose( + node: jsAttribute, + message: "Namespace is not supported for initializer declarations", + hint: "Remove the namespace from @JS attribute" + ) + } - guard let effects = collectEffects(signature: node.signature) else { - return .skipChildren - } + let parameters = parseParameters(from: node.signature.parameterClause, allowDefaults: true) - let constructor = ExportedConstructor( - abiName: "bjs_\(className)_init", - parameters: parameters, - effects: effects - ) - if case .classBody(_, let classKey) = state { - exportedClassByName[classKey]?.constructor = constructor + guard let effects = collectEffects(signature: node.signature) else { + return .skipChildren + } + + let constructor = ExportedConstructor( + abiName: "bjs_\(structName)_init", + parameters: parameters, + effects: effects + ) + exportedStructByName[structKey]?.constructor = constructor + + case .enumBody(_, _): + diagnose(node: node, message: "Initializers are not supported inside enums") + + case .topLevel, .protocolBody(_, _): + diagnose(node: node, message: "@JS init must be inside a @JS class or struct") } + return .skipChildren } @@ -753,27 +839,29 @@ public class ExportSwift { // Determine static context and validate placement let staticContext: StaticContext? - let classKey: String switch state { - case .classBody(let className, let key): - classKey = key + case .classBody(let className, _): staticContext = isStatic ? .className(className) : nil case .enumBody(let enumName, let enumKey): if !isStatic { diagnose(node: node, message: "Only static properties are supported in enums") return .skipChildren } - classKey = enumKey - let isNamespaceEnum = exportedEnumByName[enumKey]?.cases.isEmpty ?? true staticContext = isStatic ? (isNamespaceEnum ? .namespaceEnum : .enumName(enumName)) : nil - case .topLevel: diagnose(node: node, message: "@JS var must be inside a @JS class or enum") return .skipChildren case .protocolBody(let protocolName, let protocolKey): return visitProtocolProperty(node: node, protocolName: protocolName, protocolKey: protocolKey) + case .structBody(let structName, _): + if isStatic { + staticContext = .structName(structName) + } else { + diagnose(node: node, message: "@JS var must be static in structs (instance fields don't need @JS)") + return .skipChildren + } } // Process each binding (variable declaration) @@ -810,13 +898,15 @@ public class ExportSwift { staticContext: staticContext ) - if case .enumBody(_, let enumKey) = state { - if var currentEnum = exportedEnumByName[enumKey] { + if case .enumBody(_, let key) = state { + if var currentEnum = exportedEnumByName[key] { currentEnum.staticProperties.append(exportedProperty) - exportedEnumByName[enumKey] = currentEnum + exportedEnumByName[key] = currentEnum } - } else { - exportedClassByName[classKey]?.properties.append(exportedProperty) + } else if case .structBody(_, let key) = state { + exportedStructByName[key]?.properties.append(exportedProperty) + } else if case .classBody(_, let key) = state { + exportedClassByName[key]?.properties.append(exportedProperty) } } @@ -937,7 +1027,6 @@ public class ExportSwift { let emitStyle = exportedEnum.emitStyle if case .tsEnum = emitStyle { - // Check for Bool raw type limitation if exportedEnum.rawType == .bool { diagnose( node: jsAttribute, @@ -945,8 +1034,6 @@ public class ExportSwift { hint: "Use enumStyle: .const or change the raw type to String or a numeric type" ) } - - // Check for static functions limitation if !exportedEnum.staticMethods.isEmpty { diagnose( node: jsAttribute, @@ -1055,6 +1142,98 @@ public class ExportSwift { return .skipChildren } + override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + guard let jsAttribute = node.attributes.firstJSAttribute else { + return .skipChildren + } + + let name = node.name.text + + let namespaceResult = resolveNamespace(from: jsAttribute, for: node, declarationType: "struct") + guard namespaceResult.isValid else { + return .skipChildren + } + let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: name) + let explicitAccessControl = computeExplicitAtLeastInternalAccessControl( + for: node, + message: "Struct visibility must be at least internal" + ) + + var properties: [ExportedProperty] = [] + + // Process all variables in struct as readonly (value semantics) and don't require @JS + for member in node.memberBlock.members { + if let varDecl = member.decl.as(VariableDeclSyntax.self) { + let isStatic = varDecl.modifiers.contains { modifier in + modifier.name.tokenKind == .keyword(.static) || modifier.name.tokenKind == .keyword(.class) + } + + // Handled with error in visitVariable + if varDecl.attributes.hasJSAttribute() { + continue + } + // Skips static non-@JS properties + if isStatic { + continue + } + + for binding in varDecl.bindings { + guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { + continue + } + + let fieldName = pattern.identifier.text + + guard let typeAnnotation = binding.typeAnnotation else { + diagnose(node: binding, message: "Struct field must have explicit type annotation") + continue + } + + guard let fieldType = self.parent.lookupType(for: typeAnnotation.type) else { + diagnoseUnsupportedType( + node: typeAnnotation.type, + type: typeAnnotation.type.trimmedDescription + ) + continue + } + + let property = ExportedProperty( + name: fieldName, + type: fieldType, + isReadonly: true, + isStatic: false, + namespace: namespaceResult.namespace, + staticContext: nil + ) + properties.append(property) + } + } + } + + let structUniqueKey = makeKey(name: name, namespace: namespaceResult.namespace) + let exportedStruct = ExportedStruct( + name: name, + swiftCallName: swiftCallName, + explicitAccessControl: explicitAccessControl, + properties: properties, + methods: [], + namespace: namespaceResult.namespace + ) + + exportedStructByName[structUniqueKey] = exportedStruct + exportedStructNames.append(structUniqueKey) + + stateStack.push(state: .structBody(name: name, key: structUniqueKey)) + + return .visitChildren + } + + override func visitPost(_ node: StructDeclSyntax) { + if case .structBody(_, _) = stateStack.current { + stateStack.pop() + } + } + private func visitProtocolMethod( node: FunctionDeclSyntax, protocolName: String, @@ -1314,6 +1493,11 @@ public class ExportSwift { collector.exportedProtocolByName[$0]! } ) + exportedStructs.append( + contentsOf: collector.exportedStructNames.map { + collector.exportedStructByName[$0]! + } + ) return collector.errors } @@ -1463,6 +1647,11 @@ public class ExportSwift { } } + if let structDecl = typeDecl.as(StructDeclSyntax.self) { + let swiftCallName = ExportSwift.computeSwiftCallName(for: structDecl, itemName: structDecl.name.text) + return .swiftStruct(swiftCallName) + } + guard typeDecl.is(ClassDeclSyntax.self) || typeDecl.is(ActorDeclSyntax.self) else { return nil } @@ -1484,49 +1673,45 @@ public class ExportSwift { var decls: [DeclSyntax] = [] guard exportedFunctions.count > 0 || exportedClasses.count > 0 || exportedEnums.count > 0 - || exportedProtocols.count > 0 + || exportedProtocols.count > 0 || exportedStructs.count > 0 else { return nil } decls.append(Self.prelude) + let closureCodegen = ClosureCodegen() var closureSignatures: Set = [] for function in exportedFunctions { - collectClosureSignatures(from: function.parameters, into: &closureSignatures) - collectClosureSignatures(from: function.returnType, into: &closureSignatures) + closureCodegen.collectClosureSignatures(from: function.parameters, into: &closureSignatures) + closureCodegen.collectClosureSignatures(from: function.returnType, into: &closureSignatures) } for klass in exportedClasses { if let constructor = klass.constructor { - collectClosureSignatures(from: constructor.parameters, into: &closureSignatures) + closureCodegen.collectClosureSignatures(from: constructor.parameters, into: &closureSignatures) } for method in klass.methods { - collectClosureSignatures(from: method.parameters, into: &closureSignatures) - collectClosureSignatures(from: method.returnType, into: &closureSignatures) + closureCodegen.collectClosureSignatures(from: method.parameters, into: &closureSignatures) + closureCodegen.collectClosureSignatures(from: method.returnType, into: &closureSignatures) } for property in klass.properties { - collectClosureSignatures(from: property.type, into: &closureSignatures) + closureCodegen.collectClosureSignatures(from: property.type, into: &closureSignatures) } } for signature in closureSignatures.sorted(by: { $0.mangleName < $1.mangleName }) { - decls.append(contentsOf: try closureCodegen.renderClosureHelper(signature: signature)) - decls.append(try closureCodegen.renderClosureInvokeHandler(signature: signature)) + decls.append(contentsOf: try closureCodegen.renderClosureHelpers(signature)) + decls.append(try closureCodegen.renderClosureInvokeHandler(signature)) } + let protocolCodegen = ProtocolCodegen() for proto in exportedProtocols { - decls.append(contentsOf: try renderProtocolWrapper(protocol: proto)) + decls.append(contentsOf: try protocolCodegen.renderProtocolWrapper(proto, moduleName: moduleName)) } + let enumCodegen = EnumCodegen() for enumDef in exportedEnums { - switch enumDef.enumType { - case .simple: - decls.append(enumCodegen.renderCaseEnumHelpers(enumDef)) - case .rawValue: - decls.append("extension \(raw: enumDef.swiftCallName): _BridgedSwiftEnumNoPayload {}") - case .associatedValue: - decls.append(enumCodegen.renderAssociatedValueEnumHelpers(enumDef)) - case .namespace: - () + if let enumHelpers = enumCodegen.renderEnumHelpers(enumDef) { + decls.append(enumHelpers) } for staticMethod in enumDef.staticMethods { @@ -1543,6 +1728,12 @@ public class ExportSwift { } } + let structCodegen = StructCodegen() + for structDef in exportedStructs { + decls.append(structCodegen.renderStructHelpers(structDef)) + decls.append(contentsOf: try renderSingleExportedStruct(struct: structDef)) + } + for function in exportedFunctions { decls.append(try renderSingleExportedFunction(function: function)) } @@ -1592,6 +1783,9 @@ public class ExportSwift { case .closure(let signature): typeNameForIntrinsic = param.type.swiftType liftingExpr = ExprSyntax("_BJS_Closure_\(raw: signature.mangleName).bridgeJSLift(\(raw: param.name))") + case .swiftStruct(let structName): + typeNameForIntrinsic = structName + liftingExpr = ExprSyntax("\(raw: structName).bridgeJSLiftParameter()") case .optional(let wrappedType): typeNameForIntrinsic = "Optional<\(wrappedType.swiftType)>" liftingExpr = ExprSyntax( @@ -1667,6 +1861,7 @@ public class ExportSwift { } func call(name: String, returnType: BridgeType) { + generateParameterLifting() let item = renderCallStatement(callee: "\(raw: name)", returnType: returnType) append(item) } @@ -1692,8 +1887,9 @@ public class ExportSwift { } } - func callMethod(klassName: String, methodName: String, returnType: BridgeType) { + func callMethod(methodName: String, returnType: BridgeType) { let (_, selfExpr) = removeFirstLiftedParameter() + generateParameterLifting() let item = renderCallStatement( callee: "\(raw: selfExpr).\(raw: methodName)", returnType: returnType @@ -1701,7 +1897,31 @@ public class ExportSwift { append(item) } - func callPropertyGetter(klassName: String, propertyName: String, returnType: BridgeType) { + /// Generates intermediate variables for stack-using parameters if needed for LIFO compatibility + private func generateParameterLifting() { + let stackParamIndices = parameters.enumerated().compactMap { index, param -> Int? in + switch param.type { + case .swiftStruct, .optional(.swiftStruct), + .associatedValueEnum, .optional(.associatedValueEnum): + return index + default: + return nil + } + } + + guard stackParamIndices.count > 1 else { return } + + for index in stackParamIndices.reversed() { + let param = parameters[index] + let expr = liftedParameterExprs[index] + let varName = "_tmp_\(param.name)" + + append("let \(raw: varName) = \(expr)") + liftedParameterExprs[index] = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(varName))) + } + } + + func callPropertyGetter(propertyName: String, returnType: BridgeType) { let (_, selfExpr) = removeFirstLiftedParameter() if returnType == .void { append("\(raw: selfExpr).\(raw: propertyName)") @@ -1723,7 +1943,7 @@ public class ExportSwift { } } - func callPropertySetter(klassName: String, propertyName: String) { + func callPropertySetter(propertyName: String) { let (_, selfExpr) = removeFirstLiftedParameter() let (_, newValueExpr) = removeFirstLiftedParameter() append("\(raw: selfExpr).\(raw: propertyName) = \(raw: newValueExpr)") @@ -1827,541 +2047,186 @@ public class ExportSwift { func returnSignature() -> String { return abiReturnType?.swiftType ?? "Void" } + } - private struct ClosureCodegen { - func generateOptionalParameterLowering(signature: ClosureSignature) throws -> String { - var lines: [String] = [] + /// Context for property rendering that determines call behavior and ABI generation + private enum PropertyRenderingContext { + case enumStatic(enumDef: ExportedEnum) + case classStatic(klass: ExportedClass) + case classInstance(klass: ExportedClass) + case structStatic(structDef: ExportedStruct) - for (index, paramType) in signature.parameters.enumerated() { - guard case .optional(let wrappedType) = paramType else { - continue - } - let paramName = "param\(index)" - if case .swiftHeapObject = wrappedType { - lines.append( - "let (\(paramName)IsSome, \(paramName)Value) = \(paramName).bridgeJSLowerParameterWithRetain()" - ) - } else { - lines.append( - "let (\(paramName)IsSome, \(paramName)Value) = \(paramName).bridgeJSLowerParameterWithPresence()" - ) - } + var isStatic: Bool { + switch self { + case .enumStatic, .classStatic, .structStatic: + return true + case .classInstance: + return false } + } - return lines.isEmpty ? "" : lines.joined(separator: "\n") + "\n" + var className: String { + switch self { + case .enumStatic(let enumDef): + return enumDef.name + case .classStatic(let klass), .classInstance(let klass): + return klass.name + case .structStatic(let structDef): + return structDef.name + } } - func renderClosureHelper(signature: ClosureSignature) throws -> [DeclSyntax] { - let mangledName = signature.mangleName - let helperName = "_BJS_Closure_\(mangledName)" - let boxClassName = "_BJS_ClosureBox_\(mangledName)" + func callName(for property: ExportedProperty) -> String { + switch self { + case .enumStatic(let enumDef): + return property.callName(prefix: enumDef.swiftCallName) + case .classStatic, .classInstance: + return property.callName() + case .structStatic(let structDef): + return property.callName(prefix: structDef.swiftCallName) + } + } + } - let closureParams = signature.parameters.enumerated().map { index, type in - "\(type.swiftType)" - }.joined(separator: ", ") + /// Renders getter and setter Swift thunk code for a property in any context + /// This unified function eliminates duplication between enum static, class static, and class instance property rendering + private func renderSingleExportedProperty( + property: ExportedProperty, + context: PropertyRenderingContext + ) throws -> [DeclSyntax] { + var decls: [DeclSyntax] = [] - let swiftEffects = (signature.isAsync ? " async" : "") + (signature.isThrows ? " throws" : "") - let swiftReturnType = signature.returnType.swiftType - let closureType = "(\(closureParams))\(swiftEffects) -> \(swiftReturnType)" + let callName = context.callName(for: property) + let className = context.className + let isStatic = context.isStatic - var invokeParams: [(name: String, type: String)] = [("_", "Int32")] - var invokeCallArgs: [String] = ["callback.bridgeJSLowerParameter()"] + let getterBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false, isStatic: isStatic)) - for (index, paramType) in signature.parameters.enumerated() { - let paramName = "param\(index)" + if !isStatic { + try getterBuilder.liftParameter( + param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(className)) + ) + } - if case .optional(let wrappedType) = paramType { - invokeParams.append(("_", "Int32")) + if isStatic { + getterBuilder.callStaticProperty(name: callName, returnType: property.type) + } else { + getterBuilder.callPropertyGetter(propertyName: callName, returnType: property.type) + } - switch wrappedType { - case .swiftHeapObject: - invokeParams.append(("_", "UnsafeMutableRawPointer")) - case .string, .rawValueEnum(_, .string): - invokeParams.append(("_", "Int32")) - default: - let lowerInfo = try wrappedType.loweringReturnInfo() - if let wasmType = lowerInfo.returnType { - invokeParams.append(("_", wasmType.swiftType)) - } else { - invokeParams.append(("_", "Int32")) - } - } + try getterBuilder.lowerReturnValue(returnType: property.type) + decls.append(getterBuilder.render(abiName: property.getterAbiName(className: className))) - invokeCallArgs.append("\(paramName)IsSome") - invokeCallArgs.append("\(paramName)Value") - } else { - let lowerInfo = try paramType.loweringReturnInfo() - if let wasmType = lowerInfo.returnType { - invokeParams.append(("_", wasmType.swiftType)) - invokeCallArgs.append("\(paramName).bridgeJSLowerParameter()") - } else { - invokeParams.append(("_", "Int32")) - invokeCallArgs.append("\(paramName).bridgeJSLowerParameter()") - } - } - } + // Generate property setter if not readonly + if !property.isReadonly { + let setterBuilder = ExportedThunkBuilder( + effects: Effects(isAsync: false, isThrows: false, isStatic: isStatic) + ) - let invokeSignature = invokeParams.map { "\($0.name): \($0.type)" }.joined(separator: ", ") - let invokeReturnType: String - if case .optional = signature.returnType { - invokeReturnType = "Void" - } else if let wasmType = try signature.returnType.liftingReturnInfo(context: .exportSwift).valueToLift { - invokeReturnType = wasmType.swiftType - } else { - invokeReturnType = "Void" + // Lift parameters based on property type + if !isStatic { + // Instance properties need _self parameter + try setterBuilder.liftParameter( + param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(className)) + ) } - let externName = "invoke_js_callback_\(signature.moduleName)_\(mangledName)" + try setterBuilder.liftParameter( + param: Parameter(label: "value", name: "value", type: property.type) + ) - let returnLifting: String - if signature.returnType == .void { - returnLifting = "\(externName)(\(invokeCallArgs.joined(separator: ", ")))" - } else if case .optional = signature.returnType { - returnLifting = """ - \(externName)(\(invokeCallArgs.joined(separator: ", "))) - return \(signature.returnType.swiftType).bridgeJSLiftReturnFromSideChannel() - """ + if isStatic { + let klassName = callName.components(separatedBy: ".").dropLast().joined(separator: ".") + setterBuilder.callStaticPropertySetter(klassName: klassName, propertyName: property.name) } else { - returnLifting = """ - let resultId = \(externName)(\(invokeCallArgs.joined(separator: ", "))) - return \(signature.returnType.swiftType).bridgeJSLiftReturn(resultId) - """ + setterBuilder.callPropertySetter(propertyName: callName) } - let optionalLoweringCode = try generateOptionalParameterLowering(signature: signature) - - let externDecl: DeclSyntax = """ - @_extern(wasm, module: "bjs", name: "\(raw: externName)") - fileprivate func \(raw: externName)(\(raw: invokeSignature)) -> \(raw: invokeReturnType) - """ - - let boxDecl: DeclSyntax = """ - private final class \(raw: boxClassName): _BridgedSwiftClosureBox { - let closure: \(raw: closureType) - init(_ closure: @escaping \(raw: closureType)) { - self.closure = closure - } - } - - private enum \(raw: helperName) { - static func bridgeJSLower(_ closure: @escaping \(raw: closureType)) -> UnsafeMutableRawPointer { - let box = \(raw: boxClassName)(closure) - return Unmanaged.passRetained(box).toOpaque() - } - - static func bridgeJSLift(_ callbackId: Int32) -> \(raw: closureType) { - let callback = JSObject.bridgeJSLiftParameter(callbackId) - return { [callback] \(raw: signature.parameters.indices.map { "param\($0)" }.joined(separator: ", ")) in - #if arch(wasm32) - \(raw: optionalLoweringCode)\(raw: returnLifting) - #else - fatalError("Only available on WebAssembly") - #endif - } - } - } - """ - return [externDecl, boxDecl] + try setterBuilder.lowerReturnValue(returnType: .void) + decls.append(setterBuilder.render(abiName: property.setterAbiName(className: className))) } - func renderClosureInvokeHandler(signature: ClosureSignature) throws -> DeclSyntax { - let boxClassName = "_BJS_ClosureBox_\(signature.mangleName)" - let abiName = "invoke_swift_closure_\(signature.moduleName)_\(signature.mangleName)" - - var abiParams: [(name: String, type: String)] = [("boxPtr", "UnsafeMutableRawPointer")] - var liftedParams: [String] = [] - - for (index, paramType) in signature.parameters.enumerated() { - let paramName = "param\(index)" - let liftInfo = try paramType.liftParameterInfo() + return decls + } - for (argName, wasmType) in liftInfo.parameters { - let fullName = - liftInfo.parameters.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName - abiParams.append((fullName, wasmType.swiftType)) - } + func renderSingleExportedFunction(function: ExportedFunction) throws -> DeclSyntax { + let builder = ExportedThunkBuilder(effects: function.effects) + for param in function.parameters { + try builder.liftParameter(param: param) + } - let argNames = liftInfo.parameters.map { (argName, _) in - liftInfo.parameters.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName - } - liftedParams.append("\(paramType.swiftType).bridgeJSLiftParameter(\(argNames.joined(separator: ", ")))") - } - - let paramSignature = abiParams.map { "\($0.name): \($0.type)" }.joined(separator: ", ") - let closureCall = "box.closure(\(liftedParams.joined(separator: ", ")))" - - let returnCode: String - if signature.returnType == .void { - returnCode = closureCall - } else { - returnCode = """ - let result = \(closureCall) - return result.bridgeJSLowerReturn() - """ - } - - let abiReturnType: String - if signature.returnType == .void { - abiReturnType = "Void" - } else if let wasmType = try signature.returnType.loweringReturnInfo().returnType { - abiReturnType = wasmType.swiftType - } else { - abiReturnType = "Void" - } - - return """ - @_expose(wasm, "\(raw: abiName)") - @_cdecl("\(raw: abiName)") - public func _\(raw: abiName)(\(raw: paramSignature)) -> \(raw: abiReturnType) { - #if arch(wasm32) - let box = Unmanaged<\(raw: boxClassName)>.fromOpaque(boxPtr).takeUnretainedValue() - \(raw: returnCode) - #else - fatalError("Only available on WebAssembly") - #endif - } - """ - } - - } - - private struct EnumCodegen { - func renderCaseEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax { - let typeName = enumDef.swiftCallName - var initCases: [String] = [] - var valueCases: [String] = [] - for (index, enumCase) in enumDef.cases.enumerated() { - initCases.append("case \(index): self = .\(enumCase.name)") - valueCases.append("case .\(enumCase.name): return \(index)") - } - let initSwitch = (["switch bridgeJSRawValue {"] + initCases + ["default: return nil", "}"]).joined( - separator: "\n" - ) - let valueSwitch = (["switch self {"] + valueCases + ["}"]).joined(separator: "\n") - - return """ - extension \(raw: typeName): _BridgedSwiftCaseEnum { - @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { - return bridgeJSRawValue - } - @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> \(raw: typeName) { - return bridgeJSLiftParameter(value) - } - @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> \(raw: typeName) { - return \(raw: typeName)(bridgeJSRawValue: value)! - } - @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { - return bridgeJSLowerParameter() - } - - private init?(bridgeJSRawValue: Int32) { - \(raw: initSwitch) - } - - private var bridgeJSRawValue: Int32 { - \(raw: valueSwitch) - } - } - """ - } - - func renderAssociatedValueEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax { - let typeName = enumDef.swiftCallName - return """ - extension \(raw: typeName): _BridgedSwiftAssociatedValueEnum { - private static func _bridgeJSLiftFromCaseId(_ caseId: Int32) -> \(raw: typeName) { - switch caseId { - \(raw: generateStackLiftSwitchCases(enumDef: enumDef).joined(separator: "\n")) - default: fatalError("Unknown \(raw: typeName) case ID: \\(caseId)") - } - } - - // MARK: Protocol Export - - @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { - switch self { - \(raw: generateLowerParameterSwitchCases(enumDef: enumDef).joined(separator: "\n")) - } - } - - @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ caseId: Int32) -> \(raw: typeName) { - return _bridgeJSLiftFromCaseId(caseId) - } - - // MARK: ExportSwift - - @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ caseId: Int32) -> \(raw: typeName) { - return _bridgeJSLiftFromCaseId(caseId) - } - - @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { - switch self { - \(raw: generateReturnSwitchCases(enumDef: enumDef).joined(separator: "\n")) - } - } - } - """ - } - - private func generateStackLiftSwitchCases(enumDef: ExportedEnum) -> [String] { - var cases: [String] = [] - for (caseIndex, enumCase) in enumDef.cases.enumerated() { - if enumCase.associatedValues.isEmpty { - cases.append("case \(caseIndex): return .\(enumCase.name)") - } else { - var lines: [String] = [] - lines.append("case \(caseIndex):") - let argList = enumCase.associatedValues.map { associatedValue in - let paramName: String - if let label = associatedValue.label { - paramName = "\(label): " - } else { - paramName = "" - } - switch associatedValue.type { - case .string: - return - "\(paramName)String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" - case .int: - return "\(paramName)Int.bridgeJSLiftParameter(_swift_js_pop_param_int32())" - case .bool: - return "\(paramName)Bool.bridgeJSLiftParameter(_swift_js_pop_param_int32())" - case .float: - return "\(paramName)Float.bridgeJSLiftParameter(_swift_js_pop_param_f32())" - case .double: - return "\(paramName)Double.bridgeJSLiftParameter(_swift_js_pop_param_f64())" - case .optional(let wrappedType): - switch wrappedType { - case .string: - return - "\(paramName)Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32())" - case .int: - return - "\(paramName)Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" - case .bool: - return - "\(paramName)Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" - case .float: - return - "\(paramName)Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_f32())" - case .double: - return - "\(paramName)Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_f64())" - default: - return "" - } - default: - return "\(paramName)Int.bridgeJSLiftParameter(_swift_js_pop_param_int32())" - } - } - lines.append("return .\(enumCase.name)(\(argList.joined(separator: ", ")))") - cases.append(lines.joined(separator: "\n")) - } - } - return cases - } - - private func generatePayloadPushingCode( - associatedValues: [AssociatedValue] - ) -> [String] { - var bodyLines: [String] = [] - for (index, associatedValue) in associatedValues.enumerated() { - let paramName = associatedValue.label ?? "param\(index)" - switch associatedValue.type { - case .string: - bodyLines.append("var __bjs_\(paramName) = \(paramName)") - bodyLines.append("__bjs_\(paramName).withUTF8 { ptr in") - bodyLines.append("_swift_js_push_string(ptr.baseAddress, Int32(ptr.count))") - bodyLines.append("}") - case .int: - bodyLines.append("_swift_js_push_int(Int32(\(paramName)))") - case .bool: - bodyLines.append("_swift_js_push_int(\(paramName) ? 1 : 0)") - case .float: - bodyLines.append("_swift_js_push_f32(\(paramName))") - case .double: - bodyLines.append("_swift_js_push_f64(\(paramName))") - case .optional(let wrappedType): - bodyLines.append("let __bjs_isSome_\(paramName) = \(paramName) != nil") - bodyLines.append("if let __bjs_unwrapped_\(paramName) = \(paramName) {") - switch wrappedType { - case .string: - bodyLines.append("var __bjs_str_\(paramName) = __bjs_unwrapped_\(paramName)") - bodyLines.append("__bjs_str_\(paramName).withUTF8 { ptr in") - bodyLines.append("_swift_js_push_string(ptr.baseAddress, Int32(ptr.count))") - bodyLines.append("}") - case .int: - bodyLines.append("_swift_js_push_int(Int32(__bjs_unwrapped_\(paramName)))") - case .bool: - bodyLines.append("_swift_js_push_int(__bjs_unwrapped_\(paramName) ? 1 : 0)") - case .float: - bodyLines.append("_swift_js_push_f32(__bjs_unwrapped_\(paramName))") - case .double: - bodyLines.append("_swift_js_push_f64(__bjs_unwrapped_\(paramName))") - default: - bodyLines.append( - "preconditionFailure(\"BridgeJS: unsupported optional wrapped type in generated code\")" - ) - } - bodyLines.append("}") - bodyLines.append("_swift_js_push_int(__bjs_isSome_\(paramName) ? 1 : 0)") - default: - bodyLines.append( - "preconditionFailure(\"BridgeJS: unsupported associated value type in generated code\")" - ) - } - } - return bodyLines - } - - private func generateLowerParameterSwitchCases(enumDef: ExportedEnum) -> [String] { - var cases: [String] = [] - for (caseIndex, enumCase) in enumDef.cases.enumerated() { - if enumCase.associatedValues.isEmpty { - cases.append("case .\(enumCase.name):") - cases.append("return Int32(\(caseIndex))") - } else { - let payloadCode = generatePayloadPushingCode(associatedValues: enumCase.associatedValues) - let pattern = enumCase.associatedValues.enumerated() - .map { index, associatedValue in "let \(associatedValue.label ?? "param\(index)")" } - .joined(separator: ", ") - cases.append("case .\(enumCase.name)(\(pattern)):") - cases.append(contentsOf: payloadCode) - cases.append("return Int32(\(caseIndex))") - } - } - return cases - } - - private func generateReturnSwitchCases(enumDef: ExportedEnum) -> [String] { - var cases: [String] = [] - for (caseIndex, enumCase) in enumDef.cases.enumerated() { - if enumCase.associatedValues.isEmpty { - cases.append("case .\(enumCase.name):") - cases.append("_swift_js_push_tag(Int32(\(caseIndex)))") + if function.effects.isStatic, let staticContext = function.staticContext { + let callName: String + switch staticContext { + case .className(let baseName), .enumName(let baseName), .structName(let baseName): + callName = "\(baseName).\(function.name)" + case .namespaceEnum: + if let namespace = function.namespace, !namespace.isEmpty { + callName = "\(namespace.joined(separator: ".")).\(function.name)" } else { - let pattern = enumCase.associatedValues.enumerated() - .map { index, associatedValue in "let \(associatedValue.label ?? "param\(index)")" } - .joined(separator: ", ") - cases.append("case .\(enumCase.name)(\(pattern)):") - cases.append("_swift_js_push_tag(Int32(\(caseIndex)))") - let payloadCode = generatePayloadPushingCode(associatedValues: enumCase.associatedValues) - cases.append(contentsOf: payloadCode) + callName = function.name } } - return cases + builder.call(name: callName, returnType: function.returnType) + } else { + builder.call(name: function.name, returnType: function.returnType) } - } - /// Context for property rendering that determines call behavior and ABI generation - private enum PropertyRenderingContext { - case enumStatic(enumDef: ExportedEnum) - case classStatic(klass: ExportedClass) - case classInstance(klass: ExportedClass) + try builder.lowerReturnValue(returnType: function.returnType) + return builder.render(abiName: function.abiName) } - /// Renders getter and setter Swift thunk code for a property in any context - /// This unified function eliminates duplication between enum static, class static, and class instance property rendering - private func renderSingleExportedProperty( - property: ExportedProperty, - context: PropertyRenderingContext - ) throws -> [DeclSyntax] { + func renderSingleExportedStruct(struct structDef: ExportedStruct) throws -> [DeclSyntax] { var decls: [DeclSyntax] = [] - let (callName, className, isStatic): (String, String, Bool) - switch context { - case .enumStatic(let enumDef): - callName = property.callName(prefix: enumDef.swiftCallName) - className = enumDef.name - isStatic = true - case .classStatic(let klass): - callName = property.callName() - className = klass.name - isStatic = true - - case .classInstance(let klass): - callName = property.callName() - className = klass.name - isStatic = false + if let constructor = structDef.constructor { + let builder = ExportedThunkBuilder(effects: constructor.effects) + for param in constructor.parameters { + try builder.liftParameter(param: param) + } + builder.call(name: structDef.swiftCallName, returnType: .swiftStruct(structDef.swiftCallName)) + try builder.lowerReturnValue(returnType: .swiftStruct(structDef.swiftCallName)) + decls.append(builder.render(abiName: constructor.abiName)) } - let getterBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false, isStatic: isStatic)) - - if !isStatic { - try getterBuilder.liftParameter( - param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(className)) + for property in structDef.properties where property.isStatic { + decls.append( + contentsOf: try renderSingleExportedProperty( + property: property, + context: .structStatic(structDef: structDef) + ) ) } - if isStatic { - getterBuilder.callStaticProperty(name: callName, returnType: property.type) - } else { - getterBuilder.callPropertyGetter(klassName: className, propertyName: callName, returnType: property.type) - } - - try getterBuilder.lowerReturnValue(returnType: property.type) - decls.append(getterBuilder.render(abiName: property.getterAbiName(className: className))) - - // Generate property setter if not readonly - if !property.isReadonly { - let setterBuilder = ExportedThunkBuilder( - effects: Effects(isAsync: false, isThrows: false, isStatic: isStatic) - ) - - // Lift parameters based on property type - if !isStatic { - // Instance properties need _self parameter - try setterBuilder.liftParameter( - param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(className)) - ) - } - - try setterBuilder.liftParameter( - param: Parameter(label: "value", name: "value", type: property.type) - ) + for method in structDef.methods { + let builder = ExportedThunkBuilder(effects: method.effects) - if isStatic { - let klassName = callName.components(separatedBy: ".").dropLast().joined(separator: ".") - setterBuilder.callStaticPropertySetter(klassName: klassName, propertyName: property.name) + if method.effects.isStatic { + for param in method.parameters { + try builder.liftParameter(param: param) + } + builder.call(name: "\(structDef.swiftCallName).\(method.name)", returnType: method.returnType) } else { - setterBuilder.callPropertySetter(klassName: className, propertyName: callName) + try builder.liftParameter( + param: Parameter(label: nil, name: "_self", type: .swiftStruct(structDef.swiftCallName)) + ) + for param in method.parameters { + try builder.liftParameter(param: param) + } + builder.callMethod( + methodName: method.name, + returnType: method.returnType + ) } - try setterBuilder.lowerReturnValue(returnType: .void) - decls.append(setterBuilder.render(abiName: property.setterAbiName(className: className))) + try builder.lowerReturnValue(returnType: method.returnType) + decls.append(builder.render(abiName: method.abiName)) } return decls } - func renderSingleExportedFunction(function: ExportedFunction) throws -> DeclSyntax { - let builder = ExportedThunkBuilder(effects: function.effects) - for param in function.parameters { - try builder.liftParameter(param: param) - } - - if function.effects.isStatic, let staticContext = function.staticContext { - let callName: String - switch staticContext { - case .className(let baseName), .enumName(let baseName): - callName = "\(baseName).\(function.name)" - case .namespaceEnum: - if let namespace = function.namespace, !namespace.isEmpty { - callName = "\(namespace.joined(separator: ".")).\(function.name)" - } else { - callName = function.name - } - } - builder.call(name: callName, returnType: function.returnType) - } else { - builder.call(name: function.name, returnType: function.returnType) - } - - try builder.lowerReturnValue(returnType: function.returnType) - return builder.render(abiName: function.abiName) - } - /// # Example /// /// Given the following Swift code: @@ -2437,7 +2302,6 @@ public class ExportSwift { try builder.liftParameter(param: param) } builder.callMethod( - klassName: klass.swiftCallName, methodName: method.name, returnType: method.returnType ) @@ -2483,27 +2347,6 @@ public class ExportSwift { return decls } - private func collectClosureSignatures(from parameters: [Parameter], into signatures: inout Set) { - for param in parameters { - collectClosureSignatures(from: param.type, into: &signatures) - } - } - - private func collectClosureSignatures(from type: BridgeType, into signatures: inout Set) { - switch type { - case .closure(let signature): - signatures.insert(signature) - for paramType in signature.parameters { - collectClosureSignatures(from: paramType, into: &signatures) - } - collectClosureSignatures(from: signature.returnType, into: &signatures) - case .optional(let wrapped): - collectClosureSignatures(from: wrapped, into: &signatures) - default: - break - } - } - /// Generates a ConvertibleToJSValue extension for the exported class /// /// # Example @@ -2544,10 +2387,684 @@ public class ExportSwift { """ return [extensionDecl, externDecl] } +} + +// MARK: - ClosureCodegen + +struct ClosureCodegen { + func collectClosureSignatures(from parameters: [Parameter], into signatures: inout Set) { + for param in parameters { + collectClosureSignatures(from: param.type, into: &signatures) + } + } + + func collectClosureSignatures(from type: BridgeType, into signatures: inout Set) { + switch type { + case .closure(let signature): + signatures.insert(signature) + for paramType in signature.parameters { + collectClosureSignatures(from: paramType, into: &signatures) + } + collectClosureSignatures(from: signature.returnType, into: &signatures) + case .optional(let wrapped): + collectClosureSignatures(from: wrapped, into: &signatures) + default: + break + } + } + + private func generateOptionalParameterLowering(_ signature: ClosureSignature) throws -> String { + var lines: [String] = [] - /// Creates a struct that wraps a JSObject and implements protocol methods - /// by calling `@_extern(wasm)` functions that forward to JavaScript via JSObject ID - func renderProtocolWrapper(protocol proto: ExportedProtocol) throws -> [DeclSyntax] { + for (index, paramType) in signature.parameters.enumerated() { + guard case .optional(let wrappedType) = paramType else { + continue + } + let paramName = "param\(index)" + if case .swiftHeapObject = wrappedType { + lines.append( + "let (\(paramName)IsSome, \(paramName)Value) = \(paramName).bridgeJSLowerParameterWithRetain()" + ) + } else { + lines.append( + "let (\(paramName)IsSome, \(paramName)Value) = \(paramName).bridgeJSLowerParameterWithPresence()" + ) + } + } + + return lines.isEmpty ? "" : lines.joined(separator: "\n") + "\n" + } + + func renderClosureHelpers(_ signature: ClosureSignature) throws -> [DeclSyntax] { + let mangledName = signature.mangleName + let helperName = "_BJS_Closure_\(mangledName)" + let boxClassName = "_BJS_ClosureBox_\(mangledName)" + + let closureParams = signature.parameters.enumerated().map { index, type in + "\(type.swiftType)" + }.joined(separator: ", ") + + let swiftEffects = (signature.isAsync ? " async" : "") + (signature.isThrows ? " throws" : "") + let swiftReturnType = signature.returnType.swiftType + let closureType = "(\(closureParams))\(swiftEffects) -> \(swiftReturnType)" + + var invokeParams: [(name: String, type: String)] = [("_", "Int32")] + var invokeCallArgs: [String] = ["callback.bridgeJSLowerParameter()"] + + for (index, paramType) in signature.parameters.enumerated() { + let paramName = "param\(index)" + + if case .optional(let wrappedType) = paramType { + invokeParams.append(("_", "Int32")) + + switch wrappedType { + case .swiftHeapObject: + invokeParams.append(("_", "UnsafeMutableRawPointer")) + case .string, .rawValueEnum(_, .string): + invokeParams.append(("_", "Int32")) + default: + let lowerInfo = try wrappedType.loweringReturnInfo() + if let wasmType = lowerInfo.returnType { + invokeParams.append(("_", wasmType.swiftType)) + } else { + invokeParams.append(("_", "Int32")) + } + } + + invokeCallArgs.append("\(paramName)IsSome") + invokeCallArgs.append("\(paramName)Value") + } else { + let lowerInfo = try paramType.loweringReturnInfo() + if let wasmType = lowerInfo.returnType { + invokeParams.append(("_", wasmType.swiftType)) + invokeCallArgs.append("\(paramName).bridgeJSLowerParameter()") + } else { + invokeParams.append(("_", "Int32")) + invokeCallArgs.append("\(paramName).bridgeJSLowerParameter()") + } + } + } + + let invokeSignature = invokeParams.map { "\($0.name): \($0.type)" }.joined(separator: ", ") + let invokeReturnType: String + if case .optional = signature.returnType { + invokeReturnType = "Void" + } else if let wasmType = try signature.returnType.liftingReturnInfo(context: .exportSwift).valueToLift { + invokeReturnType = wasmType.swiftType + } else { + invokeReturnType = "Void" + } + + let externName = "invoke_js_callback_\(signature.moduleName)_\(mangledName)" + + let returnLifting: String + if signature.returnType == .void { + returnLifting = "\(externName)(\(invokeCallArgs.joined(separator: ", ")))" + } else if case .optional = signature.returnType { + returnLifting = """ + \(externName)(\(invokeCallArgs.joined(separator: ", "))) + return \(signature.returnType.swiftType).bridgeJSLiftReturnFromSideChannel() + """ + } else { + returnLifting = """ + let resultId = \(externName)(\(invokeCallArgs.joined(separator: ", "))) + return \(signature.returnType.swiftType).bridgeJSLiftReturn(resultId) + """ + } + + let optionalLoweringCode = try generateOptionalParameterLowering(signature) + + let externDecl: DeclSyntax = """ + @_extern(wasm, module: "bjs", name: "\(raw: externName)") + fileprivate func \(raw: externName)(\(raw: invokeSignature)) -> \(raw: invokeReturnType) + """ + + let boxDecl: DeclSyntax = """ + private final class \(raw: boxClassName): _BridgedSwiftClosureBox { + let closure: \(raw: closureType) + init(_ closure: @escaping \(raw: closureType)) { + self.closure = closure + } + } + + private enum \(raw: helperName) { + static func bridgeJSLower(_ closure: @escaping \(raw: closureType)) -> UnsafeMutableRawPointer { + let box = \(raw: boxClassName)(closure) + return Unmanaged.passRetained(box).toOpaque() + } + + static func bridgeJSLift(_ callbackId: Int32) -> \(raw: closureType) { + let callback = JSObject.bridgeJSLiftParameter(callbackId) + return { [callback] \(raw: signature.parameters.indices.map { "param\($0)" }.joined(separator: ", ")) in + #if arch(wasm32) + \(raw: optionalLoweringCode)\(raw: returnLifting) + #else + fatalError("Only available on WebAssembly") + #endif + } + } + } + """ + return [externDecl, boxDecl] + } + + func renderClosureInvokeHandler(_ signature: ClosureSignature) throws -> DeclSyntax { + let boxClassName = "_BJS_ClosureBox_\(signature.mangleName)" + let abiName = "invoke_swift_closure_\(signature.moduleName)_\(signature.mangleName)" + + var abiParams: [(name: String, type: String)] = [("boxPtr", "UnsafeMutableRawPointer")] + var liftedParams: [String] = [] + + for (index, paramType) in signature.parameters.enumerated() { + let paramName = "param\(index)" + let liftInfo = try paramType.liftParameterInfo() + + for (argName, wasmType) in liftInfo.parameters { + let fullName = + liftInfo.parameters.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName + abiParams.append((fullName, wasmType.swiftType)) + } + + let argNames = liftInfo.parameters.map { (argName, _) in + liftInfo.parameters.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName + } + liftedParams.append("\(paramType.swiftType).bridgeJSLiftParameter(\(argNames.joined(separator: ", ")))") + } + + let paramSignature = abiParams.map { "\($0.name): \($0.type)" }.joined(separator: ", ") + let closureCall = "box.closure(\(liftedParams.joined(separator: ", ")))" + + let returnCode: String + if signature.returnType == .void { + returnCode = closureCall + } else { + returnCode = """ + let result = \(closureCall) + return result.bridgeJSLowerReturn() + """ + } + + let abiReturnType: String + if signature.returnType == .void { + abiReturnType = "Void" + } else if let wasmType = try signature.returnType.loweringReturnInfo().returnType { + abiReturnType = wasmType.swiftType + } else { + abiReturnType = "Void" + } + + return """ + @_expose(wasm, "\(raw: abiName)") + @_cdecl("\(raw: abiName)") + public func _\(raw: abiName)(\(raw: paramSignature)) -> \(raw: abiReturnType) { + #if arch(wasm32) + let box = Unmanaged<\(raw: boxClassName)>.fromOpaque(boxPtr).takeUnretainedValue() + \(raw: returnCode) + #else + fatalError("Only available on WebAssembly") + #endif + } + """ + } + +} + +// MARK: - StackCodegen + +/// Helper for stack-based lifting and lowering operations. +struct StackCodegen { + /// Generates an expression to lift a value from the parameter stack. + /// - Parameter type: The BridgeType to lift + /// - Returns: An ExprSyntax representing the lift expression + func liftExpression(for type: BridgeType) -> ExprSyntax { + switch type { + case .string: + return "String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .int: + return "Int.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .bool: + return "Bool.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .float: + return "Float.bridgeJSLiftParameter(_swift_js_pop_param_f32())" + case .double: + return "Double.bridgeJSLiftParameter(_swift_js_pop_param_f64())" + case .jsObject: + return "JSObject.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .swiftHeapObject(let className): + return "\(raw: className).bridgeJSLiftParameter(_swift_js_pop_param_pointer())" + case .swiftProtocol: + // Protocols are handled via JSObject + return "JSObject.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .caseEnum(let enumName): + return "\(raw: enumName).bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .rawValueEnum(let enumName, let rawType): + switch rawType { + case .string: + return + "\(raw: enumName).bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .bool, .int, .int32, .int64, .uint, .uint32, .uint64, .float, .double: + return "\(raw: enumName).bridgeJSLiftParameter(_swift_js_pop_param_int32())" + } + case .associatedValueEnum(let enumName): + return "\(raw: enumName).bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .swiftStruct(let structName): + return "\(raw: structName).bridgeJSLiftParameter()" + case .optional(let wrappedType): + return liftOptionalExpression(wrappedType: wrappedType) + case .void: + // Void shouldn't be lifted, but return a placeholder + return "()" + case .namespaceEnum: + // Namespace enums are not passed as values + return "()" + case .closure: + return "JSObject.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + } + } + + private func liftOptionalExpression(wrappedType: BridgeType) -> ExprSyntax { + switch wrappedType { + case .string: + return + "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .int: + return "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .bool: + return "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .float: + return "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_f32())" + case .double: + return "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_f64())" + case .caseEnum(let enumName): + return + "Optional<\(raw: enumName)>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .rawValueEnum(let enumName, let rawType): + switch rawType { + case .string: + return + "Optional<\(raw: enumName)>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .bool, .int, .float, .double, .int32, .int64, .uint, .uint32, .uint64: + return + "Optional<\(raw: enumName)>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + } + case .swiftStruct(let nestedName): + return "Optional<\(raw: nestedName)>.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .swiftHeapObject(let className): + return + "Optional<\(raw: className)>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_pointer())" + case .associatedValueEnum(let enumName): + return + "Optional<\(raw: enumName)>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .jsObject: + return "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + default: + // Fallback for other optional types + return "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + } + } + + /// Generates statements to lower/push a value onto the stack. + /// - Parameters: + /// - type: The BridgeType to lower + /// - accessor: The expression to access the value (e.g., "self.name" or "paramName") + /// - varPrefix: A unique prefix for intermediate variables + /// - Returns: An array of CodeBlockItemSyntax representing the lowering statements + func lowerStatements( + for type: BridgeType, + accessor: String, + varPrefix: String + ) -> [CodeBlockItemSyntax] { + switch type { + case .string: + return [ + "var __bjs_\(raw: varPrefix) = \(raw: accessor)", + "__bjs_\(raw: varPrefix).withUTF8 { ptr in _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) }", + ] + case .int: + return ["_swift_js_push_int(Int32(\(raw: accessor)))"] + case .bool: + return ["_swift_js_push_int(\(raw: accessor) ? 1 : 0)"] + case .float: + return ["_swift_js_push_f32(\(raw: accessor))"] + case .double: + return ["_swift_js_push_f64(\(raw: accessor))"] + case .jsObject: + return ["_swift_js_push_int(\(raw: accessor).bridgeJSLowerParameter())"] + case .swiftHeapObject: + return ["_swift_js_push_pointer(\(raw: accessor).bridgeJSLowerReturn())"] + case .swiftProtocol: + return ["_swift_js_push_int(\(raw: accessor).bridgeJSLowerParameter())"] + case .caseEnum: + return ["_swift_js_push_int(Int32(\(raw: accessor).bridgeJSLowerParameter()))"] + case .rawValueEnum: + return ["_swift_js_push_int(Int32(\(raw: accessor).bridgeJSLowerParameter()))"] + case .associatedValueEnum: + return ["\(raw: accessor).bridgeJSLowerReturn()"] + case .swiftStruct: + return ["\(raw: accessor).bridgeJSLowerReturn()"] + case .optional(let wrappedType): + return lowerOptionalStatements(wrappedType: wrappedType, accessor: accessor, varPrefix: varPrefix) + case .void: + return [] + case .namespaceEnum: + return [] + case .closure: + return ["_swift_js_push_pointer(\(raw: accessor).bridgeJSLowerReturn())"] + } + } + + private func lowerOptionalStatements( + wrappedType: BridgeType, + accessor: String, + varPrefix: String + ) -> [CodeBlockItemSyntax] { + var statements: [CodeBlockItemSyntax] = [] + statements.append("let __bjs_isSome_\(raw: varPrefix) = \(raw: accessor) != nil") + statements.append("if let __bjs_unwrapped_\(raw: varPrefix) = \(raw: accessor) {") + + let innerStatements = lowerUnwrappedOptionalStatements( + wrappedType: wrappedType, + unwrappedVar: "__bjs_unwrapped_\(varPrefix)", + varPrefix: varPrefix + ) + for stmt in innerStatements { + statements.append(stmt) + } + + statements.append("}") + statements.append("_swift_js_push_int(__bjs_isSome_\(raw: varPrefix) ? 1 : 0)") + return statements + } + + private func lowerUnwrappedOptionalStatements( + wrappedType: BridgeType, + unwrappedVar: String, + varPrefix: String + ) -> [CodeBlockItemSyntax] { + switch wrappedType { + case .string: + return [ + "var __bjs_str_\(raw: varPrefix) = \(raw: unwrappedVar)", + "__bjs_str_\(raw: varPrefix).withUTF8 { ptr in _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) }", + ] + case .int: + return ["_swift_js_push_int(Int32(\(raw: unwrappedVar)))"] + case .bool: + return ["_swift_js_push_int(\(raw: unwrappedVar) ? 1 : 0)"] + case .float: + return ["_swift_js_push_f32(\(raw: unwrappedVar))"] + case .double: + return ["_swift_js_push_f64(\(raw: unwrappedVar))"] + case .caseEnum: + return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerParameter())"] + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: + return [ + "var __bjs_str_\(raw: varPrefix) = \(raw: unwrappedVar).rawValue", + "__bjs_str_\(raw: varPrefix).withUTF8 { ptr in _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) }", + ] + default: + return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerParameter())"] + } + case .swiftStruct: + return ["\(raw: unwrappedVar).bridgeJSLowerReturn()"] + case .swiftHeapObject: + return ["_swift_js_push_pointer(\(raw: unwrappedVar).bridgeJSLowerReturn())"] + case .associatedValueEnum: + return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerParameter())"] + case .jsObject: + return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerParameter())"] + default: + return ["preconditionFailure(\"BridgeJS: unsupported optional wrapped type\")"] + } + } +} + +// MARK: - EnumCodegen + +struct EnumCodegen { + private let stackCodegen = StackCodegen() + + func renderEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax? { + switch enumDef.enumType { + case .simple: + return renderCaseEnumHelpers(enumDef) + case .rawValue: + return renderRawValueEnumHelpers(enumDef) + case .associatedValue: + return renderAssociatedValueEnumHelpers(enumDef) + case .namespace: + return nil + } + } + + private func renderCaseEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax { + let typeName = enumDef.swiftCallName + var initCases: [String] = [] + var valueCases: [String] = [] + for (index, enumCase) in enumDef.cases.enumerated() { + initCases.append("case \(index): self = .\(enumCase.name)") + valueCases.append("case .\(enumCase.name): return \(index)") + } + let initSwitch = (["switch bridgeJSRawValue {"] + initCases + ["default: return nil", "}"]).joined( + separator: "\n" + ) + let valueSwitch = (["switch self {"] + valueCases + ["}"]).joined(separator: "\n") + + return """ + extension \(raw: typeName): _BridgedSwiftCaseEnum { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> \(raw: typeName) { + return bridgeJSLiftParameter(value) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> \(raw: typeName) { + return \(raw: typeName)(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSLowerParameter() + } + + private init?(bridgeJSRawValue: Int32) { + \(raw: initSwitch) + } + + private var bridgeJSRawValue: Int32 { + \(raw: valueSwitch) + } + } + """ + } + + private func renderRawValueEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax { + return "extension \(raw: enumDef.swiftCallName): _BridgedSwiftEnumNoPayload {}" + } + + private func renderAssociatedValueEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax { + let typeName = enumDef.swiftCallName + return """ + extension \(raw: typeName): _BridgedSwiftAssociatedValueEnum { + private static func _bridgeJSLiftFromCaseId(_ caseId: Int32) -> \(raw: typeName) { + switch caseId { + \(raw: generateStackLiftSwitchCases(enumDef: enumDef).joined(separator: "\n")) + default: fatalError("Unknown \(raw: typeName) case ID: \\(caseId)") + } + } + + // MARK: Protocol Export + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + switch self { + \(raw: generateLowerParameterSwitchCases(enumDef: enumDef).joined(separator: "\n")) + } + } + + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ caseId: Int32) -> \(raw: typeName) { + return _bridgeJSLiftFromCaseId(caseId) + } + + // MARK: ExportSwift + + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ caseId: Int32) -> \(raw: typeName) { + return _bridgeJSLiftFromCaseId(caseId) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + switch self { + \(raw: generateReturnSwitchCases(enumDef: enumDef).joined(separator: "\n")) + } + } + } + """ + } + + private func generateStackLiftSwitchCases(enumDef: ExportedEnum) -> [String] { + var cases: [String] = [] + for (caseIndex, enumCase) in enumDef.cases.enumerated() { + if enumCase.associatedValues.isEmpty { + cases.append("case \(caseIndex): return .\(enumCase.name)") + } else { + var lines: [String] = [] + lines.append("case \(caseIndex):") + let argList = enumCase.associatedValues.map { associatedValue in + let labelPrefix: String + if let label = associatedValue.label { + labelPrefix = "\(label): " + } else { + labelPrefix = "" + } + let liftExpr = stackCodegen.liftExpression(for: associatedValue.type) + return "\(labelPrefix)\(liftExpr)" + } + lines.append("return .\(enumCase.name)(\(argList.joined(separator: ", ")))") + cases.append(lines.joined(separator: "\n")) + } + } + return cases + } + + private func generatePayloadPushingCode( + associatedValues: [AssociatedValue] + ) -> [String] { + var bodyLines: [String] = [] + for (index, associatedValue) in associatedValues.enumerated() { + let paramName = associatedValue.label ?? "param\(index)" + let statements = stackCodegen.lowerStatements( + for: associatedValue.type, + accessor: paramName, + varPrefix: paramName + ) + for stmt in statements { + bodyLines.append(stmt.description) + } + } + return bodyLines + } + + private func generateLowerParameterSwitchCases(enumDef: ExportedEnum) -> [String] { + var cases: [String] = [] + for (caseIndex, enumCase) in enumDef.cases.enumerated() { + if enumCase.associatedValues.isEmpty { + cases.append("case .\(enumCase.name):") + cases.append("return Int32(\(caseIndex))") + } else { + let payloadCode = generatePayloadPushingCode(associatedValues: enumCase.associatedValues) + let pattern = enumCase.associatedValues.enumerated() + .map { index, associatedValue in "let \(associatedValue.label ?? "param\(index)")" } + .joined(separator: ", ") + cases.append("case .\(enumCase.name)(\(pattern)):") + cases.append(contentsOf: payloadCode) + cases.append("return Int32(\(caseIndex))") + } + } + return cases + } + + private func generateReturnSwitchCases(enumDef: ExportedEnum) -> [String] { + var cases: [String] = [] + for (caseIndex, enumCase) in enumDef.cases.enumerated() { + if enumCase.associatedValues.isEmpty { + cases.append("case .\(enumCase.name):") + cases.append("_swift_js_push_tag(Int32(\(caseIndex)))") + } else { + let pattern = enumCase.associatedValues.enumerated() + .map { index, associatedValue in "let \(associatedValue.label ?? "param\(index)")" } + .joined(separator: ", ") + cases.append("case .\(enumCase.name)(\(pattern)):") + cases.append("_swift_js_push_tag(Int32(\(caseIndex)))") + let payloadCode = generatePayloadPushingCode(associatedValues: enumCase.associatedValues) + cases.append(contentsOf: payloadCode) + } + } + return cases + } +} + +// MARK: - StructCodegen + +struct StructCodegen { + private let stackCodegen = StackCodegen() + + func renderStructHelpers(_ structDef: ExportedStruct) -> DeclSyntax { + let typeName = structDef.swiftCallName + let liftCode = generateStructLiftCode(structDef: structDef) + let lowerCode = generateStructLowerCode(structDef: structDef) + + return """ + extension \(raw: typeName): _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> \(raw: typeName) { + \(raw: liftCode.joined(separator: "\n")) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + \(raw: lowerCode.joined(separator: "\n")) + } + } + """ + } + + private func generateStructLiftCode(structDef: ExportedStruct) -> [String] { + var lines: [String] = [] + let instanceProps = structDef.properties.filter { !$0.isStatic } + + for property in instanceProps.reversed() { + let fieldName = property.name + let liftExpr = stackCodegen.liftExpression(for: property.type) + lines.append("let \(fieldName) = \(liftExpr)") + } + + let initArgs = instanceProps.map { "\($0.name): \($0.name)" }.joined(separator: ", ") + lines.append("return \(structDef.swiftCallName)(\(initArgs))") + + return lines + } + + private func generateStructLowerCode(structDef: ExportedStruct) -> [String] { + var lines: [String] = [] + let instanceProps = structDef.properties.filter { !$0.isStatic } + + for property in instanceProps { + let accessor = "self.\(property.name)" + let statements = stackCodegen.lowerStatements( + for: property.type, + accessor: accessor, + varPrefix: property.name + ) + for stmt in statements { + lines.append(stmt.description) + } + } + + return lines + } +} + +// MARK: - ProtocolCodegen + +struct ProtocolCodegen { + func renderProtocolWrapper(_ proto: ExportedProtocol, moduleName: String) throws -> [DeclSyntax] { let wrapperName = "Any\(proto.name)" let protocolName = proto.name @@ -2706,7 +3223,6 @@ public class ExportSwift { let getterBody: String if usesSideChannel { - // Optional case/raw enums use side-channel reading (Void return) getterReturnType = "" getterBody = """ \(getterAbiName)(this: Int32(bitPattern: jsObject.id)) @@ -2854,6 +3370,7 @@ extension BridgeType { case .caseEnum(let name): return name case .rawValueEnum(let name, _): return name case .associatedValueEnum(let name): return name + case .swiftStruct(let name): return name case .namespaceEnum(let name): return name case .closure(let signature): let paramTypes = signature.parameters.map { $0.swiftType }.joined(separator: ", ") @@ -2896,20 +3413,11 @@ extension BridgeType { return LiftingIntrinsicInfo(parameters: optionalParams) case .caseEnum: return .caseEnum case .rawValueEnum(_, let rawType): - switch rawType { - case .bool: return .bool - case .int: return .int - case .float: return .float - case .double: return .double - case .string: return .string - case .int32: return .int - case .int64: return .int - case .uint: return .int - case .uint32: return .int - case .uint64: return .int - } + return rawType.liftingIntrinsicInfo case .associatedValueEnum: return .associatedValueEnum + case .swiftStruct: + return LiftingIntrinsicInfo(parameters: []) case .namespaceEnum: throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters") case .closure: @@ -2931,6 +3439,7 @@ extension BridgeType { static let caseEnum = LoweringIntrinsicInfo(returnType: .i32) static let rawValueEnum = LoweringIntrinsicInfo(returnType: .i32) static let associatedValueEnum = LoweringIntrinsicInfo(returnType: nil) + static let swiftStruct = LoweringIntrinsicInfo(returnType: nil) static let optional = LoweringIntrinsicInfo(returnType: nil) } @@ -2948,20 +3457,11 @@ extension BridgeType { case .optional: return .optional case .caseEnum: return .caseEnum case .rawValueEnum(_, let rawType): - switch rawType { - case .bool: return .bool - case .int: return .int - case .float: return .float - case .double: return .double - case .string: return .string - case .int32: return .int - case .int64: return .int - case .uint: return .int - case .uint32: return .int - case .uint64: return .int - } + return rawType.loweringIntrinsicInfo case .associatedValueEnum: return .associatedValueEnum + case .swiftStruct: + return .swiftStruct case .namespaceEnum: throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters") case .closure: @@ -2970,6 +3470,28 @@ extension BridgeType { } } +extension SwiftEnumRawType { + var liftingIntrinsicInfo: BridgeType.LiftingIntrinsicInfo { + switch self { + case .bool: return .bool + case .int, .int32, .int64, .uint, .uint32, .uint64: return .int + case .float: return .float + case .double: return .double + case .string: return .string + } + } + + var loweringIntrinsicInfo: BridgeType.LoweringIntrinsicInfo { + switch self { + case .bool: return .bool + case .int, .int32, .int64, .uint, .uint32, .uint64: return .int + case .float: return .float + case .double: return .double + case .string: return .string + } + } +} + extension DeclModifierSyntax { var isAccessControl: Bool { switch self.name.tokenKind { diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 00ef5399b..a3ae96abb 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -476,6 +476,13 @@ extension BridgeType { case .exportSwift: return LoweringParameterInfo(loweredParameters: [("caseId", .i32)]) } + case .swiftStruct: + switch context { + case .importTS: + throw BridgeJSCoreError("Swift structs are not yet supported in TypeScript imports") + case .exportSwift: + return LoweringParameterInfo(loweredParameters: []) + } case .namespaceEnum: throw BridgeJSCoreError("Namespace enums cannot be used as parameters") case .optional(let wrappedType): @@ -552,6 +559,13 @@ extension BridgeType { case .exportSwift: return LiftingReturnInfo(valueToLift: .i32) } + case .swiftStruct: + switch context { + case .importTS: + throw BridgeJSCoreError("Swift structs are not yet supported in TypeScript imports") + case .exportSwift: + return LiftingReturnInfo(valueToLift: nil) + } case .namespaceEnum: throw BridgeJSCoreError("Namespace enums cannot be used as return values") case .optional(let wrappedType): diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 7ecc2086f..4f58e9a60 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -71,8 +71,8 @@ struct BridgeJSLink { var classLines: [String] = [] var dtsExportLines: [String] = [] var dtsClassLines: [String] = [] - var topLevelEnumLines: [String] = [] - var topLevelDtsEnumLines: [String] = [] + var topLevelTypeLines: [String] = [] + var topLevelDtsTypeLines: [String] = [] var importObjectBuilders: [ImportObjectBuilder] = [] var enumStaticAssignments: [String] = [] } @@ -109,29 +109,33 @@ struct BridgeJSLink { data.dtsClassLines.append(contentsOf: dtsType) } - // Process enums - if !skeleton.enums.isEmpty { - for enumDefinition in skeleton.enums { - let (jsEnum, dtsEnum) = try renderExportedEnum(enumDefinition) + // Process enums - collect top-level definitions and export entries + var enumExportEntries: [(js: [String], dts: [String])] = [] + for enumDefinition in skeleton.enums { + let (jsTopLevel, jsExportEntry, dtsType, dtsExportEntry) = try renderExportedEnum(enumDefinition) - switch enumDefinition.enumType { - case .namespace: - break - case .simple, .rawValue: - var exportedJsEnum = jsEnum - if !exportedJsEnum.isEmpty && exportedJsEnum[0].hasPrefix("const ") { - exportedJsEnum[0] = "export " + exportedJsEnum[0] - } - data.topLevelEnumLines.append(contentsOf: exportedJsEnum) - data.topLevelDtsEnumLines.append(contentsOf: dtsEnum) - case .associatedValue: - var exportedJsEnum = jsEnum - if !exportedJsEnum.isEmpty && exportedJsEnum[0].hasPrefix("const ") { - exportedJsEnum[0] = "export " + exportedJsEnum[0] - } - data.topLevelEnumLines.append(contentsOf: exportedJsEnum) - data.topLevelDtsEnumLines.append(contentsOf: dtsEnum) + // Add top-level JS const definition + if enumDefinition.enumType != .namespace { + var exportedJsEnum = jsTopLevel + if !exportedJsEnum.isEmpty && exportedJsEnum[0].hasPrefix("const ") { + exportedJsEnum[0] = "export " + exportedJsEnum[0] } + data.topLevelTypeLines.append(contentsOf: exportedJsEnum) + data.topLevelDtsTypeLines.append(contentsOf: dtsType) + } + + if !jsExportEntry.isEmpty || !dtsExportEntry.isEmpty { + enumExportEntries.append((js: jsExportEntry, dts: dtsExportEntry)) + } + } + + var structExportEntries: [(js: [String], dts: [String])] = [] + for structDefinition in skeleton.structs { + let (jsStruct, dtsType, dtsExportEntry) = try renderExportedStruct(structDefinition) + data.topLevelDtsTypeLines.append(contentsOf: dtsType) + + if structDefinition.namespace == nil && (!jsStruct.isEmpty || !dtsExportEntry.isEmpty) { + structExportEntries.append((js: jsStruct, dts: dtsExportEntry)) } } @@ -146,107 +150,14 @@ struct BridgeJSLink { } } - for enumDefinition in skeleton.enums - where enumDefinition.enumType != .namespace && enumDefinition.emitStyle != .tsEnum { - if enumDefinition.namespace != nil { - continue - } - - let enumExportPrinter = CodeFragmentPrinter() - let enumValuesName = enumDefinition.valuesName - - for function in enumDefinition.staticMethods { - let thunkBuilder = ExportedThunkBuilder(effects: function.effects) - for param in function.parameters { - try thunkBuilder.lowerParameter(param: param) - } - let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) - - let methodPrinter = CodeFragmentPrinter() - methodPrinter.write( - "\(function.name): function(\(function.parameters.map { $0.name }.joined(separator: ", "))) {" - ) - methodPrinter.indent { - methodPrinter.write(contentsOf: thunkBuilder.body) - methodPrinter.write(contentsOf: thunkBuilder.cleanupCode) - methodPrinter.write(lines: thunkBuilder.checkExceptionLines()) - if let returnExpr = returnExpr { - methodPrinter.write("return \(returnExpr);") - } - } - methodPrinter.write("},") - - enumExportPrinter.write(lines: methodPrinter.lines) - } - - let enumExportLines = enumExportPrinter.lines - - let enumPropertyPrinter = CodeFragmentPrinter() - - for property in enumDefinition.staticProperties { - let getterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) - let getterReturnExpr = try getterThunkBuilder.call( - abiName: property.getterAbiName(), - returnType: property.type - ) - - enumPropertyPrinter.write("get \(property.name)() {") - enumPropertyPrinter.indent { - enumPropertyPrinter.write(contentsOf: getterThunkBuilder.body) - enumPropertyPrinter.write(contentsOf: getterThunkBuilder.cleanupCode) - enumPropertyPrinter.write(lines: getterThunkBuilder.checkExceptionLines()) - if let returnExpr = getterReturnExpr { - enumPropertyPrinter.write("return \(returnExpr);") - } - } - enumPropertyPrinter.write("},") - - if !property.isReadonly { - let setterThunkBuilder = ExportedThunkBuilder( - effects: Effects(isAsync: false, isThrows: false) - ) - try setterThunkBuilder.lowerParameter( - param: Parameter(label: "value", name: "value", type: property.type) - ) - _ = try setterThunkBuilder.call( - abiName: property.setterAbiName(), - returnType: .void - ) - - enumPropertyPrinter.write("set \(property.name)(value) {") - enumPropertyPrinter.indent { - enumPropertyPrinter.write(contentsOf: setterThunkBuilder.body) - enumPropertyPrinter.write(contentsOf: setterThunkBuilder.cleanupCode) - enumPropertyPrinter.write(lines: setterThunkBuilder.checkExceptionLines()) - } - enumPropertyPrinter.write("},") - } - } - - let enumPropertyLines = enumPropertyPrinter.lines - - let exportsPrinter = CodeFragmentPrinter() - let dtsExportsPrinter = CodeFragmentPrinter() - - if !enumExportLines.isEmpty || !enumPropertyLines.isEmpty { - exportsPrinter.write("\(enumDefinition.name): {") - exportsPrinter.indent { - exportsPrinter.write("...\(enumValuesName),") - var allLines = enumExportLines + enumPropertyLines - if let lastLineIndex = allLines.indices.last, allLines[lastLineIndex].hasSuffix(",") { - allLines[lastLineIndex] = String(allLines[lastLineIndex].dropLast()) - } - exportsPrinter.write(lines: allLines) - } - exportsPrinter.write("},") - } else { - exportsPrinter.write("\(enumDefinition.name): \(enumValuesName),") - } - - dtsExportsPrinter.write("\(enumDefinition.name): \(enumDefinition.objectTypeName)") + for entry in enumExportEntries { + data.exportsLines.append(contentsOf: entry.js) + data.dtsExportLines.append(contentsOf: entry.dts) + } - data.exportsLines.append(contentsOf: exportsPrinter.lines) - data.dtsExportLines.append(contentsOf: dtsExportsPrinter.lines) + for entry in structExportEntries { + data.exportsLines.append(contentsOf: entry.js) + data.dtsExportLines.append(contentsOf: entry.dts) } } @@ -324,12 +235,22 @@ struct BridgeJSLink { "let \(JSGlueVariableScope.reservedTmpParamInts) = [];", "let \(JSGlueVariableScope.reservedTmpParamF32s) = [];", "let \(JSGlueVariableScope.reservedTmpParamF64s) = [];", + "let \(JSGlueVariableScope.reservedTmpRetPointers) = [];", + "let \(JSGlueVariableScope.reservedTmpParamPointers) = [];", ] + let hasStructs = exportedSkeletons.contains { skeleton in + !skeleton.structs.isEmpty + } + if hasAssociatedValueEnums { declarations.append("const enumHelpers = {};") } + if hasStructs { + declarations.append("const structHelpers = {};") + } + declarations.append("") declarations.append("let _exports = null;") declarations.append("let bjs = null;") @@ -497,6 +418,16 @@ struct BridgeJSLink { printer.write("return \(JSGlueVariableScope.reservedTmpParamF64s).pop();") } printer.write("}") + printer.write("bjs[\"swift_js_push_pointer\"] = function(pointer) {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpRetPointers).push(pointer);") + } + printer.write("}") + printer.write("bjs[\"swift_js_pop_param_pointer\"] = function() {") + printer.indent { + printer.write("return \(JSGlueVariableScope.reservedTmpParamPointers).pop();") + } + printer.write("}") printer.write("bjs[\"swift_js_return_optional_bool\"] = function(isSome, value) {") printer.indent { printer.write("if (isSome === 0) {") @@ -883,7 +814,7 @@ struct BridgeJSLink { } } - printer.write(lines: data.topLevelDtsEnumLines) + printer.write(lines: data.topLevelDtsTypeLines) // Generate Object types for const-style enums for skeleton in exportedSkeletons { @@ -1002,8 +933,7 @@ struct BridgeJSLink { let printer = CodeFragmentPrinter(header: header) printer.nextLine() - // Top-level enums section - printer.write(lines: data.topLevelEnumLines) + printer.write(lines: data.topLevelTypeLines) let topLevelNamespaceCode = namespaceBuilder.buildTopLevelNamespaceInitialization( exportedSkeletons: exportedSkeletons @@ -1015,6 +945,16 @@ struct BridgeJSLink { printer.indent { printer.write(lines: generateVariableDeclarations()) + + let allStructs = exportedSkeletons.flatMap { $0.structs } + for structDef in allStructs { + let structPrinter = CodeFragmentPrinter() + let structScope = JSGlueVariableScope() + let structCleanup = CodeFragmentPrinter() + let fragment = IntrinsicJSFragment.structHelper(structDefinition: structDef, allStructs: allStructs) + _ = fragment.printCode([structDef.name], structScope, structPrinter, structCleanup) + printer.write(lines: structPrinter.lines) + } printer.nextLine() printer.write(contentsOf: generateAddImports()) } @@ -1068,6 +1008,8 @@ struct BridgeJSLink { printer.write(lines: data.classLines) + // Struct helpers must be initialized AFTER classes are defined (to allow _exports access) + printer.write(contentsOf: structHelperAssignments()) let namespaceInitCode = namespaceBuilder.buildNamespaceInitialization( exportedSkeletons: exportedSkeletons ) @@ -1112,6 +1054,22 @@ struct BridgeJSLink { return printer } + private func structHelperAssignments() -> CodeFragmentPrinter { + let printer = CodeFragmentPrinter() + + for skeleton in exportedSkeletons { + for structDef in skeleton.structs { + printer.write( + "const \(structDef.name)Helpers = __bjs_create\(structDef.name)Helpers()(\(JSGlueVariableScope.reservedTmpParamInts), \(JSGlueVariableScope.reservedTmpParamF32s), \(JSGlueVariableScope.reservedTmpParamF64s), \(JSGlueVariableScope.reservedTmpParamPointers), \(JSGlueVariableScope.reservedTmpRetPointers), \(JSGlueVariableScope.reservedTextEncoder), \(JSGlueVariableScope.reservedSwift), enumHelpers);" + ) + printer.write("structHelpers.\(structDef.name) = \(structDef.name)Helpers;") + printer.nextLine() + } + } + + return printer + } + private func renderSwiftClassWrappers() -> [String] { var wrapperLines: [String] = [] var modulesByName: [String: [ExportedClass]] = [:] @@ -1296,14 +1254,14 @@ struct BridgeJSLink { ] } - func generateParameterList(parameters: [Parameter]) -> String { - parameters.map { param in - if let defaultValue = param.defaultValue { - let defaultJs = DefaultValueGenerator().generate(defaultValue, format: .javascript) - return "\(param.name) = \(defaultJs)" - } - return param.name - }.joined(separator: ", ") + /// Renders the thunk body (body code, cleanup, exception handling, and optional return) into a printer. + func renderFunctionBody(into printer: CodeFragmentPrinter, returnExpr: String?) { + printer.write(contentsOf: body) + printer.write(contentsOf: cleanupCode) + printer.write(lines: checkExceptionLines()) + if let returnExpr = returnExpr { + printer.write("return \(returnExpr);") + } } func renderFunction( @@ -1314,18 +1272,13 @@ struct BridgeJSLink { ) -> [String] { let printer = CodeFragmentPrinter() - let parameterList = generateParameterList(parameters: parameters) + let parameterList = DefaultValueUtils.formatParameterList(parameters) printer.write( "\(declarationPrefixKeyword.map { "\($0) "} ?? "")\(name)(\(parameterList)) {" ) printer.indent { - printer.write(contentsOf: body) - printer.write(contentsOf: cleanupCode) - printer.write(lines: checkExceptionLines()) - if let returnExpr = returnExpr { - printer.write("return \(returnExpr);") - } + renderFunctionBody(into: printer, returnExpr: returnExpr) } printer.write("}") @@ -1352,21 +1305,17 @@ struct BridgeJSLink { if enumDef.name == name || enumDef.swiftCallName == name { // Use the stored tsFullPath which has the full namespace switch type { - case .caseEnum: - return "\(enumDef.tsFullPath)Tag" - case .rawValueEnum: - return "\(enumDef.tsFullPath)Tag" - case .associatedValueEnum: - return "\(enumDef.tsFullPath)Tag" case .namespaceEnum: return enumDef.tsFullPath default: - return type.tsType + return "\(enumDef.tsFullPath)Tag" } } } } return type.tsType + case .swiftStruct(let name): + return name.components(separatedBy: ".").last ?? name case .optional(let wrapped): return "\(resolveTypeScriptType(wrapped, exportedSkeletons: exportedSkeletons)) | null" default: @@ -1390,79 +1339,114 @@ struct BridgeJSLink { /// Helper method to append JSDoc comments for parameters with default values private func appendJSDocIfNeeded(for parameters: [Parameter], to lines: inout [String]) { - let jsDocLines = DefaultValueGenerator().generateJSDoc(for: parameters) + let jsDocLines = DefaultValueUtils.formatJSDoc(for: parameters) lines.append(contentsOf: jsDocLines) } - /// Helper struct for generating default value representations - private struct DefaultValueGenerator { - enum OutputFormat { - case javascript - case typescript - } - - /// Generates default value representation for JavaScript or TypeScript - func generate(_ defaultValue: DefaultValue, format: OutputFormat) -> String { - switch defaultValue { - case .string(let value): - let escapedValue = - format == .javascript - ? escapeForJavaScript(value) - : value // TypeScript doesn't need escape in doc comments - return "\"\(escapedValue)\"" - case .int(let value): - return "\(value)" - case .float(let value): - return "\(value)" - case .double(let value): - return "\(value)" - case .bool(let value): - return value ? "true" : "false" - case .null: - return "null" - case .enumCase(let enumName, let caseName): - let simpleName = enumName.components(separatedBy: ".").last ?? enumName - let jsEnumName = format == .javascript ? "\(simpleName)\(ExportedEnum.valuesSuffix)" : simpleName - return "\(jsEnumName).\(caseName.capitalizedFirstLetter)" - case .object(let className): - return "new \(className)()" - case .objectWithArguments(let className, let args): - let argStrings = args.map { arg in - generate(arg, format: format) - } - return "new \(className)(\(argStrings.joined(separator: ", ")))" - } - } - - private func escapeForJavaScript(_ string: String) -> String { - return - string - .replacingOccurrences(of: "\\", with: "\\\\") - .replacingOccurrences(of: "\"", with: "\\\"") - } - - /// Generates JSDoc comment lines for parameters with default values - func generateJSDoc(for parameters: [Parameter]) -> [String] { - let paramsWithDefaults = parameters.filter { $0.hasDefault } - guard !paramsWithDefaults.isEmpty else { - return [] + func renderExportedStruct( + _ structDefinition: ExportedStruct + ) throws -> (js: [String], dtsType: [String], dtsExportEntry: [String]) { + let structName = structDefinition.name + let hasConstructor = structDefinition.constructor != nil + let staticMethods = structDefinition.methods.filter { $0.effects.isStatic } + let staticProperties = structDefinition.properties.filter { $0.isStatic } + + let dtsTypePrinter = CodeFragmentPrinter() + dtsTypePrinter.write("export interface \(structName) {") + let instanceProps = structDefinition.properties.filter { !$0.isStatic } + dtsTypePrinter.indent { + for property in instanceProps { + let tsType = resolveTypeScriptType(property.type) + dtsTypePrinter.write("\(property.name): \(tsType);") + } + for method in structDefinition.methods where !method.effects.isStatic { + let jsDocLines = DefaultValueUtils.formatJSDoc(for: method.parameters) + dtsTypePrinter.write(lines: jsDocLines) + let signature = renderTSSignature( + parameters: method.parameters, + returnType: method.returnType, + effects: method.effects + ) + dtsTypePrinter.write("\(method.name)\(signature);") } + } + dtsTypePrinter.write("}") + + guard hasConstructor || !staticMethods.isEmpty || !staticProperties.isEmpty else { + return (js: [], dtsType: dtsTypePrinter.lines, dtsExportEntry: []) + } + + let jsPrinter = CodeFragmentPrinter() + jsPrinter.write("\(structName): {") + try jsPrinter.indent { + // Constructor as 'init' function + if let constructor = structDefinition.constructor { + let thunkBuilder = ExportedThunkBuilder(effects: constructor.effects) + for param in constructor.parameters { + try thunkBuilder.lowerParameter(param: param) + } + let returnExpr = try thunkBuilder.call( + abiName: constructor.abiName, + returnType: .swiftStruct(structDefinition.swiftCallName) + ) - var jsDocLines: [String] = ["/**"] - for param in paramsWithDefaults { - if let defaultValue = param.defaultValue { - let defaultDoc = generate(defaultValue, format: .typescript) - jsDocLines.append(" * @param \(param.name) - Optional parameter (default: \(defaultDoc))") + let constructorPrinter = CodeFragmentPrinter() + let paramList = DefaultValueUtils.formatParameterList(constructor.parameters) + constructorPrinter.write("init: function(\(paramList)) {") + constructorPrinter.indent { + thunkBuilder.renderFunctionBody(into: constructorPrinter, returnExpr: returnExpr) } + constructorPrinter.write("},") + jsPrinter.write(lines: constructorPrinter.lines) + } + + for property in staticProperties { + let propertyLines = try renderStaticPropertyForExportObject( + property: property, + className: structName + ) + jsPrinter.write(lines: propertyLines) + } + + for method in staticMethods { + let methodLines = try renderStaticMethodForExportObject(method: method) + jsPrinter.write(lines: methodLines) } - jsDocLines.append(" */") - return jsDocLines } + jsPrinter.write("},") + + let dtsExportEntryPrinter = CodeFragmentPrinter() + dtsExportEntryPrinter.write("\(structName): {") + dtsExportEntryPrinter.indent { + if let constructor = structDefinition.constructor { + let jsDocLines = DefaultValueUtils.formatJSDoc(for: constructor.parameters) + dtsExportEntryPrinter.write(lines: jsDocLines) + dtsExportEntryPrinter.write( + "init\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftStruct(structDefinition.swiftCallName), effects: constructor.effects));" + ) + } + for property in staticProperties { + let readonly = property.isReadonly ? "readonly " : "" + dtsExportEntryPrinter.write("\(readonly)\(property.name): \(resolveTypeScriptType(property.type));") + } + for method in staticMethods { + let jsDocLines = DefaultValueUtils.formatJSDoc(for: method.parameters) + dtsExportEntryPrinter.write(lines: jsDocLines) + dtsExportEntryPrinter.write( + "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" + ) + } + } + dtsExportEntryPrinter.write("}") + + return (js: jsPrinter.lines, dtsType: dtsTypePrinter.lines, dtsExportEntry: dtsExportEntryPrinter.lines) } - func renderExportedEnum(_ enumDefinition: ExportedEnum) throws -> (js: [String], dts: [String]) { - var jsLines: [String] = [] - var dtsLines: [String] = [] + func renderExportedEnum( + _ enumDefinition: ExportedEnum + ) throws -> (jsTopLevel: [String], jsExportEntry: [String], dtsType: [String], dtsExportEntry: [String]) { + var jsTopLevelLines: [String] = [] + var dtsTypeLines: [String] = [] let scope = JSGlueVariableScope() let cleanup = CodeFragmentPrinter() let printer = CodeFragmentPrinter() @@ -1472,7 +1456,7 @@ struct BridgeJSLink { case .simple: let fragment = IntrinsicJSFragment.simpleEnumHelper(enumDefinition: enumDefinition) _ = fragment.printCode([enumValuesName], scope, printer, cleanup) - jsLines.append(contentsOf: printer.lines) + jsTopLevelLines.append(contentsOf: printer.lines) case .rawValue: guard enumDefinition.rawType != nil else { throw BridgeJSLinkError(message: "Raw value enum \(enumDefinition.name) is missing rawType") @@ -1480,20 +1464,63 @@ struct BridgeJSLink { let fragment = IntrinsicJSFragment.rawValueEnumHelper(enumDefinition: enumDefinition) _ = fragment.printCode([enumValuesName], scope, printer, cleanup) - jsLines.append(contentsOf: printer.lines) + jsTopLevelLines.append(contentsOf: printer.lines) case .associatedValue: let fragment = IntrinsicJSFragment.associatedValueEnumHelper(enumDefinition: enumDefinition) _ = fragment.printCode([enumValuesName], scope, printer, cleanup) - jsLines.append(contentsOf: printer.lines) + jsTopLevelLines.append(contentsOf: printer.lines) case .namespace: break } if enumDefinition.namespace == nil { - dtsLines.append(contentsOf: generateDeclarations(enumDefinition: enumDefinition)) + dtsTypeLines.append(contentsOf: generateDeclarations(enumDefinition: enumDefinition)) } - return (jsLines, dtsLines) + var jsExportEntryLines: [String] = [] + var dtsExportEntryLines: [String] = [] + + if enumDefinition.enumType != .namespace + && enumDefinition.emitStyle != .tsEnum + && enumDefinition.namespace == nil + { + var enumMethodLines: [String] = [] + for function in enumDefinition.staticMethods { + let methodLines = try renderStaticMethodForExportObject(method: function) + enumMethodLines.append(contentsOf: methodLines) + } + + var enumPropertyLines: [String] = [] + for property in enumDefinition.staticProperties { + let propertyLines = try renderStaticPropertyForExportObject( + property: property, + className: nil + ) + enumPropertyLines.append(contentsOf: propertyLines) + } + + let exportsPrinter = CodeFragmentPrinter() + + if !enumMethodLines.isEmpty || !enumPropertyLines.isEmpty { + exportsPrinter.write("\(enumDefinition.name): {") + exportsPrinter.indent { + exportsPrinter.write("...\(enumValuesName),") + var allLines = enumMethodLines + enumPropertyLines + if let lastLineIndex = allLines.indices.last, allLines[lastLineIndex].hasSuffix(",") { + allLines[lastLineIndex] = String(allLines[lastLineIndex].dropLast()) + } + exportsPrinter.write(lines: allLines) + } + exportsPrinter.write("},") + } else { + exportsPrinter.write("\(enumDefinition.name): \(enumValuesName),") + } + + jsExportEntryLines = exportsPrinter.lines + dtsExportEntryLines = ["\(enumDefinition.name): \(enumDefinition.objectTypeName)"] + } + + return (jsTopLevelLines, jsExportEntryLines, dtsTypeLines, dtsExportEntryLines) } private func generateDeclarations(enumDefinition: ExportedEnum) -> [String] { @@ -1622,8 +1649,8 @@ extension BridgeJSLink { staticContext: StaticContext ) throws -> (js: [String], dts: [String]) { switch staticContext { - case .className(let className): - return try renderClassStaticFunction(function: function, className: className) + case .className(let name), .structName(let name): + return try renderStaticFunction(function: function, className: name) case .enumName(let enumName): return try renderEnumStaticFunction(function: function, enumName: enumName) case .namespaceEnum: @@ -1635,7 +1662,7 @@ extension BridgeJSLink { } } - private func renderClassStaticFunction( + private func renderStaticFunction( function: ExportedFunction, className: String ) throws -> (js: [String], dts: [String]) { @@ -1676,12 +1703,7 @@ extension BridgeJSLink { let printer = CodeFragmentPrinter() printer.write("\(function.name)(\(function.parameters.map { $0.name }.joined(separator: ", "))) {") printer.indent { - printer.write(contentsOf: thunkBuilder.body) - printer.write(contentsOf: thunkBuilder.cleanupCode) - printer.write(lines: thunkBuilder.checkExceptionLines()) - if let returnExpr = returnExpr { - printer.write("return \(returnExpr);") - } + thunkBuilder.renderFunctionBody(into: printer, returnExpr: returnExpr) } printer.write("},") @@ -1718,67 +1740,51 @@ extension BridgeJSLink { return (funcLines, dtsLines) } - private func renderEnumStaticFunctionAssignment( - function: ExportedFunction, - enumName: String + /// Renders a static method for use in an export object + private func renderStaticMethodForExportObject( + method: ExportedFunction ) throws -> [String] { - let thunkBuilder = ExportedThunkBuilder(effects: function.effects) - for param in function.parameters { + let thunkBuilder = ExportedThunkBuilder(effects: method.effects) + for param in method.parameters { try thunkBuilder.lowerParameter(param: param) } - let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) - let paramList = thunkBuilder.generateParameterList(parameters: function.parameters) + let returnExpr = try thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) - let printer = CodeFragmentPrinter() - printer.write( - "\(enumName).\(function.name) = function(\(paramList)) {" + let methodPrinter = CodeFragmentPrinter() + methodPrinter.write( + "\(method.name): function(\(method.parameters.map { $0.name }.joined(separator: ", "))) {" ) - printer.indent { - printer.write(contentsOf: thunkBuilder.body) - printer.write(contentsOf: thunkBuilder.cleanupCode) - printer.write(lines: thunkBuilder.checkExceptionLines()) - if let returnExpr = returnExpr { - printer.write("return \(returnExpr);") - } + methodPrinter.indent { + thunkBuilder.renderFunctionBody(into: methodPrinter, returnExpr: returnExpr) } - printer.write("};") - - return printer.lines + methodPrinter.write("},") + return methodPrinter.lines } - /// Renders an enum static property as getter/setter assignments on the enum object - private func renderEnumStaticProperty( + /// Renders a static property getter/setter for use in an export object + private func renderStaticPropertyForExportObject( property: ExportedProperty, - enumName: String - ) throws -> (js: [String], dts: [String]) { - var jsLines: [String] = [] + className: String? + ) throws -> [String] { + let propertyPrinter = CodeFragmentPrinter() - // Generate getter assignment + // Generate getter let getterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) let getterReturnExpr = try getterThunkBuilder.call( - abiName: property.getterAbiName(), + abiName: className != nil + ? property.getterAbiName(className: className!) + : property.getterAbiName(), returnType: property.type ) - let getterLines = getterThunkBuilder.renderFunction( - name: property.name, - parameters: [], - returnExpr: getterReturnExpr, - declarationPrefixKeyword: nil - ) - - // Build Object.defineProperty call - var definePropertyLines: [String] = [] - definePropertyLines.append("Object.defineProperty(\(enumName), '\(property.name)', { get: function() {") - - // Add getter body (skip function declaration and closing brace) - if getterLines.count > 2 { - let bodyLines = Array(getterLines[1.. 2 { - let bodyLines = Array(setterLines[1.. String { + switch defaultValue { + case .string(let value): + let escapedValue = + format == .javascript + ? escapeForJavaScript(value) + : value // TypeScript doesn't need escape in doc comments + return "\"\(escapedValue)\"" + case .int(let value): + return "\(value)" + case .float(let value): + return "\(value)" + case .double(let value): + return "\(value)" + case .bool(let value): + return value ? "true" : "false" + case .null: + return "null" + case .enumCase(let enumName, let caseName): + let simpleName = enumName.components(separatedBy: ".").last ?? enumName + let jsEnumName = format == .javascript ? "\(simpleName)\(ExportedEnum.valuesSuffix)" : simpleName + return "\(jsEnumName).\(caseName.capitalizedFirstLetter)" + case .object(let className): + return "new \(className)()" + case .objectWithArguments(let className, let args): + let argStrings = args.map { arg in + Self.format(arg, as: format) + } + return "new \(className)(\(argStrings.joined(separator: ", ")))" + case .structLiteral(_, let fields): + let fieldStrings = fields.map { field in + "\(field.name): \(Self.format(field.value, as: format))" + } + return "{ \(fieldStrings.joined(separator: ", ")) }" + } + } + + private static func escapeForJavaScript(_ string: String) -> String { + return + string + .replacingOccurrences(of: "\\", with: "\\\\") + .replacingOccurrences(of: "\"", with: "\\\"") + } + + /// Generates JSDoc comment lines for parameters with default values + static func formatJSDoc(for parameters: [Parameter]) -> [String] { + let paramsWithDefaults = parameters.filter { $0.hasDefault } + guard !paramsWithDefaults.isEmpty else { + return [] + } + + var jsDocLines: [String] = ["/**"] + for param in paramsWithDefaults { + if let defaultValue = param.defaultValue { + let defaultDoc = format(defaultValue, as: .typescript) + jsDocLines.append(" * @param \(param.name) - Optional parameter (default: \(defaultDoc))") + } + } + jsDocLines.append(" */") + return jsDocLines + } + + /// Generates a JavaScript parameter list with default values + static func formatParameterList(_ parameters: [Parameter]) -> String { + return parameters.map { param in + if let defaultValue = param.defaultValue { + let defaultJs = format(defaultValue, as: .javascript) + return "\(param.name) = \(defaultJs)" + } + return param.name + }.joined(separator: ", ") + } +} + struct BridgeJSLinkError: Error { let message: String } @@ -3120,6 +3159,8 @@ extension BridgeType { return "\(name)Tag" case .associatedValueEnum(let name): return "\(name)Tag" + case .swiftStruct(let name): + return "\(name)Tag" case .namespaceEnum(let name): return name case .swiftProtocol(let name): diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index 0be72f6c6..feaf34832 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -28,10 +28,14 @@ final class JSGlueVariableScope { static let reservedTmpParamInts = "tmpParamInts" static let reservedTmpParamF32s = "tmpParamF32s" static let reservedTmpParamF64s = "tmpParamF64s" + static let reservedTmpRetPointers = "tmpRetPointers" + static let reservedTmpParamPointers = "tmpParamPointers" private var variables: Set = [ reservedSwift, + reservedInstance, reservedMemory, + reservedSetException, reservedStorageToReturnString, reservedStorageToReturnBytes, reservedStorageToReturnException, @@ -50,6 +54,8 @@ final class JSGlueVariableScope { reservedTmpParamInts, reservedTmpParamF32s, reservedTmpParamF64s, + reservedTmpRetPointers, + reservedTmpParamPointers, ] /// Returns a unique variable name in the scope based on the given name hint. @@ -72,7 +78,7 @@ final class JSGlueVariableScope { /// A fragment of JS code used to convert a value between Swift and JS. /// -/// See `BridgeJSInstrincics.swift` in the main JavaScriptKit module for Swift side lowering/lifting implementation. +/// See `BridgeJSIntrinsics.swift` in the main JavaScriptKit module for Swift side lowering/lifting implementation. struct IntrinsicJSFragment: Sendable { /// The names of the parameters that the fragment expects. let parameters: [String] @@ -324,6 +330,22 @@ struct IntrinsicJSFragment: Sendable { } printer.write("}") resultExpr = "\(isSome) ? \(enumVar) : null" + case .swiftStruct(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + let structVar = scope.variable("structValue") + printer.write("let \(structVar);") + printer.write("if (\(isSome)) {") + printer.indent { + printer.write( + "\(structVar) = structHelpers.\(base).raise(\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers));" + ) + } + printer.write("} else {") + printer.indent { + printer.write("\(structVar) = null;") + } + printer.write("}") + resultExpr = structVar default: resultExpr = "\(isSome) ? \(wrappedValue) : null" } @@ -342,6 +364,19 @@ struct IntrinsicJSFragment: Sendable { printer.write("const \(isSomeVar) = \(value) != null;") switch wrappedType { + case .swiftStruct(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + let cleanupVar = scope.variable("\(value)Cleanup") + printer.write("let \(cleanupVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + let resultVar = scope.variable("structResult") + printer.write("const \(resultVar) = structHelpers.\(base).lower(\(value));") + printer.write("\(cleanupVar) = \(resultVar).cleanup;") + } + printer.write("}") + cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") + return ["+\(isSomeVar)"] case .string, .rawValueEnum(_, .string): let bytesVar = scope.variable("\(value)Bytes") let idVar = scope.variable("\(value)Id") @@ -494,6 +529,22 @@ struct IntrinsicJSFragment: Sendable { ) } printer.write("}") + case .swiftStruct(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + let isSomeVar = scope.variable("isSome") + printer.write("const \(isSomeVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + printer.write("let \(resultVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + printer.write( + "\(resultVar) = structHelpers.\(base).raise(\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers));" + ) + } + printer.write("} else {") + printer.indent { + printer.write("\(resultVar) = null;") + } + printer.write("}") default: printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnString);") printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = undefined;") @@ -1259,6 +1310,9 @@ struct IntrinsicJSFragment: Sendable { case .associatedValueEnum(let fullName): let base = fullName.components(separatedBy: ".").last ?? fullName return .associatedEnumLowerParameter(enumBase: base) + case .swiftStruct(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + return swiftStructLowerParameter(structBase: base) case .closure: return IntrinsicJSFragment( parameters: ["closure"], @@ -1297,6 +1351,9 @@ struct IntrinsicJSFragment: Sendable { case .associatedValueEnum(let fullName): let base = fullName.components(separatedBy: ".").last ?? fullName return .associatedEnumLiftReturn(enumBase: base) + case .swiftStruct(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + return swiftStructLiftReturn(structBase: base) case .closure(let signature): let lowerFuncName = "lower_closure_\(signature.moduleName)_\(signature.mangleName)" return IntrinsicJSFragment( @@ -1374,6 +1431,26 @@ struct IntrinsicJSFragment: Sendable { } ) } + case .swiftStruct(let fullName): + switch context { + case .importTS: + throw BridgeJSLinkError( + message: + "Swift structs are not supported to be passed as parameters to imported JS functions: \(fullName)" + ) + case .exportSwift: + let base = fullName.components(separatedBy: ".").last ?? fullName + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanupCode in + let resultVar = scope.variable("structValue") + printer.write( + "const \(resultVar) = structHelpers.\(base).raise(\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers));" + ) + return [resultVar] + } + ) + } case .closure: throw BridgeJSLinkError(message: "Closure parameters not yet implemented for imported JS functions") case .namespaceEnum(let string): @@ -1428,6 +1505,16 @@ struct IntrinsicJSFragment: Sendable { case .exportSwift: return associatedValueLowerReturn(fullName: fullName) } + case .swiftStruct(let fullName): + switch context { + case .importTS: + throw BridgeJSLinkError( + message: + "Swift structs are not supported to be returned from imported JS functions: \(fullName)" + ) + case .exportSwift: + return swiftStructLowerReturn(fullName: fullName) + } case .closure: throw BridgeJSLinkError(message: "Closure return values not yet implemented for imported JS functions") case .namespaceEnum(let string): @@ -1886,4 +1973,840 @@ struct IntrinsicJSFragment: Sendable { ) } } + + static func swiftStructLowerReturn(fullName: String) -> IntrinsicJSFragment { + let base = fullName.components(separatedBy: ".").last ?? fullName + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + let value = arguments[0] + let cleanupVar = scope.variable("cleanup") + printer.write( + "const { cleanup: \(cleanupVar) } = structHelpers.\(base).lower(\(value));" + ) + cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") + return [] + } + ) + } + + static func swiftStructLowerParameter(structBase: String) -> IntrinsicJSFragment { + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + let value = arguments[0] + let cleanupVar = scope.variable("cleanup") + printer.write( + "const { cleanup: \(cleanupVar) } = structHelpers.\(structBase).lower(\(value));" + ) + cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") + return [] + } + ) + } + + static func swiftStructLiftReturn(structBase: String) -> IntrinsicJSFragment { + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanupCode in + let resultVar = scope.variable("structValue") + printer.write( + "const \(resultVar) = structHelpers.\(structBase).raise(\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers));" + ) + return [resultVar] + } + ) + } + + static func structHelper(structDefinition: ExportedStruct, allStructs: [ExportedStruct]) -> IntrinsicJSFragment { + return IntrinsicJSFragment( + parameters: ["structName"], + printCode: { arguments, scope, printer, cleanup in + let structName = arguments[0] + let capturedStructDef = structDefinition + let capturedAllStructs = allStructs + + printer.write("const __bjs_create\(structName)Helpers = () => {") + printer.indent() + printer.write( + "return (\(JSGlueVariableScope.reservedTmpParamInts), \(JSGlueVariableScope.reservedTmpParamF32s), \(JSGlueVariableScope.reservedTmpParamF64s), \(JSGlueVariableScope.reservedTmpParamPointers), \(JSGlueVariableScope.reservedTmpRetPointers), textEncoder, \(JSGlueVariableScope.reservedSwift), enumHelpers) => ({" + ) + printer.indent() + + printer.write("lower: (value) => {") + printer.indent { + generateStructLowerCode( + structDef: capturedStructDef, + allStructs: capturedAllStructs, + printer: printer + ) + } + printer.write("},") + + printer.write( + "raise: (\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers)) => {" + ) + printer.indent { + generateStructRaiseCode( + structDef: capturedStructDef, + allStructs: capturedAllStructs, + printer: printer, + attachMethods: true + ) + } + printer.write("}") + printer.unindent() + printer.write("});") + printer.unindent() + printer.write("};") + + return [] + } + ) + } + + private static func findStruct(name: String, structs: [ExportedStruct]) -> ExportedStruct? { + return structs.first(where: { $0.swiftCallName == name || $0.name == name }) + } + + private static func generateStructLowerCode( + structDef: ExportedStruct, + allStructs: [ExportedStruct], + printer: CodeFragmentPrinter + ) { + let lowerPrinter = CodeFragmentPrinter() + let lowerScope = JSGlueVariableScope() + let lowerCleanup = CodeFragmentPrinter() + lowerCleanup.indent() + + let instanceProps = structDef.properties.filter { !$0.isStatic } + for property in instanceProps { + let fragment = structFieldLowerFragment(field: property, allStructs: allStructs) + let fieldValue = "value.\(property.name)" + _ = fragment.printCode([fieldValue], lowerScope, lowerPrinter, lowerCleanup) + } + + for line in lowerPrinter.lines { + printer.write(line) + } + + if !lowerCleanup.lines.isEmpty { + printer.write("const cleanup = () => {") + printer.write(contentsOf: lowerCleanup) + printer.write("};") + printer.write("return { cleanup };") + } else { + printer.write("return { cleanup: undefined };") + } + } + + private static func generateStructRaiseCode( + structDef: ExportedStruct, + allStructs: [ExportedStruct], + printer: CodeFragmentPrinter, + attachMethods: Bool = false + ) { + let raiseScope = JSGlueVariableScope() + let raiseCleanup = CodeFragmentPrinter() + + var fieldExpressions: [(name: String, expression: String)] = [] + + let instanceProps = structDef.properties.filter { !$0.isStatic } + for property in instanceProps.reversed() { + let fragment = structFieldRaiseFragment(field: property, allStructs: allStructs) + let results = fragment.printCode([], raiseScope, printer, raiseCleanup) + + if let resultExpr = results.first { + fieldExpressions.append((property.name, resultExpr)) + } else { + fieldExpressions.append((property.name, "undefined")) + } + } + + // Construct struct object with fields in original order + let reconstructedFields = instanceProps.map { property in + let expr = fieldExpressions.first(where: { $0.name == property.name })?.expression ?? "undefined" + return "\(property.name): \(expr)" + } + + if attachMethods && !structDef.methods.filter({ !$0.effects.isStatic }).isEmpty { + let instanceVar = raiseScope.variable("instance") + printer.write("const \(instanceVar) = { \(reconstructedFields.joined(separator: ", ")) };") + + // Attach instance methods to the struct instance + for method in structDef.methods where !method.effects.isStatic { + let paramList = DefaultValueUtils.formatParameterList(method.parameters) + printer.write( + "\(instanceVar).\(method.name) = function(\(paramList)) {" + ) + printer.indent { + let methodScope = JSGlueVariableScope() + let methodCleanup = CodeFragmentPrinter() + + // Lower the struct instance (this) using the helper's lower function + let structCleanupVar = methodScope.variable("structCleanup") + printer.write( + "const { cleanup: \(structCleanupVar) } = structHelpers.\(structDef.name).lower(this);" + ) + + // Lower each parameter and collect forwarding expressions + var paramForwardings: [String] = [] + for param in method.parameters { + let fragment = try! IntrinsicJSFragment.lowerParameter(type: param.type) + let loweredValues = fragment.printCode([param.name], methodScope, printer, methodCleanup) + paramForwardings.append(contentsOf: loweredValues) + } + + // Call the Swift function with all lowered parameters + let callExpr = "instance.exports.\(method.abiName)(\(paramForwardings.joined(separator: ", ")))" + if method.returnType == .void { + printer.write("\(callExpr);") + } else { + printer.write("const ret = \(callExpr);") + } + + // Cleanup + printer.write("if (\(structCleanupVar)) { \(structCleanupVar)(); }") + printer.write(contentsOf: methodCleanup) + + // Lift return value if needed + if method.returnType != .void { + let liftFragment = try! IntrinsicJSFragment.liftReturn(type: method.returnType) + if !liftFragment.parameters.isEmpty { + let lifted = liftFragment.printCode(["ret"], methodScope, printer, methodCleanup) + if let liftedValue = lifted.first { + printer.write("return \(liftedValue);") + } + } + } + } + printer.write("}.bind(\(instanceVar));") + } + + printer.write("return \(instanceVar);") + } else { + printer.write("return { \(reconstructedFields.joined(separator: ", ")) };") + } + } + + private static func structFieldLowerFragment( + field: ExportedProperty, + allStructs: [ExportedStruct] + ) -> IntrinsicJSFragment { + switch field.type { + case .string: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + let bytesVar = scope.variable("bytes") + let idVar = scope.variable("id") + printer.write("const \(bytesVar) = textEncoder.encode(\(value));") + printer.write("const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(bytesVar).length);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar));") + cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") + return [idVar] + } + ) + case .bool: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(arguments[0]) ? 1 : 0);") + return [] + } + ) + case .int: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push((\(arguments[0]) | 0));") + return [] + } + ) + case .float: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamF32s).push(Math.fround(\(arguments[0])));") + return [] + } + ) + case .double: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamF64s).push(\(arguments[0]));") + return [] + } + ) + case .jsObject: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + let idVar = scope.variable("id") + printer.write("let \(idVar);") + printer.write("if (\(value) != null) {") + printer.indent { + printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") + } + printer.write("} else {") + printer.indent { + printer.write("\(idVar) = undefined;") + } + printer.write("}") + printer.write( + "\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar) !== undefined ? \(idVar) : 0);" + ) + cleanup.write("if(\(idVar) !== undefined && \(idVar) !== 0) {") + cleanup.indent { + cleanup.write("try {") + cleanup.indent { + cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.getObject(\(idVar));") + cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") + } + cleanup.write("} catch(e) {}") + } + cleanup.write("}") + return [idVar] + } + ) + case .optional(let wrappedType): + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + let isSomeVar = scope.variable("isSome") + printer.write("const \(isSomeVar) = \(value) != null;") + + if case .caseEnum = wrappedType { + printer.write("if (\(isSomeVar)) {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push((\(value) | 0));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + return [] + } else if case .rawValueEnum(_, let rawType) = wrappedType { + switch rawType { + case .string: + let idVar = scope.variable("id") + printer.write("let \(idVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + let bytesVar = scope.variable("bytes") + printer.write("const \(bytesVar) = textEncoder.encode(\(value));") + printer.write( + "\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));" + ) + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(bytesVar).length);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + cleanup.write( + "if(\(idVar) !== undefined) { \(JSGlueVariableScope.reservedSwift).memory.release(\(idVar)); }" + ) + return [idVar] + default: + printer.write("if (\(isSomeVar)) {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push((\(value) | 0));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + return [] + } + } else if case .swiftHeapObject = wrappedType { + let ptrVar = scope.variable("ptr") + printer.write("let \(ptrVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + printer.write("\(ptrVar) = \(value).pointer;") + printer.write("\(JSGlueVariableScope.reservedTmpParamPointers).push(\(ptrVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamPointers).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + return [] + } else if case .swiftStruct(let structName) = wrappedType { + let nestedCleanupVar = scope.variable("nestedCleanup") + printer.write("let \(nestedCleanupVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + let structResultVar = scope.variable("structResult") + printer.write("const \(structResultVar) = structHelpers.\(structName).lower(\(value));") + printer.write("\(nestedCleanupVar) = \(structResultVar).cleanup;") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + cleanup.write("if (\(nestedCleanupVar)) { \(nestedCleanupVar)(); }") + return [] + } else if case .string = wrappedType { + let idVar = scope.variable("id") + printer.write("let \(idVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + let bytesVar = scope.variable("bytes") + printer.write("const \(bytesVar) = textEncoder.encode(\(value));") + printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(bytesVar).length);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + cleanup.write( + "if(\(idVar) !== undefined) { \(JSGlueVariableScope.reservedSwift).memory.release(\(idVar)); }" + ) + return [idVar] + } else if case .jsObject = wrappedType { + let idVar = scope.variable("id") + printer.write("let \(idVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(idVar) = undefined;") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + cleanup.write("if(\(idVar) !== undefined && \(idVar) !== 0) {") + cleanup.indent { + cleanup.write("try {") + cleanup.indent { + cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.getObject(\(idVar));") + cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") + } + cleanup.write("} catch(e) {}") + } + cleanup.write("}") + return [idVar] + } else { + // Handle optional primitive types using helper + switch wrappedType { + case .int: + pushOptionalPrimitive( + value: value, + isSomeVar: isSomeVar, + stack: .tmpParamInts, + convert: "| 0", + zeroValue: "0", + printer: printer + ) + case .bool: + pushOptionalPrimitive( + value: value, + isSomeVar: isSomeVar, + stack: .tmpParamInts, + convert: "? 1 : 0", + zeroValue: "0", + printer: printer + ) + case .float: + pushOptionalPrimitive( + value: value, + isSomeVar: isSomeVar, + stack: .tmpParamF32s, + convert: "Math.fround", + zeroValue: "0.0", + printer: printer + ) + case .double: + pushOptionalPrimitive( + value: value, + isSomeVar: isSomeVar, + stack: .tmpParamF64s, + convert: nil, + zeroValue: "0.0", + printer: printer + ) + case .associatedValueEnum(let enumName): + let base = enumName.components(separatedBy: ".").last ?? enumName + let caseIdVar = scope.variable("enumCaseId") + let enumCleanupVar = scope.variable("enumCleanup") + printer.write("let \(caseIdVar), \(enumCleanupVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + let enumResultVar = scope.variable("enumResult") + printer.write("const \(enumResultVar) = enumHelpers.\(base).lower(\(value));") + printer.write("\(caseIdVar) = \(enumResultVar).caseId;") + printer.write("\(enumCleanupVar) = \(enumResultVar).cleanup;") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(caseIdVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + cleanup.write("if (\(enumCleanupVar)) { \(enumCleanupVar)(); }") + default: + // For other types (nested structs, etc.), original logic applies + let wrappedFragment = structFieldLowerFragment( + field: ExportedProperty( + name: field.name, + type: wrappedType, + isReadonly: true, + isStatic: false + ), + allStructs: allStructs + ) + printer.write("if (\(isSomeVar)) {") + printer.indent { + _ = wrappedFragment.printCode([value], scope, printer, cleanup) + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + } + return [] + } + } + ) + case .swiftStruct(let nestedName): + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + let structResultVar = scope.variable("structResult") + printer.write("const \(structResultVar) = structHelpers.\(nestedName).lower(\(value));") + cleanup.write("if (\(structResultVar).cleanup) { \(structResultVar).cleanup(); }") + return [] + } + ) + case .swiftHeapObject: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + printer.write("\(JSGlueVariableScope.reservedTmpParamPointers).push(\(value).pointer);") + return [] + } + ) + case .associatedValueEnum(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + let caseIdVar = scope.variable("caseId") + let cleanupVar = scope.variable("enumCleanup") + printer.write( + "const { caseId: \(caseIdVar), cleanup: \(cleanupVar) } = enumHelpers.\(base).lower(\(value));" + ) + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(caseIdVar));") + cleanup.write("if (\(cleanupVar)) { \(cleanupVar)(); }") + return [cleanupVar] + } + ) + case .caseEnum: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push((\(arguments[0]) | 0));") + return [] + } + ) + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + let bytesVar = scope.variable("bytes") + let idVar = scope.variable("id") + printer.write("const \(bytesVar) = textEncoder.encode(\(value));") + printer.write( + "const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));" + ) + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(bytesVar).length);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar));") + cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") + return [idVar] + } + ) + default: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push((\(arguments[0]) | 0));") + return [] + } + ) + } + case .void, .swiftProtocol, .namespaceEnum, .closure: + // These types should not appear as struct fields - return error fragment + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("throw new Error(\"Unsupported struct field type for lowering: \(field.type)\");") + return [] + } + ) + } + } + + /// Helper to push optional primitive values to stack-based parameters + private static func pushOptionalPrimitive( + value: String, + isSomeVar: String, + stack: StackType, + convert: String?, + zeroValue: String, + printer: CodeFragmentPrinter + ) { + let stackName: String + switch stack { + case .tmpParamInts: stackName = JSGlueVariableScope.reservedTmpParamInts + case .tmpParamF32s: stackName = JSGlueVariableScope.reservedTmpParamF32s + case .tmpParamF64s: stackName = JSGlueVariableScope.reservedTmpParamF64s + } + + printer.write("if (\(isSomeVar)) {") + printer.indent { + let converted: String + if let convert = convert { + if convert.starts(with: "Math.") { + converted = "\(convert)(\(value))" + } else { + converted = "\(value) \(convert)" + } + } else { + converted = value + } + printer.write("\(stackName).push(\(converted));") + } + printer.write("} else {") + printer.indent { + printer.write("\(stackName).push(\(zeroValue));") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + } + + private enum StackType { + case tmpParamInts + case tmpParamF32s + case tmpParamF64s + } + + private static func structFieldRaiseFragment( + field: ExportedProperty, + allStructs: [ExportedStruct] + ) -> IntrinsicJSFragment { + switch field.type { + case .string: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let strVar = scope.variable("string") + printer.write("const \(strVar) = \(JSGlueVariableScope.reservedTmpRetStrings).pop();") + return [strVar] + } + ) + case .bool: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let bVar = scope.variable("bool") + printer.write("const \(bVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop() !== 0;") + return [bVar] + } + ) + case .int: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let iVar = scope.variable("int") + printer.write("const \(iVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + return [iVar] + } + ) + case .float: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let fVar = scope.variable("f32") + printer.write("const \(fVar) = \(JSGlueVariableScope.reservedTmpRetF32s).pop();") + return [fVar] + } + ) + case .double: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let dVar = scope.variable("f64") + printer.write("const \(dVar) = \(JSGlueVariableScope.reservedTmpRetF64s).pop();") + return [dVar] + } + ) + case .optional(let wrappedType): + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let isSomeVar = scope.variable("isSome") + let optVar = scope.variable("optional") + printer.write("const \(isSomeVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + printer.write("let \(optVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + // Special handling for associated value enum - in struct fields, case ID is pushed to tmpRetInts + if case .associatedValueEnum(let enumName) = wrappedType { + let base = enumName.components(separatedBy: ".").last ?? enumName + let caseIdVar = scope.variable("enumCaseId") + printer.write("const \(caseIdVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + printer.write( + "\(optVar) = enumHelpers.\(base).raise(\(caseIdVar), \(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s));" + ) + } else { + let wrappedFragment = structFieldRaiseFragment( + field: ExportedProperty( + name: field.name, + type: wrappedType, + isReadonly: true, + isStatic: false + ), + allStructs: allStructs + ) + let wrappedResults = wrappedFragment.printCode([], scope, printer, cleanup) + if let wrappedResult = wrappedResults.first { + printer.write("\(optVar) = \(wrappedResult);") + } else { + printer.write("\(optVar) = undefined;") + } + } + } + printer.write("} else {") + printer.indent { + printer.write("\(optVar) = null;") + } + printer.write("}") + return [optVar] + } + ) + case .swiftStruct(let nestedName): + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let structVar = scope.variable("struct") + printer.write( + "const \(structVar) = structHelpers.\(nestedName).raise(\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers));" + ) + return [structVar] + } + ) + case .caseEnum: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let varName = scope.variable("value") + printer.write("const \(varName) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + return [varName] + } + ) + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let varName = scope.variable("value") + printer.write("const \(varName) = \(JSGlueVariableScope.reservedTmpRetStrings).pop();") + return [varName] + } + ) + default: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let varName = scope.variable("value") + printer.write("const \(varName) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + return [varName] + } + ) + } + case .swiftHeapObject(let className): + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let ptrVar = scope.variable("ptr") + let varName = scope.variable("value") + printer.write("const \(ptrVar) = \(JSGlueVariableScope.reservedTmpRetPointers).pop();") + printer.write("const \(varName) = _exports['\(className)'].__construct(\(ptrVar));") + return [varName] + } + ) + case .jsObject: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let objectIdVar = scope.variable("objectId") + let varName = scope.variable("value") + printer.write("const \(objectIdVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + printer.write("let \(varName);") + printer.write("if (\(objectIdVar) !== 0) {") + printer.indent { + printer.write( + "\(varName) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(objectIdVar));" + ) + printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(objectIdVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(varName) = null;") + } + printer.write("}") + return [varName] + } + ) + case .associatedValueEnum(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let varName = scope.variable("value") + printer.write( + "const \(varName) = enumHelpers.\(base).raise(\(JSGlueVariableScope.reservedTmpRetTag), \(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s));" + ) + return [varName] + } + ) + case .void, .swiftProtocol, .namespaceEnum, .closure: + // These types should not appear as struct fields + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + printer.write("throw new Error(\"Unsupported struct field type: \(field.type)\");") + return [] + } + ) + } + } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 12970b505..b2dae86c0 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -25,7 +25,7 @@ public struct ABINameGenerator { let contextPart: String? if let staticContext = staticContext { switch staticContext { - case .className(let name), .enumName(let name): + case .className(let name), .enumName(let name), .structName(let name): contextPart = name case .namespaceEnum: contextPart = namespacePart @@ -115,6 +115,7 @@ public enum BridgeType: Codable, Equatable, Hashable, Sendable { case associatedValueEnum(String) case namespaceEnum(String) case swiftProtocol(String) + case swiftStruct(String) indirect case closure(ClosureSignature) } @@ -159,6 +160,17 @@ public enum SwiftEnumRawType: String, CaseIterable, Codable, Sendable { } } +/// Represents a struct field with name and default value for default parameter values +public struct DefaultValueField: Codable, Equatable, Sendable { + public let name: String + public let value: DefaultValue + + public init(name: String, value: DefaultValue) { + self.name = name + self.value = value + } +} + public enum DefaultValue: Codable, Equatable, Sendable { case string(String) case int(Int) @@ -169,6 +181,7 @@ public enum DefaultValue: Codable, Equatable, Sendable { case enumCase(String, String) // enumName, caseName case object(String) // className for parameterless constructor case objectWithArguments(String, [DefaultValue]) // className, constructor argument values + case structLiteral(String, [DefaultValueField]) // structName, field name/value pairs } public struct Parameter: Codable, Equatable, Sendable { @@ -205,10 +218,51 @@ public struct Effects: Codable, Equatable, Sendable { public enum StaticContext: Codable, Equatable, Sendable { case className(String) + case structName(String) case enumName(String) case namespaceEnum } +// MARK: - Struct Skeleton + +public struct StructField: Codable, Equatable, Sendable { + public let name: String + public let type: BridgeType + + public init(name: String, type: BridgeType) { + self.name = name + self.type = type + } +} + +public struct ExportedStruct: Codable, Equatable, Sendable { + public let name: String + public let swiftCallName: String + public let explicitAccessControl: String? + public var properties: [ExportedProperty] + public var constructor: ExportedConstructor? + public var methods: [ExportedFunction] + public let namespace: [String]? + + public init( + name: String, + swiftCallName: String, + explicitAccessControl: String?, + properties: [ExportedProperty] = [], + constructor: ExportedConstructor? = nil, + methods: [ExportedFunction] = [], + namespace: [String]? + ) { + self.name = name + self.swiftCallName = swiftCallName + self.explicitAccessControl = explicitAccessControl + self.properties = properties + self.constructor = constructor + self.methods = methods + self.namespace = namespace + } +} + // MARK: - Enum Skeleton public struct AssociatedValue: Codable, Equatable, Sendable { @@ -416,7 +470,7 @@ public struct ExportedClass: Codable { } } -public struct ExportedConstructor: Codable { +public struct ExportedConstructor: Codable, Equatable, Sendable { public var abiName: String public var parameters: [Parameter] public var effects: Effects @@ -457,7 +511,7 @@ public struct ExportedProperty: Codable, Equatable, Sendable { public func callName(prefix: String? = nil) -> String { if let staticContext = staticContext { switch staticContext { - case .className(let baseName), .enumName(let baseName): + case .className(let baseName), .enumName(let baseName), .structName(let baseName): return "\(baseName).\(name)" case .namespaceEnum: if let namespace = namespace, !namespace.isEmpty { @@ -498,6 +552,7 @@ public struct ExportedSkeleton: Codable { public let functions: [ExportedFunction] public let classes: [ExportedClass] public let enums: [ExportedEnum] + public let structs: [ExportedStruct] public let protocols: [ExportedProtocol] /// Whether to expose exported APIs to the global namespace. /// @@ -511,6 +566,7 @@ public struct ExportedSkeleton: Codable { functions: [ExportedFunction], classes: [ExportedClass], enums: [ExportedEnum], + structs: [ExportedStruct] = [], protocols: [ExportedProtocol] = [], exposeToGlobal: Bool ) { @@ -518,6 +574,7 @@ public struct ExportedSkeleton: Codable { self.functions = functions self.classes = classes self.enums = enums + self.structs = structs self.protocols = protocols self.exposeToGlobal = exposeToGlobal } @@ -624,6 +681,9 @@ extension BridgeType { case .swiftProtocol: // Protocols pass JSObject IDs as Int32 return .i32 + case .swiftStruct: + // Structs use stack-based return (no direct WASM return type) + return nil case .closure: // Closures pass callback ID as Int32 return .i32 @@ -660,6 +720,8 @@ extension BridgeType { return "\(name.count)\(name)O" case .swiftProtocol(let name): return "\(name.count)\(name)P" + case .swiftStruct(let name): + return "\(name.count)\(name)V" case .closure(let signature): let params = signature.parameters.isEmpty diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift index 32c3aae73..41e118315 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift @@ -39,6 +39,11 @@ import Testing "Inputs" ) + static let multifileInputsDirectory = URL(fileURLWithPath: #filePath).deletingLastPathComponent() + .appendingPathComponent( + "MultifileInputs" + ) + static func collectInputs() -> [String] { let fileManager = FileManager.default let inputs = try! fileManager.contentsOfDirectory(atPath: Self.inputsDirectory.path) @@ -69,4 +74,73 @@ import Testing let name = url.deletingPathExtension().lastPathComponent try snapshot(swiftAPI: swiftAPI, name: name + ".Global") } + + @Test + func snapshotCrossFileTypeResolution() throws { + // Test that types defined in one file can be referenced from another file + // This tests the fix for cross-file type resolution in BridgeJS + let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule", exposeToGlobal: false) + + // Add ClassB first, then ClassA (which references ClassB) + let classBURL = Self.multifileInputsDirectory.appendingPathComponent("CrossFileClassB.swift") + let classBSourceFile = Parser.parse(source: try String(contentsOf: classBURL, encoding: .utf8)) + try swiftAPI.addSourceFile(classBSourceFile, "CrossFileClassB.swift") + + let classAURL = Self.multifileInputsDirectory.appendingPathComponent("CrossFileClassA.swift") + let classASourceFile = Parser.parse(source: try String(contentsOf: classAURL, encoding: .utf8)) + try swiftAPI.addSourceFile(classASourceFile, "CrossFileClassA.swift") + + try snapshot(swiftAPI: swiftAPI, name: "CrossFileTypeResolution") + } + + @Test + func snapshotCrossFileTypeResolutionReverseOrder() throws { + // Test that types can be resolved regardless of the order files are added + // Add ClassA first (which references ClassB), then ClassB + let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule", exposeToGlobal: false) + + let classAURL = Self.multifileInputsDirectory.appendingPathComponent("CrossFileClassA.swift") + let classASourceFile = Parser.parse(source: try String(contentsOf: classAURL, encoding: .utf8)) + try swiftAPI.addSourceFile(classASourceFile, "CrossFileClassA.swift") + + let classBURL = Self.multifileInputsDirectory.appendingPathComponent("CrossFileClassB.swift") + let classBSourceFile = Parser.parse(source: try String(contentsOf: classBURL, encoding: .utf8)) + try swiftAPI.addSourceFile(classBSourceFile, "CrossFileClassB.swift") + + try snapshot(swiftAPI: swiftAPI, name: "CrossFileTypeResolution.ReverseOrder") + } + + @Test + func snapshotCrossFileFunctionTypes() throws { + // Test that functions and methods can use cross-file types as parameters and return types + let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule", exposeToGlobal: false) + + // Add FunctionB first, then FunctionA (which references FunctionB in methods and functions) + let functionBURL = Self.multifileInputsDirectory.appendingPathComponent("CrossFileFunctionB.swift") + let functionBSourceFile = Parser.parse(source: try String(contentsOf: functionBURL, encoding: .utf8)) + try swiftAPI.addSourceFile(functionBSourceFile, "CrossFileFunctionB.swift") + + let functionAURL = Self.multifileInputsDirectory.appendingPathComponent("CrossFileFunctionA.swift") + let functionASourceFile = Parser.parse(source: try String(contentsOf: functionAURL, encoding: .utf8)) + try swiftAPI.addSourceFile(functionASourceFile, "CrossFileFunctionA.swift") + + try snapshot(swiftAPI: swiftAPI, name: "CrossFileFunctionTypes") + } + + @Test + func snapshotCrossFileFunctionTypesReverseOrder() throws { + // Test that function types can be resolved regardless of the order files are added + let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule", exposeToGlobal: false) + + // Add FunctionA first (which references FunctionB), then FunctionB + let functionAURL = Self.multifileInputsDirectory.appendingPathComponent("CrossFileFunctionA.swift") + let functionASourceFile = Parser.parse(source: try String(contentsOf: functionAURL, encoding: .utf8)) + try swiftAPI.addSourceFile(functionASourceFile, "CrossFileFunctionA.swift") + + let functionBURL = Self.multifileInputsDirectory.appendingPathComponent("CrossFileFunctionB.swift") + let functionBSourceFile = Parser.parse(source: try String(contentsOf: functionBURL, encoding: .utf8)) + try swiftAPI.addSourceFile(functionBSourceFile, "CrossFileFunctionB.swift") + + try snapshot(swiftAPI: swiftAPI, name: "CrossFileFunctionTypes.ReverseOrder") + } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/DefaultParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/DefaultParameters.swift index b670d2dbb..73274ad5c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/DefaultParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/DefaultParameters.swift @@ -28,13 +28,11 @@ @JS class DefaultGreeter { @JS var name: String - @JS init(name: String) { - self.name = name - } + @JS init(name: String) } @JS class EmptyGreeter { - @JS init() {} + @JS init() } @JS public func testComplexInit(greeter: DefaultGreeter = DefaultGreeter(name: "DefaultUser")) -> DefaultGreeter @@ -53,22 +51,25 @@ enabled: Bool = true, status: Status = .active, tag: String? = nil - ) { - self.name = name - self.count = count - self.enabled = enabled - self.status = status - self.tag = tag - } - - @JS func describe() -> String { - let tagStr = tag ?? "nil" - let statusStr: String - switch status { - case .active: statusStr = "active" - case .inactive: statusStr = "inactive" - case .pending: statusStr = "pending" - } - return "\(name):\(count):\(enabled):\(statusStr):\(tagStr)" - } + ) +} + +@JS struct Config { + var name: String + var value: Int + var enabled: Bool +} + +@JS public func testOptionalStructDefault(point: Config? = nil) -> Config? +@JS public func testOptionalStructWithValueDefault( + point: Config? = Config(name: "default", value: 42, enabled: true) +) -> Config? + +@JS struct MathOperations { + var baseValue: Double + + @JS init(baseValue: Double = 0.0) + @JS func add(a: Double, b: Double = 10.0) -> Double + @JS func multiply(a: Double, b: Double) -> Double + @JS static func subtract(a: Double, b: Double = 5.0) -> Double } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumAssociatedValue.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumAssociatedValue.swift index e099ba85b..efb6cd1b1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumAssociatedValue.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumAssociatedValue.swift @@ -10,12 +10,8 @@ enum APIResult { @JS func handle(result: APIResult) @JS func getResult() -> APIResult -@JS func roundtripAPIResult(result: APIResult) -> APIResult { - return result -} -@JS func roundTripOptionalAPIResult(result: APIResult?) -> APIResult? { - return result -} +@JS func roundtripAPIResult(result: APIResult) -> APIResult +@JS func roundTripOptionalAPIResult(result: APIResult?) -> APIResult? @JS enum ComplexResult { @@ -29,12 +25,8 @@ enum ComplexResult { @JS func handleComplex(result: ComplexResult) @JS func getComplexResult() -> ComplexResult -@JS func roundtripComplexResult(_ result: ComplexResult) -> ComplexResult { - return result -} -@JS func roundTripOptionalComplexResult(result: ComplexResult?) -> ComplexResult? { - return result -} +@JS func roundtripComplexResult(_ result: ComplexResult) -> ComplexResult +@JS func roundTripOptionalComplexResult(result: ComplexResult?) -> ComplexResult? @JS enum Utilities { @@ -45,9 +37,7 @@ enum Utilities { } } -@JS func roundTripOptionalUtilitiesResult(result: Utilities.Result?) -> Utilities.Result? { - return result -} +@JS func roundTripOptionalUtilitiesResult(result: Utilities.Result?) -> Utilities.Result? @JS(namespace: "API") @JS enum NetworkingResult { @@ -55,9 +45,7 @@ enum Utilities { case failure(String, Int) } -@JS func roundTripOptionalNetworkingResult(result: NetworkingResult?) -> NetworkingResult? { - return result -} +@JS func roundTripOptionalNetworkingResult(result: NetworkingResult?) -> NetworkingResult? @JS enum APIOptionalResult { @@ -65,6 +53,5 @@ enum APIOptionalResult { case failure(Int?, Bool?) case status(Bool?, Int?, String?) } -@JS func roundTripOptionalAPIOptionalResult(result: APIOptionalResult?) -> APIOptionalResult? { - return result -} +@JS func roundTripOptionalAPIOptionalResult(result: APIOptionalResult?) -> APIOptionalResult? +@JS func compareAPIResults(result1: APIOptionalResult?, result2: APIOptionalResult?) -> APIOptionalResult? diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftStruct.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftStruct.swift new file mode 100644 index 000000000..3415f54a9 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftStruct.swift @@ -0,0 +1,44 @@ +@JS struct DataPoint { + let x: Double + let y: Double + var label: String + var optCount: Int? + var optFlag: Bool? + + @JS init(x: Double, y: Double, label: String, optCount: Int?, optFlag: Bool?) +} + +@JS struct Address { + var street: String + var city: String + var zipCode: Int? +} + +@JS struct Person { + var name: String + var age: Int + var address: Address + var email: String? +} + +@JS class Greeter { + @JS var name: String + + @JS init(name: String) + @JS func greet() -> String +} + +@JS struct Session { + var id: Int + var owner: Greeter +} + +@JS func roundtrip(_ session: Person) -> Person + +@JS struct ConfigStruct { + @JS static let maxRetries: Int = 3 + @JS nonisolated(unsafe) static var defaultConfig: String = "production" + @JS nonisolated(unsafe) static var timeout: Double = 30.0 + @JS static var computedSetting: String { "Config: \(defaultConfig)" } + @JS static func update(_ timeout: Double) -> Double +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileClassA.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileClassA.swift new file mode 100644 index 000000000..a26cae313 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileClassA.swift @@ -0,0 +1,3 @@ +@JS class ClassA { + @JS var linkedB: ClassB? +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileClassB.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileClassB.swift new file mode 100644 index 000000000..c7c5b80d9 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileClassB.swift @@ -0,0 +1,3 @@ +@JS class ClassB { + @JS init() {} +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileFunctionA.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileFunctionA.swift new file mode 100644 index 000000000..9a2929e8e --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileFunctionA.swift @@ -0,0 +1,18 @@ +@JS class FunctionA { + @JS init() {} + + // Method that takes a cross-file type as parameter + @JS func processB(b: FunctionB) -> String { + return "Processed \(b.value)" + } + + // Method that returns a cross-file type + @JS func createB(value: String) -> FunctionB { + return FunctionB(value: value) + } +} + +// Standalone function that uses cross-file types +@JS func standaloneFunction(b: FunctionB) -> FunctionB { + return b +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileFunctionB.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileFunctionB.swift new file mode 100644 index 000000000..9c1828219 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/MultifileInputs/CrossFileFunctionB.swift @@ -0,0 +1,7 @@ +@JS class FunctionB { + @JS var value: String + + @JS init(value: String) { + self.value = value + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js index 227093668..e53708aab 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js index 372de6ec3..c9c2e484f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js index 873c9d697..c98f19719 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.d.ts index 38cbf989c..c024ecfb6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.d.ts @@ -11,6 +11,19 @@ export const StatusValues: { }; export type StatusTag = typeof StatusValues[keyof typeof StatusValues]; +export interface Config { + name: string; + value: number; + enabled: boolean; +} +export interface MathOperations { + baseValue: number; + /** + * @param b - Optional parameter (default: 10.0) + */ + add(a: number, b?: number): number; + multiply(a: number, b: number): number; +} export type StatusObject = typeof StatusValues; /// Represents a Swift heap object like a class instance or an actor instance. @@ -26,7 +39,6 @@ export interface DefaultGreeter extends SwiftHeapObject { export interface EmptyGreeter extends SwiftHeapObject { } export interface ConstructorDefaults extends SwiftHeapObject { - describe(): string; name: string; count: number; enabled: boolean; @@ -96,7 +108,25 @@ export type Exports = { * @param greeter - Optional parameter (default: new EmptyGreeter()) */ testEmptyInit(greeter?: EmptyGreeter): EmptyGreeter; + /** + * @param point - Optional parameter (default: null) + */ + testOptionalStructDefault(point?: Config | null): Config | null; + /** + * @param point - Optional parameter (default: { name: "default", value: 42, enabled: true }) + */ + testOptionalStructWithValueDefault(point?: Config | null): Config | null; Status: StatusObject + MathOperations: { + /** + * @param baseValue - Optional parameter (default: 0.0) + */ + init(baseValue?: number): MathOperations; + /** + * @param b - Optional parameter (default: 5.0) + */ + subtract(a: number, b?: number): number; + } } export type Imports = { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.js index fa7a7c06b..9836ffe95 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.js @@ -32,9 +32,59 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; + const structHelpers = {}; let _exports = null; let bjs = null; + const __bjs_createConfigHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + const bytes = textEncoder.encode(value.name); + const id = swift.memory.retain(bytes); + tmpParamInts.push(bytes.length); + tmpParamInts.push(id); + tmpParamInts.push((value.value | 0)); + tmpParamInts.push(value.enabled ? 1 : 0); + const cleanup = () => { + swift.memory.release(id); + }; + return { cleanup }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const bool = tmpRetInts.pop() !== 0; + const int = tmpRetInts.pop(); + const string = tmpRetStrings.pop(); + return { name: string, value: int, enabled: bool }; + } + }); + }; + const __bjs_createMathOperationsHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + tmpParamF64s.push(value.baseValue); + return { cleanup: undefined }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const f64 = tmpRetF64s.pop(); + const instance1 = { baseValue: f64 }; + instance1.add = function(a, b = 10.0) { + const { cleanup: structCleanup } = structHelpers.MathOperations.lower(this); + const ret = instance.exports.bjs_MathOperations_add(a, b); + if (structCleanup) { structCleanup(); } + return ret; + }.bind(instance1); + instance1.multiply = function(a, b) { + const { cleanup: structCleanup } = structHelpers.MathOperations.lower(this); + const ret = instance.exports.bjs_MathOperations_multiply(a, b); + if (structCleanup) { structCleanup(); } + return ret; + }.bind(instance1); + return instance1; + } + }); + }; return { /** @@ -97,6 +147,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; @@ -290,12 +346,6 @@ export async function createInstantiator(options, swift) { } return ConstructorDefaults.__construct(ret); } - describe() { - instance.exports.bjs_ConstructorDefaults_describe(this.pointer); - const ret = tmpRetString; - tmpRetString = undefined; - return ret; - } get name() { instance.exports.bjs_ConstructorDefaults_name_get(this.pointer); const ret = tmpRetString; @@ -348,6 +398,12 @@ export async function createInstantiator(options, swift) { } } } + const ConfigHelpers = __bjs_createConfigHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.Config = ConfigHelpers; + + const MathOperationsHelpers = __bjs_createMathOperationsHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.MathOperations = MathOperationsHelpers; + const exports = { DefaultGreeter, EmptyGreeter, @@ -428,7 +484,54 @@ export async function createInstantiator(options, swift) { const ret = instance.exports.bjs_testEmptyInit(greeter.pointer); return EmptyGreeter.__construct(ret); }, + testOptionalStructDefault: function bjs_testOptionalStructDefault(point = null) { + const isSome = point != null; + let pointCleanup; + if (isSome) { + const structResult = structHelpers.Config.lower(point); + pointCleanup = structResult.cleanup; + } + instance.exports.bjs_testOptionalStructDefault(+isSome); + const isSome1 = tmpRetInts.pop(); + let optResult; + if (isSome1) { + optResult = structHelpers.Config.raise(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + } else { + optResult = null; + } + if (pointCleanup) { pointCleanup(); } + return optResult; + }, + testOptionalStructWithValueDefault: function bjs_testOptionalStructWithValueDefault(point = { name: "default", value: 42, enabled: true }) { + const isSome = point != null; + let pointCleanup; + if (isSome) { + const structResult = structHelpers.Config.lower(point); + pointCleanup = structResult.cleanup; + } + instance.exports.bjs_testOptionalStructWithValueDefault(+isSome); + const isSome1 = tmpRetInts.pop(); + let optResult; + if (isSome1) { + optResult = structHelpers.Config.raise(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + } else { + optResult = null; + } + if (pointCleanup) { pointCleanup(); } + return optResult; + }, Status: StatusValues, + MathOperations: { + init: function(baseValue = 0.0) { + instance.exports.bjs_MathOperations_init(baseValue); + const structValue = structHelpers.MathOperations.raise(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + return structValue; + }, + subtract: function(a, b) { + const ret = instance.exports.bjs_MathOperations_static_subtract(a, b); + return ret; + }, + }, }; _exports = exports; return exports; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.d.ts index 9df9bbea3..20962c388 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.d.ts @@ -86,6 +86,7 @@ export type Exports = { roundTripOptionalUtilitiesResult(result: Utilities.ResultTag | null): Utilities.ResultTag | null; roundTripOptionalNetworkingResult(result: API.NetworkingResultTag | null): API.NetworkingResultTag | null; roundTripOptionalAPIOptionalResult(result: APIOptionalResultTag | null): APIOptionalResultTag | null; + compareAPIResults(result1: APIOptionalResultTag | null, result2: APIOptionalResultTag | null): APIOptionalResultTag | null; APIResult: APIResultObject ComplexResult: ComplexResultObject APIOptionalResult: APIOptionalResultObject diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js index c332a403e..f0fbf6f75 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js @@ -501,6 +501,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; const enumHelpers = {}; let _exports = null; @@ -567,6 +569,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; @@ -814,6 +822,33 @@ export async function createInstantiator(options, swift) { if (resultCleanup) { resultCleanup(); } return optResult; }, + compareAPIResults: function bjs_compareAPIResults(result1, result2) { + const isSome = result1 != null; + let result1CaseId, result1Cleanup; + if (isSome) { + const enumResult = enumHelpers.APIOptionalResult.lower(result1); + result1CaseId = enumResult.caseId; + result1Cleanup = enumResult.cleanup; + } + const isSome1 = result2 != null; + let result2CaseId, result2Cleanup; + if (isSome1) { + const enumResult1 = enumHelpers.APIOptionalResult.lower(result2); + result2CaseId = enumResult1.caseId; + result2Cleanup = enumResult1.cleanup; + } + instance.exports.bjs_compareAPIResults(+isSome, isSome ? result1CaseId : 0, +isSome1, isSome1 ? result2CaseId : 0); + const isNull = (tmpRetTag === -1); + let optResult; + if (isNull) { + optResult = null; + } else { + optResult = enumHelpers.APIOptionalResult.raise(tmpRetTag, tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s); + } + if (result1Cleanup) { result1Cleanup(); } + if (result2Cleanup) { result2Cleanup(); } + return optResult; + }, APIResult: APIResultValues, ComplexResult: ComplexResultValues, APIOptionalResult: APIOptionalResultValues, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js index 526e2fc91..4a0ebe848 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js @@ -50,6 +50,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -115,6 +117,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js index 9fed226d9..96127eba5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js @@ -51,6 +51,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -116,6 +118,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.Export.js index a38b50933..c766f08ef 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.Export.js @@ -70,6 +70,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -135,6 +137,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js index 9daa221b2..2331d1c78 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js @@ -101,6 +101,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -166,6 +168,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js index 6e5461190..1bfd09445 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js index 9336edc6d..c8b242720 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.Export.js index 453057fa3..737a40046 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.Export.js index 0ff8f450e..ccf941dec 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.Export.js index e422b08b6..1e1e9563a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js index 380f1a721..df998b01b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js index 24022a438..da5566616 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.Export.js index e2f13dd02..c051fcd21 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.Export.js index f9c60e068..015d5a1c0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js index c8bd621df..5e7ae2094 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js index 32f5e36f9..512b48ace 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js index 605a13ea6..f2e1bbe0c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js index 62122a832..4b486f484 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js index e37af60c9..4bfa5f698 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js index 58f32ea96..efb6b11e7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js @@ -90,6 +90,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; const enumHelpers = {}; let _exports = null; @@ -156,6 +158,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Export.js index 8522168f2..782a38605 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Export.js @@ -77,6 +77,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; const enumHelpers = {}; let _exports = null; @@ -143,6 +145,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.Export.js index 3e445992e..f0ce10325 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.Export.js @@ -77,6 +77,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; const enumHelpers = {}; let _exports = null; @@ -143,6 +145,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Export.js index 5d70c39b1..d1039d0f4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Export.js @@ -31,6 +31,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -96,6 +98,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.Export.js index 09a2b0de9..086a2ca31 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.Export.js @@ -31,6 +31,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -96,6 +98,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js index a668639d4..2f182b630 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js index e13404bbf..21caebd02 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js index f31ef1403..712563448 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js index ae86fd8f1..f1ab79072 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index e946b01ce..2def00d13 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.Export.js index 755d98fd6..74b2cfe4f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.Export.js @@ -128,6 +128,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; const enumHelpers = {}; let _exports = null; @@ -194,6 +196,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.d.ts new file mode 100644 index 000000000..1d1474510 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.d.ts @@ -0,0 +1,66 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export interface DataPoint { + x: number; + y: number; + label: string; + optCount: number | null; + optFlag: boolean | null; +} +export interface Address { + street: string; + city: string; + zipCode: number | null; +} +export interface Person { + name: string; + age: number; + address: Address; + email: string | null; +} +export interface Session { + id: number; + owner: Greeter; +} +export interface ConfigStruct { +} +/// Represents a Swift heap object like a class instance or an actor instance. +export interface SwiftHeapObject { + /// Release the heap object. + /// + /// Note: Calling this method will release the heap object and it will no longer be accessible. + release(): void; +} +export interface Greeter extends SwiftHeapObject { + greet(): string; + name: string; +} +export type Exports = { + Greeter: { + new(name: string): Greeter; + } + roundtrip(session: Person): Person; + DataPoint: { + init(x: number, y: number, label: string, optCount: number | null, optFlag: boolean | null): DataPoint; + } + ConfigStruct: { + readonly maxRetries: number; + defaultConfig: string; + timeout: number; + readonly computedSetting: string; + update(timeout: number): number; + } +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.js new file mode 100644 index 000000000..fe359bcbe --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.js @@ -0,0 +1,499 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + let tmpRetOptionalBool; + let tmpRetOptionalInt; + let tmpRetOptionalFloat; + let tmpRetOptionalDouble; + let tmpRetOptionalHeapObject; + let tmpRetTag; + let tmpRetStrings = []; + let tmpRetInts = []; + let tmpRetF32s = []; + let tmpRetF64s = []; + let tmpParamInts = []; + let tmpParamF32s = []; + let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; + const structHelpers = {}; + + let _exports = null; + let bjs = null; + const __bjs_createDataPointHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + tmpParamF64s.push(value.x); + tmpParamF64s.push(value.y); + const bytes = textEncoder.encode(value.label); + const id = swift.memory.retain(bytes); + tmpParamInts.push(bytes.length); + tmpParamInts.push(id); + const isSome = value.optCount != null; + if (isSome) { + tmpParamInts.push(value.optCount | 0); + } else { + tmpParamInts.push(0); + } + tmpParamInts.push(isSome ? 1 : 0); + const isSome1 = value.optFlag != null; + if (isSome1) { + tmpParamInts.push(value.optFlag ? 1 : 0); + } else { + tmpParamInts.push(0); + } + tmpParamInts.push(isSome1 ? 1 : 0); + const cleanup = () => { + swift.memory.release(id); + }; + return { cleanup }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const isSome = tmpRetInts.pop(); + let optional; + if (isSome) { + const bool = tmpRetInts.pop() !== 0; + optional = bool; + } else { + optional = null; + } + const isSome1 = tmpRetInts.pop(); + let optional1; + if (isSome1) { + const int = tmpRetInts.pop(); + optional1 = int; + } else { + optional1 = null; + } + const string = tmpRetStrings.pop(); + const f64 = tmpRetF64s.pop(); + const f641 = tmpRetF64s.pop(); + return { x: f641, y: f64, label: string, optCount: optional1, optFlag: optional }; + } + }); + }; + const __bjs_createAddressHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + const bytes = textEncoder.encode(value.street); + const id = swift.memory.retain(bytes); + tmpParamInts.push(bytes.length); + tmpParamInts.push(id); + const bytes1 = textEncoder.encode(value.city); + const id1 = swift.memory.retain(bytes1); + tmpParamInts.push(bytes1.length); + tmpParamInts.push(id1); + const isSome = value.zipCode != null; + if (isSome) { + tmpParamInts.push(value.zipCode | 0); + } else { + tmpParamInts.push(0); + } + tmpParamInts.push(isSome ? 1 : 0); + const cleanup = () => { + swift.memory.release(id); + swift.memory.release(id1); + }; + return { cleanup }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const isSome = tmpRetInts.pop(); + let optional; + if (isSome) { + const int = tmpRetInts.pop(); + optional = int; + } else { + optional = null; + } + const string = tmpRetStrings.pop(); + const string1 = tmpRetStrings.pop(); + return { street: string1, city: string, zipCode: optional }; + } + }); + }; + const __bjs_createPersonHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + const bytes = textEncoder.encode(value.name); + const id = swift.memory.retain(bytes); + tmpParamInts.push(bytes.length); + tmpParamInts.push(id); + tmpParamInts.push((value.age | 0)); + const structResult = structHelpers.Address.lower(value.address); + const isSome = value.email != null; + let id1; + if (isSome) { + const bytes1 = textEncoder.encode(value.email); + id1 = swift.memory.retain(bytes1); + tmpParamInts.push(bytes1.length); + tmpParamInts.push(id1); + } else { + tmpParamInts.push(0); + tmpParamInts.push(0); + } + tmpParamInts.push(isSome ? 1 : 0); + const cleanup = () => { + swift.memory.release(id); + if (structResult.cleanup) { structResult.cleanup(); } + if(id1 !== undefined) { swift.memory.release(id1); } + }; + return { cleanup }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const isSome = tmpRetInts.pop(); + let optional; + if (isSome) { + const string = tmpRetStrings.pop(); + optional = string; + } else { + optional = null; + } + const struct = structHelpers.Address.raise(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + const int = tmpRetInts.pop(); + const string1 = tmpRetStrings.pop(); + return { name: string1, age: int, address: struct, email: optional }; + } + }); + }; + const __bjs_createSessionHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + tmpParamInts.push((value.id | 0)); + tmpParamPointers.push(value.owner.pointer); + return { cleanup: undefined }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const ptr = tmpRetPointers.pop(); + const value = _exports['Greeter'].__construct(ptr); + const int = tmpRetInts.pop(); + return { id: int, owner: value }; + } + }); + }; + const __bjs_createConfigStructHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + return { cleanup: undefined }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + return { }; + } + }); + }; + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + bjs["swift_js_push_tag"] = function(tag) { + tmpRetTag = tag; + } + bjs["swift_js_push_int"] = function(v) { + tmpRetInts.push(v | 0); + } + bjs["swift_js_push_f32"] = function(v) { + tmpRetF32s.push(Math.fround(v)); + } + bjs["swift_js_push_f64"] = function(v) { + tmpRetF64s.push(v); + } + bjs["swift_js_push_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + const value = textDecoder.decode(bytes); + tmpRetStrings.push(value); + } + bjs["swift_js_pop_param_int32"] = function() { + return tmpParamInts.pop(); + } + bjs["swift_js_pop_param_f32"] = function() { + return tmpParamF32s.pop(); + } + bjs["swift_js_pop_param_f64"] = function() { + return tmpParamF64s.pop(); + } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } + bjs["swift_js_return_optional_bool"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalBool = null; + } else { + tmpRetOptionalBool = value !== 0; + } + } + bjs["swift_js_return_optional_int"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalInt = null; + } else { + tmpRetOptionalInt = value | 0; + } + } + bjs["swift_js_return_optional_float"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalFloat = null; + } else { + tmpRetOptionalFloat = Math.fround(value); + } + } + bjs["swift_js_return_optional_double"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalDouble = null; + } else { + tmpRetOptionalDouble = value; + } + } + bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + if (isSome === 0) { + tmpRetString = null; + } else { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + } + bjs["swift_js_return_optional_object"] = function(isSome, objectId) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = swift.memory.getObject(objectId); + } + } + bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) { + if (isSome === 0) { + tmpRetOptionalHeapObject = null; + } else { + tmpRetOptionalHeapObject = pointer; + } + } + bjs["swift_js_get_optional_int_presence"] = function() { + return tmpRetOptionalInt != null ? 1 : 0; + } + bjs["swift_js_get_optional_int_value"] = function() { + const value = tmpRetOptionalInt; + tmpRetOptionalInt = undefined; + return value; + } + bjs["swift_js_get_optional_string"] = function() { + const str = tmpRetString; + tmpRetString = undefined; + if (str == null) { + return -1; + } else { + const bytes = textEncoder.encode(str); + tmpRetBytes = bytes; + return bytes.length; + } + } + bjs["swift_js_get_optional_float_presence"] = function() { + return tmpRetOptionalFloat != null ? 1 : 0; + } + bjs["swift_js_get_optional_float_value"] = function() { + const value = tmpRetOptionalFloat; + tmpRetOptionalFloat = undefined; + return value; + } + bjs["swift_js_get_optional_double_presence"] = function() { + return tmpRetOptionalDouble != null ? 1 : 0; + } + bjs["swift_js_get_optional_double_value"] = function() { + const value = tmpRetOptionalDouble; + tmpRetOptionalDouble = undefined; + return value; + } + bjs["swift_js_get_optional_heap_object_pointer"] = function() { + const pointer = tmpRetOptionalHeapObject; + tmpRetOptionalHeapObject = undefined; + return pointer || 0; + } + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_Greeter_wrap"] = function(pointer) { + const obj = Greeter.__construct(pointer); + return swift.memory.retain(obj); + }; + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { + deinit(pointer); + }); + obj.registry.register(this, obj.pointer); + return obj; + } + + release() { + this.registry.unregister(this); + this.deinit(this.pointer); + } + } + class Greeter extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype); + } + + constructor(name) { + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length); + swift.memory.release(nameId); + return Greeter.__construct(ret); + } + greet() { + instance.exports.bjs_Greeter_greet(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + get name() { + instance.exports.bjs_Greeter_name_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set name(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_Greeter_name_set(this.pointer, valueId, valueBytes.length); + swift.memory.release(valueId); + } + } + const DataPointHelpers = __bjs_createDataPointHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.DataPoint = DataPointHelpers; + + const AddressHelpers = __bjs_createAddressHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.Address = AddressHelpers; + + const PersonHelpers = __bjs_createPersonHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.Person = PersonHelpers; + + const SessionHelpers = __bjs_createSessionHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.Session = SessionHelpers; + + const ConfigStructHelpers = __bjs_createConfigStructHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.ConfigStruct = ConfigStructHelpers; + + const exports = { + Greeter, + roundtrip: function bjs_roundtrip(session) { + const { cleanup: cleanup } = structHelpers.Person.lower(session); + instance.exports.bjs_roundtrip(); + const structValue = structHelpers.Person.raise(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + if (cleanup) { cleanup(); } + return structValue; + }, + DataPoint: { + init: function(x, y, label, optCount, optFlag) { + const labelBytes = textEncoder.encode(label); + const labelId = swift.memory.retain(labelBytes); + const isSome = optCount != null; + const isSome1 = optFlag != null; + instance.exports.bjs_DataPoint_init(x, y, labelId, labelBytes.length, +isSome, isSome ? optCount : 0, +isSome1, isSome1 ? optFlag : 0); + const structValue = structHelpers.DataPoint.raise(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + swift.memory.release(labelId); + return structValue; + }, + }, + ConfigStruct: { + get maxRetries() { + const ret = instance.exports.bjs_ConfigStruct_static_maxRetries_get(); + return ret; + }, + get defaultConfig() { + instance.exports.bjs_ConfigStruct_static_defaultConfig_get(); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, + set defaultConfig(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_ConfigStruct_static_defaultConfig_set(valueId, valueBytes.length); + swift.memory.release(valueId); + }, + get timeout() { + const ret = instance.exports.bjs_ConfigStruct_static_timeout_get(); + return ret; + }, + set timeout(value) { + instance.exports.bjs_ConfigStruct_static_timeout_set(value); + }, + get computedSetting() { + instance.exports.bjs_ConfigStruct_static_computedSetting_get(); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, + update: function(timeout) { + const ret = instance.exports.bjs_ConfigStruct_static_update(timeout); + return ret; + }, + }, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js index 4c3cbc213..b0684f153 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js index 059c93157..928ba5950 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js index 320609e1c..c2f477245 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index 783884efb..71511ca3c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js index b6d7a4ea3..b39042bda 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js index 627dd140c..8f8328054 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json index 2e295991b..4bb845587 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json @@ -178,5 +178,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.ReverseOrder.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.ReverseOrder.json new file mode 100644 index 000000000..faff44843 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.ReverseOrder.json @@ -0,0 +1,150 @@ +{ + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_FunctionA_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_FunctionA_processB", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "processB", + "parameters" : [ + { + "label" : "b", + "name" : "b", + "type" : { + "swiftHeapObject" : { + "_0" : "FunctionB" + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_FunctionA_createB", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "createB", + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "FunctionB" + } + } + } + ], + "name" : "FunctionA", + "properties" : [ + + ], + "swiftCallName" : "FunctionA" + }, + { + "constructor" : { + "abiName" : "bjs_FunctionB_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "string" : { + + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "FunctionB", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "value", + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "FunctionB" + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + { + "abiName" : "bjs_standaloneFunction", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "standaloneFunction", + "parameters" : [ + { + "label" : "b", + "name" : "b", + "type" : { + "swiftHeapObject" : { + "_0" : "FunctionB" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "FunctionB" + } + } + } + ], + "moduleName" : "TestModule", + "protocols" : [ + + ], + "structs" : [ + + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.ReverseOrder.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.ReverseOrder.swift new file mode 100644 index 000000000..e50c67861 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.ReverseOrder.swift @@ -0,0 +1,125 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_standaloneFunction") +@_cdecl("bjs_standaloneFunction") +public func _bjs_standaloneFunction(b: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = standaloneFunction(b: FunctionB.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionA_init") +@_cdecl("bjs_FunctionA_init") +public func _bjs_FunctionA_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = FunctionA() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionA_processB") +@_cdecl("bjs_FunctionA_processB") +public func _bjs_FunctionA_processB(_self: UnsafeMutableRawPointer, b: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = FunctionA.bridgeJSLiftParameter(_self).processB(b: FunctionB.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionA_createB") +@_cdecl("bjs_FunctionA_createB") +public func _bjs_FunctionA_createB(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = FunctionA.bridgeJSLiftParameter(_self).createB(value: String.bridgeJSLiftParameter(valueBytes, valueLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionA_deinit") +@_cdecl("bjs_FunctionA_deinit") +public func _bjs_FunctionA_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension FunctionA: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_FunctionA_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_FunctionA_wrap") +fileprivate func _bjs_FunctionA_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_FunctionA_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +@_expose(wasm, "bjs_FunctionB_init") +@_cdecl("bjs_FunctionB_init") +public func _bjs_FunctionB_init(valueBytes: Int32, valueLength: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = FunctionB(value: String.bridgeJSLiftParameter(valueBytes, valueLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionB_value_get") +@_cdecl("bjs_FunctionB_value_get") +public func _bjs_FunctionB_value_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = FunctionB.bridgeJSLiftParameter(_self).value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionB_value_set") +@_cdecl("bjs_FunctionB_value_set") +public func _bjs_FunctionB_value_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + FunctionB.bridgeJSLiftParameter(_self).value = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionB_deinit") +@_cdecl("bjs_FunctionB_deinit") +public func _bjs_FunctionB_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension FunctionB: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_FunctionB_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_FunctionB_wrap") +fileprivate func _bjs_FunctionB_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_FunctionB_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.json new file mode 100644 index 000000000..a0caf3bc9 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.json @@ -0,0 +1,150 @@ +{ + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_FunctionB_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "string" : { + + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "FunctionB", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "value", + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "FunctionB" + }, + { + "constructor" : { + "abiName" : "bjs_FunctionA_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_FunctionA_processB", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "processB", + "parameters" : [ + { + "label" : "b", + "name" : "b", + "type" : { + "swiftHeapObject" : { + "_0" : "FunctionB" + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_FunctionA_createB", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "createB", + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "FunctionB" + } + } + } + ], + "name" : "FunctionA", + "properties" : [ + + ], + "swiftCallName" : "FunctionA" + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + { + "abiName" : "bjs_standaloneFunction", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "standaloneFunction", + "parameters" : [ + { + "label" : "b", + "name" : "b", + "type" : { + "swiftHeapObject" : { + "_0" : "FunctionB" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "FunctionB" + } + } + } + ], + "moduleName" : "TestModule", + "protocols" : [ + + ], + "structs" : [ + + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.swift new file mode 100644 index 000000000..e8077c5c4 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileFunctionTypes.swift @@ -0,0 +1,125 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_standaloneFunction") +@_cdecl("bjs_standaloneFunction") +public func _bjs_standaloneFunction(b: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = standaloneFunction(b: FunctionB.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionB_init") +@_cdecl("bjs_FunctionB_init") +public func _bjs_FunctionB_init(valueBytes: Int32, valueLength: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = FunctionB(value: String.bridgeJSLiftParameter(valueBytes, valueLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionB_value_get") +@_cdecl("bjs_FunctionB_value_get") +public func _bjs_FunctionB_value_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = FunctionB.bridgeJSLiftParameter(_self).value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionB_value_set") +@_cdecl("bjs_FunctionB_value_set") +public func _bjs_FunctionB_value_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + FunctionB.bridgeJSLiftParameter(_self).value = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionB_deinit") +@_cdecl("bjs_FunctionB_deinit") +public func _bjs_FunctionB_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension FunctionB: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_FunctionB_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_FunctionB_wrap") +fileprivate func _bjs_FunctionB_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_FunctionB_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +@_expose(wasm, "bjs_FunctionA_init") +@_cdecl("bjs_FunctionA_init") +public func _bjs_FunctionA_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = FunctionA() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionA_processB") +@_cdecl("bjs_FunctionA_processB") +public func _bjs_FunctionA_processB(_self: UnsafeMutableRawPointer, b: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = FunctionA.bridgeJSLiftParameter(_self).processB(b: FunctionB.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionA_createB") +@_cdecl("bjs_FunctionA_createB") +public func _bjs_FunctionA_createB(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = FunctionA.bridgeJSLiftParameter(_self).createB(value: String.bridgeJSLiftParameter(valueBytes, valueLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_FunctionA_deinit") +@_cdecl("bjs_FunctionA_deinit") +public func _bjs_FunctionA_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension FunctionA: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_FunctionA_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_FunctionA_wrap") +fileprivate func _bjs_FunctionA_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_FunctionA_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.ReverseOrder.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.ReverseOrder.json new file mode 100644 index 000000000..aa4242cd7 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.ReverseOrder.json @@ -0,0 +1,62 @@ +{ + "classes" : [ + { + "methods" : [ + + ], + "name" : "ClassA", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "linkedB", + "type" : { + "optional" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "ClassB" + } + } + } + } + } + ], + "swiftCallName" : "ClassA" + }, + { + "constructor" : { + "abiName" : "bjs_ClassB_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + + ], + "name" : "ClassB", + "properties" : [ + + ], + "swiftCallName" : "ClassB" + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + + ], + "moduleName" : "TestModule", + "protocols" : [ + + ], + "structs" : [ + + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.ReverseOrder.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.ReverseOrder.swift new file mode 100644 index 000000000..a270c0e9d --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.ReverseOrder.swift @@ -0,0 +1,81 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_ClassA_linkedB_get") +@_cdecl("bjs_ClassA_linkedB_get") +public func _bjs_ClassA_linkedB_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = ClassA.bridgeJSLiftParameter(_self).linkedB + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassA_linkedB_set") +@_cdecl("bjs_ClassA_linkedB_set") +public func _bjs_ClassA_linkedB_set(_self: UnsafeMutableRawPointer, valueIsSome: Int32, valueValue: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + ClassA.bridgeJSLiftParameter(_self).linkedB = Optional.bridgeJSLiftParameter(valueIsSome, valueValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassA_deinit") +@_cdecl("bjs_ClassA_deinit") +public func _bjs_ClassA_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension ClassA: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ClassA_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_ClassA_wrap") +fileprivate func _bjs_ClassA_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ClassA_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +@_expose(wasm, "bjs_ClassB_init") +@_cdecl("bjs_ClassB_init") +public func _bjs_ClassB_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassB() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassB_deinit") +@_cdecl("bjs_ClassB_deinit") +public func _bjs_ClassB_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension ClassB: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ClassB_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_ClassB_wrap") +fileprivate func _bjs_ClassB_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ClassB_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.json new file mode 100644 index 000000000..e6acc7f35 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.json @@ -0,0 +1,62 @@ +{ + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_ClassB_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + + ], + "name" : "ClassB", + "properties" : [ + + ], + "swiftCallName" : "ClassB" + }, + { + "methods" : [ + + ], + "name" : "ClassA", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "linkedB", + "type" : { + "optional" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "ClassB" + } + } + } + } + } + ], + "swiftCallName" : "ClassA" + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + + ], + "moduleName" : "TestModule", + "protocols" : [ + + ], + "structs" : [ + + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.swift new file mode 100644 index 000000000..ed05f7c24 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/CrossFileTypeResolution.swift @@ -0,0 +1,81 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_ClassB_init") +@_cdecl("bjs_ClassB_init") +public func _bjs_ClassB_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ClassB() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassB_deinit") +@_cdecl("bjs_ClassB_deinit") +public func _bjs_ClassB_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension ClassB: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ClassB_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_ClassB_wrap") +fileprivate func _bjs_ClassB_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ClassB_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +@_expose(wasm, "bjs_ClassA_linkedB_get") +@_cdecl("bjs_ClassA_linkedB_get") +public func _bjs_ClassA_linkedB_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = ClassA.bridgeJSLiftParameter(_self).linkedB + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassA_linkedB_set") +@_cdecl("bjs_ClassA_linkedB_set") +public func _bjs_ClassA_linkedB_set(_self: UnsafeMutableRawPointer, valueIsSome: Int32, valueValue: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + ClassA.bridgeJSLiftParameter(_self).linkedB = Optional.bridgeJSLiftParameter(valueIsSome, valueValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ClassA_deinit") +@_cdecl("bjs_ClassA_deinit") +public func _bjs_ClassA_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension ClassA: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ClassA_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_ClassA_wrap") +fileprivate func _bjs_ClassA_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ClassA_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.json index ccb4cb289..7e614a324 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.json @@ -146,23 +146,7 @@ ] }, "methods" : [ - { - "abiName" : "bjs_ConstructorDefaults_describe", - "effects" : { - "isAsync" : false, - "isStatic" : false, - "isThrows" : false - }, - "name" : "describe", - "parameters" : [ - ], - "returnType" : { - "string" : { - - } - } - } ], "name" : "ConstructorDefaults", "properties" : [ @@ -642,10 +626,312 @@ "_0" : "EmptyGreeter" } } + }, + { + "abiName" : "bjs_testOptionalStructDefault", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "testOptionalStructDefault", + "parameters" : [ + { + "defaultValue" : { + "null" : { + + } + }, + "label" : "point", + "name" : "point", + "type" : { + "optional" : { + "_0" : { + "swiftStruct" : { + "_0" : "Config" + } + } + } + } + } + ], + "returnType" : { + "optional" : { + "_0" : { + "swiftStruct" : { + "_0" : "Config" + } + } + } + } + }, + { + "abiName" : "bjs_testOptionalStructWithValueDefault", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "testOptionalStructWithValueDefault", + "parameters" : [ + { + "defaultValue" : { + "structLiteral" : { + "_0" : "Config", + "_1" : [ + { + "name" : "name", + "value" : { + "string" : { + "_0" : "default" + } + } + }, + { + "name" : "value", + "value" : { + "int" : { + "_0" : 42 + } + } + }, + { + "name" : "enabled", + "value" : { + "bool" : { + "_0" : true + } + } + } + ] + } + }, + "label" : "point", + "name" : "point", + "type" : { + "optional" : { + "_0" : { + "swiftStruct" : { + "_0" : "Config" + } + } + } + } + } + ], + "returnType" : { + "optional" : { + "_0" : { + "swiftStruct" : { + "_0" : "Config" + } + } + } + } } ], "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + { + "methods" : [ + + ], + "name" : "Config", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "value", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "enabled", + "type" : { + "bool" : { + + } + } + } + ], + "swiftCallName" : "Config" + }, + { + "constructor" : { + "abiName" : "bjs_MathOperations_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "defaultValue" : { + "double" : { + "_0" : 0 + } + }, + "label" : "baseValue", + "name" : "baseValue", + "type" : { + "double" : { + + } + } + } + ] + }, + "methods" : [ + { + "abiName" : "bjs_MathOperations_add", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "add", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "double" : { + + } + } + }, + { + "defaultValue" : { + "double" : { + "_0" : 10 + } + }, + "label" : "b", + "name" : "b", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_MathOperations_multiply", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "multiply", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "double" : { + + } + } + }, + { + "label" : "b", + "name" : "b", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_MathOperations_static_subtract", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "subtract", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "double" : { + + } + } + }, + { + "defaultValue" : { + "double" : { + "_0" : 5 + } + }, + "label" : "b", + "name" : "b", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + }, + "staticContext" : { + "structName" : { + "_0" : "MathOperations" + } + } + } + ], + "name" : "MathOperations", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "baseValue", + "type" : { + "double" : { + + } + } + } + ], + "swiftCallName" : "MathOperations" + } ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.swift index 525782c4f..03d037e98 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.swift @@ -45,6 +45,79 @@ extension Status: _BridgedSwiftCaseEnum { } } +extension Config: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Config { + let enabled = Bool.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let value = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let name = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Config(name: name, value: value, enabled: enabled) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_name = self.name + __bjs_name.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + _swift_js_push_int(Int32(self.value)) + _swift_js_push_int(self.enabled ? 1 : 0) + } +} + +extension MathOperations: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> MathOperations { + let baseValue = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + return MathOperations(baseValue: baseValue) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_f64(self.baseValue) + } +} + +@_expose(wasm, "bjs_MathOperations_init") +@_cdecl("bjs_MathOperations_init") +public func _bjs_MathOperations_init(baseValue: Float64) -> Void { + #if arch(wasm32) + let ret = MathOperations(baseValue: Double.bridgeJSLiftParameter(baseValue)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MathOperations_add") +@_cdecl("bjs_MathOperations_add") +public func _bjs_MathOperations_add(a: Float64, b: Float64) -> Float64 { + #if arch(wasm32) + let ret = MathOperations.bridgeJSLiftParameter().add(a: Double.bridgeJSLiftParameter(a), b: Double.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MathOperations_multiply") +@_cdecl("bjs_MathOperations_multiply") +public func _bjs_MathOperations_multiply(a: Float64, b: Float64) -> Float64 { + #if arch(wasm32) + let ret = MathOperations.bridgeJSLiftParameter().multiply(a: Double.bridgeJSLiftParameter(a), b: Double.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MathOperations_static_subtract") +@_cdecl("bjs_MathOperations_static_subtract") +public func _bjs_MathOperations_static_subtract(a: Float64, b: Float64) -> Float64 { + #if arch(wasm32) + let ret = MathOperations.subtract(a: Double.bridgeJSLiftParameter(a), b: Double.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_testStringDefault") @_cdecl("bjs_testStringDefault") public func _bjs_testStringDefault(messageBytes: Int32, messageLength: Int32) -> Void { @@ -166,6 +239,28 @@ public func _bjs_testEmptyInit(greeter: UnsafeMutableRawPointer) -> UnsafeMutabl #endif } +@_expose(wasm, "bjs_testOptionalStructDefault") +@_cdecl("bjs_testOptionalStructDefault") +public func _bjs_testOptionalStructDefault(point: Int32) -> Void { + #if arch(wasm32) + let ret = testOptionalStructDefault(point: Optional.bridgeJSLiftParameter(point)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_testOptionalStructWithValueDefault") +@_cdecl("bjs_testOptionalStructWithValueDefault") +public func _bjs_testOptionalStructWithValueDefault(point: Int32) -> Void { + #if arch(wasm32) + let ret = testOptionalStructWithValueDefault(point: Optional.bridgeJSLiftParameter(point)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_DefaultGreeter_init") @_cdecl("bjs_DefaultGreeter_init") public func _bjs_DefaultGreeter_init(nameBytes: Int32, nameLength: Int32) -> UnsafeMutableRawPointer { @@ -262,17 +357,6 @@ public func _bjs_ConstructorDefaults_init(nameBytes: Int32, nameLength: Int32, c #endif } -@_expose(wasm, "bjs_ConstructorDefaults_describe") -@_cdecl("bjs_ConstructorDefaults_describe") -public func _bjs_ConstructorDefaults_describe(_self: UnsafeMutableRawPointer) -> Void { - #if arch(wasm32) - let ret = ConstructorDefaults.bridgeJSLiftParameter(_self).describe() - return ret.bridgeJSLowerReturn() - #else - fatalError("Only available on WebAssembly") - #endif -} - @_expose(wasm, "bjs_ConstructorDefaults_name_get") @_cdecl("bjs_ConstructorDefaults_name_get") public func _bjs_ConstructorDefaults_name_get(_self: UnsafeMutableRawPointer) -> Void { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json index 52279ce0d..f7e57aabd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json @@ -787,10 +787,59 @@ } } } + }, + { + "abiName" : "bjs_compareAPIResults", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "compareAPIResults", + "parameters" : [ + { + "label" : "result1", + "name" : "result1", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIOptionalResult" + } + } + } + } + }, + { + "label" : "result2", + "name" : "result2", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIOptionalResult" + } + } + } + } + } + ], + "returnType" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIOptionalResult" + } + } + } + } } ], "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.swift index 0c70fe5cc..bcc95ef4c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.swift @@ -606,4 +606,17 @@ public func _bjs_roundTripOptionalAPIOptionalResult(resultIsSome: Int32, resultC #else fatalError("Only available on WebAssembly") #endif +} + +@_expose(wasm, "bjs_compareAPIResults") +@_cdecl("bjs_compareAPIResults") +public func _bjs_compareAPIResults(result1IsSome: Int32, result1CaseId: Int32, result2IsSome: Int32, result2CaseId: Int32) -> Void { + #if arch(wasm32) + let _tmp_result2 = Optional.bridgeJSLiftParameter(result2IsSome, result2CaseId) + let _tmp_result1 = Optional.bridgeJSLiftParameter(result1IsSome, result1CaseId) + let ret = compareAPIResults(result1: _tmp_result1, result2: _tmp_result2) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json index 9effaaec7..1f4d55046 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json @@ -314,5 +314,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.Global.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.Global.json index 8aeb6c78c..98ded1fe8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.Global.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.Global.json @@ -409,5 +409,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json index 94d1bb061..24afb4eb2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json @@ -409,5 +409,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json index 4bf1644c4..b7ea9cd17 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json @@ -1492,5 +1492,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedGlobal.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedGlobal.json index 21e78b120..995c3efcf 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedGlobal.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedGlobal.json @@ -70,5 +70,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedPrivate.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedPrivate.json index d86712af6..c986a903f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedPrivate.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedPrivate.json @@ -70,5 +70,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.Global.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.Global.json index bde86c44f..a0b1efea2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.Global.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.Global.json @@ -176,5 +176,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json index 910fcfc2c..2420d592e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json @@ -176,5 +176,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Optionals.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Optionals.json index eb92b21d5..64aaa295e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Optionals.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Optionals.json @@ -693,5 +693,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json index ccd504f4d..d9edf11c3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json @@ -63,5 +63,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json index 6210c8663..79ce75dac 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json @@ -79,5 +79,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json index 14f0cc0fd..aa5fcc86b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json @@ -354,5 +354,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Protocol.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Protocol.json index 487533b38..c3107394e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Protocol.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Protocol.json @@ -798,5 +798,8 @@ } ] } + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.Global.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.Global.json index 8a2923a02..4e8d80d59 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.Global.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.Global.json @@ -330,5 +330,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.json index 13f707bda..ab399f102 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.json @@ -330,5 +330,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.Global.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.Global.json index d7af99185..472406ba9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.Global.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.Global.json @@ -333,5 +333,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.json index fb3a12b6b..6ef937981 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.json @@ -333,5 +333,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json index bec03d8fc..b70151233 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json @@ -61,5 +61,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json index 86221037d..39ec8b7a3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json @@ -28,5 +28,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json index 9f1f934ec..df4975e11 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json @@ -136,5 +136,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClosure.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClosure.json index 2a8a64040..20015ba25 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClosure.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClosure.json @@ -1069,5 +1069,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.json new file mode 100644 index 000000000..498231bf0 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.json @@ -0,0 +1,448 @@ +{ + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_Greeter_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "name", + "name" : "name", + "type" : { + "string" : { + + } + } + } + ] + }, + "methods" : [ + { + "abiName" : "bjs_Greeter_greet", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "greet", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "Greeter", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "Greeter" + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + { + "abiName" : "bjs_roundtrip", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtrip", + "parameters" : [ + { + "label" : "_", + "name" : "session", + "type" : { + "swiftStruct" : { + "_0" : "Person" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Person" + } + } + } + ], + "moduleName" : "TestModule", + "protocols" : [ + + ], + "structs" : [ + { + "constructor" : { + "abiName" : "bjs_DataPoint_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "x", + "name" : "x", + "type" : { + "double" : { + + } + } + }, + { + "label" : "y", + "name" : "y", + "type" : { + "double" : { + + } + } + }, + { + "label" : "label", + "name" : "label", + "type" : { + "string" : { + + } + } + }, + { + "label" : "optCount", + "name" : "optCount", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "label" : "optFlag", + "name" : "optFlag", + "type" : { + "optional" : { + "_0" : { + "bool" : { + + } + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "DataPoint", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "x", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "y", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "label", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optCount", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optFlag", + "type" : { + "optional" : { + "_0" : { + "bool" : { + + } + } + } + } + } + ], + "swiftCallName" : "DataPoint" + }, + { + "methods" : [ + + ], + "name" : "Address", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "street", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "city", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "zipCode", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + } + ], + "swiftCallName" : "Address" + }, + { + "methods" : [ + + ], + "name" : "Person", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "age", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "address", + "type" : { + "swiftStruct" : { + "_0" : "Address" + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "email", + "type" : { + "optional" : { + "_0" : { + "string" : { + + } + } + } + } + } + ], + "swiftCallName" : "Person" + }, + { + "methods" : [ + + ], + "name" : "Session", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "id", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "owner", + "type" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + } + ], + "swiftCallName" : "Session" + }, + { + "methods" : [ + { + "abiName" : "bjs_ConfigStruct_static_update", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "update", + "parameters" : [ + { + "label" : "_", + "name" : "timeout", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + }, + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + } + } + ], + "name" : "ConfigStruct", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : true, + "name" : "maxRetries", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : true, + "name" : "defaultConfig", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : true, + "name" : "timeout", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : true, + "name" : "computedSetting", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "ConfigStruct" + } + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.swift new file mode 100644 index 000000000..57635c626 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.swift @@ -0,0 +1,273 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +extension DataPoint: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> DataPoint { + let optFlag = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let optCount = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let label = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let y = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + let x = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + return DataPoint(x: x, y: y, label: label, optCount: optCount, optFlag: optFlag) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_f64(self.x) + _swift_js_push_f64(self.y) + var __bjs_label = self.label + __bjs_label.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + let __bjs_isSome_optCount = self.optCount != nil + if let __bjs_unwrapped_optCount = self.optCount { + _swift_js_push_int(Int32(__bjs_unwrapped_optCount)) + } + _swift_js_push_int(__bjs_isSome_optCount ? 1 : 0) + let __bjs_isSome_optFlag = self.optFlag != nil + if let __bjs_unwrapped_optFlag = self.optFlag { + _swift_js_push_int(__bjs_unwrapped_optFlag ? 1 : 0) + } + _swift_js_push_int(__bjs_isSome_optFlag ? 1 : 0) + } +} + +@_expose(wasm, "bjs_DataPoint_init") +@_cdecl("bjs_DataPoint_init") +public func _bjs_DataPoint_init(x: Float64, y: Float64, labelBytes: Int32, labelLength: Int32, optCountIsSome: Int32, optCountValue: Int32, optFlagIsSome: Int32, optFlagValue: Int32) -> Void { + #if arch(wasm32) + let ret = DataPoint(x: Double.bridgeJSLiftParameter(x), y: Double.bridgeJSLiftParameter(y), label: String.bridgeJSLiftParameter(labelBytes, labelLength), optCount: Optional.bridgeJSLiftParameter(optCountIsSome, optCountValue), optFlag: Optional.bridgeJSLiftParameter(optFlagIsSome, optFlagValue)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension Address: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Address { + let zipCode = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let city = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let street = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Address(street: street, city: city, zipCode: zipCode) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_street = self.street + __bjs_street.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + var __bjs_city = self.city + __bjs_city.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + let __bjs_isSome_zipCode = self.zipCode != nil + if let __bjs_unwrapped_zipCode = self.zipCode { + _swift_js_push_int(Int32(__bjs_unwrapped_zipCode)) + } + _swift_js_push_int(__bjs_isSome_zipCode ? 1 : 0) + } +} + +extension Person: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Person { + let email = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let address = Address.bridgeJSLiftParameter() + let age = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let name = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Person(name: name, age: age, address: address, email: email) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_name = self.name + __bjs_name.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + _swift_js_push_int(Int32(self.age)) + self.address.bridgeJSLowerReturn() + let __bjs_isSome_email = self.email != nil + if let __bjs_unwrapped_email = self.email { + var __bjs_str_email = __bjs_unwrapped_email + __bjs_str_email.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + } + _swift_js_push_int(__bjs_isSome_email ? 1 : 0) + } +} + +extension Session: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Session { + let owner = Greeter.bridgeJSLiftParameter(_swift_js_pop_param_pointer()) + let id = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + return Session(id: id, owner: owner) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_int(Int32(self.id)) + _swift_js_push_pointer(self.owner.bridgeJSLowerReturn()) + } +} + +extension ConfigStruct: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> ConfigStruct { + return ConfigStruct() + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + + } +} + +@_expose(wasm, "bjs_ConfigStruct_static_maxRetries_get") +@_cdecl("bjs_ConfigStruct_static_maxRetries_get") +public func _bjs_ConfigStruct_static_maxRetries_get() -> Int32 { + #if arch(wasm32) + let ret = ConfigStruct.maxRetries + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_defaultConfig_get") +@_cdecl("bjs_ConfigStruct_static_defaultConfig_get") +public func _bjs_ConfigStruct_static_defaultConfig_get() -> Void { + #if arch(wasm32) + let ret = ConfigStruct.defaultConfig + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_defaultConfig_set") +@_cdecl("bjs_ConfigStruct_static_defaultConfig_set") +public func _bjs_ConfigStruct_static_defaultConfig_set(valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + ConfigStruct.defaultConfig = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_timeout_get") +@_cdecl("bjs_ConfigStruct_static_timeout_get") +public func _bjs_ConfigStruct_static_timeout_get() -> Float64 { + #if arch(wasm32) + let ret = ConfigStruct.timeout + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_timeout_set") +@_cdecl("bjs_ConfigStruct_static_timeout_set") +public func _bjs_ConfigStruct_static_timeout_set(value: Float64) -> Void { + #if arch(wasm32) + ConfigStruct.timeout = Double.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_computedSetting_get") +@_cdecl("bjs_ConfigStruct_static_computedSetting_get") +public func _bjs_ConfigStruct_static_computedSetting_get() -> Void { + #if arch(wasm32) + let ret = ConfigStruct.computedSetting + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_update") +@_cdecl("bjs_ConfigStruct_static_update") +public func _bjs_ConfigStruct_static_update(timeout: Float64) -> Float64 { + #if arch(wasm32) + let ret = ConfigStruct.update(_: Double.bridgeJSLiftParameter(timeout)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundtrip") +@_cdecl("bjs_roundtrip") +public func _bjs_roundtrip() -> Void { + #if arch(wasm32) + let ret = roundtrip(_: Person.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_init") +@_cdecl("bjs_Greeter_init") +public func _bjs_Greeter_init(nameBytes: Int32, nameLength: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_greet") +@_cdecl("bjs_Greeter_greet") +public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Greeter.bridgeJSLiftParameter(_self).greet() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_name_get") +@_cdecl("bjs_Greeter_name_get") +public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Greeter.bridgeJSLiftParameter(_self).name + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_name_set") +@_cdecl("bjs_Greeter_name_set") +public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + Greeter.bridgeJSLiftParameter(_self).name = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_deinit") +@_cdecl("bjs_Greeter_deinit") +public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Greeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_Greeter_wrap") +fileprivate func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json index 9b2e61044..ef42dc6cf 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json @@ -28,5 +28,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json index 508107ee6..c80650aff 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json @@ -28,5 +28,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/PackageToJS/Templates/platforms/browser.d.ts b/Plugins/PackageToJS/Templates/platforms/browser.d.ts index babe3f487..dcfb5f617 100644 --- a/Plugins/PackageToJS/Templates/platforms/browser.d.ts +++ b/Plugins/PackageToJS/Templates/platforms/browser.d.ts @@ -11,7 +11,7 @@ export function defaultBrowserSetup(options: { getImports: () => Imports, /* #endif */ /* #if USE_SHARED_MEMORY */ - spawnWorker: (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker, + spawnWorker?: (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker, /* #endif */ }): Promise diff --git a/Plugins/PackageToJS/Templates/platforms/browser.js b/Plugins/PackageToJS/Templates/platforms/browser.js index 3fce7c556..d471e8415 100644 --- a/Plugins/PackageToJS/Templates/platforms/browser.js +++ b/Plugins/PackageToJS/Templates/platforms/browser.js @@ -118,7 +118,7 @@ export async function defaultBrowserSetup(options) { /* #endif */ /* #if USE_SHARED_MEMORY */ const memory = new WebAssembly.Memory(MEMORY_TYPE); - const threadChannel = new DefaultBrowserThreadRegistry(options.spawnWorker) + const threadChannel = new DefaultBrowserThreadRegistry(options.spawnWorker || createDefaultWorkerFactory()) /* #endif */ return { diff --git a/Plugins/PackageToJS/Templates/platforms/node.d.ts b/Plugins/PackageToJS/Templates/platforms/node.d.ts index ca0e826c4..4b8d95843 100644 --- a/Plugins/PackageToJS/Templates/platforms/node.d.ts +++ b/Plugins/PackageToJS/Templates/platforms/node.d.ts @@ -7,10 +7,10 @@ export type DefaultNodeSetupOptions = { /* #endif */ onExit?: (code: number) => void, /* #if USE_SHARED_MEMORY */ - spawnWorker: (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker, + spawnWorker?: (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker, /* #endif */ } -export function defaultNodeSetup(options: DefaultNodeSetupOptions): Promise +export function defaultNodeSetup(options?: DefaultNodeSetupOptions): Promise export function createDefaultWorkerFactory(preludeScript?: string): (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker diff --git a/Plugins/PackageToJS/Templates/platforms/node.js b/Plugins/PackageToJS/Templates/platforms/node.js index 2fc0e8d15..3e1253965 100644 --- a/Plugins/PackageToJS/Templates/platforms/node.js +++ b/Plugins/PackageToJS/Templates/platforms/node.js @@ -113,7 +113,7 @@ class DefaultNodeThreadRegistry { /* #endif */ /** @type {import('./node.d.ts').defaultNodeSetup} */ -export async function defaultNodeSetup(options) { +export async function defaultNodeSetup(options = {}) { const path = await import("node:path"); const { fileURLToPath } = await import("node:url"); const { readFile } = await import("node:fs/promises") @@ -134,7 +134,7 @@ export async function defaultNodeSetup(options) { const module = await WebAssembly.compile(new Uint8Array(await readFile(path.join(pkgDir, MODULE_PATH)))) /* #if USE_SHARED_MEMORY */ const memory = new WebAssembly.Memory(MEMORY_TYPE); - const threadChannel = new DefaultNodeThreadRegistry(options.spawnWorker) + const threadChannel = new DefaultNodeThreadRegistry(options.spawnWorker || createDefaultWorkerFactory()) /* #endif */ return { diff --git a/Plugins/PackageToJS/Tests/ExampleTests.swift b/Plugins/PackageToJS/Tests/ExampleTests.swift index e673e6630..6e2b4e1c2 100644 --- a/Plugins/PackageToJS/Tests/ExampleTests.swift +++ b/Plugins/PackageToJS/Tests/ExampleTests.swift @@ -355,18 +355,6 @@ extension Trait where Self == ConditionTrait { // FIXME: This test fails on the current main snapshot #if !compiler(>=6.3) - @Test(.requireEmbeddedSwiftInToolchain(triple: "wasm32-unknown-none-wasm")) - func embeddedWasmUnknownNone() throws { - try withPackage(at: "Examples/Embedded") { packageDir, _, runSwift in - try runSwift( - ["package", "--triple", "wasm32-unknown-none-wasm", "js", "-c", "release"], - [ - "JAVASCRIPTKIT_EXPERIMENTAL_EMBEDDED_WASM": "true" - ] - ) - } - } - @Test(.requireEmbeddedSwiftInSwiftSDK()) func embeddedWasmUnknownWasi() throws { let swiftSDKID = try #require(Self.getEmbeddedSwiftSDKID()) diff --git a/README.md b/README.md index 2f41b7a31..c18cc1ccf 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,3 @@ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for details on ## Sponsoring [Become a gold or platinum sponsor](https://github.com/sponsors/swiftwasm/) and contact maintainers to add your logo on our README on Github with a link to your site. - - - - diff --git a/Sources/JavaScriptKit/BridgeJSInstrincics.swift b/Sources/JavaScriptKit/BridgeJSIntrinsics.swift similarity index 96% rename from Sources/JavaScriptKit/BridgeJSInstrincics.swift rename to Sources/JavaScriptKit/BridgeJSIntrinsics.swift index b0e5ec395..b3262cb9e 100644 --- a/Sources/JavaScriptKit/BridgeJSInstrincics.swift +++ b/Sources/JavaScriptKit/BridgeJSIntrinsics.swift @@ -316,6 +316,15 @@ public protocol _BridgedSwiftAssociatedValueEnum: _BridgedSwiftTypeLoweredIntoVo @_spi(BridgeJS) consuming func bridgeJSLowerReturn() -> Void } +/// A protocol that Swift struct types conform to. +/// +/// The conformance is automatically synthesized by the BridgeJS code generator. +public protocol _BridgedSwiftStruct: _BridgedSwiftTypeLoweredIntoVoidType { + // MARK: ExportSwift + @_spi(BridgeJS) static func bridgeJSLiftParameter() -> Self + @_spi(BridgeJS) consuming func bridgeJSLowerReturn() -> Void +} + extension _BridgedSwiftEnumNoPayload where Self: RawRepresentable, RawValue == String { // MARK: ImportTS @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> Int32 { rawValue.bridgeJSLowerParameter() } @@ -618,6 +627,24 @@ func _swift_js_return_optional_double(_ isSome: Int32, _ value: Float64) { } #endif +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_push_pointer") +@_spi(BridgeJS) public func _swift_js_push_pointer(_ pointer: UnsafeMutableRawPointer) +#else +@_spi(BridgeJS) public func _swift_js_push_pointer(_ pointer: UnsafeMutableRawPointer) { + _onlyAvailableOnWasm() +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_pop_param_pointer") +@_spi(BridgeJS) public func _swift_js_pop_param_pointer() -> UnsafeMutableRawPointer +#else +@_spi(BridgeJS) public func _swift_js_pop_param_pointer() -> UnsafeMutableRawPointer { + _onlyAvailableOnWasm() +} +#endif + extension Optional where Wrapped == Bool { // MARK: ImportTS @@ -1225,3 +1252,27 @@ extension Optional where Wrapped: _BridgedSwiftAssociatedValueEnum { } } } + +// MARK: Optional Struct Support + +extension Optional where Wrapped: _BridgedSwiftStruct { + // MARK: ExportSwift + + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ isSome: Int32) -> Wrapped? { + if isSome == 0 { + return nil + } else { + return Wrapped.bridgeJSLiftParameter() + } + } + + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void { + switch consume self { + case .none: + _swift_js_push_int(0) // Push only isSome=0 (no struct fields) + case .some(let value): + value.bridgeJSLowerReturn() // Push all struct fields FIRST + _swift_js_push_int(1) // Then push isSome=1 LAST (so it's popped FIRST by JS) + } + } +} diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md index 44019bf6d..c70533055 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md @@ -65,6 +65,7 @@ This command will: - - +- - - - diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Default-Parameters.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Default-Parameters.md index 11574f0d0..9eec622f0 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Default-Parameters.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Default-Parameters.md @@ -107,41 +107,45 @@ The following default value types are supported for both function and constructo | Nil for optionals | `nil` | `null` | | Enum cases (shorthand) | `.north` | `Direction.North` | | Enum cases (qualified) | `Direction.north` | `Direction.North` | -| Object initialization (no args) | `MyClass()` | `new MyClass()` | -| Object initialization (literal args) | `MyClass("value", 42)` | `new MyClass("value", 42)` | +| Class initialization (no args) | `MyClass()` | `new MyClass()` | +| Class initialization (literal args) | `MyClass("value", 42)` | `new MyClass("value", 42)` | +| Struct initialization | `Point(x: 1.0, y: 2.0)` | `{ x: 1.0, y: 2.0 }` | -## Working with Class Instances as Default Parameters +## Working with Class and Struct Defaults -You can use class initialization expressions as default values: +You can use class or struct initialization expressions as default values: ```swift +@JS struct Point { + var x: Double + var y: Double +} + @JS class Config { var setting: String - - @JS init(setting: String) { - self.setting = setting - } + @JS init(setting: String) { self.setting = setting } } -@JS public func process(config: Config = Config(setting: "default")) -> String { - return "Using: \(config.setting)" -} +@JS public func processPoint(point: Point = Point(x: 0.0, y: 0.0)) -> String +@JS public func processConfig(config: Config = Config(setting: "default")) -> String ``` -In JavaScript: +In JavaScript, structs become object literals while classes use constructor calls: ```javascript -exports.process(); // "Using: default" +exports.processPoint(); // uses default { x: 0.0, y: 0.0 } +exports.processPoint({ x: 5.0, y: 10.0 }); // custom struct +exports.processConfig(); // uses default new Config("default") const custom = new exports.Config("custom"); -exports.process(custom); // "Using: custom" +exports.processConfig(custom); // custom instance custom.release(); ``` -**Limitations for object initialization:** -- Constructor arguments must be literal values (`"text"`, `42`, `true`, `false`, `nil`) -- Complex expressions in constructor arguments are not supported -- Computed properties or method calls as arguments are not supported +**Requirements:** +- Constructor/initializer arguments must be literal values (`"text"`, `42`, `true`, `false`, `nil`) +- Struct initializers must use labeled arguments (e.g., `Point(x: 1.0, y: 2.0)`) +- Complex expressions, computed properties, or method calls are not supported ## Unsupported Default Value Types diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md new file mode 100644 index 000000000..e20a324c2 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md @@ -0,0 +1,180 @@ +# Exporting Swift Structs to JS + +Learn how to export Swift structs to JavaScript. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +To export a Swift struct, mark it with `@JS`: + +```swift +import JavaScriptKit + +@JS struct Point { + var x: Double + var y: Double + var label: String +} + +@JS struct Address { + var street: String + var city: String + var zipCode: Int? +} + +@JS struct Person { + var name: String + var age: Int + var address: Address + var email: String? +} + +@JS func createPerson(name: String, age: Int, street: String, city: String) -> Person +@JS func updateEmail(person: Person, email: String?) -> Person +``` + +In JavaScript: + +```javascript +import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; +const { exports } = await init({}); + +const person = exports.createPerson("Alice", 30, "123 Main St", "NYC"); +console.log(person.name); // "Alice" +console.log(person.address.street); // "123 Main St" +console.log(person.email); // null + +const updated = exports.updateEmail(person, "alice@example.com"); +console.log(updated.email); // "alice@example.com" +``` + +The generated TypeScript declarations: + +```typescript +export interface Point { + x: number; + y: number; + label: string; +} + +export interface Address { + street: string; + city: string; + zipCode: number | null; +} + +export interface Person { + name: string; + age: number; + address: Address; + email: string | null; +} + +export type Exports = { + createPerson(name: string, age: number, street: string, city: string): Person; + updateEmail(person: Person, email: string | null): Person; +} +``` + +## Instance Fields vs Static Properties + +**Instance fields** (part of the struct value) are automatically exported - no `@JS` needed: +```swift +@JS struct Point { + var x: Double // Auto-exported + var y: Double // Auto-exported +} +``` + +**Static properties** (not part of instance) require `@JS`: +```swift +@JS struct Config { + var name: String + + @JS nonisolated(unsafe) static var defaultTimeout: Double = 30.0 + @JS static let maxRetries: Int = 3 +} +``` + +In JavaScript: +```javascript +console.log(exports.Config.defaultTimeout); // 30.0 +exports.Config.defaultTimeout = 60.0; +console.log(exports.Config.maxRetries); // 3 (readonly) +``` + +## Struct Methods + +Structs can have instance and static methods, both require @JS annotation: + +```swift +@JS struct Calculator { + @JS func add(a: Double, b: Double) -> Double { + return a + b + } + + @JS static func multiply(x: Double, y: Double) -> Double { + return x * y + } +} +``` + +In JavaScript: + +```javascript +const calc = {}; +console.log(exports.useMathOperations(calc, 5.0, 3.0)); // Uses instance methods +console.log(exports.Calculator.multiply(4.0, 5.0)); // Static method +``` + +## Struct Initializers + +Struct initializers are exported as static `init` methods, not constructors: + +```swift +@JS struct Point { + var x: Double + var y: Double + + @JS init(x: Double, y: Double) { + self.x = x + self.y = y + } +} +``` + +In JavaScript: + +```javascript +const point = exports.Point.init(10.0, 20.0); +console.log(point.x); // 10.0 +``` + +This differs from classes, where `@JS init` maps to a JavaScript constructor using `new`: + +```javascript +// Class: uses `new` +const cart = new exports.ShoppingCart(); + +// Struct: uses static `init` method +const point = exports.Point.init(10.0, 20.0); +``` + +## Supported Features + +| Feature | Status | +|:--------|:-------| +| Stored fields with supported types | ✅ | +| Optional fields | ✅ | +| Nested structs | ✅ | +| Instance methods | ✅ | +| Static methods | ✅ | +| Static properties | ✅ | +| Property observers (`willSet`, `didSet`) | ❌ | +| Generics | ❌ | +| Conformances | ❌ | + +## See Also + +- diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/JavaScript-Interop-Cheat-Sheet.md b/Sources/JavaScriptKit/Documentation.docc/Articles/JavaScript-Interop-Cheat-Sheet.md new file mode 100644 index 000000000..4280a4637 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/JavaScript-Interop-Cheat-Sheet.md @@ -0,0 +1,376 @@ +# JavaScript Interop Cheat Sheet + +Practical recipes for manipulating JavaScript values from Swift with JavaScriptKit. Each section shows the shortest path to access, call, or convert the APIs you interact with the most. + +## Access JavaScript Values + +### Global entry points + +```swift +let global: JSObject = JSObject.global +let document: JSObject = global.document.object! +let math: JSObject = global.Math.object! +``` + +- Use ``JSObject/global`` for `globalThis` and drill into properties. +- Accessing through [dynamic member lookup](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0195-dynamic-member-lookup.md) returns ``JSValue``; call `.object`, `.number`, `.string`, etc. to unwrap a concrete type (callable values are represented as ``JSObject`` as well). +- Prefer storing ``JSObject`` references (`document` above) when you call multiple members to avoid repeated conversions (for performance). + +### Properties, subscripts, and symbols + +```swift +extension JSObject { + public subscript(_ name: String) -> JSValue { get set } + public subscript(_ index: Int) -> JSValue { get set } + public subscript(_ name: JSSymbol) -> JSValue { get set } + /// Use this API when you want to avoid repeated String serialization overhead + public subscript(_ name: JSString) -> JSValue { get set } + /// A convenience method of `subscript(_ name: String) -> JSValue` + /// to access the member through Dynamic Member Lookup. + /// ```swift + /// let document: JSObject = JSObject.global.document.object! + /// ``` + public subscript(dynamicMember name: String) -> JSValue { get set } +} +extension JSValue { + /// An unsafe convenience method of `JSObject.subscript(_ index: Int) -> JSValue` + /// - Precondition: `self` must be a JavaScript Object. + public subscript(dynamicMember name: String) -> JSValue + public subscript(_ index: Int) -> JSValue +} +``` + +**Example** + +```swift +document.title = .string("Swift <3 Web") +let obj = JSObject.global.myObject.object! +let value = obj["key"].string // Access object property with String +let propName = JSString("key") +let value2 = obj[propName].string // Access object property with JSString + +let array = JSObject.global.Array.object!.new(1, 2, 3) +array[0] = .number(10) // Assign to array index + +let symbol = JSSymbol("secret") +let data = obj[symbol].object +``` + +## Call Functions and Methods + +```swift +extension JSObject { + /// Call this function with given `arguments` using [Callable values of user-defined nominal types](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0253-callable.md) + /// ```swift + /// let alert = JSObject.global.alert.object! + /// alert("Hello from Swift") + /// ``` + public func callAsFunction(_ arguments: ConvertibleToJSValue...) -> JSValue + public func callAsFunction(this: JSObject, _ arguments: ConvertibleToJSValue...) -> JSValue + + /// Returns the `name` member method binding this object as `this` context. + public subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)? { get } + public subscript(_ name: JSString) -> ((ConvertibleToJSValue...) -> JSValue)? { get } + /// A convenience method of `subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)?` to access the member through Dynamic Member Lookup. + /// ```swift + /// let document = JSObject.global.document.object! + /// let divElement = document.createElement!("div") + /// ``` + public subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) -> JSValue)? { get } +} +extension JSValue { + /// An unsafe convenience method of `JSObject.subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)?` + /// - Precondition: `self` must be a JavaScript Object and specified member should be a callable object. + public subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) -> JSValue) +} +``` + +**Example** + +```swift +let alert = JSObject.global.alert.object! +alert("Hello from Swift") + +let console = JSObject.global.console.object! +_ = console.log!("Cheat sheet ready", 1, true) + +let document = JSObject.global.document.object! +let button = document.createElement!("button").object! +_ = button.classList.add("primary") +``` + +- **Dynamic Member Lookup and `!`**: When calling a method on ``JSObject`` (like `createElement!`), it returns an optional closure, so `!` is used to unwrap and call it. In contrast, calling on ``JSValue`` (like `button.classList.add`) returns a non-optional closure that traps on failure for convenience. + +Need to bind manually? Grab the function object and supply `this`: + +```swift +let appendChild = document.body.appendChild.object! +appendChild(this: document.body.object!, document.createElement!("div")) +``` + +### Passing options objects + +When JavaScript APIs require an options object, create one using ``JSObject``: + +```swift +public class JSObject: ExpressibleByDictionaryLiteral { + /// Creates an empty JavaScript object (equivalent to {} or new Object()) + public init() + + /// Creates a new object with the key-value pairs in the dictionary literal + public init(dictionaryLiteral elements: (String, JSValue)...) +} +``` + +**Example** + +```swift +// Create options object with dictionary literal +let listeningOptions: JSObject = ["once": .boolean(true), "passive": .boolean(true)] +button.addEventListener!("click", handler, listeningOptions) + +// Create empty object and add properties +let fetchOptions = JSObject() +fetchOptions["method"] = .string("POST") +let headers: JSObject = ["Content-Type": .string("application/json")] +fetchOptions["headers"] = headers.jsValue +fetchOptions["body"] = "{}".jsValue + +let fetch = JSObject.global.fetch.object! +let response = fetch("https://api.example.com", fetchOptions) +``` + +### Throwing JavaScript + +JavaScript exceptions surface as ``JSException``. Wrap the function (or object) in a throwing helper. + +```swift +// Method +let JSON = JSObject.global.JSON.object! +do { + let value = try JSON.throwing.parse!("{\"flag\":true}") +} catch let error as JSException { + print("Invalid JSON", error) +} + +// Function +let validateAge: JSObject = JSObject.global.validateAge.object! +do { + try validateAge.throws(-3) +} catch let error as JSException { + print("Validation failed:", error) +} +``` + +- Use ``JSObject/throwing`` to access object methods that may throw JavaScript exceptions. +- Use ``JSObject/throws`` to call the callable object itself that may throw JavaScript exceptions. + +### Constructors and `new` + +```swift +let url = JSObject.global.URL.object!.new("https://example.com", "https://example.com") +let searchParams = url.searchParams.object! +``` + +Use ``JSThrowingFunction/new(_:)`` (via `throws.new`) when the constructor can throw. + +## Convert Between Swift and JavaScript + +### Swift -> JavaScript + +Types conforming to ``ConvertibleToJSValue`` can be converted via the `.jsValue` property. Conversion behavior depends on the context: + +| Swift type | JavaScript result | Notes | +|------------|------------------|-------| +| `Bool` | `JSValue.boolean(Bool)` | | +| `String` | `JSValue.string(JSString)` | Wrapped in ``JSString`` to avoid extra copies | +| `Int`, `UInt`, `Int8-32`, `UInt8-32`, `Float`, `Double` | `JSValue.number(Double)` | All numeric types convert to `Double` | +| `Int64`, `UInt64` | `JSValue.bigInt(JSBigInt)` | Converted to `BigInt` (requires `import JavaScriptBigIntSupport`) | +| `Data` | `Uint8Array` | Converted to `Uint8Array` (requires `import JavaScriptFoundationCompat`) | +| `Array` where `Element: ConvertibleToJSValue` | JavaScript Array | Each element converted via `.jsValue` | +| `Dictionary` where `Value: ConvertibleToJSValue` | Plain JavaScript object | Keys must be `String` | +| `Optional.none` | `JSValue.null` | Use ``JSValue/undefined`` when you specifically need `undefined` | +| `Optional.some(wrapped)` | `wrapped.jsValue` | | +| ``JSValue``, ``JSObject``, ``JSString`` | Passed through | No conversion needed | + +**Function arguments**: Automatic conversion when passing to JavaScript functions: + +```swift +let alert = JSObject.global.alert.object! +alert("Hello") // String automatically converts via .jsValue + +let console = JSObject.global.console.object! +console.log!("Count", 42, true) // All arguments auto-convert +``` + +**Property assignment**: Explicit conversion required: + +```swift +let obj = JSObject.global.myObject.object! +let count: Int = 42 +let message = "Hello" + +obj["count"] = count.jsValue +obj["message"] = message.jsValue + +obj.count = count.jsValue +obj.message = message.jsValue + +// Alternative: use JSValue static methods +obj["count"] = .number(Double(count)) +obj.message = .string(message) +divElement.innerText = .string("Count \(count)") +canvasElement.width = .number(Double(size)) +``` + +### JavaScript -> Swift + +Access JavaScript values through ``JSValue`` accessors: + +```swift +let jsValue: JSValue = // ... some JavaScript value + +// Primitive types via direct accessors (most common pattern) +let message: String? = jsValue.string +let n: Double? = jsValue.number +let flag: Bool? = jsValue.boolean +let obj: JSObject? = jsValue.object + +// Access nested properties through JSObject subscripts +if let obj = jsValue.object { + let nested = obj.key.string + let arrayItem = obj.items[0].string + let count = obj.count.number +} + +// Arrays (if elements conform to ConstructibleFromJSValue) +if let items = [String].construct(from: jsValue) { + // Use items +} + +// Dictionaries (if values conform to ConstructibleFromJSValue) +if let data = [String: Int].construct(from: jsValue) { + // Use data +} + +// For complex Decodable types, use JSValueDecoder +struct User: Decodable { + let name: String + let age: Int +} +let user = try JSValueDecoder().decode(User.self, from: jsValue) +``` + +## Pass Swift Closures back to JavaScript + +```swift +public class JSClosure: JSObject, JSClosureProtocol { + public init(_ body: @escaping (sending [JSValue]) -> JSValue) + public static func async( + priority: TaskPriority? = nil, + _ body: @escaping (sending [JSValue]) async throws(JSException) -> JSValue + ) -> JSClosure + public func release() +} + +public class JSOneshotClosure: JSObject, JSClosureProtocol { + public init(_ body: @escaping (sending [JSValue]) -> JSValue) + public static func async( + priority: TaskPriority? = nil, + _ body: @escaping (sending [JSValue]) async throws(JSException) -> JSValue + ) -> JSOneshotClosure + public func release() +} +``` + +**Example** + +```swift +let document = JSObject.global.document.object! +let console = JSObject.global.console.object! + +// Persistent closure - keep reference while JavaScript can call it +let button = document.createElement!("button").object! +let handler = JSClosure { args in + console.log!("Clicked", args[0]) + return .undefined +} +button.addEventListener!("click", handler) + +// One-shot closure - automatically released after first call +button.addEventListener!( + "click", + JSOneshotClosure { _ in + console.log!("One-off click") + return .undefined + }, + ["once": true] +) + +// Async closure - bridges Swift async to JavaScript Promise +let asyncHandler = JSClosure.async { _ async throws(JSException) -> JSValue in + try! await Task.sleep(nanoseconds: 1_000_000) + console.log!("Async closure finished") + return .undefined +} +button.addEventListener!("async", asyncHandler) +``` + +## Promises and `async/await` + +```swift +public final class JSPromise: JSBridgedClass { + public init(unsafelyWrapping object: JSObject) + public init(resolver: @escaping (@escaping (Result) -> Void) -> Void) + public static func async( + body: @escaping () async throws(JSException) -> Void + ) -> JSPromise + public static func async( + body: @escaping () async throws(JSException) -> JSValue + ) -> JSPromise + + public enum Result { + case success(JSValue) + case failure(JSValue) + } + + // Available when JavaScriptEventLoop is linked + public var value: JSValue { get async throws(JSException) } + public var result: Result { get async } +} +``` + +**Example** + +```swift +import JavaScriptEventLoop + +JavaScriptEventLoop.installGlobalExecutor() + +let console = JSObject.global.console.object! +let fetch = JSObject.global.fetch.object! + +// Wrap existing JavaScript Promise and await from Swift +Task { + do { + let response = try await JSPromise( + unsafelyWrapping: fetch("https://example.com").object! + ).value + console.log!("Fetched data", response) + } catch let error as JSException { + console.error!("Fetch failed", error.thrownValue) + } +} + +// Expose Swift async work to JavaScript +let swiftPromise = JSPromise.async { + try await Task.sleep(nanoseconds: 1_000_000_000) + return .string("Swift async complete") +} +``` + +- Wrap existing promise-returning APIs with ``JSPromise/init(unsafelyWrapping:)``. +- Use `JSPromise.async(body:)` (with `Void` or `JSValue` return type) to expose Swift `async/await` work to JavaScript callers. +- To await JavaScript `Promise` from Swift, import `JavaScriptEventLoop`, call `JavaScriptEventLoop.installGlobalExecutor()` early, and use the `value` property. +- The `value` property suspends until the promise resolves or rejects, rethrowing rejections as ``JSException``. + diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Package-Output-Structure.md b/Sources/JavaScriptKit/Documentation.docc/Articles/Package-Output-Structure.md new file mode 100644 index 000000000..af9b0d2bd --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/Package-Output-Structure.md @@ -0,0 +1,155 @@ +# Package Output Structure + +Understand the structure and contents of the JavaScript package generated by the `swift package js` command. + +## Overview + +When you run `swift package --swift-sdk $SWIFT_SDK_ID js`, the PackageToJS plugin compiles your Swift code to WebAssembly and generates a JavaScript package in `.build/plugins/PackageToJS/outputs/Package/`. This package contains all the necessary files to run your Swift application in JavaScript environments (browser or Node.js). + +## Package Structure + +The output package has the following structure: + +``` +.build/plugins/PackageToJS/outputs/Package/ +├── ProductName.wasm # Compiled WebAssembly module +├── index.js # Main entry point for browser environments +├── index.d.ts # TypeScript type definitions for index.js +├── instantiate.js # Low-level instantiation API +├── instantiate.d.ts # TypeScript type definitions for instantiate.js +├── package.json # npm package metadata +└── platforms/ + ├── browser.js # Browser-specific platform setup + ├── browser.d.ts # TypeScript definitions for browser.js + ├── node.js # Node.js-specific platform setup + └── node.d.ts # TypeScript definitions for node.js +``` + +## Using the Package + +### In Browser + +```html + + + + + + +``` + +### In Node.js + +```javascript +import { instantiate } from './.build/plugins/PackageToJS/outputs/Package/instantiate.js'; +import { defaultNodeSetup } from './.build/plugins/PackageToJS/outputs/Package/platforms/node.js'; + +async function main() { + const options = await defaultNodeSetup(); + await instantiate(options); +} + +main(); +``` + +> Tip: For a complete Node.js setup example, see the [Node.js example](https://github.com/swiftwasm/JavaScriptKit/tree/main/Examples/NodeJS). + +### With Bundlers (Vite, Webpack, etc.) + +The generated package can be consumed by JavaScript bundlers: + +```bash +npm install .build/plugins/PackageToJS/outputs/Package +``` + +Then import it in your JavaScript code: + +```javascript +import { init } from 'package-name'; +await init(); +``` + +## Core Files + +### WebAssembly Module (`ProductName.wasm`) + +The compiled WebAssembly binary containing your Swift code. The filename matches your SwiftPM product name (e.g., `Basic.wasm` for a product named "Basic"). + +### Entry Point (`index.js`) + +The main entry point for browser environments. It provides a convenient `init()` function that handles module instantiation with default settings. + +```javascript +import { init } from './.build/plugins/PackageToJS/outputs/Package/index.js'; + +// Initialize with default browser setup +await init(); +``` + +For packages with BridgeJS imports, you can provide custom imports: + +```javascript +import { init } from './.build/plugins/PackageToJS/outputs/Package/index.js'; + +await init({ + getImports: () => ({ + // Your custom imports + }) +}); +``` + +### Instantiation API (`instantiate.js`) + +A lower-level API for more control over module instantiation. Use this when you need to customize the WebAssembly instantiation process or WASI setup. + +```javascript +import { instantiate } from './.build/plugins/PackageToJS/outputs/Package/instantiate.js'; +import { defaultBrowserSetup } from './.build/plugins/PackageToJS/outputs/Package/platforms/browser.js'; + +const options = await defaultBrowserSetup({ + module: fetch('./ProductName.wasm'), + // ... other options +}); + +const { instance, swift, exports } = await instantiate(options); +``` + +### Platform-Specific Setup + +The `platforms/` directory contains platform-specific setup functions: +- `platforms/browser.js` - Provides `defaultBrowserSetup()` for browser environments +- `platforms/node.js` - Provides `defaultNodeSetup()` for Node.js environments + +## Package Metadata (`package.json`) + +The generated `package.json` includes: + +```json +{ + "name": "package-name", + "version": "0.0.0", + "type": "module", + "private": true, + "exports": { + ".": "./index.js", + "./wasm": "./ProductName.wasm" + }, + "dependencies": { + "@bjorn3/browser_wasi_shim": "0.3.0" + } +} +``` + +The `exports` field allows importing the package as an npm dependency: + +```javascript +import { init } from '.build/plugins/PackageToJS/outputs/Package'; +``` + +## TypeScript Support + +All JavaScript files have corresponding `.d.ts` TypeScript definition files, providing full type safety when using the package in TypeScript projects. + diff --git a/Sources/JavaScriptKit/Documentation.docc/Documentation.md b/Sources/JavaScriptKit/Documentation.docc/Documentation.md index d506c3867..51ee62539 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Documentation.md +++ b/Sources/JavaScriptKit/Documentation.docc/Documentation.md @@ -51,6 +51,8 @@ Check out the [examples](https://github.com/swiftwasm/JavaScriptKit/tree/main/Ex ### Articles +- +- - - - diff --git a/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h b/Sources/_CJavaScriptKit/include/BridgeJSIntrinsics.h similarity index 74% rename from Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h rename to Sources/_CJavaScriptKit/include/BridgeJSIntrinsics.h index 7ca098609..f396d1f23 100644 --- a/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h +++ b/Sources/_CJavaScriptKit/include/BridgeJSIntrinsics.h @@ -1,5 +1,5 @@ -#ifndef _CJavaScriptKit_BridgeJSInstrincics_h -#define _CJavaScriptKit_BridgeJSInstrincics_h +#ifndef _CJavaScriptKit_BridgeJSIntrinsics_h +#define _CJavaScriptKit_BridgeJSIntrinsics_h #include #include "WasmGlobalMacros.h" diff --git a/Sources/_CJavaScriptKit/include/module.modulemap b/Sources/_CJavaScriptKit/include/module.modulemap index 3afe12831..f2d3c162e 100644 --- a/Sources/_CJavaScriptKit/include/module.modulemap +++ b/Sources/_CJavaScriptKit/include/module.modulemap @@ -1,5 +1,5 @@ module _CJavaScriptKit { header "_CJavaScriptKit.h" - header "BridgeJSInstrincics.h" + header "BridgeJSIntrinsics.h" export * } diff --git a/Tests/BridgeJSGlobalTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSGlobalTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 979311c86..955b78f69 100644 --- a/Tests/BridgeJSGlobalTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSGlobalTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -533,5 +533,8 @@ "moduleName" : "BridgeJSGlobalTests", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index ff3d9dbed..286c9a380 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -528,6 +528,32 @@ typealias OptionalAge = Int? return value } +@JS func compareAPIResults(_ r1: APIResult?, _ r2: APIResult?) -> String { + let r1Str: String + switch r1 { + case .none: r1Str = "nil" + case .some(.success(let msg)): r1Str = "success:\(msg)" + case .some(.failure(let code)): r1Str = "failure:\(code)" + case .some(.info): r1Str = "info" + case .some(.flag(let b)): r1Str = "flag:\(b)" + case .some(.rate(let r)): r1Str = "rate:\(r)" + case .some(.precise(let p)): r1Str = "precise:\(p)" + } + + let r2Str: String + switch r2 { + case .none: r2Str = "nil" + case .some(.success(let msg)): r2Str = "success:\(msg)" + case .some(.failure(let code)): r2Str = "failure:\(code)" + case .some(.info): r2Str = "info" + case .some(.flag(let b)): r2Str = "flag:\(b)" + case .some(.rate(let r)): r2Str = "rate:\(r)" + case .some(.precise(let p)): r2Str = "precise:\(p)" + } + + return "r1:\(r1Str),r2:\(r2Str)" +} + @JS func roundTripOptionalComplexResult(_ result: ComplexResult?) -> ComplexResult? { return result } @@ -1206,6 +1232,139 @@ enum APIOptionalResult { } } +// MARK: - Struct Tests + +@JS struct DataPoint { + let x: Double + let y: Double + var label: String + var optCount: Int? + var optFlag: Bool? + + @JS init(x: Double, y: Double, label: String, optCount: Int?, optFlag: Bool?) { + self.x = x + self.y = y + self.label = label + self.optCount = optCount + self.optFlag = optFlag + } +} + +@JS struct Address { + var street: String + var city: String + var zipCode: Int? +} + +@JS struct Contact { + var name: String + var age: Int + var address: Address + var email: String? + var secondaryAddress: Address? +} + +@JS struct Config { + var name: String + var theme: Theme? + var direction: Direction? + var status: Status +} + +@JS struct SessionData { + var id: Int + var owner: Greeter? +} + +@JS struct ValidationReport { + var id: Int + var result: APIResult + var status: Status? + var outcome: APIResult? +} + +@JS struct MathOperations { + var baseValue: Double + + @JS init(baseValue: Double = 0.0) { + self.baseValue = baseValue + } + + @JS func add(a: Double, b: Double = 10.0) -> Double { + return baseValue + a + b + } + + @JS func multiply(a: Double, b: Double) -> Double { + return a * b + } + + @JS static func subtract(a: Double, b: Double) -> Double { + return a - b + } +} + +@JS func testStructDefault( + point: DataPoint = DataPoint(x: 1.0, y: 2.0, label: "default", optCount: nil, optFlag: nil) +) -> String { + return "\(point.x),\(point.y),\(point.label)" +} + +@JS struct ConfigStruct { + var name: String + var value: Int + + @JS nonisolated(unsafe) static var defaultConfig: String = "production" + @JS static let maxRetries: Int = 3 + @JS nonisolated(unsafe) static var timeout: Double = 30.0 + + @JS static var computedSetting: String { + return "Config: \(defaultConfig)" + } +} + +@JS func roundTripDataPoint(_ data: DataPoint) -> DataPoint { + return data +} + +@JS func roundTripContact(_ contact: Contact) -> Contact { + return contact +} + +@JS func roundTripConfig(_ config: Config) -> Config { + return config +} + +@JS func roundTripSessionData(_ session: SessionData) -> SessionData { + return session +} + +@JS func roundTripValidationReport(_ report: ValidationReport) -> ValidationReport { + return report +} + +@JS func updateValidationReport(_ newResult: APIResult?, _ report: ValidationReport) -> ValidationReport { + return ValidationReport( + id: report.id, + result: newResult ?? report.result, + status: report.status, + outcome: report.outcome + ) +} + +@JS class Container { + @JS var location: DataPoint + @JS var config: Config? + + @JS init(location: DataPoint, config: Config?) { + self.location = location + self.config = config + } +} + +@JS func testContainerWithStruct(_ point: DataPoint) -> Container { + return Container(location: point, config: nil) +} + class ExportAPITests: XCTestCase { func testAll() { var hasDeinitGreeter = false diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 9923eedd5..addeedb1e 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -2008,6 +2008,313 @@ public func _bjs_StaticPropertyNamespace_NestedProperties_static_nestedDouble_se #endif } +extension DataPoint: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> DataPoint { + let optFlag = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let optCount = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let label = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let y = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + let x = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + return DataPoint(x: x, y: y, label: label, optCount: optCount, optFlag: optFlag) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_f64(self.x) + _swift_js_push_f64(self.y) + var __bjs_label = self.label + __bjs_label.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + let __bjs_isSome_optCount = self.optCount != nil + if let __bjs_unwrapped_optCount = self.optCount { + _swift_js_push_int(Int32(__bjs_unwrapped_optCount)) + } + _swift_js_push_int(__bjs_isSome_optCount ? 1 : 0) + let __bjs_isSome_optFlag = self.optFlag != nil + if let __bjs_unwrapped_optFlag = self.optFlag { + _swift_js_push_int(__bjs_unwrapped_optFlag ? 1 : 0) + } + _swift_js_push_int(__bjs_isSome_optFlag ? 1 : 0) + } +} + +@_expose(wasm, "bjs_DataPoint_init") +@_cdecl("bjs_DataPoint_init") +public func _bjs_DataPoint_init(x: Float64, y: Float64, labelBytes: Int32, labelLength: Int32, optCountIsSome: Int32, optCountValue: Int32, optFlagIsSome: Int32, optFlagValue: Int32) -> Void { + #if arch(wasm32) + let ret = DataPoint(x: Double.bridgeJSLiftParameter(x), y: Double.bridgeJSLiftParameter(y), label: String.bridgeJSLiftParameter(labelBytes, labelLength), optCount: Optional.bridgeJSLiftParameter(optCountIsSome, optCountValue), optFlag: Optional.bridgeJSLiftParameter(optFlagIsSome, optFlagValue)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension Address: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Address { + let zipCode = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let city = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let street = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Address(street: street, city: city, zipCode: zipCode) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_street = self.street + __bjs_street.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + var __bjs_city = self.city + __bjs_city.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + let __bjs_isSome_zipCode = self.zipCode != nil + if let __bjs_unwrapped_zipCode = self.zipCode { + _swift_js_push_int(Int32(__bjs_unwrapped_zipCode)) + } + _swift_js_push_int(__bjs_isSome_zipCode ? 1 : 0) + } +} + +extension Contact: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Contact { + let secondaryAddress = Optional
.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let email = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let address = Address.bridgeJSLiftParameter() + let age = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let name = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Contact(name: name, age: age, address: address, email: email, secondaryAddress: secondaryAddress) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_name = self.name + __bjs_name.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + _swift_js_push_int(Int32(self.age)) + self.address.bridgeJSLowerReturn() + let __bjs_isSome_email = self.email != nil + if let __bjs_unwrapped_email = self.email { + var __bjs_str_email = __bjs_unwrapped_email + __bjs_str_email.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + } + _swift_js_push_int(__bjs_isSome_email ? 1 : 0) + let __bjs_isSome_secondaryAddress = self.secondaryAddress != nil + if let __bjs_unwrapped_secondaryAddress = self.secondaryAddress { + __bjs_unwrapped_secondaryAddress.bridgeJSLowerReturn() + } + _swift_js_push_int(__bjs_isSome_secondaryAddress ? 1 : 0) + } +} + +extension Config: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Config { + let status = Status.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let direction = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let theme = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let name = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Config(name: name, theme: theme, direction: direction, status: status) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_name = self.name + __bjs_name.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + let __bjs_isSome_theme = self.theme != nil + if let __bjs_unwrapped_theme = self.theme { + var __bjs_str_theme = __bjs_unwrapped_theme.rawValue + __bjs_str_theme.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + } + _swift_js_push_int(__bjs_isSome_theme ? 1 : 0) + let __bjs_isSome_direction = self.direction != nil + if let __bjs_unwrapped_direction = self.direction { + _swift_js_push_int(__bjs_unwrapped_direction.bridgeJSLowerParameter()) + } + _swift_js_push_int(__bjs_isSome_direction ? 1 : 0) + _swift_js_push_int(Int32(self.status.bridgeJSLowerParameter())) + } +} + +extension SessionData: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> SessionData { + let owner = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_pointer()) + let id = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + return SessionData(id: id, owner: owner) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_int(Int32(self.id)) + let __bjs_isSome_owner = self.owner != nil + if let __bjs_unwrapped_owner = self.owner { + _swift_js_push_pointer(__bjs_unwrapped_owner.bridgeJSLowerReturn()) + } + _swift_js_push_int(__bjs_isSome_owner ? 1 : 0) + } +} + +extension ValidationReport: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> ValidationReport { + let outcome = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let status = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let result = APIResult.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let id = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + return ValidationReport(id: id, result: result, status: status, outcome: outcome) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_int(Int32(self.id)) + self.result.bridgeJSLowerReturn() + let __bjs_isSome_status = self.status != nil + if let __bjs_unwrapped_status = self.status { + _swift_js_push_int(__bjs_unwrapped_status.bridgeJSLowerParameter()) + } + _swift_js_push_int(__bjs_isSome_status ? 1 : 0) + let __bjs_isSome_outcome = self.outcome != nil + if let __bjs_unwrapped_outcome = self.outcome { + _swift_js_push_int(__bjs_unwrapped_outcome.bridgeJSLowerParameter()) + } + _swift_js_push_int(__bjs_isSome_outcome ? 1 : 0) + } +} + +extension MathOperations: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> MathOperations { + let baseValue = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + return MathOperations(baseValue: baseValue) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_f64(self.baseValue) + } +} + +@_expose(wasm, "bjs_MathOperations_init") +@_cdecl("bjs_MathOperations_init") +public func _bjs_MathOperations_init(baseValue: Float64) -> Void { + #if arch(wasm32) + let ret = MathOperations(baseValue: Double.bridgeJSLiftParameter(baseValue)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MathOperations_add") +@_cdecl("bjs_MathOperations_add") +public func _bjs_MathOperations_add(a: Float64, b: Float64) -> Float64 { + #if arch(wasm32) + let ret = MathOperations.bridgeJSLiftParameter().add(a: Double.bridgeJSLiftParameter(a), b: Double.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MathOperations_multiply") +@_cdecl("bjs_MathOperations_multiply") +public func _bjs_MathOperations_multiply(a: Float64, b: Float64) -> Float64 { + #if arch(wasm32) + let ret = MathOperations.bridgeJSLiftParameter().multiply(a: Double.bridgeJSLiftParameter(a), b: Double.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MathOperations_static_subtract") +@_cdecl("bjs_MathOperations_static_subtract") +public func _bjs_MathOperations_static_subtract(a: Float64, b: Float64) -> Float64 { + #if arch(wasm32) + let ret = MathOperations.subtract(a: Double.bridgeJSLiftParameter(a), b: Double.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ConfigStruct: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> ConfigStruct { + let value = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let name = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return ConfigStruct(name: name, value: value) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_name = self.name + __bjs_name.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + _swift_js_push_int(Int32(self.value)) + } +} + +@_expose(wasm, "bjs_ConfigStruct_static_defaultConfig_get") +@_cdecl("bjs_ConfigStruct_static_defaultConfig_get") +public func _bjs_ConfigStruct_static_defaultConfig_get() -> Void { + #if arch(wasm32) + let ret = ConfigStruct.defaultConfig + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_defaultConfig_set") +@_cdecl("bjs_ConfigStruct_static_defaultConfig_set") +public func _bjs_ConfigStruct_static_defaultConfig_set(valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + ConfigStruct.defaultConfig = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_maxRetries_get") +@_cdecl("bjs_ConfigStruct_static_maxRetries_get") +public func _bjs_ConfigStruct_static_maxRetries_get() -> Int32 { + #if arch(wasm32) + let ret = ConfigStruct.maxRetries + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_timeout_get") +@_cdecl("bjs_ConfigStruct_static_timeout_get") +public func _bjs_ConfigStruct_static_timeout_get() -> Float64 { + #if arch(wasm32) + let ret = ConfigStruct.timeout + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_timeout_set") +@_cdecl("bjs_ConfigStruct_static_timeout_set") +public func _bjs_ConfigStruct_static_timeout_set(value: Float64) -> Void { + #if arch(wasm32) + ConfigStruct.timeout = Double.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_computedSetting_get") +@_cdecl("bjs_ConfigStruct_static_computedSetting_get") +public func _bjs_ConfigStruct_static_computedSetting_get() -> Void { + #if arch(wasm32) + let ret = ConfigStruct.computedSetting + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_roundTripVoid") @_cdecl("bjs_roundTripVoid") public func _bjs_roundTripVoid() -> Void { @@ -3079,6 +3386,19 @@ public func _bjs_roundTripOptionalAPIResult(valueIsSome: Int32, valueCaseId: Int #endif } +@_expose(wasm, "bjs_compareAPIResults") +@_cdecl("bjs_compareAPIResults") +public func _bjs_compareAPIResults(r1IsSome: Int32, r1CaseId: Int32, r2IsSome: Int32, r2CaseId: Int32) -> Void { + #if arch(wasm32) + let _tmp_r2 = Optional.bridgeJSLiftParameter(r2IsSome, r2CaseId) + let _tmp_r1 = Optional.bridgeJSLiftParameter(r1IsSome, r1CaseId) + let ret = compareAPIResults(_: _tmp_r1, _: _tmp_r2) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_roundTripOptionalComplexResult") @_cdecl("bjs_roundTripOptionalComplexResult") public func _bjs_roundTripOptionalComplexResult(resultIsSome: Int32, resultCaseId: Int32) -> Void { @@ -3298,6 +3618,96 @@ public func _bjs_makeAdder(base: Int32) -> UnsafeMutableRawPointer { #endif } +@_expose(wasm, "bjs_testStructDefault") +@_cdecl("bjs_testStructDefault") +public func _bjs_testStructDefault() -> Void { + #if arch(wasm32) + let ret = testStructDefault(point: DataPoint.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripDataPoint") +@_cdecl("bjs_roundTripDataPoint") +public func _bjs_roundTripDataPoint() -> Void { + #if arch(wasm32) + let ret = roundTripDataPoint(_: DataPoint.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripContact") +@_cdecl("bjs_roundTripContact") +public func _bjs_roundTripContact() -> Void { + #if arch(wasm32) + let ret = roundTripContact(_: Contact.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripConfig") +@_cdecl("bjs_roundTripConfig") +public func _bjs_roundTripConfig() -> Void { + #if arch(wasm32) + let ret = roundTripConfig(_: Config.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripSessionData") +@_cdecl("bjs_roundTripSessionData") +public func _bjs_roundTripSessionData() -> Void { + #if arch(wasm32) + let ret = roundTripSessionData(_: SessionData.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripValidationReport") +@_cdecl("bjs_roundTripValidationReport") +public func _bjs_roundTripValidationReport() -> Void { + #if arch(wasm32) + let ret = roundTripValidationReport(_: ValidationReport.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_updateValidationReport") +@_cdecl("bjs_updateValidationReport") +public func _bjs_updateValidationReport(newResultIsSome: Int32, newResultCaseId: Int32) -> Void { + #if arch(wasm32) + let _tmp_report = ValidationReport.bridgeJSLiftParameter() + let _tmp_newResult = Optional.bridgeJSLiftParameter(newResultIsSome, newResultCaseId) + let ret = updateValidationReport(_: _tmp_newResult, _: _tmp_report) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_testContainerWithStruct") +@_cdecl("bjs_testContainerWithStruct") +public func _bjs_testContainerWithStruct() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = testContainerWithStruct(_: DataPoint.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_Greeter_init") @_cdecl("bjs_Greeter_init") public func _bjs_Greeter_init(nameBytes: Int32, nameLength: Int32) -> UnsafeMutableRawPointer { @@ -5448,4 +5858,80 @@ fileprivate func _bjs_TextProcessor_wrap(_: UnsafeMutableRawPointer) -> Int32 fileprivate func _bjs_TextProcessor_wrap(_: UnsafeMutableRawPointer) -> Int32 { fatalError("Only available on WebAssembly") } +#endif + +@_expose(wasm, "bjs_Container_init") +@_cdecl("bjs_Container_init") +public func _bjs_Container_init(config: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let _tmp_config = Optional.bridgeJSLiftParameter(config) + let _tmp_location = DataPoint.bridgeJSLiftParameter() + let ret = Container(location: _tmp_location, config: _tmp_config) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Container_location_get") +@_cdecl("bjs_Container_location_get") +public func _bjs_Container_location_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Container.bridgeJSLiftParameter(_self).location + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Container_location_set") +@_cdecl("bjs_Container_location_set") +public func _bjs_Container_location_set(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Container.bridgeJSLiftParameter(_self).location = DataPoint.bridgeJSLiftParameter() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Container_config_get") +@_cdecl("bjs_Container_config_get") +public func _bjs_Container_config_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Container.bridgeJSLiftParameter(_self).config + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Container_config_set") +@_cdecl("bjs_Container_config_set") +public func _bjs_Container_config_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + Container.bridgeJSLiftParameter(_self).config = Optional.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Container_deinit") +@_cdecl("bjs_Container_deinit") +public func _bjs_Container_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Container: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_Container_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Container_wrap") +fileprivate func _bjs_Container_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_Container_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} #endif \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 75964debb..872af0251 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -3118,6 +3118,71 @@ ], "swiftCallName" : "TextProcessor" + }, + { + "constructor" : { + "abiName" : "bjs_Container_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "location", + "name" : "location", + "type" : { + "swiftStruct" : { + "_0" : "DataPoint" + } + } + }, + { + "label" : "config", + "name" : "config", + "type" : { + "optional" : { + "_0" : { + "swiftStruct" : { + "_0" : "Config" + } + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "Container", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "location", + "type" : { + "swiftStruct" : { + "_0" : "DataPoint" + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "config", + "type" : { + "optional" : { + "_0" : { + "swiftStruct" : { + "_0" : "Config" + } + } + } + } + } + ], + "swiftCallName" : "Container" } ], "enums" : [ @@ -6826,6 +6891,48 @@ } } }, + { + "abiName" : "bjs_compareAPIResults", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "compareAPIResults", + "parameters" : [ + { + "label" : "_", + "name" : "r1", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIResult" + } + } + } + } + }, + { + "label" : "_", + "name" : "r2", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIResult" + } + } + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, { "abiName" : "bjs_roundTripOptionalComplexResult", "effects" : { @@ -7536,116 +7643,376 @@ } } } - } - ], - "moduleName" : "BridgeJSRuntimeTests", - "protocols" : [ + }, { - "methods" : [ + "abiName" : "bjs_testStructDefault", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "testStructDefault", + "parameters" : [ { - "abiName" : "bjs_DataProcessor_increment", - "effects" : { - "isAsync" : false, - "isStatic" : false, - "isThrows" : false - }, - "name" : "increment", - "parameters" : [ - { - "label" : "by", - "name" : "amount", - "type" : { - "int" : { + "defaultValue" : { + "structLiteral" : { + "_0" : "DataPoint", + "_1" : [ + { + "name" : "x", + "value" : { + "float" : { + "_0" : 1 + } + } + }, + { + "name" : "y", + "value" : { + "float" : { + "_0" : 2 + } + } + }, + { + "name" : "label", + "value" : { + "string" : { + "_0" : "default" + } + } + }, + { + "name" : "optCount", + "value" : { + "null" : { + + } + } + }, + { + "name" : "optFlag", + "value" : { + "null" : { + } + } } - } + ] } - ], - "returnType" : { - "void" : { - + }, + "label" : "point", + "name" : "point", + "type" : { + "swiftStruct" : { + "_0" : "DataPoint" } } - }, - { - "abiName" : "bjs_DataProcessor_getValue", - "effects" : { - "isAsync" : false, - "isStatic" : false, - "isThrows" : false - }, - "name" : "getValue", - "parameters" : [ - - ], - "returnType" : { - "int" : { + } + ], + "returnType" : { + "string" : { + } + } + }, + { + "abiName" : "bjs_roundTripDataPoint", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripDataPoint", + "parameters" : [ + { + "label" : "_", + "name" : "data", + "type" : { + "swiftStruct" : { + "_0" : "DataPoint" } } - }, + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "DataPoint" + } + } + }, + { + "abiName" : "bjs_roundTripContact", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripContact", + "parameters" : [ { - "abiName" : "bjs_DataProcessor_setLabelElements", - "effects" : { - "isAsync" : false, - "isStatic" : false, - "isThrows" : false - }, - "name" : "setLabelElements", - "parameters" : [ - { - "label" : "_", - "name" : "labelPrefix", - "type" : { - "string" : { - - } - } - }, - { - "label" : "_", - "name" : "labelSuffix", - "type" : { - "string" : { - - } - } - } - ], - "returnType" : { - "void" : { - + "label" : "_", + "name" : "contact", + "type" : { + "swiftStruct" : { + "_0" : "Contact" } } - }, + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Contact" + } + } + }, + { + "abiName" : "bjs_roundTripConfig", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripConfig", + "parameters" : [ { - "abiName" : "bjs_DataProcessor_getLabel", - "effects" : { - "isAsync" : false, - "isStatic" : false, - "isThrows" : false - }, - "name" : "getLabel", - "parameters" : [ - - ], - "returnType" : { - "string" : { - + "label" : "_", + "name" : "config", + "type" : { + "swiftStruct" : { + "_0" : "Config" } } - }, - { - "abiName" : "bjs_DataProcessor_isEven", - "effects" : { - "isAsync" : false, - "isStatic" : false, - "isThrows" : false - }, - "name" : "isEven", - "parameters" : [ - - ], + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Config" + } + } + }, + { + "abiName" : "bjs_roundTripSessionData", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripSessionData", + "parameters" : [ + { + "label" : "_", + "name" : "session", + "type" : { + "swiftStruct" : { + "_0" : "SessionData" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "SessionData" + } + } + }, + { + "abiName" : "bjs_roundTripValidationReport", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripValidationReport", + "parameters" : [ + { + "label" : "_", + "name" : "report", + "type" : { + "swiftStruct" : { + "_0" : "ValidationReport" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "ValidationReport" + } + } + }, + { + "abiName" : "bjs_updateValidationReport", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "updateValidationReport", + "parameters" : [ + { + "label" : "_", + "name" : "newResult", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIResult" + } + } + } + } + }, + { + "label" : "_", + "name" : "report", + "type" : { + "swiftStruct" : { + "_0" : "ValidationReport" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "ValidationReport" + } + } + }, + { + "abiName" : "bjs_testContainerWithStruct", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "testContainerWithStruct", + "parameters" : [ + { + "label" : "_", + "name" : "point", + "type" : { + "swiftStruct" : { + "_0" : "DataPoint" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "Container" + } + } + } + ], + "moduleName" : "BridgeJSRuntimeTests", + "protocols" : [ + { + "methods" : [ + { + "abiName" : "bjs_DataProcessor_increment", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "increment", + "parameters" : [ + { + "label" : "by", + "name" : "amount", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_DataProcessor_getValue", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getValue", + "parameters" : [ + + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_DataProcessor_setLabelElements", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "setLabelElements", + "parameters" : [ + { + "label" : "_", + "name" : "labelPrefix", + "type" : { + "string" : { + + } + } + }, + { + "label" : "_", + "name" : "labelSuffix", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_DataProcessor_getLabel", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getLabel", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_DataProcessor_isEven", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "isEven", + "parameters" : [ + + ], "returnType" : { "bool" : { @@ -7919,5 +8286,638 @@ } ] } + ], + "structs" : [ + { + "constructor" : { + "abiName" : "bjs_DataPoint_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "x", + "name" : "x", + "type" : { + "double" : { + + } + } + }, + { + "label" : "y", + "name" : "y", + "type" : { + "double" : { + + } + } + }, + { + "label" : "label", + "name" : "label", + "type" : { + "string" : { + + } + } + }, + { + "label" : "optCount", + "name" : "optCount", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "label" : "optFlag", + "name" : "optFlag", + "type" : { + "optional" : { + "_0" : { + "bool" : { + + } + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "DataPoint", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "x", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "y", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "label", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optCount", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optFlag", + "type" : { + "optional" : { + "_0" : { + "bool" : { + + } + } + } + } + } + ], + "swiftCallName" : "DataPoint" + }, + { + "methods" : [ + + ], + "name" : "Address", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "street", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "city", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "zipCode", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + } + ], + "swiftCallName" : "Address" + }, + { + "methods" : [ + + ], + "name" : "Contact", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "age", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "address", + "type" : { + "swiftStruct" : { + "_0" : "Address" + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "email", + "type" : { + "optional" : { + "_0" : { + "string" : { + + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "secondaryAddress", + "type" : { + "optional" : { + "_0" : { + "swiftStruct" : { + "_0" : "Address" + } + } + } + } + } + ], + "swiftCallName" : "Contact" + }, + { + "methods" : [ + + ], + "name" : "Config", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "theme", + "type" : { + "optional" : { + "_0" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "direction", + "type" : { + "optional" : { + "_0" : { + "caseEnum" : { + "_0" : "Direction" + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "status", + "type" : { + "caseEnum" : { + "_0" : "Status" + } + } + } + ], + "swiftCallName" : "Config" + }, + { + "methods" : [ + + ], + "name" : "SessionData", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "id", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "owner", + "type" : { + "optional" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + } + } + } + ], + "swiftCallName" : "SessionData" + }, + { + "methods" : [ + + ], + "name" : "ValidationReport", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "id", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "result", + "type" : { + "associatedValueEnum" : { + "_0" : "APIResult" + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "status", + "type" : { + "optional" : { + "_0" : { + "caseEnum" : { + "_0" : "Status" + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "outcome", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIResult" + } + } + } + } + } + ], + "swiftCallName" : "ValidationReport" + }, + { + "constructor" : { + "abiName" : "bjs_MathOperations_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "defaultValue" : { + "double" : { + "_0" : 0 + } + }, + "label" : "baseValue", + "name" : "baseValue", + "type" : { + "double" : { + + } + } + } + ] + }, + "methods" : [ + { + "abiName" : "bjs_MathOperations_add", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "add", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "double" : { + + } + } + }, + { + "defaultValue" : { + "double" : { + "_0" : 10 + } + }, + "label" : "b", + "name" : "b", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_MathOperations_multiply", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "multiply", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "double" : { + + } + } + }, + { + "label" : "b", + "name" : "b", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_MathOperations_static_subtract", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "subtract", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "double" : { + + } + } + }, + { + "label" : "b", + "name" : "b", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + }, + "staticContext" : { + "structName" : { + "_0" : "MathOperations" + } + } + } + ], + "name" : "MathOperations", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "baseValue", + "type" : { + "double" : { + + } + } + } + ], + "swiftCallName" : "MathOperations" + }, + { + "methods" : [ + + ], + "name" : "ConfigStruct", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "value", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : true, + "name" : "defaultConfig", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : true, + "name" : "maxRetries", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : true, + "name" : "timeout", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : true, + "name" : "computedSetting", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "ConfigStruct" + } ] } \ No newline at end of file diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index cfd898b31..a5611523c 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -571,6 +571,15 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { assert.deepEqual(exports.roundTripOptionalAPIResult(p1), p1); assert.deepEqual(exports.roundTripOptionalComplexResult(cl1), cl1); + const apiSuccess = { tag: exports.APIResult.Tag.Success, param0: "test success" }; + const apiFailure = { tag: exports.APIResult.Tag.Failure, param0: 404 }; + const apiInfo = { tag: exports.APIResult.Tag.Info }; + + assert.equal(exports.compareAPIResults(apiSuccess, apiFailure), "r1:success:test success,r2:failure:404"); + assert.equal(exports.compareAPIResults(null, apiInfo), "r1:nil,r2:info"); + assert.equal(exports.compareAPIResults(apiFailure, null), "r1:failure:404,r2:nil"); + assert.equal(exports.compareAPIResults(null, null), "r1:nil,r2:nil"); + const optionalGreeter = new exports.Greeter("Schrödinger"); const optionalGreeter2 = exports.roundTripOptionalClass(optionalGreeter); assert.equal(optionalGreeter2?.greet() ?? "", "Hello, Schrödinger!"); @@ -701,6 +710,7 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { testProtocolSupport(exports); testClosureSupport(exports); + testStructSupport(exports); } /** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ function testClosureSupport(exports) { @@ -896,6 +906,112 @@ function testClosureSupport(exports) { processor.release(); } +/** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ +function testStructSupport(exports) { + const data1 = { x: 1.5, y: 2.5, label: "Point", optCount: 42, optFlag: true }; + assert.deepEqual(exports.roundTripDataPoint(data1), data1); + const data2 = { x: 0.0, y: 0.0, label: "", optCount: null, optFlag: null }; + assert.deepEqual(exports.roundTripDataPoint(data2), data2); + + const contact1 = { + name: "Alice", + age: 30, + address: { street: "123 Main St", city: "NYC", zipCode: 10001 }, + email: "alice@test.com", + secondaryAddress: { street: "456 Oak Ave", city: "LA", zipCode: null } + }; + assert.deepEqual(exports.roundTripContact(contact1), contact1); + const contact2 = { + name: "Bob", + age: 25, + address: { street: "789 Pine Rd", city: "SF", zipCode: null }, + email: null, + secondaryAddress: null + }; + assert.deepEqual(exports.roundTripContact(contact2), contact2); + + const config1 = { + name: "prod", + theme: exports.Theme.Dark, + direction: exports.Direction.North, + status: exports.Status.Success + }; + assert.deepEqual(exports.roundTripConfig(config1), config1); + const config2 = { + name: "dev", + theme: null, + direction: null, + status: exports.Status.Loading + }; + assert.deepEqual(exports.roundTripConfig(config2), config2); + + const owner1 = new exports.Greeter("TestUser"); + const session1 = { id: 123, owner: owner1 }; + const resultSession1 = exports.roundTripSessionData(session1); + assert.equal(resultSession1.id, 123); + assert.equal(resultSession1.owner.greet(), "Hello, TestUser!"); + const session2 = { id: 456, owner: null }; + assert.deepEqual(exports.roundTripSessionData(session2), session2); + owner1.release(); + resultSession1.owner.release(); + + const report1 = { + id: 100, + result: { tag: exports.APIResult.Tag.Success, param0: "ok" }, + status: exports.Status.Success, + outcome: { tag: exports.APIResult.Tag.Info } + }; + assert.deepEqual(exports.roundTripValidationReport(report1), report1); + const report2 = { + id: 200, + result: { tag: exports.APIResult.Tag.Failure, param0: 404 }, + status: null, + outcome: null + }; + assert.deepEqual(exports.roundTripValidationReport(report2), report2); + + const origReport = { + id: 999, + result: { tag: exports.APIResult.Tag.Failure, param0: 500 }, + status: exports.Status.Error, + outcome: { tag: exports.APIResult.Tag.Info } + }; + const updatedReport = exports.updateValidationReport( + { tag: exports.APIResult.Tag.Success, param0: "updated" }, + origReport + ); + assert.deepEqual(updatedReport.result, { tag: exports.APIResult.Tag.Success, param0: "updated" }); + assert.deepEqual(exports.updateValidationReport(null, origReport).result, origReport.result); + + assert.equal(exports.MathOperations.subtract(10.0, 4.0), 6.0); + const mathOps = exports.MathOperations.init(); + assert.equal(mathOps.baseValue, 0.0); + assert.equal(mathOps.add(5.0, 3.0), 8.0); + assert.equal(mathOps.multiply(4.0, 7.0), 28.0); + + const mathOps2 = exports.MathOperations.init(100.0); + assert.equal(mathOps2.baseValue, 100.0); + assert.equal(mathOps2.add(5.0, 3.0), 108.0); + + assert.equal(mathOps.add(5.0), 15.0); + assert.equal(mathOps2.add(5.0), 115.0); + + assert.equal(exports.testStructDefault(), "1.0,2.0,default"); + const customPoint = { x: 10.0, y: 20.0, label: "custom", optCount: null, optFlag: null }; + assert.equal(exports.testStructDefault(customPoint), "10.0,20.0,custom"); + + const container = exports.testContainerWithStruct({ x: 5.0, y: 10.0, label: "test", optCount: null, optFlag: true }); + assert.equal(container.location.x, 5.0); + assert.equal(container.config, null); + container.release(); + + assert.equal(exports.ConfigStruct.defaultConfig, "production"); + assert.equal(exports.ConfigStruct.maxRetries, 3); + assert.equal(exports.ConfigStruct.computedSetting, "Config: production"); + exports.ConfigStruct.defaultConfig = "staging"; + assert.equal(exports.ConfigStruct.computedSetting, "Config: staging"); + exports.ConfigStruct.defaultConfig = "production"; +} /** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ async function BridgeJSRuntimeTests_runAsyncWorks(exports) { diff --git a/Utilities/format.swift b/Utilities/format.swift index 9df282ad7..d26cbe2f3 100755 --- a/Utilities/format.swift +++ b/Utilities/format.swift @@ -38,7 +38,7 @@ func which(_ executable: String) -> URL? { /// Runs the `swift-format` command with the given arguments in the project root. func swiftFormat(_ arguments: [String]) throws { - guard let swiftFormat = which("swift-format") else { + guard let swiftFormat = which("swift") else { print("swift-format not found in PATH") exit(1) } @@ -93,7 +93,7 @@ switch arguments.first { case "lint": try swiftFormat(["lint", "--parallel", "--recursive"] + filesToFormat()) case "format", nil: - try swiftFormat(["format", "--parallel", "--in-place", "--recursive"] + filesToFormat()) + try swiftFormat(["format", "format", "--parallel", "--in-place", "--recursive"] + filesToFormat()) case let subcommand?: print("Unknown subcommand: \(subcommand)") print("Usage: format.swift lint|format")