Skip to content

Commit 7b0ef78

Browse files
authored
Merge pull request #524 from swiftwasm/katei/14ce-add-support-for
BridgeJS: support closure types in imported JS APIs
2 parents 987b088 + cef2d6f commit 7b0ef78

File tree

18 files changed

+1302
-287
lines changed

18 files changed

+1302
-287
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ClosureCodegen.swift

Lines changed: 337 additions & 0 deletions
Large diffs are not rendered by default.

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 0 additions & 265 deletions
Original file line numberDiff line numberDiff line change
@@ -47,30 +47,6 @@ public class ExportSwift {
4747
func renderSwiftGlue() throws -> String? {
4848
var decls: [DeclSyntax] = []
4949

50-
let closureCodegen = ClosureCodegen()
51-
var closureSignatures: Set<ClosureSignature> = []
52-
for function in skeleton.functions {
53-
closureCodegen.collectClosureSignatures(from: function.parameters, into: &closureSignatures)
54-
closureCodegen.collectClosureSignatures(from: function.returnType, into: &closureSignatures)
55-
}
56-
for klass in skeleton.classes {
57-
if let constructor = klass.constructor {
58-
closureCodegen.collectClosureSignatures(from: constructor.parameters, into: &closureSignatures)
59-
}
60-
for method in klass.methods {
61-
closureCodegen.collectClosureSignatures(from: method.parameters, into: &closureSignatures)
62-
closureCodegen.collectClosureSignatures(from: method.returnType, into: &closureSignatures)
63-
}
64-
for property in klass.properties {
65-
closureCodegen.collectClosureSignatures(from: property.type, into: &closureSignatures)
66-
}
67-
}
68-
69-
for signature in closureSignatures.sorted(by: { $0.mangleName < $1.mangleName }) {
70-
decls.append(contentsOf: try closureCodegen.renderClosureHelpers(signature))
71-
decls.append(try closureCodegen.renderClosureInvokeHandler(signature))
72-
}
73-
7450
let protocolCodegen = ProtocolCodegen()
7551
for proto in skeleton.protocols {
7652
decls.append(contentsOf: try protocolCodegen.renderProtocolWrapper(proto, moduleName: moduleName))
@@ -773,247 +749,6 @@ public class ExportSwift {
773749
}
774750
}
775751

776-
// MARK: - ClosureCodegen
777-
778-
struct ClosureCodegen {
779-
func collectClosureSignatures(from parameters: [Parameter], into signatures: inout Set<ClosureSignature>) {
780-
for param in parameters {
781-
collectClosureSignatures(from: param.type, into: &signatures)
782-
}
783-
}
784-
785-
func collectClosureSignatures(from type: BridgeType, into signatures: inout Set<ClosureSignature>) {
786-
switch type {
787-
case .closure(let signature):
788-
signatures.insert(signature)
789-
for paramType in signature.parameters {
790-
collectClosureSignatures(from: paramType, into: &signatures)
791-
}
792-
collectClosureSignatures(from: signature.returnType, into: &signatures)
793-
case .optional(let wrapped):
794-
collectClosureSignatures(from: wrapped, into: &signatures)
795-
default:
796-
break
797-
}
798-
}
799-
800-
func renderClosureHelpers(_ signature: ClosureSignature) throws -> [DeclSyntax] {
801-
let mangledName = signature.mangleName
802-
let helperName = "_BJS_Closure_\(mangledName)"
803-
let boxClassName = "_BJS_ClosureBox_\(mangledName)"
804-
805-
let closureParams = signature.parameters.enumerated().map { index, type in
806-
"\(type.swiftType)"
807-
}.joined(separator: ", ")
808-
809-
let swiftEffects = (signature.isAsync ? " async" : "") + (signature.isThrows ? " throws" : "")
810-
let swiftReturnType = signature.returnType.swiftType
811-
let closureType = "(\(closureParams))\(swiftEffects) -> \(swiftReturnType)"
812-
813-
let externName = "invoke_js_callback_\(signature.moduleName)_\(mangledName)"
814-
815-
// Use CallJSEmission to generate the callback invocation
816-
let builder = ImportTS.CallJSEmission(
817-
moduleName: "bjs",
818-
abiName: externName,
819-
context: .exportSwift
820-
)
821-
822-
// Lower the callback parameter
823-
try builder.lowerParameter(param: Parameter(label: nil, name: "callback", type: .jsObject(nil)))
824-
825-
// Lower each closure parameter
826-
for (index, paramType) in signature.parameters.enumerated() {
827-
try builder.lowerParameter(param: Parameter(label: nil, name: "param\(index)", type: paramType))
828-
}
829-
830-
// Generate the call and return value lifting
831-
try builder.call(returnType: signature.returnType)
832-
try builder.liftReturnValue(returnType: signature.returnType)
833-
834-
// Get the body code
835-
let bodyCode = builder.getBody()
836-
837-
// Generate extern declaration using CallJSEmission
838-
let externDecl = builder.renderImportDecl()
839-
840-
let boxClassDecl: DeclSyntax = """
841-
private final class \(raw: boxClassName): _BridgedSwiftClosureBox {
842-
let closure: \(raw: closureType)
843-
init(_ closure: @escaping \(raw: closureType)) {
844-
self.closure = closure
845-
}
846-
}
847-
"""
848-
849-
let helperEnumDecl = EnumDeclSyntax(
850-
modifiers: DeclModifierListSyntax {
851-
DeclModifierSyntax(name: .keyword(.private))
852-
},
853-
name: .identifier(helperName),
854-
memberBlockBuilder: {
855-
DeclSyntax(
856-
FunctionDeclSyntax(
857-
modifiers: DeclModifierListSyntax {
858-
DeclModifierSyntax(name: .keyword(.static))
859-
},
860-
name: .identifier("bridgeJSLower"),
861-
signature: FunctionSignatureSyntax(
862-
parameterClause: FunctionParameterClauseSyntax {
863-
FunctionParameterSyntax(
864-
firstName: .wildcardToken(),
865-
secondName: .identifier("closure"),
866-
colon: .colonToken(),
867-
type: TypeSyntax("@escaping \(raw: closureType)")
868-
)
869-
},
870-
returnClause: ReturnClauseSyntax(
871-
arrow: .arrowToken(),
872-
type: IdentifierTypeSyntax(name: .identifier("UnsafeMutableRawPointer"))
873-
)
874-
),
875-
body: CodeBlockSyntax {
876-
"let box = \(raw: boxClassName)(closure)"
877-
"return Unmanaged.passRetained(box).toOpaque()"
878-
}
879-
)
880-
)
881-
882-
DeclSyntax(
883-
FunctionDeclSyntax(
884-
modifiers: DeclModifierListSyntax {
885-
DeclModifierSyntax(name: .keyword(.static))
886-
},
887-
name: .identifier("bridgeJSLift"),
888-
signature: FunctionSignatureSyntax(
889-
parameterClause: FunctionParameterClauseSyntax {
890-
FunctionParameterSyntax(
891-
firstName: .wildcardToken(),
892-
secondName: .identifier("callbackId"),
893-
colon: .colonToken(),
894-
type: IdentifierTypeSyntax(name: .identifier("Int32"))
895-
)
896-
},
897-
returnClause: ReturnClauseSyntax(
898-
arrow: .arrowToken(),
899-
type: IdentifierTypeSyntax(name: .identifier(closureType))
900-
)
901-
),
902-
body: CodeBlockSyntax {
903-
"let callback = JSObject.bridgeJSLiftParameter(callbackId)"
904-
ReturnStmtSyntax(
905-
expression: ClosureExprSyntax(
906-
leftBrace: .leftBraceToken(),
907-
signature: ClosureSignatureSyntax(
908-
capture: ClosureCaptureClauseSyntax(
909-
leftSquare: .leftSquareToken(),
910-
items: ClosureCaptureListSyntax {
911-
#if canImport(SwiftSyntax602)
912-
ClosureCaptureSyntax(
913-
name: .identifier("", presence: .missing),
914-
initializer: InitializerClauseSyntax(
915-
equal: .equalToken(presence: .missing),
916-
nil,
917-
value: ExprSyntax("callback")
918-
),
919-
trailingTrivia: nil
920-
)
921-
#else
922-
ClosureCaptureSyntax(
923-
expression: ExprSyntax("callback")
924-
)
925-
#endif
926-
},
927-
rightSquare: .rightSquareToken()
928-
),
929-
parameterClause: .simpleInput(
930-
ClosureShorthandParameterListSyntax {
931-
for (index, _) in signature.parameters.enumerated() {
932-
ClosureShorthandParameterSyntax(name: .identifier("param\(index)"))
933-
}
934-
}
935-
),
936-
inKeyword: .keyword(.in)
937-
),
938-
statements: CodeBlockItemListSyntax {
939-
SwiftCodePattern.buildWasmConditionalCompilation(wasmBody: bodyCode.statements)
940-
},
941-
rightBrace: .rightBraceToken()
942-
)
943-
)
944-
}
945-
)
946-
)
947-
}
948-
)
949-
return [externDecl, boxClassDecl, DeclSyntax(helperEnumDecl)]
950-
}
951-
952-
func renderClosureInvokeHandler(_ signature: ClosureSignature) throws -> DeclSyntax {
953-
let boxClassName = "_BJS_ClosureBox_\(signature.mangleName)"
954-
let abiName = "invoke_swift_closure_\(signature.moduleName)_\(signature.mangleName)"
955-
956-
// Build ABI parameters directly with WasmCoreType (no string conversion needed)
957-
var abiParams: [(name: String, type: WasmCoreType)] = [("boxPtr", .pointer)]
958-
var liftedParams: [String] = []
959-
960-
for (index, paramType) in signature.parameters.enumerated() {
961-
let paramName = "param\(index)"
962-
let liftInfo = try paramType.liftParameterInfo()
963-
964-
for (argName, wasmType) in liftInfo.parameters {
965-
let fullName =
966-
liftInfo.parameters.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName
967-
abiParams.append((fullName, wasmType))
968-
}
969-
970-
let argNames = liftInfo.parameters.map { (argName, _) in
971-
liftInfo.parameters.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName
972-
}
973-
liftedParams.append("\(paramType.swiftType).bridgeJSLiftParameter(\(argNames.joined(separator: ", ")))")
974-
}
975-
976-
let closureCallExpr = ExprSyntax("box.closure(\(raw: liftedParams.joined(separator: ", ")))")
977-
978-
// Determine return type
979-
let abiReturnWasmType: WasmCoreType?
980-
if signature.returnType == .void {
981-
abiReturnWasmType = nil
982-
} else if let wasmType = try signature.returnType.loweringReturnInfo().returnType {
983-
abiReturnWasmType = wasmType
984-
} else {
985-
abiReturnWasmType = nil
986-
}
987-
988-
// Build signature using SwiftSignatureBuilder
989-
let funcSignature = SwiftSignatureBuilder.buildABIFunctionSignature(
990-
abiParameters: abiParams,
991-
returnType: abiReturnWasmType
992-
)
993-
994-
// Build body
995-
let body = CodeBlockItemListSyntax {
996-
"let box = Unmanaged<\(raw: boxClassName)>.fromOpaque(boxPtr).takeUnretainedValue()"
997-
if signature.returnType == .void {
998-
closureCallExpr
999-
} else {
1000-
"let result = \(closureCallExpr)"
1001-
"return result.bridgeJSLowerReturn()"
1002-
}
1003-
}
1004-
1005-
// Build function declaration using helper
1006-
let funcDecl = SwiftCodePattern.buildExposedFunctionDecl(
1007-
abiName: abiName,
1008-
signature: funcSignature,
1009-
body: body
1010-
)
1011-
1012-
return DeclSyntax(funcDecl)
1013-
}
1014-
1015-
}
1016-
1017752
// MARK: - StackCodegen
1018753

1019754
/// Helper for stack-based lifting and lowering operations.

0 commit comments

Comments
 (0)