diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Data.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Data.swift index 11aa38a2..3dcbc613 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Data.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Data.swift @@ -35,3 +35,14 @@ public func getDataCount(_ data: Data) -> Int { public func compareData(_ data1: Data, _ data2: Data) -> Bool { data1 == data2 } + +// ==== ----------------------------------------------------------------------- +// MARK: DataProtocol generic parameter + +public func getDataCountGeneric(_ data: D) -> Int { + data.count +} + +public func compareDataGeneric(_ data1: D1, _ data2: D2) -> Bool { + data1.elementsEqual(data2) +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DataTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DataTest.java index 1088b176..a6024ca1 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DataTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DataTest.java @@ -128,4 +128,31 @@ void data_toByteArray_roundTrip() { assertArrayEquals(original, result); } } + + // DataProtocol generic parameter tests + + @Test + void data_getCountGeneric() { + try (var arena = SwiftArena.ofConfined()) { + byte[] bytes = new byte[] { 1, 2, 3, 4, 5 }; + var data = Data.fromByteArray(bytes, arena); + assertEquals(5, MySwiftLibrary.getDataCountGeneric(data)); + } + } + + @Test + void data_compareDataGeneric() { + try (var arena = SwiftArena.ofConfined()) { + byte[] bytes1 = new byte[] { 1, 2, 3 }; + byte[] bytes2 = new byte[] { 1, 2, 3 }; + byte[] bytes3 = new byte[] { 1, 2, 4 }; + + var data1 = Data.fromByteArray(bytes1, arena); + var data2 = Data.fromByteArray(bytes2, arena); + var data3 = Data.fromByteArray(bytes3, arena); + + assertTrue(MySwiftLibrary.compareDataGeneric(data1, data2)); + assertFalse(MySwiftLibrary.compareDataGeneric(data1, data3)); + } + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift index 2f19b39a..3d36ee34 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift @@ -25,6 +25,13 @@ extension JNISwift2JavaGenerator { var wrappers = [ImportedNominalType: JavaInterfaceSwiftWrapper]() for type in types where type.swiftNominal.kind == .protocol { + // Skip protocols that have a known representative concrete type (e.g. DataProtocol). + if let knownKind = type.swiftNominal.knownTypeKind, + SwiftKnownTypes.representativeType(of: knownKind) != nil + { + continue + } + do { let translator = JavaInterfaceProtocolWrapperGenerator() wrappers[type] = try translator.generate(for: type) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index b2cde4b6..01b7090c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -1088,6 +1088,9 @@ extension JNISwift2JavaGenerator { case .foundationData, .essentialsData: return .class(package: nil, name: "Data") + case .foundationDataProtocol, .essentialsDataProtocol: + return .class(package: nil, name: "DataProtocol") + case .foundationUUID, .essentialsUUID: return .javaUtilUUID diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift index a1a21fd6..198a1c14 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift @@ -144,11 +144,17 @@ struct SwiftKnownTypes { } /// Returns the known representative concrete type if there is one for the - /// given protocol kind. E.g. `String` for `StringProtocol` + /// given protocol kind. E.g. `Data` for `DataProtocol` func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftType? { + guard let kind = Self.representativeType(of: knownProtocol) else { return nil } + return .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[kind])) + } + + /// Returns the representative concrete type kind for a protocol, if one exists + static func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftKnownTypeDeclKind? { switch knownProtocol { - case .foundationDataProtocol: return self.foundationData - case .essentialsDataProtocol: return self.essentialsData + case .foundationDataProtocol: return .foundationData + case .essentialsDataProtocol: return .essentialsData default: return nil } } diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 890e2752..ab35dcd0 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -63,6 +63,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ✅ | | Generic type: `struct S` | ❌ | ✅ | | Functions or properties using generic type param: `struct S { func f(_: T) {} }` | ❌ | ❌ | +| Generic parameters over `some DataProtocol` handled with efficient Java type | ✅ | ✅ | | Generic type specialization and conditional extensions: `struct S{} extension S where T == Value {}` | ❌ | ✅ | | Static functions or properties in generic type | ❌ | ❌ | | Generic parameters in functions: `func f(x: T)` | ❌ | ✅ | diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index 47d5a473..e67a3db8 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -578,4 +578,103 @@ final class DataImportTests { ) } + // ==== ----------------------------------------------------------------------- + // MARK: JNI DataProtocol generic parameter + + @Test("Import DataProtocol: JNI generic parameter") + func dataProtocol_jni_genericParameter() throws { + let text = """ + import Foundation + + public struct MyResult { + public init() {} + } + public func processData(data: D) -> MyResult + """ + + try assertOutput( + input: text, + .jni, + .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + public static MyResult processData(D data, SwiftArena swiftArena) { + """ + ] + ) + } + + @Test("Import DataProtocol: JNI multiple generic parameters") + func dataProtocol_jni_multipleGenericParameters() throws { + let text = """ + import Foundation + + public func verify(first: D1, second: D2) -> Bool + """ + + try assertOutput( + input: text, + .jni, + .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + public static boolean verify(D1 first, D2 second) { + """ + ] + ) + } + + @Test("Import DataProtocol: JNI generic parameter Swift thunk") + func dataProtocol_jni_genericParameter_swiftThunk() throws { + let text = """ + import Foundation + + public struct MyResult { + public init() {} + } + public func processData(data: D) -> MyResult + """ + + try assertOutput( + input: text, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public func Java_com_example_swift_SwiftModule__00024processData__Ljava_lang_Object_2(environment: UnsafeMutablePointer!, thisClass: jclass, data: jobject?) -> jlong { + """, + """ + result$.initialize(to: SwiftModule.processData(data: dataswiftObject$)) + """, + ] + ) + } + + @Test("Import DataProtocol: JNI mixed generic and some Swift thunk") + func dataProtocol_jni_multipleGenericParameters_swiftThunk() throws { + let text = """ + import Foundation + + public func verify(first: D1, second: some DataProtocol) -> Bool + """ + + try assertOutput( + input: text, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public func Java_com_example_swift_SwiftModule__00024verify__Ljava_lang_Object_2Ljava_lang_Object_2(environment: UnsafeMutablePointer!, thisClass: jclass, first: jobject?, second: jobject?) -> jboolean { + """, + """ + return SwiftModule.verify(first: firstswiftObject$, second: secondswiftObject$).getJNILocalRefValue(in: environment) + """, + ] + ) + } + }