Skip to content

Commit 0bdba49

Browse files
authored
jextract/jni: support generic params : DataProtocol (#669)
* jextract/jni: support generic params : DataProtocol Functions with `<D: DataProtocol>` generic constraints failed to translate because `.foundationDataProtocol` and `.essentialsDataProtocol` fell through to the default case in `translateGenericTypeParameter`. * include whole opening thunks for reference in tests * add a line item for the feature matrix
1 parent f5b6c66 commit 0bdba49

7 files changed

Lines changed: 157 additions & 3 deletions

File tree

Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Data.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,14 @@ public func getDataCount(_ data: Data) -> Int {
3535
public func compareData(_ data1: Data, _ data2: Data) -> Bool {
3636
data1 == data2
3737
}
38+
39+
// ==== -----------------------------------------------------------------------
40+
// MARK: DataProtocol generic parameter
41+
42+
public func getDataCountGeneric<D: DataProtocol>(_ data: D) -> Int {
43+
data.count
44+
}
45+
46+
public func compareDataGeneric<D1: DataProtocol, D2: DataProtocol>(_ data1: D1, _ data2: D2) -> Bool {
47+
data1.elementsEqual(data2)
48+
}

Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DataTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,31 @@ void data_toByteArray_roundTrip() {
128128
assertArrayEquals(original, result);
129129
}
130130
}
131+
132+
// DataProtocol generic parameter tests
133+
134+
@Test
135+
void data_getCountGeneric() {
136+
try (var arena = SwiftArena.ofConfined()) {
137+
byte[] bytes = new byte[] { 1, 2, 3, 4, 5 };
138+
var data = Data.fromByteArray(bytes, arena);
139+
assertEquals(5, MySwiftLibrary.getDataCountGeneric(data));
140+
}
141+
}
142+
143+
@Test
144+
void data_compareDataGeneric() {
145+
try (var arena = SwiftArena.ofConfined()) {
146+
byte[] bytes1 = new byte[] { 1, 2, 3 };
147+
byte[] bytes2 = new byte[] { 1, 2, 3 };
148+
byte[] bytes3 = new byte[] { 1, 2, 4 };
149+
150+
var data1 = Data.fromByteArray(bytes1, arena);
151+
var data2 = Data.fromByteArray(bytes2, arena);
152+
var data3 = Data.fromByteArray(bytes3, arena);
153+
154+
assertTrue(MySwiftLibrary.compareDataGeneric(data1, data2));
155+
assertFalse(MySwiftLibrary.compareDataGeneric(data1, data3));
156+
}
157+
}
131158
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ extension JNISwift2JavaGenerator {
2525
var wrappers = [ImportedNominalType: JavaInterfaceSwiftWrapper]()
2626

2727
for type in types where type.swiftNominal.kind == .protocol {
28+
// Skip protocols that have a known representative concrete type (e.g. DataProtocol).
29+
if let knownKind = type.swiftNominal.knownTypeKind,
30+
SwiftKnownTypes.representativeType(of: knownKind) != nil
31+
{
32+
continue
33+
}
34+
2835
do {
2936
let translator = JavaInterfaceProtocolWrapperGenerator()
3037
wrappers[type] = try translator.generate(for: type)

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,9 @@ extension JNISwift2JavaGenerator {
10941094
case .foundationData, .essentialsData:
10951095
return .class(package: nil, name: "Data")
10961096

1097+
case .foundationDataProtocol, .essentialsDataProtocol:
1098+
return .class(package: nil, name: "DataProtocol")
1099+
10971100
case .foundationUUID, .essentialsUUID:
10981101
return .javaUtilUUID
10991102

Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,17 @@ struct SwiftKnownTypes {
144144
}
145145

146146
/// Returns the known representative concrete type if there is one for the
147-
/// given protocol kind. E.g. `String` for `StringProtocol`
147+
/// given protocol kind. E.g. `Data` for `DataProtocol`
148148
func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftType? {
149+
guard let kind = Self.representativeType(of: knownProtocol) else { return nil }
150+
return .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[kind]))
151+
}
152+
153+
/// Returns the representative concrete type kind for a protocol, if one exists
154+
static func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftKnownTypeDeclKind? {
149155
switch knownProtocol {
150-
case .foundationDataProtocol: return self.foundationData
151-
case .essentialsDataProtocol: return self.essentialsData
156+
case .foundationDataProtocol: return .foundationData
157+
case .essentialsDataProtocol: return .essentialsData
152158
default: return nil
153159
}
154160
}

Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S
6363
| Dictionaries: `[String: Int]`, `[K:V]` |||
6464
| Generic type: `struct S<T>` |||
6565
| Functions or properties using generic type param: `struct S<T> { func f(_: T) {} }` |||
66+
| Generic parameters over `some DataProtocol` handled with efficient Java type |||
6667
| Generic type specialization and conditional extensions: `struct S<T>{} extension S where T == Value {}` |||
6768
| Static functions or properties in generic type |||
6869
| Generic parameters in functions: `func f<T: A & B>(x: T)` |||

Tests/JExtractSwiftTests/DataImportTests.swift

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,4 +578,103 @@ final class DataImportTests {
578578
)
579579
}
580580

581+
// ==== -----------------------------------------------------------------------
582+
// MARK: JNI DataProtocol generic parameter
583+
584+
@Test("Import DataProtocol: JNI generic parameter")
585+
func dataProtocol_jni_genericParameter() throws {
586+
let text = """
587+
import Foundation
588+
589+
public struct MyResult {
590+
public init() {}
591+
}
592+
public func processData<D: DataProtocol>(data: D) -> MyResult
593+
"""
594+
595+
try assertOutput(
596+
input: text,
597+
.jni,
598+
.java,
599+
detectChunkByInitialLines: 2,
600+
expectedChunks: [
601+
"""
602+
public static <D extends DataProtocol> MyResult processData(D data, SwiftArena swiftArena) {
603+
"""
604+
]
605+
)
606+
}
607+
608+
@Test("Import DataProtocol: JNI multiple generic parameters")
609+
func dataProtocol_jni_multipleGenericParameters() throws {
610+
let text = """
611+
import Foundation
612+
613+
public func verify<D1: DataProtocol, D2: DataProtocol>(first: D1, second: D2) -> Bool
614+
"""
615+
616+
try assertOutput(
617+
input: text,
618+
.jni,
619+
.java,
620+
detectChunkByInitialLines: 2,
621+
expectedChunks: [
622+
"""
623+
public static <D1 extends DataProtocol, D2 extends DataProtocol> boolean verify(D1 first, D2 second) {
624+
"""
625+
]
626+
)
627+
}
628+
629+
@Test("Import DataProtocol: JNI generic parameter Swift thunk")
630+
func dataProtocol_jni_genericParameter_swiftThunk() throws {
631+
let text = """
632+
import Foundation
633+
634+
public struct MyResult {
635+
public init() {}
636+
}
637+
public func processData<D: DataProtocol>(data: D) -> MyResult
638+
"""
639+
640+
try assertOutput(
641+
input: text,
642+
.jni,
643+
.swift,
644+
detectChunkByInitialLines: 1,
645+
expectedChunks: [
646+
"""
647+
public func Java_com_example_swift_SwiftModule__00024processData__Ljava_lang_Object_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, data: jobject?) -> jlong {
648+
""",
649+
"""
650+
result$.initialize(to: SwiftModule.processData(data: dataswiftObject$))
651+
""",
652+
]
653+
)
654+
}
655+
656+
@Test("Import DataProtocol: JNI mixed generic and some Swift thunk")
657+
func dataProtocol_jni_multipleGenericParameters_swiftThunk() throws {
658+
let text = """
659+
import Foundation
660+
661+
public func verify<D1: DataProtocol>(first: D1, second: some DataProtocol) -> Bool
662+
"""
663+
664+
try assertOutput(
665+
input: text,
666+
.jni,
667+
.swift,
668+
detectChunkByInitialLines: 1,
669+
expectedChunks: [
670+
"""
671+
public func Java_com_example_swift_SwiftModule__00024verify__Ljava_lang_Object_2Ljava_lang_Object_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, first: jobject?, second: jobject?) -> jboolean {
672+
""",
673+
"""
674+
return SwiftModule.verify(first: firstswiftObject$, second: secondswiftObject$).getJNILocalRefValue(in: environment)
675+
""",
676+
]
677+
)
678+
}
679+
581680
}

0 commit comments

Comments
 (0)