Skip to content

Commit d3a5164

Browse files
committed
BridgeJS: Support for multiple associated values in enums using binary buffer format
1 parent 2d23296 commit d3a5164

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+5408
-537
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 186 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class ExportSwift {
2525
private var exportedClasses: [ExportedClass] = []
2626
private var exportedEnums: [ExportedEnum] = []
2727
private var typeDeclResolver: TypeDeclResolver = TypeDeclResolver()
28+
private let enumCodegen: EnumCodegen = EnumCodegen()
2829

2930
public init(progress: ProgressReporting, moduleName: String) {
3031
self.progress = progress
@@ -524,6 +525,24 @@ public class ExportSwift {
524525
)
525526
}
526527

528+
if currentEnum.cases.contains(where: { !$0.associatedValues.isEmpty }) {
529+
for enumCase in currentEnum.cases {
530+
for associatedValue in enumCase.associatedValues {
531+
switch associatedValue.type {
532+
case .string, .int, .float, .double, .bool:
533+
break
534+
default:
535+
diagnose(
536+
node: node,
537+
message: "Unsupported associated value type: \(associatedValue.type.swiftType)",
538+
hint:
539+
"Only primitive types (String, Int, Float, Double, Bool) are supported in associated-value enums"
540+
)
541+
}
542+
}
543+
}
544+
}
545+
527546
let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: enumName)
528547
let explicitAccessControl = computeExplicitAtLeastInternalAccessControl(
529548
for: node,
@@ -753,10 +772,15 @@ public class ExportSwift {
753772
decls.append(Self.prelude)
754773

755774
for enumDef in exportedEnums {
756-
if enumDef.enumType == .simple {
757-
decls.append(renderCaseEnumHelpers(enumDef))
758-
} else {
775+
switch enumDef.enumType {
776+
case .simple:
777+
decls.append(enumCodegen.renderCaseEnumHelpers(enumDef))
778+
case .rawValue:
759779
decls.append("extension \(raw: enumDef.swiftCallName): _BridgedSwiftEnumNoPayload {}")
780+
case .associatedValue:
781+
decls.append(enumCodegen.renderAssociatedValueEnumHelpers(enumDef))
782+
case .namespace:
783+
()
760784
}
761785
}
762786

@@ -770,45 +794,6 @@ public class ExportSwift {
770794
return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n")
771795
}
772796

773-
func renderCaseEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax {
774-
let typeName = enumDef.swiftCallName
775-
var initCases: [String] = []
776-
var valueCases: [String] = []
777-
for (index, c) in enumDef.cases.enumerated() {
778-
initCases.append("case \(index): self = .\(c.name)")
779-
valueCases.append("case .\(c.name): return \(index)")
780-
}
781-
let initSwitch = (["switch bridgeJSRawValue {"] + initCases + ["default: return nil", "}"]).joined(
782-
separator: "\n"
783-
)
784-
let valueSwitch = (["switch self {"] + valueCases + ["}"]).joined(separator: "\n")
785-
786-
return """
787-
extension \(raw: typeName) {
788-
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 {
789-
return bridgeJSRawValue
790-
}
791-
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> \(raw: typeName) {
792-
return \(raw: typeName)(bridgeJSRawValue: value)!
793-
}
794-
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> \(raw: typeName) {
795-
return \(raw: typeName)(bridgeJSRawValue: value)!
796-
}
797-
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 {
798-
return bridgeJSRawValue
799-
}
800-
801-
private init?(bridgeJSRawValue: Int32) {
802-
\(raw: initSwitch)
803-
}
804-
805-
private var bridgeJSRawValue: Int32 {
806-
\(raw: valueSwitch)
807-
}
808-
}
809-
"""
810-
}
811-
812797
class ExportedThunkBuilder {
813798
var body: [CodeBlockItemSyntax] = []
814799
var liftedParameterExprs: [ExprSyntax] = []
@@ -1006,6 +991,159 @@ public class ExportSwift {
1006991
}
1007992
}
1008993

994+
private struct EnumCodegen {
995+
func renderCaseEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax {
996+
let typeName = enumDef.swiftCallName
997+
var initCases: [String] = []
998+
var valueCases: [String] = []
999+
for (index, enumCase) in enumDef.cases.enumerated() {
1000+
initCases.append("case \(index): self = .\(enumCase.name)")
1001+
valueCases.append("case .\(enumCase.name): return \(index)")
1002+
}
1003+
let initSwitch = (["switch bridgeJSRawValue {"] + initCases + ["default: return nil", "}"]).joined(
1004+
separator: "\n"
1005+
)
1006+
let valueSwitch = (["switch self {"] + valueCases + ["}"]).joined(separator: "\n")
1007+
1008+
return """
1009+
extension \(raw: typeName) {
1010+
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 {
1011+
return bridgeJSRawValue
1012+
}
1013+
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> \(raw: typeName) {
1014+
return \(raw: typeName)(bridgeJSRawValue: value)!
1015+
}
1016+
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> \(raw: typeName) {
1017+
return \(raw: typeName)(bridgeJSRawValue: value)!
1018+
}
1019+
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 {
1020+
return bridgeJSRawValue
1021+
}
1022+
1023+
private init?(bridgeJSRawValue: Int32) {
1024+
\(raw: initSwitch)
1025+
}
1026+
1027+
private var bridgeJSRawValue: Int32 {
1028+
\(raw: valueSwitch)
1029+
}
1030+
}
1031+
"""
1032+
}
1033+
1034+
func renderAssociatedValueEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax {
1035+
let typeName = enumDef.swiftCallName
1036+
return """
1037+
private extension \(raw: typeName) {
1038+
static func bridgeJSLiftParameter(_ caseId: Int32, _ paramsId: Int32, _ paramsLen: Int32) -> \(raw: typeName) {
1039+
let params: [UInt8] = .init(unsafeUninitializedCapacity: Int(paramsLen)) { buf, initializedCount in
1040+
_swift_js_init_memory(paramsId, buf.baseAddress.unsafelyUnwrapped)
1041+
initializedCount = Int(paramsLen)
1042+
}
1043+
return params.withUnsafeBytes { raw in
1044+
var reader = _BJSBinaryReader(raw: raw)
1045+
switch caseId {
1046+
\(raw: generateBinaryLiftSwitchCases(enumDef: enumDef).joined(separator: "\n"))
1047+
default: fatalError("Unknown \(raw: typeName) case ID: \\(caseId)")
1048+
}
1049+
}
1050+
}
1051+
1052+
func bridgeJSLowerReturn() {
1053+
switch self {
1054+
\(raw: generateReturnSwitchCases(enumDef: enumDef).joined(separator: "\n"))
1055+
}
1056+
}
1057+
}
1058+
"""
1059+
}
1060+
1061+
private func generateBinaryLiftSwitchCases(enumDef: ExportedEnum) -> [String] {
1062+
var cases: [String] = []
1063+
for (caseIndex, enumCase) in enumDef.cases.enumerated() {
1064+
if enumCase.associatedValues.isEmpty {
1065+
cases.append("case \(caseIndex): return .\(enumCase.name)")
1066+
} else {
1067+
var lines: [String] = []
1068+
lines.append("case \(caseIndex):")
1069+
lines.append("reader.readParamCount(expected: \(enumCase.associatedValues.count))")
1070+
var argList: [String] = []
1071+
1072+
for (paramIndex, associatedValue) in enumCase.associatedValues.enumerated() {
1073+
let paramName = associatedValue.label ?? "param\(paramIndex)"
1074+
argList.append(paramName)
1075+
1076+
switch associatedValue.type {
1077+
case .string:
1078+
lines.append("reader.expectTag(.string)")
1079+
lines.append("let \(paramName) = reader.readString()")
1080+
case .int:
1081+
lines.append("reader.expectTag(.int32)")
1082+
lines.append("let \(paramName) = Int(reader.readInt32())")
1083+
case .bool:
1084+
lines.append("reader.expectTag(.bool)")
1085+
lines.append("let \(paramName) = Int32(reader.readUInt8()) != 0")
1086+
case .float:
1087+
lines.append("reader.expectTag(.float32)")
1088+
lines.append("let \(paramName) = reader.readFloat32()")
1089+
case .double:
1090+
lines.append("reader.expectTag(.float64)")
1091+
lines.append("let \(paramName) = reader.readFloat64()")
1092+
default:
1093+
lines.append("reader.expectTag(.int32)")
1094+
lines.append("let \(paramName) = reader.readInt32()")
1095+
}
1096+
}
1097+
1098+
lines.append("return .\(enumCase.name)(\(argList.joined(separator: ", ")))")
1099+
cases.append(lines.joined(separator: "\n"))
1100+
}
1101+
}
1102+
return cases
1103+
}
1104+
1105+
private func generateReturnSwitchCases(enumDef: ExportedEnum) -> [String] {
1106+
var cases: [String] = []
1107+
for (caseIndex, enumCase) in enumDef.cases.enumerated() {
1108+
if enumCase.associatedValues.isEmpty {
1109+
cases.append("case .\(enumCase.name):")
1110+
cases.append("_swift_js_return_tag(Int32(\(caseIndex)))")
1111+
} else {
1112+
var bodyLines: [String] = []
1113+
bodyLines.append("_swift_js_return_tag(Int32(\(caseIndex)))")
1114+
for (index, associatedValue) in enumCase.associatedValues.enumerated() {
1115+
let paramName = associatedValue.label ?? "param\(index)"
1116+
switch associatedValue.type {
1117+
case .string:
1118+
bodyLines.append("var __bjs_\(paramName) = \(paramName)")
1119+
bodyLines.append("__bjs_\(paramName).withUTF8 { ptr in")
1120+
bodyLines.append("_swift_js_return_string(ptr.baseAddress, Int32(ptr.count))")
1121+
bodyLines.append("}")
1122+
case .int:
1123+
bodyLines.append("_swift_js_return_int(Int32(\(paramName)))")
1124+
case .bool:
1125+
bodyLines.append("_swift_js_return_bool(\(paramName) ? 1 : 0)")
1126+
case .float:
1127+
bodyLines.append("_swift_js_return_f32(\(paramName))")
1128+
case .double:
1129+
bodyLines.append("_swift_js_return_f64(\(paramName))")
1130+
default:
1131+
bodyLines.append(
1132+
"preconditionFailure(\"BridgeJS: unsupported associated value type in generated code\")"
1133+
)
1134+
}
1135+
}
1136+
let pattern = enumCase.associatedValues.enumerated()
1137+
.map { index, associatedValue in "let \(associatedValue.label ?? "param\(index)")" }
1138+
.joined(separator: ", ")
1139+
cases.append("case .\(enumCase.name)(\(pattern)):")
1140+
cases.append(contentsOf: bodyLines)
1141+
}
1142+
}
1143+
return cases
1144+
}
1145+
}
1146+
10091147
func renderSingleExportedFunction(function: ExportedFunction) throws -> DeclSyntax {
10101148
let builder = ExportedThunkBuilder(effects: function.effects)
10111149
for param in function.parameters {
@@ -1264,6 +1402,9 @@ extension BridgeType {
12641402
static let swiftHeapObject = LiftingIntrinsicInfo(parameters: [("value", .pointer)])
12651403
static let void = LiftingIntrinsicInfo(parameters: [])
12661404
static let caseEnum = LiftingIntrinsicInfo(parameters: [("value", .i32)])
1405+
static let associatedValueEnum = LiftingIntrinsicInfo(parameters: [
1406+
("caseId", .i32), ("paramsId", .i32), ("paramsLen", .i32),
1407+
])
12671408
}
12681409

12691410
func liftParameterInfo() throws -> LiftingIntrinsicInfo {
@@ -1291,7 +1432,7 @@ extension BridgeType {
12911432
case .uint64: return .int
12921433
}
12931434
case .associatedValueEnum:
1294-
throw BridgeJSCoreError("Associated value enums are not supported to pass as parameters")
1435+
return .associatedValueEnum
12951436
case .namespaceEnum:
12961437
throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters")
12971438
}
@@ -1310,6 +1451,7 @@ extension BridgeType {
13101451
static let void = LoweringIntrinsicInfo(returnType: nil)
13111452
static let caseEnum = LoweringIntrinsicInfo(returnType: .i32)
13121453
static let rawValueEnum = LoweringIntrinsicInfo(returnType: .i32)
1454+
static let associatedValueEnum = LoweringIntrinsicInfo(returnType: nil)
13131455
}
13141456

13151457
func loweringReturnInfo() throws -> LoweringIntrinsicInfo {
@@ -1337,7 +1479,7 @@ extension BridgeType {
13371479
case .uint64: return .int
13381480
}
13391481
case .associatedValueEnum:
1340-
throw BridgeJSCoreError("Associated value enums are not supported to pass as parameters")
1482+
return .associatedValueEnum
13411483
case .namespaceEnum:
13421484
throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters")
13431485
}

0 commit comments

Comments
 (0)