Skip to content

Commit 61fe95e

Browse files
committed
Make closure parameters as sending
1 parent 4244627 commit 61fe95e

File tree

12 files changed

+759
-678
lines changed

12 files changed

+759
-678
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ClosureCodegen.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ public struct ClosureCodegen {
1212
public init() {}
1313

1414
private func swiftClosureType(for signature: ClosureSignature) -> String {
15-
let closureParams = signature.parameters.map { "\($0.closureSwiftType)" }.joined(separator: ", ")
15+
let sendingPrefix = signature.sendingParameters ? "sending " : ""
16+
let closureParams = signature.parameters.map { "\(sendingPrefix)\($0.closureSwiftType)" }.joined(
17+
separator: ", "
18+
)
1619
let swiftEffects = (signature.isAsync ? " async" : "") + (signature.isThrows ? " throws" : "")
1720
let swiftReturnType = signature.returnType.closureSwiftType
1821
return "(\(closureParams))\(swiftEffects) -> \(swiftReturnType)"
@@ -192,8 +195,10 @@ public struct ClosureCodegen {
192195

193196
// When async imports exist, inject closure signatures for the typed resolve
194197
// and reject callbacks used by _bjs_awaitPromise.
195-
// - Reject always uses (JSValue) -> Void
198+
// - Reject always uses (sending JSValue) -> Void
196199
// - Resolve uses a typed closure matching the return type (or () -> Void for void)
200+
// All async callback closures use `sending` parameters so values can be
201+
// transferred through the checked continuation without Sendable constraints.
197202
if let imported = skeleton.imported {
198203
for file in imported.children {
199204
for function in file.functions where function.effects.isAsync {
@@ -202,7 +207,8 @@ public struct ClosureCodegen {
202207
ClosureSignature(
203208
parameters: [.jsValue],
204209
returnType: .void,
205-
moduleName: skeleton.moduleName
210+
moduleName: skeleton.moduleName,
211+
sendingParameters: true
206212
)
207213
)
208214
// Resolve callback (typed per return type)
@@ -219,7 +225,8 @@ public struct ClosureCodegen {
219225
ClosureSignature(
220226
parameters: [function.returnType],
221227
returnType: .void,
222-
moduleName: skeleton.moduleName
228+
moduleName: skeleton.moduleName,
229+
sendingParameters: true
223230
)
224231
)
225232
}
@@ -230,7 +237,8 @@ public struct ClosureCodegen {
230237
ClosureSignature(
231238
parameters: [.jsValue],
232239
returnType: .void,
233-
moduleName: skeleton.moduleName
240+
moduleName: skeleton.moduleName,
241+
sendingParameters: true
234242
)
235243
)
236244
if method.returnType == .void {
@@ -246,7 +254,8 @@ public struct ClosureCodegen {
246254
ClosureSignature(
247255
parameters: [method.returnType],
248256
returnType: .void,
249-
moduleName: skeleton.moduleName
257+
moduleName: skeleton.moduleName,
258+
sendingParameters: true
250259
)
251260
)
252261
}

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ public struct ImportTS {
299299
let innerBody = body
300300
body = CodeFragmentPrinter()
301301

302-
let rejectFactory = "makeRejectClosure: { JSTypedClosure<(JSValue) -> Void>($0) }"
302+
let rejectFactory = "makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>($0) }"
303303
if originalReturnType == .void {
304304
let resolveFactory = "makeResolveClosure: { JSTypedClosure<() -> Void>($0) }"
305305
body.write(
@@ -308,7 +308,7 @@ public struct ImportTS {
308308
} else {
309309
let resolveSwiftType = originalReturnType.closureSwiftType
310310
let resolveFactory =
311-
"makeResolveClosure: { JSTypedClosure<(\(resolveSwiftType)) -> Void>($0) }"
311+
"makeResolveClosure: { JSTypedClosure<(sending \(resolveSwiftType)) -> Void>($0) }"
312312
body.write(
313313
"let resolved = try await _bjs_awaitPromise(\(resolveFactory), \(rejectFactory)) { resolveRef, rejectRef in"
314314
)

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,9 @@ public struct BridgeJSLink {
641641
walker.walk(unified)
642642
var closureSignatures = walker.visitor.signatures
643643

644-
// Inject closure signatures for async import resolve/reject callbacks
644+
// Inject closure signatures for async import resolve/reject callbacks.
645+
// All use sendingParameters: true so values can be transferred
646+
// through checked continuations without Sendable constraints.
645647
if let imported = unified.imported {
646648
for file in imported.children {
647649
for function in file.functions where function.effects.isAsync {
@@ -650,7 +652,8 @@ public struct BridgeJSLink {
650652
ClosureSignature(
651653
parameters: [.jsValue],
652654
returnType: .void,
653-
moduleName: moduleName
655+
moduleName: moduleName,
656+
sendingParameters: true
654657
)
655658
)
656659
// Resolve callback (typed per return type)
@@ -667,7 +670,8 @@ public struct BridgeJSLink {
667670
ClosureSignature(
668671
parameters: [function.returnType],
669672
returnType: .void,
670-
moduleName: moduleName
673+
moduleName: moduleName,
674+
sendingParameters: true
671675
)
672676
)
673677
}
@@ -678,7 +682,8 @@ public struct BridgeJSLink {
678682
ClosureSignature(
679683
parameters: [.jsValue],
680684
returnType: .void,
681-
moduleName: moduleName
685+
moduleName: moduleName,
686+
sendingParameters: true
682687
)
683688
)
684689
if method.returnType == .void {
@@ -694,7 +699,8 @@ public struct BridgeJSLink {
694699
ClosureSignature(
695700
parameters: [method.returnType],
696701
returnType: .void,
697-
moduleName: moduleName
702+
moduleName: moduleName,
703+
sendingParameters: true
698704
)
699705
)
700706
}
@@ -2296,20 +2302,18 @@ extension BridgeJSLink {
22962302

22972303
/// Generates the call expression for an async import.
22982304
///
2299-
/// Instead of lowering the return value, this assigns the result to `promise`
2300-
/// and passes the resolve/reject closure refs to `.then`.
2305+
/// Chains `.then(resolve, reject)` directly on the returned Promise.
23012306
func callAsync(name: String, fromObjectExpr: String) {
23022307
let calleeExpr = Self.propertyAccessExpr(objectExpr: fromObjectExpr, propertyName: name)
23032308
let callExpr = "\(calleeExpr)(\(parameterForwardings.joined(separator: ", ")))"
2304-
body.write("const promise = \(callExpr);")
2305-
body.write("promise.then(resolve, reject);")
2309+
body.write("\(callExpr).then(resolve, reject);")
23062310
}
23072311

23082312
/// Renders an async import function with resolve/reject closure refs.
23092313
///
23102314
/// The generated function takes `resolveRef` and `rejectRef` as the first parameters,
2311-
/// wraps the import call in try/catch, attaches Promise handlers, and
2312-
/// calls reject on synchronous errors.
2315+
/// looks up the resolve/reject closures from memory, and executes the body which
2316+
/// chains `.then(resolve, reject)` on the import's returned Promise.
23132317
func renderAsyncFunction(name: String?) -> [String] {
23142318
let printer = CodeFragmentPrinter()
23152319
let allParams = ["resolveRef", "rejectRef"] + parameterNames
@@ -2318,15 +2322,7 @@ extension BridgeJSLink {
23182322
let s = JSGlueVariableScope.reservedSwift
23192323
printer.write("const resolve = \(s).memory.getObject(resolveRef);")
23202324
printer.write("const reject = \(s).memory.getObject(rejectRef);")
2321-
printer.write("try {")
2322-
printer.indent {
2323-
printer.write(contentsOf: body)
2324-
}
2325-
printer.write("} catch (error) {")
2326-
printer.indent {
2327-
printer.write("reject(error);")
2328-
}
2329-
printer.write("}")
2325+
printer.write(contentsOf: body)
23302326
}
23312327
printer.write("}")
23322328
return printer.lines
@@ -2390,8 +2386,7 @@ extension BridgeJSLink {
23902386
let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)"
23912387
let calleeExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name)
23922388
let callExpr = "\(calleeExpr)(\(parameterForwardings.joined(separator: ", ")))"
2393-
body.write("const promise = \(callExpr);")
2394-
body.write("promise.then(resolve, reject);")
2389+
body.write("\(callExpr).then(resolve, reject);")
23952390
}
23962391

23972392
func callStaticMethod(on objectExpr: String, name: String, returnType: BridgeType) throws -> String? {

Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,24 +101,31 @@ public struct ClosureSignature: Codable, Equatable, Hashable, Sendable {
101101
public let isAsync: Bool
102102
public let isThrows: Bool
103103
public let moduleName: String
104+
/// When true, closure parameters are annotated with `sending` in Swift.
105+
/// Used for async Promise resolve/reject callbacks where values are
106+
/// transferred through a continuation.
107+
public let sendingParameters: Bool
104108

105109
public init(
106110
parameters: [BridgeType],
107111
returnType: BridgeType,
108112
moduleName: String,
109113
isAsync: Bool = false,
110-
isThrows: Bool = false
114+
isThrows: Bool = false,
115+
sendingParameters: Bool = false
111116
) {
112117
self.parameters = parameters
113118
self.returnType = returnType
114119
self.moduleName = moduleName
115120
self.isAsync = isAsync
116121
self.isThrows = isThrows
122+
self.sendingParameters = sendingParameters
117123
let paramPart =
118124
parameters.isEmpty
119125
? "y"
120126
: parameters.map { $0.mangleTypeName }.joined()
121-
let signaturePart = "\(paramPart)_\(returnType.mangleTypeName)"
127+
let sendingPart = sendingParameters ? "s" : ""
128+
let signaturePart = "\(sendingPart)\(paramPart)_\(returnType.mangleTypeName)"
122129
self.mangleName = "\(moduleName.count)\(moduleName)\(signaturePart)"
123130
}
124131
}

0 commit comments

Comments
 (0)