diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift index d44985907..349062ef1 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift @@ -21,3 +21,13 @@ public enum EnumWithBacktick { case `let` case `default` } + +public enum EnumWithCaseNameValue { + case success(Success) + public struct Success { + public init(message: String) { + self.message = message + } + public var message: String + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java index d3de624be..b7bb5897f 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java @@ -51,10 +51,10 @@ public void afterAll() { } @Benchmark - public Vehicle.Motorbike getAssociatedValues(BenchmarkState state, Blackhole bh) { - Vehicle.Motorbike motorbike = state.vehicle.getAsMotorbike().orElseThrow(); + public Vehicle.Case.Motorbike getAssociatedValues(BenchmarkState state, Blackhole bh) { + Vehicle.Case.Motorbike motorbike = state.vehicle.getAsMotorbike().orElseThrow(); bh.consume(motorbike.arg0()); bh.consume(motorbike.horsePower()); return motorbike; } -} \ No newline at end of file +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java index 4cd2d2886..e9e01891a 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java @@ -24,7 +24,7 @@ public class EnumTest { void enumWithValueCases() { try (var arena = SwiftArena.ofConfined()) { EnumWithValueCases e = EnumWithValueCases.firstCase(48, arena); - EnumWithValueCases.FirstCase c = (EnumWithValueCases.FirstCase) e.getCase(); + EnumWithValueCases.Case.FirstCase c = (EnumWithValueCases.Case.FirstCase) e.getCase(); assertNotNull(c); } } @@ -36,4 +36,17 @@ void enumWithBacktick() { assertTrue(e.getAsDefault().isPresent()); } } + + @Test + void enumWithCaseNameValue() { + try (var arena = SwiftArena.ofConfined()) { + var success = EnumWithCaseNameValue.Success.init("ok", arena); + EnumWithCaseNameValue e = EnumWithCaseNameValue.success(success, arena); + + switch (e.getCase(arena)) { + case EnumWithCaseNameValue.Case.Success(var s): + assertEquals("ok", s.getMessage()); + } + } + } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java index 18e60b595..e5c414ed5 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java @@ -65,8 +65,8 @@ void genericEnum() { try (var arena = SwiftArena.ofConfined()) { GenericEnum value = MySwiftLibrary.makeIntGenericEnum(arena); switch (value.getCase()) { - case GenericEnum.Foo _ -> assertTrue(value.getAsFoo().isPresent()); - case GenericEnum.Bar _ -> assertTrue(value.getAsBar().isPresent()); + case GenericEnum.Case.Foo _ -> assertTrue(value.getAsFoo().isPresent()); + case GenericEnum.Case.Bar _ -> assertTrue(value.getAsBar().isPresent()); } } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java index 25bc415c8..8fbe14c76 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java @@ -92,7 +92,7 @@ void upgrade() { void getAsBicycle() { try (var arena = SwiftArena.ofConfined()) { Vehicle vehicle = Vehicle.bicycle(arena); - Vehicle.Bicycle bicycle = vehicle.getAsBicycle().orElseThrow(); + Vehicle.Case.Bicycle bicycle = vehicle.getAsBicycle().orElseThrow(); assertNotNull(bicycle); } } @@ -101,7 +101,7 @@ void getAsBicycle() { void getAsCar() { try (var arena = SwiftArena.ofConfined()) { Vehicle vehicle = Vehicle.car("BMW", Optional.empty(), arena); - Vehicle.Car car = vehicle.getAsCar().orElseThrow(); + Vehicle.Case.Car car = vehicle.getAsCar().orElseThrow(); assertEquals("BMW", car.arg0()); vehicle = Vehicle.car("BMW", Optional.of("Long trailer"), arena); @@ -114,7 +114,7 @@ void getAsCar() { void getAsMotorbike() { try (var arena = SwiftArena.ofConfined()) { Vehicle vehicle = Vehicle.motorbike("Yamaha", 750, OptionalInt.empty(), arena); - Vehicle.Motorbike motorbike = vehicle.getAsMotorbike().orElseThrow(); + Vehicle.Case.Motorbike motorbike = vehicle.getAsMotorbike().orElseThrow(); assertEquals("Yamaha", motorbike.arg0()); assertEquals(750, motorbike.horsePower()); assertEquals(OptionalInt.empty(), motorbike.helmets()); @@ -129,7 +129,7 @@ void getAsMotorbike() { void getAsTransformer() { try (var arena = SwiftArena.ofConfined()) { Vehicle vehicle = Vehicle.transformer(Vehicle.bicycle(arena), Vehicle.car("BMW", Optional.empty(), arena), arena); - Vehicle.Transformer transformer = vehicle.getAsTransformer(arena).orElseThrow(); + Vehicle.Case.Transformer transformer = vehicle.getAsTransformer(arena).orElseThrow(); assertTrue(transformer.front().getAsBicycle().isPresent()); assertEquals("BMW", transformer.back().getAsCar().orElseThrow().arg0()); } @@ -139,7 +139,7 @@ void getAsTransformer() { void getAsBoat() { try (var arena = SwiftArena.ofConfined()) { Vehicle vehicle = Vehicle.boat(OptionalInt.of(10), Optional.of((short) 1), arena); - Vehicle.Boat boat = vehicle.getAsBoat().orElseThrow(); + Vehicle.Case.Boat boat = vehicle.getAsBoat().orElseThrow(); assertEquals(OptionalInt.of(10), boat.passengers()); assertEquals(Optional.of((short) 1), boat.length()); } @@ -149,10 +149,10 @@ void getAsBoat() { void associatedValuesAreCopied() { try (var arena = SwiftArena.ofConfined()) { Vehicle vehicle = Vehicle.car("BMW", Optional.empty(), arena); - Vehicle.Car car = vehicle.getAsCar().orElseThrow(); + Vehicle.Case.Car car = vehicle.getAsCar().orElseThrow(); assertEquals("BMW", car.arg0()); vehicle.upgrade(); - Vehicle.Motorbike motorbike = vehicle.getAsMotorbike().orElseThrow(); + Vehicle.Case.Motorbike motorbike = vehicle.getAsMotorbike().orElseThrow(); assertNotNull(motorbike); // Motorbike should still remain assertEquals("BMW", car.arg0()); @@ -174,7 +174,7 @@ void getCase() { try (var arena = SwiftArena.ofConfined()) { Vehicle vehicle = Vehicle.bicycle(arena); Vehicle.Case caseElement = vehicle.getCase(arena); - assertInstanceOf(Vehicle.Bicycle.class, caseElement); + assertInstanceOf(Vehicle.Case.Bicycle.class, caseElement); } } @@ -183,23 +183,23 @@ void switchGetCase() { try (var arena = SwiftArena.ofConfined()) { Vehicle vehicle = Vehicle.car("BMW", Optional.empty(), arena); switch (vehicle.getCase(arena)) { - case Vehicle.Bicycle b: + case Vehicle.Case.Bicycle b: fail("Was bicycle"); break; - case Vehicle.Car car: + case Vehicle.Case.Car car: assertEquals("BMW", car.arg0()); break; - case Vehicle.Motorbike motorbike: + case Vehicle.Case.Motorbike motorbike: fail("Was motorbike"); break; - case Vehicle.Transformer transformer: + case Vehicle.Case.Transformer transformer: fail("Was transformer"); break; - case Vehicle.Boat b: + case Vehicle.Case.Boat b: fail("Was boat"); break; } } } -} \ No newline at end of file +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 3377af730..3a8e78928 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -497,7 +497,30 @@ extension JNISwift2JavaGenerator { return } - printer.print("public sealed interface Case {}") + printer.printBraceBlock("public sealed interface Case") { printer in + for enumCase in decl.cases { + guard let translatedCase = self.translatedEnumCase(for: enumCase) else { + continue + } + + let members = translatedCase.translatedValues.map { + $0.parameter.renderParameter() + } + let caseName = enumCase.name.firstCharacterUppercased + + // Print record + printer.printBraceBlock("record \(caseName)(\(members.joined(separator: ", "))) implements Case") { + printer in + let nativeParameters = zip(translatedCase.translatedValues, translatedCase.parameterConversions).map { + value, + conversion in + "\(conversion.native.javaType) \(value.parameter.name)" + } + + printer.print("record _NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") + } + } + } printer.println() let requiresSwiftArena = decl.cases.compactMap { @@ -539,24 +562,6 @@ extension JNISwift2JavaGenerator { return } - let members = translatedCase.translatedValues.map { - $0.parameter.renderParameter() - } - - let caseName = enumCase.name.firstCharacterUppercased - - // Print record - printer.printBraceBlock("public record \(caseName)(\(members.joined(separator: ", "))) implements Case") { - printer in - let nativeParameters = zip(translatedCase.translatedValues, translatedCase.parameterConversions).map { - value, - conversion in - "\(conversion.native.javaType) \(value.parameter.name)" - } - - printer.print("record _NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") - } - self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction, skipMethodBody: false) printer.println() } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 2ff2aa470..f7b6e2d24 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -129,7 +129,8 @@ extension JNISwift2JavaGenerator { let caseName = enumCase.name.firstCharacterUppercased let enumName = enumCase.enumType.nominalTypeDecl.name - let nativeParametersType = JavaType.class(package: nil, name: "\(caseName)._NativeParameters") + let caseType = JavaType.class(package: nil, name: "Case.\(caseName)") + let nativeParametersType = JavaType.class(package: nil, name: "Case.\(caseName)._NativeParameters") let getAsCaseName = "getAs\(caseName)" // If the case has no parameters, we can skip the native call. let constructRecordConversion = JavaNativeConversionStep.method( @@ -138,7 +139,7 @@ extension JNISwift2JavaGenerator { arguments: [ .constructJavaClass( .commaSeparated(conversions.map(\.translated.conversion)), - .class(package: nil, name: caseName), + caseType, ) ], ) @@ -179,7 +180,7 @@ extension JNISwift2JavaGenerator { ), parameters: [], resultType: TranslatedResult( - javaType: .class(package: nil, name: "Optional", typeParameters: [.class(package: nil, name: caseName)]), + javaType: .optional(caseType), outParameters: conversions.flatMap(\.translated.outParameters), conversion: enumCase.parameters.isEmpty ? constructRecordConversion diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 913f4c0d0..48ce45f93 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -409,7 +409,7 @@ extension JNISwift2JavaGenerator { } private func renderEnumCaseCacheInit(_ enumCase: TranslatedEnumCase) -> String { - let nativeParametersClassName = "\(enumCase.enumName)$\(enumCase.name)$_NativeParameters" + let nativeParametersClassName = "\(enumCase.enumName)$Case$\(enumCase.name)$_NativeParameters" let methodSignature = MethodSignature( resultType: .void, parameterTypes: enumCase.parameterConversions.map(\.native.javaType), diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 34cb7f458..59f72e3ba 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -45,6 +45,11 @@ extension JavaType { .class(package: "java.lang", name: "Object") } + /// The description of the type java.util.Optional. + static func optional(_ T: JavaType) -> JavaType { + .class(package: "java.util", name: "Optional", typeParameters: [T]) + } + /// The description of the type java.util.concurrent.CompletableFuture static func completableFuture(_ T: JavaType) -> JavaType { .class(package: "java.util.concurrent", name: "CompletableFuture", typeParameters: [T.boxedType]) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 4a681d866..5e9b4f7de 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -208,7 +208,7 @@ You can then instantiate a case of `Vehicle` by using one of the static methods: ```java try (var arena = SwiftArena.ofConfined()) { Vehicle vehicle = Vehicle.car("BMW", arena); - Optional car = vehicle.getAsCar(); + Optional car = vehicle.getAsCar(); assertEquals("BMW", car.orElseThrow().arg0()); } ``` @@ -217,10 +217,10 @@ As you can see above, to access the associated values of a case you can call one ```java try (var arena = SwiftArena.ofConfined()) { Vehicle vehicle = Vehicle.bycicle("My Brand", arena); - Optional car = vehicle.getAsCar(); + Optional car = vehicle.getAsCar(); assertFalse(car.isPresent()); - Optional bicycle = vehicle.getAsBicycle(); + Optional bicycle = vehicle.getAsBicycle(); assertEquals("My Brand", bicycle.orElseThrow().maker()); } ``` @@ -246,10 +246,10 @@ If you are running Java 21+ you can use [pattern matching for switch](https://op ```java Vehicle vehicle = ...; switch (vehicle.getCase()) { - case Vehicle.Bicycle b: + case Vehicle.Case.Bicycle b: System.out.println("Bicycle maker: " + b.maker()); break; - case Vehicle.Car c: + case Vehicle.Case.Car c: System.out.println("Car: " + c.arg0()); break; } @@ -258,7 +258,7 @@ or even, destructuring the records in the switch statement's pattern match direc ```java Vehicle vehicle = ...; switch (vehicle.getCase()) { - case Vehicle.Car(var name, var unused): + case Vehicle.Case.Car(var name, var unused): System.out.println("Car: " + name); break; default: @@ -270,9 +270,9 @@ For Java 16+ you can use [pattern matching for instanceof](https://openjdk.org/j ```java Vehicle vehicle = ...; Vehicle.Case case = vehicle.getCase(); -if (case instanceof Vehicle.Bicycle b) { +if (case instanceof Vehicle.Case.Bicycle b) { System.out.println("Bicycle maker: " + b.maker()); -} else if(case instanceof Vehicle.Car c) { +} else if(case instanceof Vehicle.Case.Car c) { System.out.println("Car: " + c.arg0()); } ``` @@ -280,11 +280,11 @@ For any previous Java versions you can resort to casting the `Case` to the expec ```java Vehicle vehicle = ...; Vehicle.Case case = vehicle.getCase(); -if (case instanceof Vehicle.Bicycle) { - Vehicle.Bicycle b = (Vehicle.Bicycle) case; +if (case instanceof Vehicle.Case.Bicycle) { + Vehicle.Bicycle b = (Vehicle.Case.Bicycle) case; System.out.println("Bicycle maker: " + b.maker()); -} else if(case instanceof Vehicle.Car) { - Vehicle.Car c = (Vehicle.Car) case; +} else if(case instanceof Vehicle.Case.Car) { + Vehicle.Car c = (Vehicle.Case.Car) case; System.out.println("Car: " + c.arg0()); } ``` diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index d906df9a1..a158616d6 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -163,7 +163,17 @@ struct JNIEnumTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - public sealed interface Case {} + public sealed interface Case { + record First() implements Case { + record _NativeParameters() {} + } + record Second(java.lang.String arg0) implements Case { + record _NativeParameters(java.lang.String arg0) {} + } + record Third(long x, int y) implements Case { + record _NativeParameters(long x, int y) {} + } + } """, """ public Case getCase() { @@ -176,21 +186,6 @@ struct JNIEnumTests { throw new RuntimeException("Unknown discriminator value " + discriminator); } """, - """ - public record First() implements Case { - record _NativeParameters() {} - } - """, - """ - public record Second(java.lang.String arg0) implements Case { - record _NativeParameters(java.lang.String arg0) {} - } - """, - """ - public record Third(long x, int y) implements Case { - record _NativeParameters(long x, int y) {} - } - """, ] ) } @@ -270,29 +265,29 @@ struct JNIEnumTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - public Optional getAsFirst() { + public java.util.Optional getAsFirst() { if (getDiscriminator() != Discriminator.FIRST) { return Optional.empty(); } - return Optional.of(new First()); + return Optional.of(new Case.First()); } """, """ - public Optional getAsSecond() { + public java.util.Optional getAsSecond() { if (getDiscriminator() != Discriminator.SECOND) { return Optional.empty(); } - Second._NativeParameters $nativeParameters = MyEnum.$getAsSecond(this.$memoryAddress()); - return Optional.of(new Second($nativeParameters.arg0)); + Case.Second._NativeParameters $nativeParameters = MyEnum.$getAsSecond(this.$memoryAddress()); + return Optional.of(new Case.Second($nativeParameters.arg0)); } """, """ - public Optional getAsThird() { + public java.util.Optional getAsThird() { if (getDiscriminator() != Discriminator.THIRD) { return Optional.empty(); } - Third._NativeParameters $nativeParameters = MyEnum.$getAsThird(this.$memoryAddress()); - return Optional.of(new Third($nativeParameters.x, $nativeParameters.y)); + Case.Third._NativeParameters $nativeParameters = MyEnum.$getAsThird(this.$memoryAddress()); + return Optional.of(new Case.Third($nativeParameters.x, $nativeParameters.y)); } """, ] @@ -309,8 +304,8 @@ struct JNIEnumTests { expectedChunks: [ """ enum _JNI_MyEnum { - static let myEnumSecondCache = _JNIMethodIDCache(className: "com/example/swift/MyEnum$Second$_NativeParameters", methods: [.init(name: "", signature: "(Ljava/lang/String;)V")]) - static let myEnumThirdCache = _JNIMethodIDCache(className: "com/example/swift/MyEnum$Third$_NativeParameters", methods: [.init(name: "", signature: "(JI)V")]) + static let myEnumSecondCache = _JNIMethodIDCache(className: "com/example/swift/MyEnum$Case$Second$_NativeParameters", methods: [.init(name: "", signature: "(Ljava/lang/String;)V")]) + static let myEnumThirdCache = _JNIMethodIDCache(className: "com/example/swift/MyEnum$Case$Third$_NativeParameters", methods: [.init(name: "", signature: "(JI)V")]) } """, """ diff --git a/Tests/JExtractSwiftTests/JavaKeywordTests.swift b/Tests/JExtractSwiftTests/JavaKeywordTests.swift index eea723480..135eda574 100644 --- a/Tests/JExtractSwiftTests/JavaKeywordTests.swift +++ b/Tests/JExtractSwiftTests/JavaKeywordTests.swift @@ -70,7 +70,7 @@ struct JavaKeywordTests { public static MyEnum null_(SwiftArena swiftArena) { """, """ - public record Null() implements Case { + record Null() implements Case { """, ] ) @@ -95,10 +95,10 @@ struct JavaKeywordTests { public static MyEnumWithValue instanceof_(java.lang.String arg0, SwiftArena swiftArena) { """, """ - public record Instanceof(java.lang.String arg0) implements Case { + record Instanceof(java.lang.String arg0) implements Case { """, """ - private static native Instanceof._NativeParameters $getAsInstanceof(long selfPointer); + private static native Case.Instanceof._NativeParameters $getAsInstanceof(long selfPointer); """, ] ) @@ -133,7 +133,7 @@ struct JavaKeywordTests { public static MyEnum default_(SwiftArena swiftArena) { """, """ - public record Default() implements Case { + record Default() implements Case { """, ] ) diff --git a/Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift b/Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift index c388584ba..7392a1a0e 100644 --- a/Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift +++ b/Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift @@ -47,7 +47,7 @@ struct SwiftEscapedNameTests { detectChunkByInitialLines: 1, expectedChunks: [ "public static MyEnum let(SwiftArena swiftArena) {", - "public record Let() implements Case {", + "record Let() implements Case {", ], ) }