diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index ceaaae8f1..6c283a3b4 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -232,7 +232,9 @@ public struct BridgeJSLink { 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 { + data.topLevelDtsTypeLines.append(contentsOf: dtsType) + } if structDefinition.namespace == nil && (!jsStruct.isEmpty || !dtsExportEntry.isEmpty) { structExportEntries.append((js: jsStruct, dts: dtsExportEntry)) @@ -492,7 +494,7 @@ public struct BridgeJSLink { printer.write("bjs[\"swift_js_struct_lower_\(structDef.abiName)\"] = function(objectId) {") printer.indent { printer.write( - "\(JSGlueVariableScope.reservedStructHelpers).\(structDef.name).lower(\(JSGlueVariableScope.reservedSwift).memory.getObject(objectId));" + "\(JSGlueVariableScope.reservedStructHelpers).\(structDef.abiName).lower(\(JSGlueVariableScope.reservedSwift).memory.getObject(objectId));" ) } printer.write("}") @@ -500,7 +502,7 @@ public struct BridgeJSLink { printer.write("bjs[\"swift_js_struct_lift_\(structDef.abiName)\"] = function() {") printer.indent { printer.write( - "const value = \(JSGlueVariableScope.reservedStructHelpers).\(structDef.name).lift();" + "const value = \(JSGlueVariableScope.reservedStructHelpers).\(structDef.abiName).lift();" ) printer.write("return \(JSGlueVariableScope.reservedSwift).memory.retain(value);") } @@ -1005,7 +1007,7 @@ public struct BridgeJSLink { let structScope = JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry) let fragment = IntrinsicJSFragment.structHelper(structDefinition: structDef, allStructs: allStructs) _ = try fragment.printCode( - [structDef.name], + [structDef.abiName], IntrinsicJSFragment.PrintCodeContext( scope: structScope, printer: structPrinter, @@ -1159,10 +1161,10 @@ public struct BridgeJSLink { for skeleton in skeletons.compactMap(\.exported) { for structDef in skeleton.structs { printer.write( - "const \(structDef.name)Helpers = __bjs_create\(structDef.name)Helpers();" + "const \(structDef.abiName)Helpers = __bjs_create\(structDef.abiName)Helpers();" ) printer.write( - "\(JSGlueVariableScope.reservedStructHelpers).\(structDef.name) = \(structDef.name)Helpers;" + "\(JSGlueVariableScope.reservedStructHelpers).\(structDef.abiName) = \(structDef.abiName)Helpers;" ) printer.nextLine() } @@ -2616,6 +2618,7 @@ extension BridgeJSLink { var functions: [ExportedFunction] = [] var classes: [ExportedClass] = [] var enums: [ExportedEnum] = [] + var structs: [ExportedStruct] = [] var staticProperties: [ExportedProperty] = [] var functionJsLines: [(name: String, lines: [String])] = [] var functionDtsLines: [(name: String, lines: [String])] = [] @@ -2664,6 +2667,14 @@ extension BridgeJSLink { currentNode.content.classes.append(klass) } + for structDef in skeleton.structs where structDef.namespace != nil { + var currentNode = rootNode + for part in structDef.namespace! { + currentNode = currentNode.addChild(part) + } + currentNode.content.structs.append(structDef) + } + for enumDef in skeleton.enums where enumDef.namespace != nil && enumDef.enumType != .namespace { var currentNode = rootNode for part in enumDef.namespace! { @@ -2845,8 +2856,18 @@ extension BridgeJSLink { } } + private func hasExportContent(node: NamespaceNode) -> Bool { + if !node.content.classDtsLines.isEmpty || !node.content.enumDtsLines.isEmpty + || !node.content.functionDtsLines.isEmpty || !node.content.staticProperties.isEmpty + { + return true + } + return node.children.values.contains(where: { hasExportContent(node: $0) }) + } + private func printExportsTypeHierarchy(node: NamespaceNode, printer: CodeFragmentPrinter) { for (childName, childNode) in node.children.sorted(by: { $0.key < $1.key }) { + guard hasExportContent(node: childNode) else { continue } printer.write("\(childName): {") printer.indent { for (_, lines) in childNode.content.classDtsLines.sorted(by: { $0.name < $1.name }) { @@ -2983,8 +3004,8 @@ extension BridgeJSLink { renderTSSignatureCallback: @escaping ([Parameter], BridgeType, Effects) -> String ) { func hasContent(node: NamespaceNode) -> Bool { - // Enums are always included - if !node.content.enums.isEmpty { + // Enums and structs are always included + if !node.content.enums.isEmpty || !node.content.structs.isEmpty { return true } @@ -3164,6 +3185,23 @@ extension BridgeJSLink { } } + // Generate struct interface definitions + let sortedStructs = childNode.content.structs.sorted { $0.name < $1.name } + for structDef in sortedStructs { + let instanceProps = structDef.properties.filter { !$0.isStatic } + printer.write("export interface \(structDef.name) {") + printer.indent { + for property in instanceProps { + let tsType = BridgeJSLink.resolveTypeScriptType( + property.type, + exportedSkeletons: exportedSkeletons + ) + printer.write("\(property.name): \(tsType);") + } + } + printer.write("}") + } + // Only include functions and properties when exposeToGlobal is true if exposeToGlobal { let sortedFunctions = childNode.content.functions.sorted { $0.name < $1.name } @@ -3610,7 +3648,7 @@ extension BridgeType { case .associatedValueEnum(let name): return "\(name)Tag" case .swiftStruct(let name): - return name.components(separatedBy: ".").last ?? name + return name 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 1ad397f71..accc2a287 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -944,7 +944,7 @@ struct IntrinsicJSFragment: Sendable { fullName: String, kind: JSOptionalKind ) -> IntrinsicJSFragment { - let base = fullName.components(separatedBy: ".").last ?? fullName + let base = fullName.replacingOccurrences(of: ".", with: "_") let absenceLiteral = kind.absenceLiteral return IntrinsicJSFragment( parameters: [], @@ -1300,7 +1300,7 @@ struct IntrinsicJSFragment: Sendable { let base = fullName.components(separatedBy: ".").last ?? fullName return .associatedEnumLowerParameter(enumBase: base) case .swiftStruct(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName + let base = fullName.replacingOccurrences(of: ".", with: "_") return swiftStructLowerParameter(structBase: base) case .closure: return IntrinsicJSFragment( @@ -1360,7 +1360,7 @@ struct IntrinsicJSFragment: Sendable { let base = fullName.components(separatedBy: ".").last ?? fullName return .associatedEnumLiftReturn(enumBase: base) case .swiftStruct(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName + let base = fullName.replacingOccurrences(of: ".", with: "_") return swiftStructLiftReturn(structBase: base) case .closure: return IntrinsicJSFragment( @@ -1446,7 +1446,7 @@ struct IntrinsicJSFragment: Sendable { case .importTS: return .jsObjectLiftRetainedObjectId case .exportSwift: - let base = fullName.components(separatedBy: ".").last ?? fullName + let base = fullName.replacingOccurrences(of: ".", with: "_") return IntrinsicJSFragment( parameters: [], printCode: { arguments, context in @@ -1805,7 +1805,7 @@ struct IntrinsicJSFragment: Sendable { } static func swiftStructLowerReturn(fullName: String) -> IntrinsicJSFragment { - swiftStructLower(structBase: fullName.components(separatedBy: ".").last ?? fullName) + swiftStructLower(structBase: fullName.replacingOccurrences(of: ".", with: "_")) } static func swiftStructLowerParameter(structBase: String) -> IntrinsicJSFragment { @@ -2008,7 +2008,7 @@ struct IntrinsicJSFragment: Sendable { } ) case .swiftStruct(let fullName): - let structBase = fullName.components(separatedBy: ".").last ?? fullName + let structBase = fullName.replacingOccurrences(of: ".", with: "_") return IntrinsicJSFragment( parameters: [], printCode: { arguments, context in @@ -2130,7 +2130,7 @@ struct IntrinsicJSFragment: Sendable { } ) case .swiftStruct(let fullName): - let structBase = fullName.components(separatedBy: ".").last ?? fullName + let structBase = fullName.replacingOccurrences(of: ".", with: "_") return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in @@ -2426,7 +2426,7 @@ struct IntrinsicJSFragment: Sendable { ) try printer.indent { printer.write( - "\(JSGlueVariableScope.reservedStructHelpers).\(structDef.name).lower(this);" + "\(JSGlueVariableScope.reservedStructHelpers).\(structDef.abiName).lower(this);" ) var paramForwardings: [String] = [] @@ -2502,7 +2502,7 @@ struct IntrinsicJSFragment: Sendable { let printer = context.printer let value = arguments[0] printer.write( - "\(JSGlueVariableScope.reservedStructHelpers).\(nestedName).lower(\(value));" + "\(JSGlueVariableScope.reservedStructHelpers).\(nestedName.replacingOccurrences(of: ".", with: "_")).lower(\(value));" ) return [] } @@ -2540,7 +2540,7 @@ struct IntrinsicJSFragment: Sendable { let (scope, printer) = (context.scope, context.printer) let structVar = scope.variable("struct") printer.write( - "const \(structVar) = \(JSGlueVariableScope.reservedStructHelpers).\(nestedName).lift();" + "const \(structVar) = \(JSGlueVariableScope.reservedStructHelpers).\(nestedName.replacingOccurrences(of: ".", with: "_")).lift();" ) return [structVar] } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/NestedType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/NestedType.swift index bd2bcc4fe..12fccb379 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/NestedType.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/NestedType.swift @@ -8,3 +8,14 @@ var score: Double } } + +@JS class Player { + @JS func getTag() -> String { + return "player" + } + + @JS struct Stats { + var level: Int + var rating: String + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.json index e8666bbc4..f924b3eba 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.json @@ -26,6 +26,32 @@ ], "swiftCallName" : "User" + }, + { + "methods" : [ + { + "abiName" : "bjs_Player_getTag", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getTag", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "Player", + "properties" : [ + + ], + "swiftCallName" : "Player" } ], "enums" : [ @@ -79,6 +105,47 @@ } ], "swiftCallName" : "User.Stats" + }, + { + "methods" : [ + + ], + "name" : "Stats", + "namespace" : [ + "Player" + ], + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "level", + "namespace" : [ + "Player" + ], + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "rating", + "namespace" : [ + "Player" + ], + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "Player.Stats" } ] }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.swift index 35ead0856..ed1a080e9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.swift @@ -46,6 +46,54 @@ fileprivate func _bjs_struct_lift_User_Stats_extern() -> Int32 { return _bjs_struct_lift_User_Stats_extern() } +extension Player.Stats: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> Player.Stats { + let rating = String.bridgeJSStackPop() + let level = Int.bridgeJSStackPop() + return Player.Stats(level: level, rating: rating) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.level.bridgeJSStackPush() + self.rating.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_Player_Stats(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_Player_Stats())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_Player_Stats") +fileprivate func _bjs_struct_lower_Player_Stats_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_Player_Stats_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_Player_Stats(_ objectId: Int32) -> Void { + return _bjs_struct_lower_Player_Stats_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_Player_Stats") +fileprivate func _bjs_struct_lift_Player_Stats_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_Player_Stats_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_Player_Stats() -> Int32 { + return _bjs_struct_lift_Player_Stats_extern() +} + @_expose(wasm, "bjs_User_getName") @_cdecl("bjs_User_getName") public func _bjs_User_getName(_ _self: UnsafeMutableRawPointer) -> Void { @@ -86,4 +134,46 @@ fileprivate func _bjs_User_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> In #endif @inline(never) fileprivate func _bjs_User_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_User_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_Player_getTag") +@_cdecl("bjs_Player_getTag") +public func _bjs_Player_getTag(_ _self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Player.bridgeJSLiftParameter(_self).getTag() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Player_deinit") +@_cdecl("bjs_Player_deinit") +public func _bjs_Player_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension Player: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_Player_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_Player_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_Player_wrap") +fileprivate func _bjs_Player_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_Player_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_Player_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_Player_wrap_extern(pointer) } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.d.ts index 2d3942e06..4e966661e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.d.ts @@ -4,9 +4,17 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. -export interface Stats { - health: number; - score: number; +export namespace Player { + export interface Stats { + level: number; + rating: string; + } +} +export namespace User { + export interface Stats { + health: number; + score: number; + } } /// Represents a Swift heap object like a class instance or an actor instance. export interface SwiftHeapObject { @@ -18,9 +26,14 @@ export interface SwiftHeapObject { export interface User extends SwiftHeapObject { getName(): string; } +export interface Player extends SwiftHeapObject { + getTag(): string; +} export type Exports = { User: { } + Player: { + } } export type Imports = { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js index cf24e7e2d..f276877e0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js @@ -30,7 +30,7 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createStatsHelpers = () => ({ + const __bjs_createUser_StatsHelpers = () => ({ lower: (value) => { i32Stack.push((value.health | 0)); f64Stack.push(value.score); @@ -41,6 +41,20 @@ export async function createInstantiator(options, swift) { return { health: int, score: f64 }; } }); + const __bjs_createPlayer_StatsHelpers = () => ({ + lower: (value) => { + i32Stack.push((value.level | 0)); + const bytes = textEncoder.encode(value.rating); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + const int = i32Stack.pop(); + return { level: int, rating: string }; + } + }); return { /** @@ -110,10 +124,17 @@ export async function createInstantiator(options, swift) { return i64Stack.pop(); } bjs["swift_js_struct_lower_User_Stats"] = function(objectId) { - structHelpers.Stats.lower(swift.memory.getObject(objectId)); + structHelpers.User_Stats.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_User_Stats"] = function() { - const value = structHelpers.Stats.lift(); + const value = structHelpers.User_Stats.lift(); + return swift.memory.retain(value); + } + bjs["swift_js_struct_lower_Player_Stats"] = function(objectId) { + structHelpers.Player_Stats.lower(swift.memory.getObject(objectId)); + } + bjs["swift_js_struct_lift_Player_Stats"] = function() { + const value = structHelpers.Player_Stats.lift(); return swift.memory.retain(value); } bjs["swift_js_return_optional_bool"] = function(isSome, value) { @@ -210,6 +231,10 @@ export async function createInstantiator(options, swift) { if (!importObject["TestModule"]) { importObject["TestModule"] = {}; } + importObject["TestModule"]["bjs_Player_wrap"] = function(pointer) { + const obj = _exports['Player'].__construct(pointer); + return swift.memory.retain(obj); + }; importObject["TestModule"]["bjs_User_wrap"] = function(pointer) { const obj = _exports['User'].__construct(pointer); return swift.memory.retain(obj); @@ -291,11 +316,31 @@ export async function createInstantiator(options, swift) { return ret; } } - const StatsHelpers = __bjs_createStatsHelpers(); - structHelpers.Stats = StatsHelpers; + class Player extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Player_deinit, Player.prototype, null); + } + + getTag() { + instance.exports.bjs_Player_getTag(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + } + const User_StatsHelpers = __bjs_createUser_StatsHelpers(); + structHelpers.User_Stats = User_StatsHelpers; + + const Player_StatsHelpers = __bjs_createPlayer_StatsHelpers(); + structHelpers.Player_Stats = Player_StatsHelpers; const exports = { User, + Player, + Player: { + }, + User: { + }, }; _exports = exports; return exports; diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index c81622f0d..c6e216203 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -1299,6 +1299,24 @@ enum GraphOperations { } } +@JS enum NestedStructGroupA { + @JS struct Metadata { + var label: String + var count: Int + } + + @JS static func roundtripMetadata(_ m: Metadata) -> Metadata { m } +} + +@JS enum NestedStructGroupB { + @JS struct Metadata { + var tag: String + var value: Double + } + + @JS static func roundtripMetadata(_ m: Metadata) -> Metadata { m } +} + class ExportAPITests: XCTestCase { func testAll() { var hasDeinitGreeter = false diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index 771838716..f695917db 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -4809,6 +4809,28 @@ public func _bjs_StaticPropertyNamespace_NestedProperties_static_nestedDouble_se #endif } +@_expose(wasm, "bjs_NestedStructGroupA_static_roundtripMetadata") +@_cdecl("bjs_NestedStructGroupA_static_roundtripMetadata") +public func _bjs_NestedStructGroupA_static_roundtripMetadata() -> Void { + #if arch(wasm32) + let ret = NestedStructGroupA.roundtripMetadata(_: NestedStructGroupA.Metadata.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_NestedStructGroupB_static_roundtripMetadata") +@_cdecl("bjs_NestedStructGroupB_static_roundtripMetadata") +public func _bjs_NestedStructGroupB_static_roundtripMetadata() -> Void { + #if arch(wasm32) + let ret = NestedStructGroupB.roundtripMetadata(_: NestedStructGroupB.Metadata.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_IntegerTypesSupportExports_static_roundTripInt") @_cdecl("bjs_IntegerTypesSupportExports_static_roundTripInt") public func _bjs_IntegerTypesSupportExports_static_roundTripInt(_ v: Int32) -> Int32 { @@ -5319,6 +5341,102 @@ extension APIOptionalResult: _BridgedSwiftAssociatedValueEnum { } } +extension NestedStructGroupA.Metadata: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> NestedStructGroupA.Metadata { + let count = Int.bridgeJSStackPop() + let label = String.bridgeJSStackPop() + return NestedStructGroupA.Metadata(label: label, count: count) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.label.bridgeJSStackPush() + self.count.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_NestedStructGroupA_Metadata(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_NestedStructGroupA_Metadata())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_NestedStructGroupA_Metadata") +fileprivate func _bjs_struct_lower_NestedStructGroupA_Metadata_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_NestedStructGroupA_Metadata_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_NestedStructGroupA_Metadata(_ objectId: Int32) -> Void { + return _bjs_struct_lower_NestedStructGroupA_Metadata_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_NestedStructGroupA_Metadata") +fileprivate func _bjs_struct_lift_NestedStructGroupA_Metadata_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_NestedStructGroupA_Metadata_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_NestedStructGroupA_Metadata() -> Int32 { + return _bjs_struct_lift_NestedStructGroupA_Metadata_extern() +} + +extension NestedStructGroupB.Metadata: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> NestedStructGroupB.Metadata { + let value = Double.bridgeJSStackPop() + let tag = String.bridgeJSStackPop() + return NestedStructGroupB.Metadata(tag: tag, value: value) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.tag.bridgeJSStackPush() + self.value.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_NestedStructGroupB_Metadata(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_NestedStructGroupB_Metadata())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_NestedStructGroupB_Metadata") +fileprivate func _bjs_struct_lower_NestedStructGroupB_Metadata_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_NestedStructGroupB_Metadata_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_NestedStructGroupB_Metadata(_ objectId: Int32) -> Void { + return _bjs_struct_lower_NestedStructGroupB_Metadata_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_NestedStructGroupB_Metadata") +fileprivate func _bjs_struct_lift_NestedStructGroupB_Metadata_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_NestedStructGroupB_Metadata_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_NestedStructGroupB_Metadata() -> Int32 { + return _bjs_struct_lift_NestedStructGroupB_Metadata_extern() +} + extension Point: _BridgedSwiftStruct { @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> Point { let y = Int.bridgeJSStackPop() diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index 7a3e7f7fe..5222902e0 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -9163,6 +9163,100 @@ { "cases" : [ + ], + "emitStyle" : "const", + "name" : "NestedStructGroupA", + "staticMethods" : [ + { + "abiName" : "bjs_NestedStructGroupA_static_roundtripMetadata", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "roundtripMetadata", + "namespace" : [ + "NestedStructGroupA" + ], + "parameters" : [ + { + "label" : "_", + "name" : "m", + "type" : { + "swiftStruct" : { + "_0" : "NestedStructGroupA.Metadata" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "NestedStructGroupA.Metadata" + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "NestedStructGroupA" + } + } + } + ], + "staticProperties" : [ + + ], + "swiftCallName" : "NestedStructGroupA", + "tsFullPath" : "NestedStructGroupA" + }, + { + "cases" : [ + + ], + "emitStyle" : "const", + "name" : "NestedStructGroupB", + "staticMethods" : [ + { + "abiName" : "bjs_NestedStructGroupB_static_roundtripMetadata", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "roundtripMetadata", + "namespace" : [ + "NestedStructGroupB" + ], + "parameters" : [ + { + "label" : "_", + "name" : "m", + "type" : { + "swiftStruct" : { + "_0" : "NestedStructGroupB.Metadata" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "NestedStructGroupB.Metadata" + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "NestedStructGroupB" + } + } + } + ], + "staticProperties" : [ + + ], + "swiftCallName" : "NestedStructGroupB", + "tsFullPath" : "NestedStructGroupB" + }, + { + "cases" : [ + ], "emitStyle" : "const", "name" : "IntegerTypesSupportExports", @@ -14890,6 +14984,85 @@ } ], "structs" : [ + { + "methods" : [ + + ], + "name" : "Metadata", + "namespace" : [ + "NestedStructGroupA" + ], + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "label", + "namespace" : [ + "NestedStructGroupA" + ], + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "count", + "namespace" : [ + "NestedStructGroupA" + ], + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "NestedStructGroupA.Metadata" + }, + { + "methods" : [ + + ], + "name" : "Metadata", + "namespace" : [ + "NestedStructGroupB" + ], + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "tag", + "namespace" : [ + "NestedStructGroupB" + ], + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "value", + "namespace" : [ + "NestedStructGroupB" + ], + "type" : { + "double" : { + + } + } + } + ], + "swiftCallName" : "NestedStructGroupB.Metadata" + }, { "methods" : [ diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 10c73ec2f..42aa9feaf 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -1023,6 +1023,17 @@ function testStructSupport(exports) { const fooContainerResult2 = exports.roundTripFooContainer(fooContainer2); assert.equal(fooContainerResult2.foo.value, "first"); assert.equal(fooContainerResult2.optionalFoo, null); + + // Test nested structs with same short name under different parents + const metaA = { label: "hello", count: 42 }; + const metaAResult = exports.NestedStructGroupA.roundtripMetadata(metaA); + assert.equal(metaAResult.label, "hello"); + assert.equal(metaAResult.count, 42); + + const metaB = { tag: "world", value: 3.14 }; + const metaBResult = exports.NestedStructGroupB.roundtripMetadata(metaB); + assert.equal(metaBResult.tag, "world"); + assert.equal(metaBResult.value, 3.14); } /** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */