diff --git a/Samples/SwiftJavaExtractJNIKotlinSampleApp/src/test/kotlin/com/example/kotlin/JavaEnumsTest.kt b/Samples/SwiftJavaExtractJNIKotlinSampleApp/src/test/kotlin/com/example/kotlin/JavaEnumsTest.kt index c1c35ec50..aff86d471 100644 --- a/Samples/SwiftJavaExtractJNIKotlinSampleApp/src/test/kotlin/com/example/kotlin/JavaEnumsTest.kt +++ b/Samples/SwiftJavaExtractJNIKotlinSampleApp/src/test/kotlin/com/example/kotlin/JavaEnumsTest.kt @@ -24,36 +24,36 @@ import kotlin.test.assertTrue class JavaEnumsTest { @Test fun exhaustiveWhen() { - val label = when (Orders.fill(OrderStatus.PENDING)) { - OrderStatus.PENDING -> "pending" - OrderStatus.FILLED -> "filled" - OrderStatus.CANCELLED -> "cancelled" + val label = when (Orders.fill(OrderStatus.pending)) { + OrderStatus.pending -> "pending" + OrderStatus.filled -> "filled" + OrderStatus.cancelled -> "cancelled" } assertEquals("filled", label) } @Test fun constantsAreTheValuesSwiftReturns() { - assertSame(OrderStatus.FILLED, Orders.fill(OrderStatus.PENDING)) + assertSame(OrderStatus.filled, Orders.fill(OrderStatus.pending)) assertEquals(OrderStatus.entries, Orders.all().toList()) - assertEquals("cancelled", OrderStatus.CANCELLED.rawValue) - assertTrue(OrderStatus.CANCELLED.isFinal) + assertEquals("cancelled", OrderStatus.cancelled.rawValue) + assertTrue(OrderStatus.cancelled.isFinal) } @Test fun everywhereAValueCrosses() = runBlocking { - assertSame(OrderStatus.PENDING, Orders.status("a")) - assertSame(OrderStatus.FILLED, Orders.byID()["a"]) - assertSame(OrderStatus.CANCELLED, Order.init("a", OrderStatus.CANCELLED).status) - assertSame(OrderStatus.FILLED, (Orders.event(OrderStatus.FILLED).case as OrderEvent.Case.Changed).arg0) + assertSame(OrderStatus.pending, Orders.status("a")) + assertSame(OrderStatus.filled, Orders.byID()["a"]) + assertSame(OrderStatus.cancelled, Order.init("a", OrderStatus.cancelled).status) + assertSame(OrderStatus.filled, (Orders.event(OrderStatus.filled).case as OrderEvent.Case.Changed).arg0) var seen: OrderStatus? = null Orders.watch { seen = it } - assertSame(OrderStatus.CANCELLED, seen) - assertSame(OrderStatus.FILLED, Orders.settle().await()) + assertSame(OrderStatus.cancelled, seen) + assertSame(OrderStatus.filled, Orders.settle().await()) } @Test fun manyRoundTrips() { - repeat(10_000) { assertSame(OrderStatus.FILLED, Orders.fill(OrderStatus.FILLED)) } + repeat(10_000) { assertSame(OrderStatus.filled, Orders.fill(OrderStatus.filled)) } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 25627d2d2..e41a17ca1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -313,7 +313,7 @@ extension JNISwift2JavaGenerator { private func printConcreteType(_ printer: inout JavaPrinter, _ decl: ExtractedNominalType) { printNominal(&printer, decl) { printer in if self.isJavaEnum(decl) { - printer.print(decl.cases.map { $0.name.uppercased() }.joined(separator: ", ") + ";") + printer.print(decl.cases.map { Self.javaEnumConstantName($0) }.joined(separator: ", ") + ";") printer.println() } printer.print( @@ -679,6 +679,12 @@ extension JNISwift2JavaGenerator { /// Whether `decl` is printed as a Java `enum` (`payloadlessEnumsAsJavaEnums`): a non-generic, /// non-error Swift enum whose cases have no associated values. + /// The Swift case name, as Swift and Kotlin code spell the case; a Java keyword gets a trailing `_`. + static func javaEnumConstantName(_ enumCase: ExtractedEnumCase) -> String { + let name = enumCase.name.trimmingCharacters(in: CharacterSet(charactersIn: "`")) + return JavaIdentifierFactory.javaKeywords.contains(name) ? name + "_" : name + } + func isJavaEnum(_ decl: ExtractedNominalType) -> Bool { config.effectivePayloadlessEnumsAsJavaEnums && decl.swiftNominal.kind == .enum && !decl.swiftNominal.isGeneric && !decl.cases.isEmpty && decl.cases.allSatisfy(\.parameters.isEmpty) && !self.isSwiftErrorType(decl) diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 1e8cb1ed4..b74e695c3 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -189,7 +189,7 @@ public struct Configuration: Codable { } /// If true, Swift enums whose cases have no associated values are generated as Java `enum`s, - /// with one constant per case, instead of classes wrapping a Swift value. Only supported in + /// with one constant per case named like the case, instead of classes wrapping a Swift value. Only supported in /// `jni` mode. public var payloadlessEnumsAsJavaEnums: Bool? public var effectivePayloadlessEnumsAsJavaEnums: Bool { diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaConfigFile.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaConfigFile.md index c34a1fd8e..e8b6f5e5c 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaConfigFile.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaConfigFile.md @@ -262,7 +262,7 @@ extract or translate, and lists each one. Skipped declarations are always report - **Default:** `false` If true, Swift enums whose cases have no associated values are generated as Java `enum`s, -with one constant per case, instead of classes wrapping a Swift value. Only supported in +with one constant per case named like the case, instead of classes wrapping a Swift value. Only supported in `jni` mode. --- diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaEnumTests.swift index d1a71dd32..399821634 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIJavaEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaEnumTests.swift @@ -32,6 +32,23 @@ struct JNIJavaEnumTests { } """ + @Test + func javaKeywordCaseIsEscaped() throws { + try assertOutput( + input: """ + public enum Policy { + case `default` + case strict + } + """, + config: config, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: ["default_, strict;"] + ) + } + @Test func casesBecomeConstants() throws { try assertOutput( @@ -42,7 +59,8 @@ struct JNIJavaEnumTests { detectChunkByInitialLines: 1, expectedChunks: [ "public enum Phase implements JNISwiftInstance {", - "IDLE, LOADING;", + // Swift case names, as Swift and Kotlin code spell them. + "idle, loading;", "static final long[] ADDRESSES = { Phase.$idle(), Phase.$loading() };", """ public static Phase wrapMemoryAddressUnsafe(long selfPointer) {