Skip to content

Commit 6d2c070

Browse files
committed
BridgeJS: Emit static methods and properties on namespaced class entries
The TypeScript `.d.ts` namespace-entry builder for `@JS(namespace:)` classes only emitted the constructor, silently dropping any `@JS static func` or `@JS static var` declared on the class. The equivalent path for non-namespaced classes (`renderExportedClass` → `dtsExportEntryPrinter`) iterates `klass.methods.filter(\.effects.isStatic)` and the static subset of `klass.properties`, so the output mismatched between the two paths. The generated JS class still carries the static members via `declarationPrefixKeyword: "static"` in `renderExportedClass`, and the namespace tree references it by symbol, so the JavaScript runtime works. TypeScript consumers, however, see an incomplete type and cannot call the static factory through `typeof MyNamespace.MyClass` without a hand-written augmentation. Mirror the non-namespaced path inside the `renderClassEntry` closure in `generateTypeScript` so namespaced classes emit their static methods and static properties alongside `new(...)`. Extended `Namespaces.swift` to exercise the codepath by adding a static factory and a static readonly property on the existing `__Swift.Foundation.Greeter` class. The Namespaces snapshot set captures the fixed output.
1 parent af88676 commit 6d2c070

10 files changed

Lines changed: 165 additions & 0 deletions

File tree

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,19 @@ public struct BridgeJSLink {
901901
"new\(self.renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name), effects: constructor.effects));"
902902
)
903903
}
904+
// Static methods and static properties belong on the namespace
905+
// entry alongside the constructor (same shape that
906+
// `renderExportedClass` produces for non-namespaced classes via
907+
// `dtsExportEntryPrinter`).
908+
for method in klass.methods where method.effects.isStatic {
909+
printer.write(
910+
"\(method.name)\(self.renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));"
911+
)
912+
}
913+
for property in klass.properties where property.isStatic {
914+
let readonly = property.isReadonly ? "readonly " : ""
915+
printer.write("\(readonly)\(property.name): \(property.type.tsType);")
916+
}
904917
}
905918
printer.write("}")
906919
return printer.lines

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@
1616
func changeName(name: String) {
1717
self.name = name
1818
}
19+
20+
// Static methods and properties on a namespaced class must land on the
21+
// class's namespace entry (alongside `new`), not on the instance
22+
// interface and not silently dropped. Regression test for the
23+
// `@JS(namespace:)` + `@JS static func` bug where the hierarchical
24+
// exports builder only emitted the constructor.
25+
@JS static func makeDefault() -> Greeter {
26+
return Greeter(name: "World")
27+
}
28+
29+
@JS static var defaultGreeting: String { "Hello, world!" }
1930
}
2031

2132
@JS(namespace: "Utils.Converters") class Converter {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.Global.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,28 @@
3838

3939
}
4040
}
41+
},
42+
{
43+
"abiName" : "bjs___Swift_Foundation_Greeter_static_makeDefault",
44+
"effects" : {
45+
"isAsync" : false,
46+
"isStatic" : true,
47+
"isThrows" : false
48+
},
49+
"name" : "makeDefault",
50+
"parameters" : [
51+
52+
],
53+
"returnType" : {
54+
"swiftHeapObject" : {
55+
"_0" : "Greeter"
56+
}
57+
},
58+
"staticContext" : {
59+
"className" : {
60+
"_0" : "__Swift_Foundation_Greeter"
61+
}
62+
}
4163
}
4264
],
4365
"name" : "Greeter",
@@ -46,7 +68,21 @@
4668
"Foundation"
4769
],
4870
"properties" : [
71+
{
72+
"isReadonly" : true,
73+
"isStatic" : true,
74+
"name" : "defaultGreeting",
75+
"staticContext" : {
76+
"className" : {
77+
"_0" : "__Swift_Foundation_Greeter"
78+
}
79+
},
80+
"type" : {
81+
"string" : {
4982

83+
}
84+
}
85+
}
5086
],
5187
"swiftCallName" : "Greeter"
5288
},

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Namespaces.Global.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,28 @@ public func _bjs___Swift_Foundation_Greeter_greet(_ _self: UnsafeMutableRawPoint
4242
#endif
4343
}
4444

