diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index ca7bdc3c2..9515fd7cf 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -433,7 +433,11 @@ public class ExportSwift { switch self { case .enumStatic(let enumDef): return property.callName(prefix: enumDef.swiftCallName) - case .classStatic, .classInstance: + case .classStatic(let klass): + // property.callName() would use staticContext (the ABI name) as prefix; + // use swiftCallName directly so the emitted expression is valid Swift. + return "\(klass.swiftCallName).\(property.name)" + case .classInstance: return property.callName() case .structStatic(let structDef): return property.callName(prefix: structDef.swiftCallName) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 63b628eb3..60b34ff16 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -130,6 +130,7 @@ public struct BridgeJSLink { var classLines: [String] = [] var dtsExportLines: [String] = [] var dtsClassLines: [String] = [] + var namespacedClassDtsExportEntries: [String: [String]] = [:] var topLevelTypeLines: [String] = [] var topLevelDtsTypeLines: [String] = [] var importObjectBuilders: [ImportObjectBuilder] = [] @@ -161,13 +162,14 @@ public struct BridgeJSLink { for klass in skeleton.classes { let (jsType, dtsType, dtsExportEntry) = try renderExportedClass(klass) data.classLines.append(contentsOf: jsType) + data.dtsClassLines.append(contentsOf: dtsType) if klass.namespace == nil { data.exportsLines.append("\(klass.name),") data.dtsExportLines.append(contentsOf: dtsExportEntry) + } else { + data.namespacedClassDtsExportEntries[klass.name] = dtsExportEntry } - - data.dtsClassLines.append(contentsOf: dtsType) } // Process enums - collect top-level definitions and export entries @@ -884,32 +886,21 @@ public struct BridgeJSLink { printer.write(lines: generateImportedTypeDefinitions()) // Exports type + let hierarchicalExportLines = namespaceBuilder.buildHierarchicalExportsType( + exportedSkeletons: exportedSkeletons, + renderClassEntry: { klass in + data.namespacedClassDtsExportEntries[klass.name] ?? [] + }, + renderFunctionSignature: { function in + "\(function.name)\(self.renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" + } + ) printer.write("export type Exports = {") printer.indent { // Add non-namespaced items printer.write(lines: data.dtsExportLines) - // Add hierarchical namespaced items - let hierarchicalLines = namespaceBuilder.buildHierarchicalExportsType( - exportedSkeletons: exportedSkeletons, - renderClassEntry: { klass in - let printer = CodeFragmentPrinter() - printer.write("\(klass.name): {") - printer.indent { - if let constructor = klass.constructor { - printer.write( - "new\(self.renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name), effects: constructor.effects));" - ) - } - } - printer.write("}") - return printer.lines - }, - renderFunctionSignature: { function in - "\(function.name)\(self.renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" - } - ) - printer.write(lines: hierarchicalLines) + printer.write(lines: hierarchicalExportLines) } printer.write("}") diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Namespaces.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Namespaces.swift index cbe146ff5..7cd63c698 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Namespaces.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/Namespaces.swift @@ -16,6 +16,17 @@ func changeName(name: String) { self.name = name } + + // Static methods and properties on a namespaced class must land on the + // class's namespace entry (alongside `new`), not on the instance + // interface and not silently dropped. Regression test for the + // `@JS(namespace:)` + `@JS static func` bug where the hierarchical + // exports builder only emitted the constructor. + @JS static func makeDefault() -> Greeter { + return Greeter(name: "World") + } + + @JS static var defaultGreeting: String { "Hello, world!" } } @JS(namespace: "Utils.Converters") class Converter { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.Global.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.Global.json index 9ca72009d..080f9f959 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.Global.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.Global.json @@ -38,6 +38,28 @@ } } + }, + { + "abiName" : "bjs___Swift_Foundation_Greeter_static_makeDefault", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "makeDefault", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + }, + "staticContext" : { + "className" : { + "_0" : "__Swift_Foundation_Greeter" + } + } } ], "name" : "Greeter", @@ -46,7 +68,21 @@ "Foundation" ], "properties" : [ + { + "isReadonly" : true, + "isStatic" : true, + "name" : "defaultGreeting", + "staticContext" : { + "className" : { + "_0" : "__Swift_Foundation_Greeter" + } + }, + "type" : { + "string" : { + } + } + } ], "swiftCallName" : "Greeter" }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.Global.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.Global.swift index 86f0b8478..baa867eed 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.Global.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.Global.swift @@ -42,6 +42,28 @@ public func _bjs___Swift_Foundation_Greeter_greet(_ _self: UnsafeMutableRawPoint #endif } +@_expose(wasm, "bjs___Swift_Foundation_Greeter_static_makeDefault") +@_cdecl("bjs___Swift_Foundation_Greeter_static_makeDefault") +public func _bjs___Swift_Foundation_Greeter_static_makeDefault() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Greeter.makeDefault() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs___Swift_Foundation_Greeter_static_defaultGreeting_get") +@_cdecl("bjs___Swift_Foundation_Greeter_static_defaultGreeting_get") +public func _bjs___Swift_Foundation_Greeter_static_defaultGreeting_get() -> Void { + #if arch(wasm32) + let ret = Greeter.defaultGreeting + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs___Swift_Foundation_Greeter_deinit") @_cdecl("bjs___Swift_Foundation_Greeter_deinit") public func _bjs___Swift_Foundation_Greeter_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.json index 0713a2f30..d471eeaca 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.json @@ -38,6 +38,28 @@ } } + }, + { + "abiName" : "bjs___Swift_Foundation_Greeter_static_makeDefault", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "makeDefault", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + }, + "staticContext" : { + "className" : { + "_0" : "__Swift_Foundation_Greeter" + } + } } ], "name" : "Greeter", @@ -46,7 +68,21 @@ "Foundation" ], "properties" : [ + { + "isReadonly" : true, + "isStatic" : true, + "name" : "defaultGreeting", + "staticContext" : { + "className" : { + "_0" : "__Swift_Foundation_Greeter" + } + }, + "type" : { + "string" : { + } + } + } ], "swiftCallName" : "Greeter" }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.swift index 86f0b8478..baa867eed 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.swift @@ -42,6 +42,28 @@ public func _bjs___Swift_Foundation_Greeter_greet(_ _self: UnsafeMutableRawPoint #endif } +@_expose(wasm, "bjs___Swift_Foundation_Greeter_static_makeDefault") +@_cdecl("bjs___Swift_Foundation_Greeter_static_makeDefault") +public func _bjs___Swift_Foundation_Greeter_static_makeDefault() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Greeter.makeDefault() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs___Swift_Foundation_Greeter_static_defaultGreeting_get") +@_cdecl("bjs___Swift_Foundation_Greeter_static_defaultGreeting_get") +public func _bjs___Swift_Foundation_Greeter_static_defaultGreeting_get() -> Void { + #if arch(wasm32) + let ret = Greeter.defaultGreeting + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs___Swift_Foundation_Greeter_deinit") @_cdecl("bjs___Swift_Foundation_Greeter_deinit") public func _bjs___Swift_Foundation_Greeter_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.d.ts index 4b7851c3e..1353220bc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.d.ts @@ -34,6 +34,7 @@ declare global { class Greeter { constructor(name: string); greet(): string; + makeDefault(): Greeter; release(): void; } class UUID { @@ -87,6 +88,8 @@ export type Exports = { Foundation: { Greeter: { new(name: string): Greeter; + makeDefault(): Greeter; + readonly defaultGreeting: string; } UUID: { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js index 4a6ee1990..f4596dba7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js @@ -268,6 +268,16 @@ export async function createInstantiator(options, swift) { tmpRetString = undefined; return ret; } + static makeDefault() { + const ret = instance.exports.bjs___Swift_Foundation_Greeter_static_makeDefault(); + return Greeter.__construct(ret); + } + static get defaultGreeting() { + instance.exports.bjs___Swift_Foundation_Greeter_static_defaultGreeting_get(); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } } class Converter extends SwiftHeapObject { static __construct(ptr) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.d.ts index fff65ce6d..6b2d65cd8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.d.ts @@ -47,6 +47,8 @@ export type Exports = { Foundation: { Greeter: { new(name: string): Greeter; + makeDefault(): Greeter; + readonly defaultGreeting: string; } UUID: { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js index 267d96152..92ce69cbb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js @@ -268,6 +268,16 @@ export async function createInstantiator(options, swift) { tmpRetString = undefined; return ret; } + static makeDefault() { + const ret = instance.exports.bjs___Swift_Foundation_Greeter_static_makeDefault(); + return Greeter.__construct(ret); + } + static get defaultGreeting() { + instance.exports.bjs___Swift_Foundation_Greeter_static_defaultGreeting_get(); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } } class Converter extends SwiftHeapObject { static __construct(ptr) { diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 3db9497fc..6bfa40d65 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -433,6 +433,12 @@ class UUID { @JS func uuidString() -> String { return value } + + @JS static func fromValue(_ value: String) -> UUID { + return UUID(value: value) + } + + @JS static var placeholder: String { "00000000-0000-0000-0000-000000000000" } } @JS func createUUID(value: String) -> UUID { diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index 71e5ef537..c91ddb81e 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -8602,6 +8602,28 @@ public func _bjs___Swift_Foundation_UUID_uuidString(_ _self: UnsafeMutableRawPoi #endif } +@_expose(wasm, "bjs___Swift_Foundation_UUID_static_fromValue") +@_cdecl("bjs___Swift_Foundation_UUID_static_fromValue") +public func _bjs___Swift_Foundation_UUID_static_fromValue(_ valueBytes: Int32, _ valueLength: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = UUID.fromValue(_: String.bridgeJSLiftParameter(valueBytes, valueLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs___Swift_Foundation_UUID_static_placeholder_get") +@_cdecl("bjs___Swift_Foundation_UUID_static_placeholder_get") +public func _bjs___Swift_Foundation_UUID_static_placeholder_get() -> Void { + #if arch(wasm32) + let ret = UUID.placeholder + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs___Swift_Foundation_UUID_deinit") @_cdecl("bjs___Swift_Foundation_UUID_deinit") public func _bjs___Swift_Foundation_UUID_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index 60b02020d..46ae95a32 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -1108,6 +1108,36 @@ } } + }, + { + "abiName" : "bjs___Swift_Foundation_UUID_static_fromValue", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "fromValue", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "UUID" + } + }, + "staticContext" : { + "className" : { + "_0" : "__Swift_Foundation_UUID" + } + } } ], "name" : "UUID", @@ -1116,7 +1146,21 @@ "Foundation" ], "properties" : [ + { + "isReadonly" : true, + "isStatic" : true, + "name" : "placeholder", + "staticContext" : { + "className" : { + "_0" : "__Swift_Foundation_UUID" + } + }, + "type" : { + "string" : { + } + } + } ], "swiftCallName" : "UUID" }, diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index bb285aaee..0af033226 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -624,6 +624,11 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { roundTrippedUUID.release(); uuid.release(); + const uuidFromStatic = exports.__Swift.Foundation.UUID.fromValue("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); + assert.equal(uuidFromStatic.uuidString(), "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); + uuidFromStatic.release(); + assert.equal(exports.__Swift.Foundation.UUID.placeholder, "00000000-0000-0000-0000-000000000000"); + const createdServer = exports.createHTTPServer(); createdServer.call(exports.Networking.API.Method.Get); createdServer.release();