Skip to content

Commit 731a047

Browse files
committed
BridgeJS: Naive implementation for swiftheapobject
BridgeJS: Improve SwiftHeapObject handling in import side
1 parent 395ca24 commit 731a047

File tree

16 files changed

+614
-44
lines changed

16 files changed

+614
-44
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,7 +2213,7 @@ public class ExportSwift {
22132213

22142214
var externParams: [String] = ["this: Int32"]
22152215
for param in method.parameters {
2216-
let loweringInfo = try param.type.loweringParameterInfo()
2216+
let loweringInfo = try param.type.loweringParameterInfo(context: .protocolExport)
22172217
assert(
22182218
loweringInfo.loweredParameters.count == 1,
22192219
"Protocol parameters must lower to a single WASM type"
@@ -2239,7 +2239,7 @@ public class ExportSwift {
22392239
"""
22402240
} else {
22412241
returnTypeStr = " -> \(method.returnType.swiftType)"
2242-
let liftingInfo = try method.returnType.liftingReturnInfo()
2242+
let liftingInfo = try method.returnType.liftingReturnInfo(context: .protocolExport)
22432243
if let abiType = liftingInfo.valueToLift {
22442244
externReturnType = " -> \(abiType.swiftType)"
22452245
} else {
@@ -2308,7 +2308,7 @@ public class ExportSwift {
23082308
)
23092309

23102310
// Generate getter
2311-
let liftingInfo = try property.type.liftingReturnInfo()
2311+
let liftingInfo = try property.type.liftingReturnInfo(context: .protocolExport)
23122312
let getterReturnType: String
23132313
let getterCallCode: String
23142314

@@ -2340,7 +2340,7 @@ public class ExportSwift {
23402340
"""
23412341
} else {
23422342
// Readwrite property - getter and setter
2343-
let loweringInfo = try property.type.loweringParameterInfo()
2343+
let loweringInfo = try property.type.loweringParameterInfo(context: .protocolExport)
23442344
assert(
23452345
loweringInfo.loweredParameters.count == 1,
23462346
"Protocol property setters must lower to a single WASM parameter"

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ extension BridgeType {
424424
static let void = LoweringParameterInfo(loweredParameters: [])
425425
}
426426

427-
func loweringParameterInfo() throws -> LoweringParameterInfo {
427+
func loweringParameterInfo(context: BridgeContext = .importTS) throws -> LoweringParameterInfo {
428428
switch self {
429429
case .bool: return .bool
430430
case .int: return .int
@@ -433,8 +433,18 @@ extension BridgeType {
433433
case .string: return .string
434434
case .jsObject: return .jsObject
435435
case .void: return .void
436-
case .swiftHeapObject:
437-
throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures")
436+
case .swiftHeapObject(let className):
437+
switch context {
438+
case .importTS:
439+
throw BridgeJSCoreError(
440+
"""
441+
swiftHeapObject '\(className)' is not supported in TypeScript imports.
442+
Swift classes can only be used in @JS protocols where Swift owns the instance.
443+
"""
444+
)
445+
case .protocolExport:
446+
return LoweringParameterInfo(loweredParameters: [("pointer", .pointer)])
447+
}
438448
case .swiftProtocol:
439449
throw BridgeJSCoreError("swiftProtocol is not supported in imported signatures")
440450
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:
@@ -456,7 +466,7 @@ extension BridgeType {
456466
static let void = LiftingReturnInfo(valueToLift: nil)
457467
}
458468

459-
func liftingReturnInfo() throws -> LiftingReturnInfo {
469+
func liftingReturnInfo(context: BridgeContext = .importTS) throws -> LiftingReturnInfo {
460470
switch self {
461471
case .bool: return .bool
462472
case .int: return .int
@@ -465,8 +475,18 @@ extension BridgeType {
465475
case .string: return .string
466476
case .jsObject: return .jsObject
467477
case .void: return .void
468-
case .swiftHeapObject:
469-
throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures")
478+
case .swiftHeapObject(let className):
479+
switch context {
480+
case .importTS:
481+
throw BridgeJSCoreError(
482+
"""
483+
swiftHeapObject '\(className)' cannot be returned from imported TypeScript functions.
484+
JavaScript cannot create Swift heap objects.
485+
"""
486+
)
487+
case .protocolExport:
488+
return LiftingReturnInfo(valueToLift: .pointer)
489+
}
470490
case .swiftProtocol:
471491
throw BridgeJSCoreError("swiftProtocol is not supported in imported signatures")
472492
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,21 +1856,23 @@ extension BridgeJSLink {
18561856
let body: CodeFragmentPrinter
18571857
let scope: JSGlueVariableScope
18581858
let cleanupCode: CodeFragmentPrinter
1859+
let context: BridgeContext
18591860
var parameterNames: [String] = []
18601861
var parameterForwardings: [String] = []
18611862

1862-
init() {
1863+
init(context: BridgeContext = .importTS) {
18631864
self.body = CodeFragmentPrinter()
18641865
self.scope = JSGlueVariableScope()
18651866
self.cleanupCode = CodeFragmentPrinter()
1867+
self.context = context
18661868
}
18671869

18681870
func liftSelf() {
18691871
parameterNames.append("self")
18701872
}
18711873

18721874
func liftParameter(param: Parameter) throws {
1873-
let liftingFragment = try IntrinsicJSFragment.liftParameter(type: param.type)
1875+
let liftingFragment = try IntrinsicJSFragment.liftParameter(type: param.type, context: context)
18741876
assert(
18751877
liftingFragment.parameters.count >= 1,
18761878
"Lifting fragment should have at least one parameter to lift"
@@ -1927,7 +1929,7 @@ extension BridgeJSLink {
19271929
}
19281930

19291931
private func call(callExpr: String, returnType: BridgeType) throws -> String? {
1930-
let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: returnType)
1932+
let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: returnType, context: context)
19311933
let returnExpr: String?
19321934
if loweringFragment.parameters.count == 0 {
19331935
body.write("\(callExpr);")
@@ -1947,7 +1949,7 @@ extension BridgeJSLink {
19471949
func callConstructor(name: String) throws -> String? {
19481950
let call = "new imports.\(name)(\(parameterForwardings.joined(separator: ", ")))"
19491951
let type: BridgeType = .jsObject(name)
1950-
let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: type)
1952+
let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: type, context: context)
19511953
return try lowerReturnValue(returnType: type, returnExpr: call, loweringFragment: loweringFragment)
19521954
}
19531955

@@ -2444,7 +2446,7 @@ extension BridgeJSLink {
24442446
className: `protocol`.name
24452447
)
24462448

2447-
let getterThunkBuilder = ImportedThunkBuilder()
2449+
let getterThunkBuilder = ImportedThunkBuilder(context: .protocolExport)
24482450
getterThunkBuilder.liftSelf()
24492451
let returnExpr = try getterThunkBuilder.callPropertyGetter(name: property.name, returnType: property.type)
24502452
let getterLines = getterThunkBuilder.renderFunction(
@@ -2462,7 +2464,7 @@ extension BridgeJSLink {
24622464
operation: "set",
24632465
className: `protocol`.name
24642466
)
2465-
let setterThunkBuilder = ImportedThunkBuilder()
2467+
let setterThunkBuilder = ImportedThunkBuilder(context: .protocolExport)
24662468
setterThunkBuilder.liftSelf()
24672469
try setterThunkBuilder.liftParameter(
24682470
param: Parameter(label: nil, name: "value", type: property.type)
@@ -2482,7 +2484,7 @@ extension BridgeJSLink {
24822484
protocol: ExportedProtocol,
24832485
method: ExportedFunction
24842486
) throws {
2485-
let thunkBuilder = ImportedThunkBuilder()
2487+
let thunkBuilder = ImportedThunkBuilder(context: .protocolExport)
24862488
thunkBuilder.liftSelf()
24872489
for param in method.parameters {
24882490
try thunkBuilder.liftParameter(param: param)

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,20 @@ struct IntrinsicJSFragment: Sendable {
219219
}
220220
)
221221
}
222+
static func swiftHeapObjectLiftParameter(_ name: String) -> IntrinsicJSFragment {
223+
return IntrinsicJSFragment(
224+
parameters: ["pointer"],
225+
printCode: { arguments, scope, printer, cleanupCode in
226+
return ["\(name).__construct(\(arguments[0]))"]
227+
}
228+
)
229+
}
230+
static let swiftHeapObjectLowerReturn = IntrinsicJSFragment(
231+
parameters: ["value"],
232+
printCode: { arguments, scope, printer, cleanupCode in
233+
return ["\(arguments[0]).pointer"]
234+
}
235+
)
222236

223237
static func associatedEnumLowerParameter(enumBase: String) -> IntrinsicJSFragment {
224238
IntrinsicJSFragment(
@@ -468,17 +482,23 @@ struct IntrinsicJSFragment: Sendable {
468482
// MARK: - ImportedJS
469483

470484
/// Returns a fragment that lifts Wasm core values to JS values for parameters
471-
static func liftParameter(type: BridgeType) throws -> IntrinsicJSFragment {
485+
///
486+
/// - Parameters:
487+
/// - type: The bridge type to lift
488+
/// - context: The bridge context (defaults to .importTS for backward compatibility)
489+
static func liftParameter(type: BridgeType, context: BridgeContext = .importTS) throws -> IntrinsicJSFragment {
472490
switch type {
473491
case .int, .float, .double: return .identity
474492
case .bool: return .boolLiftParameter
475493
case .string: return .stringLiftParameter
476494
case .jsObject: return .jsObjectLiftParameter
477495
case .swiftHeapObject(let name):
478-
throw BridgeJSLinkError(
479-
message:
480-
"Swift heap objects are not supported to be passed as parameters to imported JS functions: \(name)"
481-
)
496+
guard context == .protocolExport else {
497+
throw BridgeJSLinkError(
498+
message: "swiftHeapObject '\(name)' can only be used in protocol exports, not in \(context)"
499+
)
500+
}
501+
return .swiftHeapObjectLiftParameter(name)
482502
case .swiftProtocol: return .jsObjectLiftParameter
483503
case .void:
484504
throw BridgeJSLinkError(
@@ -509,16 +529,23 @@ struct IntrinsicJSFragment: Sendable {
509529
}
510530

511531
/// Returns a fragment that lowers a JS value to Wasm core values for return values
512-
static func lowerReturn(type: BridgeType) throws -> IntrinsicJSFragment {
532+
///
533+
/// - Parameters:
534+
/// - type: The bridge type to lower
535+
/// - context: The bridge context (defaults to .importTS for backward compatibility)
536+
static func lowerReturn(type: BridgeType, context: BridgeContext = .importTS) throws -> IntrinsicJSFragment {
513537
switch type {
514538
case .int, .float, .double: return .identity
515539
case .bool: return .boolLowerReturn
516540
case .string: return .stringLowerReturn
517541
case .jsObject: return .jsObjectLowerReturn
518-
case .swiftHeapObject:
519-
throw BridgeJSLinkError(
520-
message: "Swift heap objects are not supported to be returned from imported JS functions"
521-
)
542+
case .swiftHeapObject(let name):
543+
guard context == .protocolExport else {
544+
throw BridgeJSLinkError(
545+
message: "swiftHeapObject '\(name)' can only be used in protocol exports, not in \(context)"
546+
)
547+
}
548+
return .swiftHeapObjectLowerReturn
522549
case .swiftProtocol: return .jsObjectLowerReturn
523550
case .void: return .void
524551
case .optional(let wrappedType):

Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ public struct ABINameGenerator {
6767

6868
// MARK: - Types
6969

70+
/// Context for bridge operations that determines which types are valid
71+
public enum BridgeContext: Sendable {
72+
case importTS
73+
case protocolExport
74+
}
75+
7076
public enum BridgeType: Codable, Equatable, Sendable {
7177
case int, float, double, string, bool, jsObject(String?), swiftHeapObject(String), void
7278
indirect case optional(BridgeType)

Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Protocol.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
import JavaScriptKit
22

3+
@JS class Helper {
4+
@JS var value: Int
5+
6+
@JS init(value: Int) {
7+
self.value = value
8+
}
9+
10+
@JS func increment() {
11+
value += 1
12+
}
13+
}
14+
315
@JS protocol MyViewControllerDelegate {
416
var eventCount: Int { get set }
517
var delegateName: String { get }
@@ -8,6 +20,8 @@ import JavaScriptKit
820
func onCountUpdated(count: Int) -> Bool
921
func onLabelUpdated(_ prefix: String, _ suffix: String)
1022
func isCountEven() -> Bool
23+
func onHelperUpdated(_ helper: Helper)
24+
func createHelper() -> Helper
1125
}
1226

1327
@JS class MyViewController {
@@ -40,4 +54,8 @@ import JavaScriptKit
4054
@JS func checkEvenCount() -> Bool {
4155
return delegate.isCountEven()
4256
}
57+
58+
@JS func sendHelper(_ helper: Helper) {
59+
delegate.onHelperUpdated(helper)
60+
}
4361
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export interface MyViewControllerDelegate {
1010
onCountUpdated(count: number): boolean;
1111
onLabelUpdated(prefix: string, suffix: string): void;
1212
isCountEven(): boolean;
13+
onHelperUpdated(helper: Helper): void;
14+
createHelper(): Helper;
1315
eventCount: number;
1416
readonly delegateName: string;
1517
}
@@ -21,16 +23,24 @@ export interface SwiftHeapObject {
2123
/// Note: Calling this method will release the heap object and it will no longer be accessible.
2224
release(): void;
2325
}
26+
export interface Helper extends SwiftHeapObject {
27+
increment(): void;
28+
value: number;
29+
}
2430
export interface MyViewController extends SwiftHeapObject {
2531
triggerEvent(): void;
2632
updateValue(value: string): void;
2733
updateCount(count: number): boolean;
2834
updateLabel(prefix: string, suffix: string): void;
2935
checkEvenCount(): boolean;
36+
sendHelper(helper: Helper): void;
3037
delegate: MyViewControllerDelegate;
3138
secondDelegate: MyViewControllerDelegate | null;
3239
}
3340
export type Exports = {
41+
Helper: {
42+
new(value: number): Helper;
43+
}
3444
MyViewController: {
3545
new(delegate: MyViewControllerDelegate): MyViewController;
3646
}

0 commit comments

Comments
 (0)