Skip to content

Commit 5b177a5

Browse files
committed
BridgeJS: Support for multiple associated values in enums using binary buffer format
1 parent eb33e5d commit 5b177a5

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

+5415
-537
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 193 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,31 @@ public class ExportSwift {
524525
)
525526
}
526527

528+
if currentEnum.cases.contains(where: { !$0.associatedValues.isEmpty }) {
529+
if case .tsEnum = emitStyle {
530+
diagnose(
531+
node: jsAttribute,
532+
message: "TypeScript enum style is not supported for associated value enums",
533+
hint: "Use enumStyle: .const in order to map associated-value enums"
534+
)
535+
}
536+
for enumCase in currentEnum.cases {
537+
for associatedValue in enumCase.associatedValues {
538+
switch associatedValue.type {
539+
case .string, .int, .float, .double, .bool:
540+
break
541+
default:
542+
diagnose(
543+
node: node,
544+
message: "Unsupported associated value type: \(associatedValue.type.swiftType)",
545+
hint:
546+
"Only primitive types (String, Int, Float, Double, Bool) are supported in associated-value enums"
547+
)
548+
}
549+
}
550+
}
551+
}
552+
527553
let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: enumName)
528554
let explicitAccessControl = computeExplicitAtLeastInternalAccessControl(
529555
for: node,
@@ -753,10 +779,15 @@ public class ExportSwift {
753779
decls.append(Self.prelude)
754780

755781
for enumDef in exportedEnums {
756-
if enumDef.enumType == .simple {
757-
decls.append(renderCaseEnumHelpers(enumDef))
758-
} else {
782+
switch enumDef.enumType {
783+
case .simple:
784+
decls.append(enumCodegen.renderCaseEnumHelpers(enumDef))
785+
case .rawValue:
759786
decls.append("extension \(raw: enumDef.swiftCallName): _BridgedSwiftEnumNoPayload {}")
787+
case .associatedValue:
788+
decls.append(enumCodegen.renderAssociatedValueEnumHelpers(enumDef))
789+
case .namespace:
790+
()
760791
}
761792
}
762793

@@ -770,45 +801,6 @@ public class ExportSwift {
770801
return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n")
771802
}
772803

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-
812804
class ExportedThunkBuilder {
813805
var body: [CodeBlockItemSyntax] = []
814806
var liftedParameterExprs: [ExprSyntax] = []
@@ -1006,6 +998,159 @@ public class ExportSwift {
1006998
}
1007999
}
10081000

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

12691417
func liftParameterInfo() throws -> LiftingIntrinsicInfo {
@@ -1291,7 +1439,7 @@ extension BridgeType {
12911439
case .uint64: return .int
12921440
}
12931441
case .associatedValueEnum:
1294-
throw BridgeJSCoreError("Associated value enums are not supported to pass as parameters")
1442+
return .associatedValueEnum
12951443
case .namespaceEnum:
12961444
throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters")
12971445
}
@@ -1310,6 +1458,7 @@ extension BridgeType {
13101458
static let void = LoweringIntrinsicInfo(returnType: nil)
13111459
static let caseEnum = LoweringIntrinsicInfo(returnType: .i32)
13121460
static let rawValueEnum = LoweringIntrinsicInfo(returnType: .i32)
1461+
static let associatedValueEnum = LoweringIntrinsicInfo(returnType: nil)
13131462
}
13141463

13151464
func loweringReturnInfo() throws -> LoweringIntrinsicInfo {
@@ -1337,7 +1486,7 @@ extension BridgeType {
13371486
case .uint64: return .int
13381487
}
13391488
case .associatedValueEnum:
1340-
throw BridgeJSCoreError("Associated value enums are not supported to pass as parameters")
1489+
return .associatedValueEnum
13411490
case .namespaceEnum:
13421491
throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters")
13431492
}

0 commit comments

Comments
 (0)