Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -492,15 +492,15 @@ 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("}")

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);")
}
Expand Down Expand Up @@ -1005,7 +1005,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,
Expand Down Expand Up @@ -1159,10 +1159,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()
}
Expand Down Expand Up @@ -1497,7 +1497,7 @@ public struct BridgeJSLink {
func renderExportedStruct(
_ structDefinition: ExportedStruct
) throws -> (js: [String], dtsType: [String], dtsExportEntry: [String]) {
let structName = structDefinition.name
let structName = structDefinition.abiName
let hasConstructor = structDefinition.constructor != nil
let staticMethods = structDefinition.methods.filter { $0.effects.isStatic }
let staticProperties = structDefinition.properties.filter { $0.isStatic }
Expand Down Expand Up @@ -3610,7 +3610,7 @@ extension BridgeType {
case .associatedValueEnum(let name):
return "\(name)Tag"
case .swiftStruct(let name):
return name.components(separatedBy: ".").last ?? name
return name.replacingOccurrences(of: ".", with: "_")
case .namespaceEnum(let name):
return name
case .swiftProtocol(let name):
Expand Down
12 changes: 6 additions & 6 deletions Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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] = []
Expand Down Expand Up @@ -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 []
}
Expand Down Expand Up @@ -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]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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" : [
Expand Down Expand Up @@ -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"
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Player>.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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
// To update this file, just rebuild your project or run
// `swift package bridge-js`.

export interface Stats {
export interface User_Stats {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if UserStats would be more aesthetically pleasing? I guess this could conflict with a separate struct just called UserStats in Swift.

More work but I think we could use TS namespaces here as well:

export namespace User {
    export interface Stats {
    }
}

I believe that this will automatically merge with the interface Player, if it exists.

Copy link
Copy Markdown
Member Author

@krodak krodak May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey William, on the naming - yeah, UserStats would read nicer, but as you noted it could collide with an actual UserStats struct. The underscore keeps it unambiguous and mirrors the internal ABI name and aligns with current approach elsewhere in the code.

As for TS namespace idea - we do already emit namespace blocks in the declare global path (for exposeToGlobal: true), so the mechanism exists and could be reused. Current approach aligns with use of underscore in other places, but that could be considered, depending on @kateinoigakukun preference.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more sense to use TS-namespace to represent the hierarchical types for public user-facing APIs.

health: number;
score: number;
}
export interface Player_Stats {
level: number;
rating: string;
}
/// Represents a Swift heap object like a class instance or an actor instance.
export interface SwiftHeapObject {
/// Release the heap object.
Expand All @@ -18,9 +22,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 = {
}
Expand Down
Loading
Loading