Skip to content

Commit 51ed738

Browse files
authored
jextract/jni: handle tuples with array elements (#685)
1 parent fee1c3d commit 51ed738

5 files changed

Lines changed: 89 additions & 3 deletions

File tree

Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,7 @@ public func makeBigTuple() -> (
4141
11, 12, 13, 14.0
4242
)
4343
}
44+
45+
public func namedByteArrayTuple() -> (name: [UInt8], another: [UInt8]) {
46+
(name: [1, 2, 3], another: [4, 5])
47+
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,15 @@ void makeBigTuple() {
8484
assertEquals(13L, result.$14);
8585
assertEquals(14.0f, result.$15);
8686
}
87+
88+
@Test
89+
void namedByteArrayTuple() {
90+
var result = MySwiftLibrary.namedByteArrayTuple();
91+
92+
assertArrayEquals(new byte[] { 1, 2, 3 }, result.name());
93+
assertArrayEquals(new byte[] { 4, 5 }, result.another());
94+
95+
assertArrayEquals(new byte[] { 1, 2, 3 }, (byte[]) result.$0);
96+
assertArrayEquals(new byte[] { 4, 5 }, (byte[]) result.$1);
97+
}
8798
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,8 +1236,19 @@ extension JNISwift2JavaGenerator {
12361236
elements: tupleElements
12371237
)
12381238

1239+
// Collect annotations from tuple elements - if any element is @Unsigned,
1240+
// propagate that to the method level
1241+
var tupleAnnotations: [JavaAnnotation] = []
1242+
for element in elements {
1243+
let elementAnnotations = getJavaTypeAnnotations(swiftType: element.type, config: config)
1244+
for annotation in elementAnnotations where !tupleAnnotations.contains(annotation) {
1245+
tupleAnnotations.append(annotation)
1246+
}
1247+
}
1248+
12391249
return TranslatedResult(
12401250
javaType: javaResultType,
1251+
annotations: tupleAnnotations,
12411252
outParameters: outParameters,
12421253
conversion: javaNativeConversionStep
12431254
)
@@ -1731,10 +1742,18 @@ extension JNISwift2JavaGenerator {
17311742
func render(type: JavaType) -> String {
17321743
switch self {
17331744
case .newArray(let javaType, let size):
1734-
"new \(javaType)[\(size)]"
1745+
// For array element types like byte[], we need "new byte[size][]"
1746+
// not "new byte[][size]"
1747+
var baseType = javaType
1748+
var extraDimensions = ""
1749+
while case .array(let inner) = baseType {
1750+
extraDimensions += "[]"
1751+
baseType = inner
1752+
}
1753+
return "new \(baseType)[\(size)]\(extraDimensions)"
17351754

17361755
case .new:
1737-
"new \(type)()"
1756+
return "new \(type)()"
17381757
}
17391758
}
17401759
}

SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@
3232
* in a method signature corresponds to a Swift {@code UInt64} type, and therefore
3333
* negative values reported by the signed {@code long} should instead be interpreted positive values,
3434
* larger than {@code Long.MAX_VALUE} that are just not representable using a signed {@code long}.
35+
* <p/>
36+
* If this annotation is used on a method, it refers to the return type using an unsigned integer.
3537
*/
3638
@Documented
3739
@Label("Unsigned integer type")
3840
@Description("Value should be interpreted as unsigned data type")
39-
@Target({TYPE_USE, PARAMETER, FIELD})
41+
@Target({TYPE_USE, PARAMETER, FIELD, METHOD})
4042
@Retention(RetentionPolicy.RUNTIME)
4143
public @interface Unsigned {
4244
}

Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,4 +311,54 @@ struct JNIArrayTest {
311311
]
312312
)
313313
}
314+
315+
// ==== -----------------------------------------------------------------------
316+
// MARK: Tuples with array elements
317+
318+
@Test("Import: () -> (name: [UInt8], another: [UInt8]) (Java)")
319+
func tupleByteArrays_java() throws {
320+
try assertOutput(
321+
input: "public func namedByteArrayTuple() -> (name: [UInt8], another: [UInt8]) {}",
322+
.jni,
323+
.java,
324+
detectChunkByInitialLines: 1,
325+
expectedChunks: [
326+
"""
327+
@Unsigned
328+
public static LabeledTuple_namedByteArrayTuple_name_another<byte[], byte[]> namedByteArrayTuple() {
329+
byte[][] result_0$ = new byte[1][];
330+
byte[][] result_1$ = new byte[1][];
331+
SwiftModule.$namedByteArrayTuple(result_0$, result_1$);
332+
return new LabeledTuple_namedByteArrayTuple_name_another<byte[], byte[]>(result_0$[0], result_1$[0]);
333+
}
334+
""",
335+
"""
336+
private static native void $namedByteArrayTuple(byte[][] result_0$, byte[][] result_1$);
337+
""",
338+
]
339+
)
340+
}
341+
342+
@Test("Import: () -> (name: [UInt8], another: [UInt8]) (Swift)")
343+
func tupleByteArrays_swift() throws {
344+
try assertOutput(
345+
input: "public func namedByteArrayTuple() -> (name: [UInt8], another: [UInt8]) {}",
346+
.jni,
347+
.swift,
348+
detectChunkByInitialLines: 1,
349+
expectedChunks: [
350+
"""
351+
@_cdecl("Java_com_example_swift_SwiftModule__00024namedByteArrayTuple___3_3B_3_3B")
352+
public func Java_com_example_swift_SwiftModule__00024namedByteArrayTuple___3_3B_3_3B(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, result_0$: jobjectArray?, result_1$: jobjectArray?) {
353+
let tupleResult$ = SwiftModule.namedByteArrayTuple()
354+
let element_0_jni$ = tupleResult$.name.getJNILocalRefValue(in: environment)
355+
environment.interface.SetObjectArrayElement(environment, result_0$, 0, element_0_jni$)
356+
let element_1_jni$ = tupleResult$.another.getJNILocalRefValue(in: environment)
357+
environment.interface.SetObjectArrayElement(environment, result_1$, 0, element_1_jni$)
358+
return
359+
}
360+
"""
361+
]
362+
)
363+
}
314364
}

0 commit comments

Comments
 (0)