Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<D: DataProtocol>(_ data: D) -> Int {
data.count
}

public func compareDataGeneric<D1: DataProtocol, D2: DataProtocol>(_ data1: D1, _ data2: D2) -> Bool {
data1.elementsEqual(data2)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 9 additions & 3 deletions Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` | ❌ | ✅ |
| Functions or properties using generic type param: `struct S<T> { func f(_: T) {} }` | ❌ | ❌ |
| Generic parameters over `some DataProtocol` handled with efficient Java type | ✅ | ✅ |
| Generic type specialization and conditional extensions: `struct S<T>{} extension S where T == Value {}` | ❌ | ✅ |
| Static functions or properties in generic type | ❌ | ❌ |
| Generic parameters in functions: `func f<T: A & B>(x: T)` | ❌ | ✅ |
Expand Down
99 changes: 99 additions & 0 deletions Tests/JExtractSwiftTests/DataImportTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<D: DataProtocol>(data: D) -> MyResult
"""

try assertOutput(
input: text,
.jni,
.java,
detectChunkByInitialLines: 2,
expectedChunks: [
"""
public static <D extends DataProtocol> 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<D1: DataProtocol, D2: DataProtocol>(first: D1, second: D2) -> Bool
"""

try assertOutput(
input: text,
.jni,
.java,
detectChunkByInitialLines: 2,
expectedChunks: [
"""
public static <D1 extends DataProtocol, D2 extends DataProtocol> 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<D: DataProtocol>(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<JNIEnv?>!, 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<D1: DataProtocol>(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<JNIEnv?>!, thisClass: jclass, first: jobject?, second: jobject?) -> jboolean {
""",
"""
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand... man that's a lot of code for the chunk... I'll just take the imp[important bits maybe

return SwiftModule.verify(first: firstswiftObject$, second: secondswiftObject$).getJNILocalRefValue(in: environment)
""",
]
)
}

}
Loading