diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift index 32a07f774..b3038f977 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift @@ -41,3 +41,7 @@ public func makeBigTuple() -> ( 11, 12, 13, 14.0 ) } + +public func namedByteArrayTuple() -> (name: [UInt8], another: [UInt8]) { + (name: [1, 2, 3], another: [4, 5]) +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java index a3ad4b197..1a110eb2a 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java @@ -84,4 +84,15 @@ void makeBigTuple() { assertEquals(13L, result.$14); assertEquals(14.0f, result.$15); } + + @Test + void namedByteArrayTuple() { + var result = MySwiftLibrary.namedByteArrayTuple(); + + assertArrayEquals(new byte[] { 1, 2, 3 }, result.name()); + assertArrayEquals(new byte[] { 4, 5 }, result.another()); + + assertArrayEquals(new byte[] { 1, 2, 3 }, (byte[]) result.$0); + assertArrayEquals(new byte[] { 4, 5 }, (byte[]) result.$1); + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index f5a2fe320..ddb88b9f4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -1236,8 +1236,19 @@ extension JNISwift2JavaGenerator { elements: tupleElements ) + // Collect annotations from tuple elements - if any element is @Unsigned, + // propagate that to the method level + var tupleAnnotations: [JavaAnnotation] = [] + for element in elements { + let elementAnnotations = getJavaTypeAnnotations(swiftType: element.type, config: config) + for annotation in elementAnnotations where !tupleAnnotations.contains(annotation) { + tupleAnnotations.append(annotation) + } + } + return TranslatedResult( javaType: javaResultType, + annotations: tupleAnnotations, outParameters: outParameters, conversion: javaNativeConversionStep ) @@ -1731,10 +1742,18 @@ extension JNISwift2JavaGenerator { func render(type: JavaType) -> String { switch self { case .newArray(let javaType, let size): - "new \(javaType)[\(size)]" + // For array element types like byte[], we need "new byte[size][]" + // not "new byte[][size]" + var baseType = javaType + var extraDimensions = "" + while case .array(let inner) = baseType { + extraDimensions += "[]" + baseType = inner + } + return "new \(baseType)[\(size)]\(extraDimensions)" case .new: - "new \(type)()" + return "new \(type)()" } } } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java index 4bf8e3544..93906c51f 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java @@ -32,11 +32,13 @@ * in a method signature corresponds to a Swift {@code UInt64} type, and therefore * negative values reported by the signed {@code long} should instead be interpreted positive values, * larger than {@code Long.MAX_VALUE} that are just not representable using a signed {@code long}. + *

+ * If this annotation is used on a method, it refers to the return type using an unsigned integer. */ @Documented @Label("Unsigned integer type") @Description("Value should be interpreted as unsigned data type") -@Target({TYPE_USE, PARAMETER, FIELD}) +@Target({TYPE_USE, PARAMETER, FIELD, METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Unsigned { } diff --git a/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift b/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift index 3be27da76..753b750c2 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift @@ -311,4 +311,54 @@ struct JNIArrayTest { ] ) } + + // ==== ----------------------------------------------------------------------- + // MARK: Tuples with array elements + + @Test("Import: () -> (name: [UInt8], another: [UInt8]) (Java)") + func tupleByteArrays_java() throws { + try assertOutput( + input: "public func namedByteArrayTuple() -> (name: [UInt8], another: [UInt8]) {}", + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @Unsigned + public static LabeledTuple_namedByteArrayTuple_name_another namedByteArrayTuple() { + byte[][] result_0$ = new byte[1][]; + byte[][] result_1$ = new byte[1][]; + SwiftModule.$namedByteArrayTuple(result_0$, result_1$); + return new LabeledTuple_namedByteArrayTuple_name_another(result_0$[0], result_1$[0]); + } + """, + """ + private static native void $namedByteArrayTuple(byte[][] result_0$, byte[][] result_1$); + """, + ] + ) + } + + @Test("Import: () -> (name: [UInt8], another: [UInt8]) (Swift)") + func tupleByteArrays_swift() throws { + try assertOutput( + input: "public func namedByteArrayTuple() -> (name: [UInt8], another: [UInt8]) {}", + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024namedByteArrayTuple___3_3B_3_3B") + public func Java_com_example_swift_SwiftModule__00024namedByteArrayTuple___3_3B_3_3B(environment: UnsafeMutablePointer!, thisClass: jclass, result_0$: jobjectArray?, result_1$: jobjectArray?) { + let tupleResult$ = SwiftModule.namedByteArrayTuple() + let element_0_jni$ = tupleResult$.name.getJNILocalRefValue(in: environment) + environment.interface.SetObjectArrayElement(environment, result_0$, 0, element_0_jni$) + let element_1_jni$ = tupleResult$.another.getJNILocalRefValue(in: environment) + environment.interface.SetObjectArrayElement(environment, result_1$, 0, element_1_jni$) + return + } + """ + ] + ) + } }