Skip to content

Commit d03e74c

Browse files
committed
BridgeJS: Support for case / raw type Swift -> TS code generation
1 parent 15e9491 commit d03e74c

Some content is hidden

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

52 files changed

+2859
-4
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 302 additions & 2 deletions
Large diffs are not rendered by default.

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ public struct ImportTS {
123123
)
124124
)
125125
abiParameterSignatures.append((param.name, .i32))
126+
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:
127+
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
126128
case .jsObject(_?):
127129
abiParameterSignatures.append((param.name, .i32))
128130
abiParameterForwardings.append(
@@ -181,6 +183,8 @@ public struct ImportTS {
181183
}
182184
"""
183185
)
186+
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:
187+
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
184188
case .jsObject(let name):
185189
abiReturnType = .i32
186190
if let name = name {

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ struct BridgeJSLink {
6969
var dtsClassLines: [String] = []
7070
var namespacedFunctions: [ExportedFunction] = []
7171
var namespacedClasses: [ExportedClass] = []
72+
var enumConstantLines: [String] = []
73+
var dtsEnumLines: [String] = []
7274

7375
if exportedSkeletons.contains(where: { $0.classes.count > 0 }) {
7476
classLines.append(
@@ -96,6 +98,15 @@ struct BridgeJSLink {
9698
}
9799
}
98100

101+
if !skeleton.enums.isEmpty {
102+
for enumDef in skeleton.enums {
103+
let (jsEnum, dtsEnum) = try renderExportedEnum(enumDef)
104+
enumConstantLines.append(contentsOf: jsEnum)
105+
exportsLines.append("\(enumDef.name),")
106+
dtsEnumLines.append(contentsOf: dtsEnum)
107+
}
108+
}
109+
99110
for function in skeleton.functions {
100111
var (js, dts) = renderExportedFunction(function: function)
101112

@@ -135,6 +146,7 @@ struct BridgeJSLink {
135146
.map { $0.indent(count: 12) }.joined(separator: "\n")
136147
exportsSection = """
137148
\(classLines.map { $0.indent(count: 12) }.joined(separator: "\n"))
149+
\(enumConstantLines.map { $0.indent(count: 12) }.joined(separator: "\n"))
138150
const exports = {
139151
\(exportsLines.map { $0.indent(count: 16) }.joined(separator: "\n"))
140152
};
@@ -147,6 +159,7 @@ struct BridgeJSLink {
147159
} else {
148160
exportsSection = """
149161
\(classLines.map { $0.indent(count: 12) }.joined(separator: "\n"))
162+
\(enumConstantLines.map { $0.indent(count: 12) }.joined(separator: "\n"))
150163
return {
151164
\(exportsLines.map { $0.indent(count: 16) }.joined(separator: "\n"))
152165
};
@@ -227,6 +240,7 @@ struct BridgeJSLink {
227240
var dtsLines: [String] = []
228241
dtsLines.append(contentsOf: namespaceDeclarations())
229242
dtsLines.append(contentsOf: dtsClassLines)
243+
dtsLines.append(contentsOf: dtsEnumLines)
230244
dtsLines.append(contentsOf: generateImportedTypeDefinitions())
231245
dtsLines.append("export type Exports = {")
232246
dtsLines.append(contentsOf: dtsExportLines.map { $0.indent(count: 4) })
@@ -437,6 +451,29 @@ struct BridgeJSLink {
437451
cleanupLines.append("swift.memory.release(\(bytesIdLabel));")
438452
parameterForwardings.append(bytesIdLabel)
439453
parameterForwardings.append("\(bytesLabel).length")
454+
case .caseEnum(_):
455+
// Case enum: JavaScript receives a number (0,1,2...), pass directly to WASM
456+
parameterForwardings.append("\(param.name) | 0")
457+
case .rawValueEnum(_, let rawType):
458+
switch rawType {
459+
case "String":
460+
let bytesLabel = "\(param.name)Bytes"
461+
let bytesIdLabel = "\(param.name)Id"
462+
bodyLines.append("const \(bytesLabel) = textEncoder.encode(\(param.name));")
463+
bodyLines.append("const \(bytesIdLabel) = swift.memory.retain(\(bytesLabel));")
464+
cleanupLines.append("swift.memory.release(\(bytesIdLabel));")
465+
parameterForwardings.append(bytesIdLabel)
466+
parameterForwardings.append("\(bytesLabel).length")
467+
case "Bool":
468+
parameterForwardings.append("\(param.name) ? 1 : 0")
469+
default:
470+
parameterForwardings.append("\(param.name)")
471+
}
472+
case .associatedValueEnum:
473+
parameterForwardings.append("0")
474+
parameterForwardings.append("0")
475+
case .namespaceEnum:
476+
break
440477
case .jsObject:
441478
parameterForwardings.append("swift.memory.retain(\(param.name))")
442479
case .swiftHeapObject:
@@ -468,6 +505,30 @@ struct BridgeJSLink {
468505
bodyLines.append("const ret = tmpRetString;")
469506
bodyLines.append("tmpRetString = undefined;")
470507
returnExpr = "ret"
508+
case .caseEnum(_):
509+
// Case enum: WASM returns Int32, use directly as JavaScript number
510+
bodyLines.append("const ret = \(call);")
511+
returnExpr = "ret"
512+
case .rawValueEnum(_, let rawType):
513+
switch rawType {
514+
case "String":
515+
bodyLines.append("\(call);")
516+
bodyLines.append("const ret = tmpRetString;")
517+
bodyLines.append("tmpRetString = undefined;")
518+
returnExpr = "ret"
519+
case "Bool":
520+
bodyLines.append("const ret = \(call);")
521+
returnExpr = "ret !== 0"
522+
default:
523+
bodyLines.append("const ret = \(call);")
524+
returnExpr = "ret"
525+
}
526+
case .associatedValueEnum:
527+
bodyLines.append("\(call);")
528+
returnExpr = "\"\""
529+
case .namespaceEnum:
530+
bodyLines.append("\(call);")
531+
returnExpr = "undefined"
471532
case .int, .float, .double:
472533
bodyLines.append("const ret = \(call);")
473534
returnExpr = "ret"
@@ -541,6 +602,86 @@ struct BridgeJSLink {
541602
"(\(parameters.map { "\($0.name): \($0.type.tsType)" }.joined(separator: ", "))): \(returnTypeWithEffect)"
542603
}
543604

605+
func renderExportedEnum(_ enumDef: ExportedEnum) throws -> (js: [String], dts: [String]) {
606+
var jsLines: [String] = []
607+
var dtsLines: [String] = []
608+
609+
switch enumDef.enumType {
610+
case .simple:
611+
jsLines.append("const \(enumDef.name) = {")
612+
for (index, enumCase) in enumDef.cases.enumerated() {
613+
let caseName = enumCase.name.capitalizedFirstLetter
614+
jsLines.append(" \(caseName): \(index),".indent(count: 0))
615+
}
616+
jsLines.append("};")
617+
jsLines.append("")
618+
619+
dtsLines.append("export const \(enumDef.name): {")
620+
for (index, enumCase) in enumDef.cases.enumerated() {
621+
let caseName = enumCase.name.capitalizedFirstLetter
622+
dtsLines.append(" readonly \(caseName): \(index);")
623+
}
624+
dtsLines.append("};")
625+
dtsLines.append("export type \(enumDef.name) = typeof \(enumDef.name)[keyof typeof \(enumDef.name)];")
626+
dtsLines.append("")
627+
case .rawValue:
628+
guard let rawType = enumDef.rawType else {
629+
throw BridgeJSLinkError(message: "Raw value enum \(enumDef.name) is missing rawType")
630+
}
631+
632+
jsLines.append("const \(enumDef.name) = {")
633+
for enumCase in enumDef.cases {
634+
let caseName = enumCase.name.capitalizedFirstLetter
635+
let rawValue = enumCase.rawValue ?? enumCase.name
636+
let formattedValue: String
637+
638+
switch rawType {
639+
case "String":
640+
formattedValue = "\"\(rawValue)\""
641+
case "Bool":
642+
formattedValue = rawValue.lowercased() == "true" ? "true" : "false"
643+
case "Float", "Double":
644+
formattedValue = rawValue
645+
default:
646+
formattedValue = rawValue
647+
}
648+
649+
jsLines.append("\(caseName): \(formattedValue),".indent(count: 4))
650+
}
651+
jsLines.append("};")
652+
jsLines.append("")
653+
654+
dtsLines.append("export const \(enumDef.name): {")
655+
for enumCase in enumDef.cases {
656+
let caseName = enumCase.name.capitalizedFirstLetter
657+
let rawValue = enumCase.rawValue ?? enumCase.name
658+
let formattedValue: String
659+
660+
switch rawType {
661+
case "String":
662+
formattedValue = "\"\(rawValue)\""
663+
case "Bool":
664+
formattedValue = rawValue.lowercased() == "true" ? "true" : "false"
665+
case "Float", "Double":
666+
formattedValue = rawValue
667+
default:
668+
formattedValue = rawValue
669+
}
670+
671+
dtsLines.append(" readonly \(caseName): \(formattedValue);")
672+
}
673+
dtsLines.append("};")
674+
dtsLines.append("export type \(enumDef.name) = typeof \(enumDef.name)[keyof typeof \(enumDef.name)];")
675+
dtsLines.append("")
676+
677+
case .associatedValue, .namespace:
678+
jsLines.append("// TODO: Implement \(enumDef.enumType) enum: \(enumDef.name)")
679+
dtsLines.append("// TODO: Implement \(enumDef.enumType) enum: \(enumDef.name)")
680+
}
681+
682+
return (jsLines, dtsLines)
683+
}
684+
544685
func renderExportedFunction(function: ExportedFunction) -> (js: [String], dts: [String]) {
545686
let thunkBuilder = ExportedThunkBuilder(effects: function.effects)
546687
for param in function.parameters {
@@ -698,6 +839,24 @@ struct BridgeJSLink {
698839
bodyLines.append("const \(stringObjectName) = swift.memory.getObject(\(param.name));")
699840
bodyLines.append("swift.memory.release(\(param.name));")
700841
parameterForwardings.append(stringObjectName)
842+
case .caseEnum(_):
843+
parameterForwardings.append(param.name)
844+
case .rawValueEnum(_, let rawType):
845+
switch rawType {
846+
case "String":
847+
let stringObjectName = "\(param.name)Object"
848+
bodyLines.append("const \(stringObjectName) = swift.memory.getObject(\(param.name));")
849+
bodyLines.append("swift.memory.release(\(param.name));")
850+
parameterForwardings.append(stringObjectName)
851+
case "Bool":
852+
parameterForwardings.append("\(param.name) !== 0")
853+
default:
854+
parameterForwardings.append(param.name)
855+
}
856+
case .associatedValueEnum:
857+
parameterForwardings.append("\"\"")
858+
case .namespaceEnum:
859+
break
701860
case .jsObject:
702861
parameterForwardings.append("swift.memory.getObject(\(param.name))")
703862
default:
@@ -769,6 +928,22 @@ struct BridgeJSLink {
769928
case .string:
770929
bodyLines.append("tmpRetBytes = textEncoder.encode(ret);")
771930
return "tmpRetBytes.length"
931+
case .caseEnum(_):
932+
return "ret"
933+
case .rawValueEnum(_, let rawType):
934+
switch rawType {
935+
case "String":
936+
bodyLines.append("tmpRetBytes = textEncoder.encode(ret);")
937+
return "tmpRetBytes.length"
938+
case "Bool":
939+
return "ret ? 1 : 0"
940+
default:
941+
return "ret"
942+
}
943+
case .associatedValueEnum:
944+
return "0"
945+
case .namespaceEnum:
946+
return "0"
772947
case .int, .float, .double:
773948
return "ret"
774949
case .bool:
@@ -947,6 +1122,11 @@ extension String {
9471122
func indent(count: Int) -> String {
9481123
return String(repeating: " ", count: count) + self
9491124
}
1125+
1126+
var capitalizedFirstLetter: String {
1127+
guard !isEmpty else { return self }
1128+
return prefix(1).uppercased() + dropFirst()
1129+
}
9501130
}
9511131

9521132
extension BridgeType {
@@ -968,6 +1148,14 @@ extension BridgeType {
9681148
return name ?? "any"
9691149
case .swiftHeapObject(let name):
9701150
return name
1151+
case .caseEnum(let name):
1152+
return name
1153+
case .rawValueEnum(let name, _):
1154+
return name
1155+
case .associatedValueEnum(let name):
1156+
return name
1157+
case .namespaceEnum(let name):
1158+
return name
9711159
}
9721160
}
9731161
}

0 commit comments

Comments
 (0)