Skip to content

Commit c47beee

Browse files
authored
Merge pull request #668 from PassiveLogic/kr/fix-namespace-exports-access
BridgeJS: fix namespaced Swift class access in JS codegen
2 parents bf69cdb + 8ebcc22 commit c47beee

25 files changed

+870
-33
lines changed

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -978,7 +978,9 @@ public struct BridgeJSLink {
978978
[structDef.name],
979979
IntrinsicJSFragment.PrintCodeContext(
980980
scope: structScope,
981-
printer: structPrinter
981+
printer: structPrinter,
982+
hasDirectAccessToSwiftClass: false,
983+
classNamespaces: intrinsicRegistry.classNamespaces
982984
)
983985
)
984986
bodyPrinter.write(lines: structPrinter.lines)
@@ -995,7 +997,9 @@ public struct BridgeJSLink {
995997
[enumDef.valuesName],
996998
IntrinsicJSFragment.PrintCodeContext(
997999
scope: enumScope,
998-
printer: enumPrinter
1000+
printer: enumPrinter,
1001+
hasDirectAccessToSwiftClass: false,
1002+
classNamespaces: intrinsicRegistry.classNamespaces
9991003
)
10001004
)
10011005
bodyPrinter.write(lines: enumPrinter.lines)
@@ -1079,6 +1083,14 @@ public struct BridgeJSLink {
10791083

10801084
public func link() throws -> (outputJs: String, outputDts: String) {
10811085
intrinsicRegistry.reset()
1086+
intrinsicRegistry.classNamespaces = skeletons.reduce(into: [:]) { result, unified in
1087+
guard let skeleton = unified.exported else { return }
1088+
for klass in skeleton.classes {
1089+
if let namespace = klass.namespace {
1090+
result[klass.name] = namespace
1091+
}
1092+
}
1093+
}
10821094
let data = try collectLinkData()
10831095
let outputJs = try generateJavaScript(data: data)
10841096
let outputDts = generateTypeScript(data: data)
@@ -1144,8 +1156,11 @@ public struct BridgeJSLink {
11441156

11451157
for klass in classes.sorted(by: { $0.name < $1.name }) {
11461158
let wrapperFunctionName = "bjs_\(klass.name)_wrap"
1159+
let namespacePath = (klass.namespace ?? []).map { ".\($0)" }.joined()
1160+
let exportsPath =
1161+
namespacePath.isEmpty ? "_exports['\(klass.name)']" : "_exports\(namespacePath).\(klass.name)"
11471162
wrapperLines.append("importObject[\"\(moduleName)\"][\"\(wrapperFunctionName)\"] = function(pointer) {")
1148-
wrapperLines.append(" const obj = _exports['\(klass.name)'].__construct(pointer);")
1163+
wrapperLines.append(" const obj = \(exportsPath).__construct(pointer);")
11491164
wrapperLines.append(" return \(JSGlueVariableScope.reservedSwift).memory.retain(obj);")
11501165
wrapperLines.append("};")
11511166
}
@@ -1252,7 +1267,8 @@ public struct BridgeJSLink {
12521267
self.context = IntrinsicJSFragment.PrintCodeContext(
12531268
scope: scope,
12541269
printer: body,
1255-
hasDirectAccessToSwiftClass: hasDirectAccessToSwiftClass
1270+
hasDirectAccessToSwiftClass: hasDirectAccessToSwiftClass,
1271+
classNamespaces: intrinsicRegistry.classNamespaces
12561272
)
12571273
}
12581274

@@ -2147,7 +2163,9 @@ extension BridgeJSLink {
21472163
self.context = context
21482164
self.printContext = IntrinsicJSFragment.PrintCodeContext(
21492165
scope: scope,
2150-
printer: body
2166+
printer: body,
2167+
hasDirectAccessToSwiftClass: false,
2168+
classNamespaces: intrinsicRegistry.classNamespaces
21512169
)
21522170
}
21532171

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,36 @@ struct IntrinsicJSFragment: Sendable {
137137
/// The printer to print the main fragment code.
138138
var printer: CodeFragmentPrinter
139139
/// Whether the fragment has direct access to the SwiftHeapObject classes.
140-
/// If false, the fragment needs to use `_exports["<class name>"]` to access the class.
140+
/// If false, the fragment needs to use `_exports` to access the class.
141141
var hasDirectAccessToSwiftClass: Bool = true
142+
/// Maps class names to their namespace path components for resolving `_exports` access.
143+
var classNamespaces: [String: [String]] = [:]
142144

143145
func with<T>(_ keyPath: WritableKeyPath<PrintCodeContext, T>, _ value: T) -> PrintCodeContext {
144146
var new = self
145147
new[keyPath: keyPath] = value
146148
return new
147149
}
150+
151+
private func unqualifiedClassName(for qualifiedName: String) -> String {
152+
qualifiedName.split(separator: ".").last.map(String.init) ?? qualifiedName
153+
}
154+
155+
private func exportsAccess(forClass name: String) -> String {
156+
if let namespace = classNamespaces[name], !namespace.isEmpty {
157+
let path = namespace.map { ".\($0)" }.joined()
158+
return "_exports\(path).\(name)"
159+
}
160+
return "_exports['\(name)']"
161+
}
162+
163+
func classReference(forQualifiedName qualifiedName: String) -> String {
164+
if hasDirectAccessToSwiftClass {
165+
return unqualifiedClassName(for: qualifiedName)
166+
}
167+
let unqualified = unqualifiedClassName(for: qualifiedName)
168+
return exportsAccess(forClass: unqualified)
169+
}
148170
}
149171

150172
/// A function that prints the fragment code.
@@ -555,8 +577,9 @@ struct IntrinsicJSFragment: Sendable {
555577
return IntrinsicJSFragment(
556578
parameters: ["value"],
557579
printCode: { arguments, context in
580+
let classRef = context.classReference(forQualifiedName: name)
558581
return [
559-
"\(context.hasDirectAccessToSwiftClass ? name : "_exports['\(name)']").__construct(\(arguments[0]))"
582+
"\(classRef).__construct(\(arguments[0]))"
560583
]
561584
}
562585
)
@@ -565,7 +588,8 @@ struct IntrinsicJSFragment: Sendable {
565588
return IntrinsicJSFragment(
566589
parameters: ["pointer"],
567590
printCode: { arguments, context in
568-
return ["_exports['\(name)'].__construct(\(arguments[0]))"]
591+
let classRef = context.classReference(forQualifiedName: name)
592+
return ["\(classRef).__construct(\(arguments[0]))"]
569593
}
570594
)
571595
}
@@ -874,10 +898,8 @@ struct IntrinsicJSFragment: Sendable {
874898
printer.write(
875899
"\(JSGlueVariableScope.reservedStorageToReturnOptionalHeapObject) = undefined;"
876900
)
877-
let constructExpr =
878-
context.hasDirectAccessToSwiftClass
879-
? "\(className).__construct(\(pointerVar))"
880-
: "_exports['\(className)'].__construct(\(pointerVar))"
901+
let classRef = context.classReference(forQualifiedName: className)
902+
let constructExpr = "\(classRef).__construct(\(pointerVar))"
881903
printer.write(
882904
"const \(resultVar) = \(pointerVar) === null ? \(absenceLiteral) : \(constructExpr);"
883905
)
@@ -2075,8 +2097,9 @@ struct IntrinsicJSFragment: Sendable {
20752097
let (scope, printer) = (context.scope, context.printer)
20762098
let ptrVar = scope.variable("ptr")
20772099
let objVar = scope.variable("obj")
2100+
let classRef = context.classReference(forQualifiedName: className)
20782101
printer.write("const \(ptrVar) = \(scope.popPointer());")
2079-
printer.write("const \(objVar) = _exports['\(className)'].__construct(\(ptrVar));")
2102+
printer.write("const \(objVar) = \(classRef).__construct(\(ptrVar));")
20802103
return [objVar]
20812104
}
20822105
)

Plugins/BridgeJS/Sources/BridgeJSLink/JSIntrinsicRegistry.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import BridgeJSUtilities
55
/// Registry for JS helper intrinsics used during code generation.
66
final class JSIntrinsicRegistry {
77
private var entries: [String: [String]] = [:]
8+
var classNamespaces: [String: [String]] = [:]
89

910
var isEmpty: Bool {
1011
entries.isEmpty
@@ -19,6 +20,7 @@ final class JSIntrinsicRegistry {
1920

2021
func reset() {
2122
entries.removeAll()
23+
classNamespaces.removeAll()
2224
}
2325

2426
func emitLines() -> [String] {

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,19 @@ class UUID {
3232
Foundation.UUID().uuidString
3333
}
3434
}
35+
36+
@JS(namespace: "Collections") class Container {
37+
var items: [Greeter]
38+
39+
@JS init() {
40+
self.items = []
41+
}
42+
43+
@JS func getItems() -> [Greeter] {
44+
return items
45+
}
46+
47+
@JS func addItem(_ item: Greeter) {
48+
items.append(item)
49+
}
50+
}

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,75 @@
128128

129129
],
130130
"swiftCallName" : "UUID"
131+
},
132+
{
133+
"constructor" : {
134+
"abiName" : "bjs_Container_init",
135+
"effects" : {
136+
"isAsync" : false,
137+
"isStatic" : false,
138+
"isThrows" : false
139+
},
140+
"parameters" : [
141+
142+
]
143+
},
144+
"methods" : [
145+
{
146+
"abiName" : "bjs_Container_getItems",
147+
"effects" : {
148+
"isAsync" : false,
149+
"isStatic" : false,
150+
"isThrows" : false
151+
},
152+
"name" : "getItems",
153+
"parameters" : [
154+
155+
],
156+
"returnType" : {
157+
"array" : {
158+
"_0" : {
159+
"swiftHeapObject" : {
160+
"_0" : "Greeter"
161+
}
162+
}
163+
}
164+
}
165+
},
166+
{
167+
"abiName" : "bjs_Container_addItem",
168+
"effects" : {
169+
"isAsync" : false,
170+
"isStatic" : false,
171+
"isThrows" : false
172+
},
173+
"name" : "addItem",
174+
"parameters" : [
175+
{
176+
"label" : "_",
177+
"name" : "item",
178+
"type" : {
179+
"swiftHeapObject" : {
180+
"_0" : "Greeter"
181+
}
182+
}
183+
}
184+
],
185+
"returnType" : {
186+
"void" : {
187+
188+
}
189+
}
190+
}
191+
],
192+
"name" : "Container",
193+
"namespace" : [
194+
"Collections"
195+
],
196+
"properties" : [
197+
198+
],
199+
"swiftCallName" : "Container"
131200
}
132201
],
133202
"enums" : [

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,64 @@ fileprivate func _bjs_UUID_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> In
157157
#endif
158158
@inline(never) fileprivate func _bjs_UUID_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 {
159159
return _bjs_UUID_wrap_extern(pointer)
160+
}
161+
162+
@_expose(wasm, "bjs_Container_init")
163+
@_cdecl("bjs_Container_init")
164+
public func _bjs_Container_init() -> UnsafeMutableRawPointer {
165+
#if arch(wasm32)
166+
let ret = Container()
167+
return ret.bridgeJSLowerReturn()
168+
#else
169+
fatalError("Only available on WebAssembly")
170+
#endif
171+
}
172+
173+
@_expose(wasm, "bjs_Container_getItems")
174+
@_cdecl("bjs_Container_getItems")
175+
public func _bjs_Container_getItems(_ _self: UnsafeMutableRawPointer) -> Void {
176+
#if arch(wasm32)
177+
let ret = Container.bridgeJSLiftParameter(_self).getItems()
178+
ret.bridgeJSStackPush()
179+
#else
180+
fatalError("Only available on WebAssembly")
181+
#endif
182+
}
183+
184+
@_expose(wasm, "bjs_Container_addItem")
185+
@_cdecl("bjs_Container_addItem")
186+
public func _bjs_Container_addItem(_ _self: UnsafeMutableRawPointer, _ item: UnsafeMutableRawPointer) -> Void {
187+
#if arch(wasm32)
188+
Container.bridgeJSLiftParameter(_self).addItem(_: Greeter.bridgeJSLiftParameter(item))
189+
#else
190+
fatalError("Only available on WebAssembly")
191+
#endif
192+
}
193+
194+
@_expose(wasm, "bjs_Container_deinit")
195+
@_cdecl("bjs_Container_deinit")
196+
public func _bjs_Container_deinit(_ pointer: UnsafeMutableRawPointer) -> Void {
197+
#if arch(wasm32)
198+
Unmanaged<Container>.fromOpaque(pointer).release()
199+
#else
200+
fatalError("Only available on WebAssembly")
201+
#endif
202+
}
203+
204+
extension Container: ConvertibleToJSValue, _BridgedSwiftHeapObject {
205+
var jsValue: JSValue {
206+
return .object(JSObject(id: UInt32(bitPattern: _bjs_Container_wrap(Unmanaged.passRetained(self).toOpaque()))))
207+
}
208+
}
209+
210+
#if arch(wasm32)
211+
@_extern(wasm, module: "TestModule", name: "bjs_Container_wrap")
212+
fileprivate func _bjs_Container_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32
213+
#else
214+
fileprivate func _bjs_Container_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 {
215+
fatalError("Only available on WebAssembly")
216+
}
217+
#endif
218+
@inline(never) fileprivate func _bjs_Container_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 {
219+
return _bjs_Container_wrap_extern(pointer)
160220
}

0 commit comments

Comments
 (0)