@@ -267,6 +267,24 @@ public class ExportSwift {
267
267
return namespaceString. split ( separator: " . " ) . map ( String . init)
268
268
}
269
269
270
+ private func extractEnumStyle(
271
+ from jsAttribute: AttributeSyntax
272
+ ) -> EnumEmitStyle ? {
273
+ guard let arguments = jsAttribute. arguments? . as ( LabeledExprListSyntax . self) ,
274
+ let styleArg = arguments. first ( where: { $0. label? . text == " enumStyle " } )
275
+ else {
276
+ return nil
277
+ }
278
+ let text = styleArg. expression. trimmedDescription
279
+ if text. contains ( " tsEnum " ) {
280
+ return . tsEnum
281
+ }
282
+ if text. contains ( " const " ) {
283
+ return . const
284
+ }
285
+ return nil
286
+ }
287
+
270
288
override func visit( _ node: InitializerDeclSyntax ) -> SyntaxVisitorContinueKind {
271
289
guard node. attributes. hasJSAttribute ( ) else { return . skipChildren }
272
290
guard case . classBody( let className, _) = state else {
@@ -318,9 +336,6 @@ public class ExportSwift {
318
336
let name = node. name. text
319
337
320
338
guard let jsAttribute = node. attributes. firstJSAttribute else {
321
- if case . enumBody( _) = state {
322
- return . skipChildren
323
- }
324
339
return . skipChildren
325
340
}
326
341
@@ -355,14 +370,14 @@ public class ExportSwift {
355
370
}
356
371
357
372
override func visitPost( _ node: ClassDeclSyntax ) {
358
- stateStack. pop ( )
373
+ // Make sure we pop the state stack only if we're in a class body state (meaning we successfully pushed)
374
+ if case . classBody( _, _) = stateStack. current {
375
+ stateStack. pop ( )
376
+ }
359
377
}
360
378
361
379
override func visit( _ node: EnumDeclSyntax ) -> SyntaxVisitorContinueKind {
362
380
guard node. attributes. hasJSAttribute ( ) else {
363
- if case . enumBody( _) = state {
364
- return . skipChildren
365
- }
366
381
return . skipChildren
367
382
}
368
383
@@ -402,6 +417,10 @@ public class ExportSwift {
402
417
guard let jsAttribute = node. attributes. firstJSAttribute,
403
418
let enumName = currentEnum. name
404
419
else {
420
+ // Only pop if we have a valid enum that was processed
421
+ if case . enumBody( _) = stateStack. current {
422
+ stateStack. pop ( )
423
+ }
405
424
return
406
425
}
407
426
@@ -415,13 +434,26 @@ public class ExportSwift {
415
434
effectiveNamespace = computedNamespace
416
435
}
417
436
437
+ let emitStyle = extractEnumStyle ( from: jsAttribute) ?? . const
438
+ if case . tsEnum = emitStyle,
439
+ let raw = currentEnum. rawType,
440
+ let rawEnum = SwiftEnumRawType . from ( raw) , rawEnum == . bool
441
+ {
442
+ diagnose (
443
+ node: jsAttribute,
444
+ message: " TypeScript enum style is not supported for Bool raw-value enums " ,
445
+ hint: " Use enumStyle: .const or change the raw type to String or a numeric type "
446
+ )
447
+ }
448
+
418
449
let swiftCallName = ExportSwift . computeSwiftCallName ( for: node, itemName: enumName)
419
450
let exportedEnum = ExportedEnum (
420
451
name: enumName,
421
452
swiftCallName: swiftCallName,
422
453
cases: currentEnum. cases,
423
454
rawType: currentEnum. rawType,
424
- namespace: effectiveNamespace
455
+ namespace: effectiveNamespace,
456
+ emitStyle: emitStyle
425
457
)
426
458
exportedEnumByName [ enumName] = exportedEnum
427
459
exportedEnumNames. append ( enumName)
@@ -614,6 +646,11 @@ public class ExportSwift {
614
646
return nil
615
647
}
616
648
decls. append ( Self . prelude)
649
+
650
+ for enumDef in exportedEnums where enumDef. enumType == . simple {
651
+ decls. append ( renderCaseEnumHelpers ( enumDef) )
652
+ }
653
+
617
654
for function in exportedFunctions {
618
655
decls. append ( renderSingleExportedFunction ( function: function) )
619
656
}
@@ -624,6 +661,32 @@ public class ExportSwift {
624
661
return decls. map { $0. formatted ( using: format) . description } . joined ( separator: " \n \n " )
625
662
}
626
663
664
+ func renderCaseEnumHelpers( _ enumDef: ExportedEnum ) -> DeclSyntax {
665
+ let typeName = enumDef. swiftCallName
666
+ var initCases : [ String ] = [ ]
667
+ var valueCases : [ String ] = [ ]
668
+ for (index, c) in enumDef. cases. enumerated ( ) {
669
+ initCases. append ( " case \( index) : self = . \( c. name) " )
670
+ valueCases. append ( " case . \( c. name) : return \( index) " )
671
+ }
672
+ let initSwitch = ( [ " switch bridgeJSRawValue { " ] + initCases + [ " default: return nil " , " } " ] ) . joined (
673
+ separator: " \n "
674
+ )
675
+ let valueSwitch = ( [ " switch self { " ] + valueCases + [ " } " ] ) . joined ( separator: " \n " )
676
+
677
+ return """
678
+ extension \( raw: typeName) {
679
+ init?(bridgeJSRawValue: Int32) {
680
+ \( raw: initSwitch)
681
+ }
682
+
683
+ var bridgeJSRawValue: Int32 {
684
+ \( raw: valueSwitch)
685
+ }
686
+ }
687
+ """
688
+ }
689
+
627
690
class ExportedThunkBuilder {
628
691
var body : [ CodeBlockItemSyntax ] = [ ]
629
692
var abiParameterForwardings : [ LabeledExprSyntax ] = [ ]
@@ -700,7 +763,7 @@ public class ExportSwift {
700
763
abiParameterForwardings. append (
701
764
LabeledExprSyntax (
702
765
label: param. label,
703
- expression: ExprSyntax ( " \( raw: enumName) (rawValue: Int( \( raw: param. name) ))! " )
766
+ expression: ExprSyntax ( " \( raw: enumName) (bridgeJSRawValue: \( raw: param. name) )! " )
704
767
)
705
768
)
706
769
abiParameterSignatures. append ( ( param. name, . i32) )
@@ -770,7 +833,6 @@ public class ExportSwift {
770
833
)
771
834
abiParameterSignatures. append ( ( param. name, . i32) )
772
835
case . swiftHeapObject:
773
- // UnsafeMutableRawPointer is passed as an i32 pointer
774
836
let objectExpr : ExprSyntax =
775
837
" Unmanaged< \( raw: param. type. swiftType) >.fromOpaque( \( raw: param. name) ).takeUnretainedValue() "
776
838
abiParameterForwardings. append (
@@ -885,7 +947,7 @@ public class ExportSwift {
885
947
)
886
948
case . caseEnum:
887
949
abiReturnType = . i32
888
- append ( " return Int32( ret.rawValue) " )
950
+ append ( " return ret.bridgeJSRawValue " )
889
951
case . rawValueEnum( _, let rawType) :
890
952
if rawType == . string {
891
953
append (
0 commit comments