Skip to content

BridgeJS: Restore new SwiftClass-style constructor by using Object.create #411

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 3 commits into from
Aug 16, 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
2 changes: 1 addition & 1 deletion Examples/ExportSwift/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js";
const { exports } = await init({});

const Greeter = exports.Greeter;
const greeter = Greeter.init("World");
const greeter = new Greeter("World");
const circle = exports.renderCircleSVG(100);

// Display the results
Expand Down
4 changes: 2 additions & 2 deletions Examples/PlayBridgeJS/Sources/JavaScript/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class BridgeJSPlayground {
createTS2Skeleton: this.createTS2Skeleton
}
});
this.playBridgeJS = exports.PlayBridgeJS.init();
this.playBridgeJS = new exports.PlayBridgeJS();
console.log('BridgeJS initialized successfully');
} catch (error) {
console.error('Failed to initialize BridgeJS:', error);
Expand Down Expand Up @@ -162,4 +162,4 @@ export class BridgeJSPlayground {
hideError() {
this.errorDisplay.classList.remove('show');
}
}
}
28 changes: 12 additions & 16 deletions Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,16 @@ struct BridgeJSLink {
let swiftHeapObjectClassJs = """
/// Represents a Swift heap object like a class instance or an actor instance.
class SwiftHeapObject {
static __construct(ptr, deinit) {
return new SwiftHeapObject(ptr, deinit);
}

constructor(pointer, deinit) {
this.pointer = pointer;
this.hasReleased = false;
this.deinit = deinit;
this.registry = new FinalizationRegistry((pointer) => {
static __wrap(pointer, deinit, prototype) {
const obj = Object.create(prototype);
obj.pointer = pointer;
obj.hasReleased = false;
obj.deinit = deinit;
obj.registry = new FinalizationRegistry((pointer) => {
deinit(pointer);
});
this.registry.register(this, this.pointer);
obj.registry.register(this, obj.pointer);
return obj;
}

release() {
Expand Down Expand Up @@ -525,13 +523,11 @@ struct BridgeJSLink {
var constructorLines: [String] = []
constructorLines.append("static __construct(ptr) {")
constructorLines.append(
"return new \(klass.name)(ptr, instance.exports.bjs_\(klass.name)_deinit);".indent(count: 4)
"return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_\(klass.name)_deinit, \(klass.name).prototype);"
.indent(count: 4)
)
constructorLines.append("}")
constructorLines.append("")
constructorLines.append("constructor(pointer, deinit) {")
constructorLines.append("super(pointer, deinit);".indent(count: 4))
constructorLines.append("}")
jsLines.append(contentsOf: constructorLines.map { $0.indent(count: 4) })

if let constructor: ExportedConstructor = klass.constructor {
Expand All @@ -541,7 +537,7 @@ struct BridgeJSLink {
}
var funcLines: [String] = []
funcLines.append("")
funcLines.append("static init(\(constructor.parameters.map { $0.name }.joined(separator: ", "))) {")
funcLines.append("constructor(\(constructor.parameters.map { $0.name }.joined(separator: ", "))) {")
let returnExpr = thunkBuilder.callConstructor(abiName: constructor.abiName)
funcLines.append(contentsOf: thunkBuilder.bodyLines.map { $0.indent(count: 4) })
funcLines.append(contentsOf: thunkBuilder.cleanupLines.map { $0.indent(count: 4) })
Expand All @@ -551,7 +547,7 @@ struct BridgeJSLink {
jsLines.append(contentsOf: funcLines.map { $0.indent(count: 4) })

dtsExportEntryLines.append(
"init\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name)));"
"constructor\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name)));"
.indent(count: 4)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ export interface UUID extends SwiftHeapObject {
}
export type Exports = {
Greeter: {
init(name: string): Greeter;
constructor(name: string): Greeter;
}
Converter: {
init(): Converter;
constructor(): Converter;
}
UUID: {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,16 @@ export async function createInstantiator(options, swift) {
const js = swift.memory.heap;
/// Represents a Swift heap object like a class instance or an actor instance.
class SwiftHeapObject {
static __construct(ptr, deinit) {
return new SwiftHeapObject(ptr, deinit);
}

constructor(pointer, deinit) {
this.pointer = pointer;
this.hasReleased = false;
this.deinit = deinit;
this.registry = new FinalizationRegistry((pointer) => {
static __wrap(pointer, deinit, prototype) {
const obj = Object.create(prototype);
obj.pointer = pointer;
obj.hasReleased = false;
obj.deinit = deinit;
obj.registry = new FinalizationRegistry((pointer) => {
deinit(pointer);
});
this.registry.register(this, this.pointer);
obj.registry.register(this, obj.pointer);
return obj;
}

release() {
Expand All @@ -81,14 +79,11 @@ export async function createInstantiator(options, swift) {
}
class Greeter extends SwiftHeapObject {
static __construct(ptr) {
return new Greeter(ptr, instance.exports.bjs_Greeter_deinit);
return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype);
}

constructor(pointer, deinit) {
super(pointer, deinit);
}

static init(name) {
constructor(name) {
const nameBytes = textEncoder.encode(name);
const nameId = swift.memory.retain(nameBytes);
const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length);
Expand All @@ -104,14 +99,11 @@ export async function createInstantiator(options, swift) {
}
class Converter extends SwiftHeapObject {
static __construct(ptr) {
return new Converter(ptr, instance.exports.bjs_Converter_deinit);
return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Converter_deinit, Converter.prototype);
}

constructor(pointer, deinit) {
super(pointer, deinit);
}

static init() {
constructor() {
const ret = instance.exports.bjs_Converter_init();
return Converter.__construct(ret);
}
Expand All @@ -124,12 +116,9 @@ export async function createInstantiator(options, swift) {
}
class UUID extends SwiftHeapObject {
static __construct(ptr) {
return new UUID(ptr, instance.exports.bjs_UUID_deinit);
return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_UUID_deinit, UUID.prototype);
}

constructor(pointer, deinit) {
super(pointer, deinit);
}
uuidString() {
instance.exports.bjs_UUID_uuidString(this.pointer);
const ret = tmpRetString;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface Greeter extends SwiftHeapObject {
}
export type Exports = {
Greeter: {
init(name: string): Greeter;
constructor(name: string): Greeter;
}
takeGreeter(greeter: Greeter): void;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,16 @@ export async function createInstantiator(options, swift) {
const js = swift.memory.heap;
/// Represents a Swift heap object like a class instance or an actor instance.
class SwiftHeapObject {
static __construct(ptr, deinit) {
return new SwiftHeapObject(ptr, deinit);
}

constructor(pointer, deinit) {
this.pointer = pointer;
this.hasReleased = false;
this.deinit = deinit;
this.registry = new FinalizationRegistry((pointer) => {
static __wrap(pointer, deinit, prototype) {
const obj = Object.create(prototype);
obj.pointer = pointer;
obj.hasReleased = false;
obj.deinit = deinit;
obj.registry = new FinalizationRegistry((pointer) => {
deinit(pointer);
});
this.registry.register(this, this.pointer);
obj.registry.register(this, obj.pointer);
return obj;
}

release() {
Expand All @@ -81,14 +79,11 @@ export async function createInstantiator(options, swift) {
}
class Greeter extends SwiftHeapObject {
static __construct(ptr) {
return new Greeter(ptr, instance.exports.bjs_Greeter_deinit);
return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype);
}

constructor(pointer, deinit) {
super(pointer, deinit);
}

static init(name) {
constructor(name) {
const nameBytes = textEncoder.encode(name);
const nameId = swift.memory.retain(nameBytes);
const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ In JavaScript:
import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js";
const { exports } = await init({});

const cart = exports.ShoppingCart.init();
const cart = new exports.ShoppingCart();
cart.addItem("Laptop", 999.99, 1);
cart.addItem("Mouse", 24.99, 2);
console.log(`Items in cart: ${cart.getItemCount()}`);
Expand All @@ -158,7 +158,7 @@ export interface ShoppingCart extends SwiftHeapObject {

export type Exports = {
ShoppingCart: {
init(): ShoppingCart;
new(): ShoppingCart;
}
}
```
Expand All @@ -175,8 +175,8 @@ You can export functions to specific namespaces by providing a namespace paramet
import JavaScriptKit

// Export a function to a custom namespace
@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String {
return "namespaced"
@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String {
return "namespaced"
}
```

Expand Down
2 changes: 1 addition & 1 deletion Tests/prelude.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) {
assert.equal(exports.roundTripString(v), v);
}

const g = exports.Greeter.init("John");
const g = new exports.Greeter("John");
assert.equal(g.greet(), "Hello, John!");
g.changeName("Jane");
assert.equal(g.greet(), "Hello, Jane!");
Expand Down