45+
@_expose(wasm, "bjs___Swift_Foundation_Greeter_static_makeDefault")
46+
@_cdecl("bjs___Swift_Foundation_Greeter_static_makeDefault")
47+
public func _bjs___Swift_Foundation_Greeter_static_makeDefault() -> UnsafeMutableRawPointer {
48+
#if arch(wasm32)
49+
let ret = Greeter.makeDefault()
50+
return ret.bridgeJSLowerReturn()
51+
#else
52+
fatalError("Only available on WebAssembly")
53+
#endif
54+
}
55+
56+
@_expose(wasm, "bjs___Swift_Foundation_Greeter_static_defaultGreeting_get")
57+
@_cdecl("bjs___Swift_Foundation_Greeter_static_defaultGreeting_get")
58+
public func _bjs___Swift_Foundation_Greeter_static_defaultGreeting_get() -> Void {
59+
#if arch(wasm32)
60+
let ret = __Swift_Foundation_Greeter.defaultGreeting
61+
return ret.bridgeJSLowerReturn()
62+
#else
63+
fatalError("Only available on WebAssembly")
64+
#endif
65+
}
66+
4567
@_expose(wasm, "bjs___Swift_Foundation_Greeter_deinit")
4668
@_cdecl("bjs___Swift_Foundation_Greeter_deinit")
4769
public func _bjs___Swift_Foundation_Greeter_deinit(_ pointer: UnsafeMutableRawPointer) -> Void {

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,28 @@
3838

3939
}
4040
}
41+
},
42+
{
43+
"abiName" : "bjs___Swift_Foundation_Greeter_static_makeDefault",
44+
"effects" : {
45+
"isAsync" : false,
46+
"isStatic" : true,
47+
"isThrows" : false
48+
},
49+
"name" : "makeDefault",
50+
"parameters" : [
51+
52+
],
53+
"returnType" : {
54+
"swiftHeapObject" : {
55+
"_0" : "Greeter"
56+
}
57+
},
58+
"staticContext" : {
59+
"className" : {
60+
"_0" : "__Swift_Foundation_Greeter"
61+
}
62+
}
4163
}
4264
],
4365
"name" : "Greeter",
@@ -46,7 +68,21 @@
4668
"Foundation"
4769
],
4870
"properties" : [
71+
{
72+
"isReadonly" : true,
73+
"isStatic" : true,
74+
"name" : "defaultGreeting",
75+
"staticContext" : {
76+
"className" : {
77+
"_0" : "__Swift_Foundation_Greeter"
78+
}
79+
},
80+
"type" : {
81+
"string" : {
4982

83+
}
84+
}
85+
}
5086
],
5187
"swiftCallName" : "Greeter"
5288
},

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,28 @@ public func _bjs___Swift_Foundation_Greeter_greet(_ _self: UnsafeMutableRawPoint
4242
#endif
4343
}
4444

45+
@_expose(wasm, "bjs___Swift_Foundation_Greeter_static_makeDefault")
46+
@_cdecl("bjs___Swift_Foundation_Greeter_static_makeDefault")
47+
public func _bjs___Swift_Foundation_Greeter_static_makeDefault() -> UnsafeMutableRawPointer {
48+
#if arch(wasm32)
49+
let ret = Greeter.makeDefault()
50+
return ret.bridgeJSLowerReturn()
51+
#else
52+
fatalError("Only available on WebAssembly")
53+
#endif
54+
}
55+
56+
@_expose(wasm, "bjs___Swift_Foundation_Greeter_static_defaultGreeting_get")
57+
@_cdecl("bjs___Swift_Foundation_Greeter_static_defaultGreeting_get")
58+
public func _bjs___Swift_Foundation_Greeter_static_defaultGreeting_get() -> Void {
59+
#if arch(wasm32)
60+
let ret = __Swift_Foundation_Greeter.defaultGreeting
61+
return ret.bridgeJSLowerReturn()
62+
#else
63+
fatalError("Only available on WebAssembly")
64+
#endif
65+
}
66+
4567
@_expose(wasm, "bjs___Swift_Foundation_Greeter_deinit")
4668
@_cdecl("bjs___Swift_Foundation_Greeter_deinit")
4769
public func _bjs___Swift_Foundation_Greeter_deinit(_ pointer: UnsafeMutableRawPointer) -> Void {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ declare global {
3434
class Greeter {
3535
constructor(name: string);
3636
greet(): string;
37+
makeDefault(): Greeter;
3738
release(): void;
3839
}
3940
class UUID {
@@ -87,6 +88,8 @@ export type Exports = {
8788
Foundation: {
8889
Greeter: {
8990
new(name: string): Greeter;
91+
makeDefault(): Greeter;
92+
readonly defaultGreeting: string;
9093
}
9194
UUID: {
9295
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,16 @@ export async function createInstantiator(options, swift) {
268268
tmpRetString = undefined;
269269
return ret;
270270
}
271+
static makeDefault() {
272+
const ret = instance.exports.bjs___Swift_Foundation_Greeter_static_makeDefault();
273+
return Greeter.__construct(ret);
274+
}
275+
static get defaultGreeting() {
276+
instance.exports.bjs___Swift_Foundation_Greeter_static_defaultGreeting_get();
277+
const ret = tmpRetString;
278+
tmpRetString = undefined;
279+
return ret;
280+
}
271281
}
272282
class Converter extends SwiftHeapObject {
273283
static __construct(ptr) {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export type Exports = {
4747
Foundation: {
4848
Greeter: {
4949
new(name: string): Greeter;
50+
makeDefault(): Greeter;
51+
readonly defaultGreeting: string;
5052
}
5153
UUID: {
5254
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,16 @@ export async function createInstantiator(options, swift) {
268268
tmpRetString = undefined;
269269
return ret;
270270
}
271+
static makeDefault() {
272+
const ret = instance.exports.bjs___Swift_Foundation_Greeter_static_makeDefault();
273+
return Greeter.__construct(ret);
274+
}
275+
static get defaultGreeting() {
276+
instance.exports.bjs___Swift_Foundation_Greeter_static_defaultGreeting_get();
277+
const ret = tmpRetString;
278+
tmpRetString = undefined;
279+
return ret;
280+
}
271281
}
272282
class Converter extends SwiftHeapObject {
273283
static __construct(ptr) {

0 commit comments

Comments
 (0)