Skip to content

BridgeJS: Add support for JSObject in exported Swift interface #372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 13, 2025
Merged
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
6 changes: 6 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ struct BridgeJSLink {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
\(importObjectBuilders.flatMap { $0.importedLines }.map { $0.indent(count: 12) }.joined(separator: "\n"))
},
setInstance: (i) => {
Expand Down
32 changes: 26 additions & 6 deletions Plugins/BridgeJS/Sources/BridgeJSTool/ExportSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,9 @@ class ExportSwift {
return nil
}
guard let typeDecl = typeDeclResolver.lookupType(for: identifier) else {
print("Failed to lookup type \(type.trimmedDescription): not found in typeDeclResolver")
return nil
}
guard typeDecl.is(ClassDeclSyntax.self) || typeDecl.is(ActorDeclSyntax.self) else {
print("Failed to lookup type \(type.trimmedDescription): is not a class or actor")
return nil
}
return .swiftHeapObject(typeDecl.name.text)
Expand All @@ -237,10 +235,16 @@ class ExportSwift {
//
// To update this file, just rebuild your project or run
// `swift package bridge-js`.

@_spi(JSObject_id) import JavaScriptKit

@_extern(wasm, module: "bjs", name: "return_string")
private func _return_string(_ ptr: UnsafePointer<UInt8>?, _ len: Int32)
@_extern(wasm, module: "bjs", name: "init_memory")
private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer<UInt8>?)

@_extern(wasm, module: "bjs", name: "swift_js_retain")
private func _swift_js_retain(_ ptr: Int32) -> Int32
"""

func renderSwiftGlue() -> String? {
Expand Down Expand Up @@ -317,11 +321,19 @@ class ExportSwift {
)
abiParameterSignatures.append((bytesLabel, .i32))
abiParameterSignatures.append((lengthLabel, .i32))
case .jsObject:
case .jsObject(nil):
abiParameterForwardings.append(
LabeledExprSyntax(
label: param.label,
expression: ExprSyntax("\(raw: param.name)")
expression: ExprSyntax("JSObject(id: UInt32(bitPattern: \(raw: param.name)))")
)
)
abiParameterSignatures.append((param.name, .i32))
case .jsObject(let name):
abiParameterForwardings.append(
LabeledExprSyntax(
label: param.label,
expression: ExprSyntax("\(raw: name)(takingThis: UInt32(bitPattern: \(raw: param.name)))")
)
)
abiParameterSignatures.append((param.name, .i32))
Expand Down Expand Up @@ -404,10 +416,16 @@ class ExportSwift {
}
"""
)
case .jsObject:
case .jsObject(nil):
body.append(
"""
return _swift_js_retain(Int32(bitPattern: ret.id))
"""
)
case .jsObject(_?):
body.append(
"""
return ret.id
return _swift_js_retain(Int32(bitPattern: ret.this.id))
"""
)
case .swiftHeapObject:
Expand Down Expand Up @@ -566,6 +584,8 @@ extension BridgeType {
self = .bool
case "Void":
self = .void
case "JSObject":
self = .jsObject(nil)
default:
return nil
}
Expand Down
3 changes: 0 additions & 3 deletions Plugins/BridgeJS/Sources/BridgeJSTool/ImportTS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,6 @@ struct ImportTS {

@_extern(wasm, module: "bjs", name: "init_memory_with_result")
private func _init_memory_with_result(_ ptr: UnsafePointer<UInt8>?, _ len: Int32)

@_extern(wasm, module: "bjs", name: "free_jsobject")
private func _free_jsobject(_ ptr: Int32) -> Void
"""

func renderSwiftThunk(
Expand Down
3 changes: 2 additions & 1 deletion Plugins/BridgeJS/Sources/JavaScript/src/processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ export class TypeProcessor {

for (const member of node.members) {
if (ts.isPropertyDeclaration(member)) {
// TODO
const property = this.visitPropertyDecl(member);
if (property) properties.push(property);
} else if (ts.isMethodDeclaration(member)) {
const decl = this.visitFunctionLikeDecl(member);
if (decl) methods.push(decl);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export class Greeter {
name: string;
readonly age: number;
constructor(name: string);
greet(): string;
changeName(name: string): void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};
TestModule["bjs_checkArray"] = function bjs_checkArray(a) {
options.imports.checkArray(swift.memory.getObject(a));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};
TestModule["bjs_returnAnimatable"] = function bjs_returnAnimatable() {
let ret = options.imports.returnAnimatable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}

},
setInstance: (i) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};
TestModule["bjs_check"] = function bjs_check(a, b) {
options.imports.check(a, b);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}

},
setInstance: (i) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};
TestModule["bjs_checkNumber"] = function bjs_checkNumber() {
let ret = options.imports.checkNumber();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}

},
setInstance: (i) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};
TestModule["bjs_checkString"] = function bjs_checkString(a) {
const aObject = swift.memory.getObject(a);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}

},
setInstance: (i) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};
TestModule["bjs_checkString"] = function bjs_checkString() {
let ret = options.imports.checkString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}

},
setInstance: (i) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};
TestModule["bjs_checkSimple"] = function bjs_checkSimple(a) {
options.imports.checkSimple(a);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,33 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};
TestModule["bjs_Greeter_init"] = function bjs_Greeter_init(name) {
const nameObject = swift.memory.getObject(name);
swift.memory.release(name);
let ret = new options.imports.Greeter(nameObject);
return swift.memory.retain(ret);
}
TestModule["bjs_Greeter_name_get"] = function bjs_Greeter_name_get(self) {
let ret = swift.memory.getObject(self).name;
tmpRetBytes = textEncoder.encode(ret);
return tmpRetBytes.length;
}
TestModule["bjs_Greeter_name_set"] = function bjs_Greeter_name_set(self, newValue) {
const newValueObject = swift.memory.getObject(newValue);
swift.memory.release(newValue);
swift.memory.getObject(self).name = newValueObject;
}
TestModule["bjs_Greeter_age_get"] = function bjs_Greeter_age_get(self) {
let ret = swift.memory.getObject(self).age;
return ret;
}
TestModule["bjs_Greeter_greet"] = function bjs_Greeter_greet(self) {
let ret = swift.memory.getObject(self).greet();
tmpRetBytes = textEncoder.encode(ret);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}

},
setInstance: (i) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export async function createInstantiator(options, swift) {
target.set(tmpRetBytes);
tmpRetBytes = undefined;
}
bjs["swift_js_retain"] = function(id) {
return swift.memory.retainByRef(id);
}
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};
TestModule["bjs_check"] = function bjs_check() {
options.imports.check();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
//
// To update this file, just rebuild your project or run
// `swift package bridge-js`.

@_spi(JSObject_id) import JavaScriptKit

@_extern(wasm, module: "bjs", name: "return_string")
private func _return_string(_ ptr: UnsafePointer<UInt8>?, _ len: Int32)
@_extern(wasm, module: "bjs", name: "init_memory")
private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer<UInt8>?)

@_extern(wasm, module: "bjs", name: "swift_js_retain")
private func _swift_js_retain(_ ptr: Int32) -> Int32

@_expose(wasm, "bjs_check")
@_cdecl("bjs_check")
public func _bjs_check(a: Int32, b: Float32, c: Float64, d: Int32) -> Void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
//
// To update this file, just rebuild your project or run
// `swift package bridge-js`.

@_spi(JSObject_id) import JavaScriptKit

@_extern(wasm, module: "bjs", name: "return_string")
private func _return_string(_ ptr: UnsafePointer<UInt8>?, _ len: Int32)
@_extern(wasm, module: "bjs", name: "init_memory")
private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer<UInt8>?)

@_extern(wasm, module: "bjs", name: "swift_js_retain")
private func _swift_js_retain(_ ptr: Int32) -> Int32

@_expose(wasm, "bjs_checkInt")
@_cdecl("bjs_checkInt")
public func _bjs_checkInt() -> Int32 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
//
// To update this file, just rebuild your project or run
// `swift package bridge-js`.

@_spi(JSObject_id) import JavaScriptKit

@_extern(wasm, module: "bjs", name: "return_string")
private func _return_string(_ ptr: UnsafePointer<UInt8>?, _ len: Int32)
@_extern(wasm, module: "bjs", name: "init_memory")
private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer<UInt8>?)

@_extern(wasm, module: "bjs", name: "swift_js_retain")
private func _swift_js_retain(_ ptr: Int32) -> Int32

@_expose(wasm, "bjs_checkString")
@_cdecl("bjs_checkString")
public func _bjs_checkString(aBytes: Int32, aLen: Int32) -> Void {
Expand Down
Loading