Skip to content

Commit c58b664

Browse files
committed
jextract: handle nested arrays, e.g. [[UInt8]]
This implements support of nested arrays of primitives or objects and relies on updated jni-core in swiftlang/swift-java-jni-core#17
1 parent b83ab0d commit c58b664

File tree

12 files changed

+445
-13
lines changed

12 files changed

+445
-13
lines changed

Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,15 @@ public func stringArray(array: [String]) -> [String] {
5757
public func objectArray(array: [MySwiftClass]) -> [MySwiftClass] {
5858
array
5959
}
60+
61+
public func nestedByteArray(array: [[UInt8]]) -> [[UInt8]] {
62+
array
63+
}
64+
65+
public func nestedLongArray(array: [[Int64]]) -> [[Int64]] {
66+
array
67+
}
68+
69+
public func nestedStringArray(array: [[String]]) -> [[String]] {
70+
array
71+
}

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

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,76 @@ void objectArray() {
102102
assertEquals(3, MySwiftLibrary.objectArray(input, arena).length);
103103
}
104104
}
105-
}
105+
106+
@Test
107+
void nestedByteArray() {
108+
byte[][] input = new byte[][] {
109+
{ 1, 2, 3 },
110+
{ 4, 5 },
111+
{ 6 }
112+
};
113+
byte[][] result = MySwiftLibrary.nestedByteArray(input);
114+
assertEquals(input.length, result.length);
115+
assertArrayEquals(input[0], result[0]);
116+
assertArrayEquals(input[1], result[1]);
117+
assertArrayEquals(input[2], result[2]);
118+
}
119+
120+
@Test
121+
void nestedByteArray_empty() {
122+
byte[][] input = new byte[][] {};
123+
byte[][] result = MySwiftLibrary.nestedByteArray(input);
124+
assertEquals(0, result.length);
125+
}
126+
127+
@Test
128+
void nestedByteArray_emptyInner() {
129+
byte[][] input = new byte[][] { {}, { 1 }, {} };
130+
byte[][] result = MySwiftLibrary.nestedByteArray(input);
131+
assertEquals(3, result.length);
132+
assertArrayEquals(new byte[] {}, result[0]);
133+
assertArrayEquals(new byte[] { 1 }, result[1]);
134+
assertArrayEquals(new byte[] {}, result[2]);
135+
}
136+
137+
@Test
138+
void nestedLongArray() {
139+
long[][] input = new long[][] {
140+
{ 100, 200, 300 },
141+
{ 400, 500 }
142+
};
143+
long[][] result = MySwiftLibrary.nestedLongArray(input);
144+
assertEquals(input.length, result.length);
145+
assertArrayEquals(input[0], result[0]);
146+
assertArrayEquals(input[1], result[1]);
147+
}
148+
149+
@Test
150+
void nestedStringArray() {
151+
String[][] input = new String[][] {
152+
{ "hello", "world" },
153+
{ "foo", "bar", "baz" }
154+
};
155+
String[][] result = MySwiftLibrary.nestedStringArray(input);
156+
assertEquals(input.length, result.length);
157+
assertArrayEquals(input[0], result[0]);
158+
assertArrayEquals(input[1], result[1]);
159+
}
160+
161+
@Test
162+
void nestedStringArray_empty() {
163+
String[][] input = new String[][] {};
164+
String[][] result = MySwiftLibrary.nestedStringArray(input);
165+
assertEquals(0, result.length);
166+
}
167+
168+
@Test
169+
void nestedStringArray_emptyInner() {
170+
String[][] input = new String[][] { {}, { "a" }, {} };
171+
String[][] result = MySwiftLibrary.nestedStringArray(input);
172+
assertEquals(3, result.length);
173+
assertArrayEquals(new String[] {}, result[0]);
174+
assertArrayEquals(new String[] { "a" }, result[1]);
175+
assertArrayEquals(new String[] {}, result[2]);
176+
}
177+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ void fishBoxHasExpectedMethods() throws Exception {
4444

4545
@Test
4646
void fishBoxDoesNotHaveGenericTypeParameter() {
47-
// FishBox is a concrete specialization no generic type parameters
47+
// FishBox is a concrete specialization - no generic type parameters
4848
assertEquals(0, FishBox.class.getTypeParameters().length,
4949
"FishBox should have no generic type parameters");
5050
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ void insertIntoStringToLongDictionary() {
6363
assertEquals(2L, modified.get("world"));
6464
assertEquals(42L, modified.get("swift"));
6565

66-
// The original dictionary is unchanged (Swift value semantics it's a copy)
66+
// The original dictionary is unchanged (Swift value semantics - it's a copy)
6767
assertEquals(2, original.size());
6868
assertNull(original.get("swift"));
6969
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ void insertIntoStringSet() {
6161
assertTrue(modified.contains("world"));
6262
assertTrue(modified.contains("swift"));
6363

64-
// The original set is unchanged (Swift value semantics it's a copy)
64+
// The original set is unchanged (Swift value semantics - it's a copy)
6565
assertEquals(2, original.size());
6666
assertFalse(original.contains("swift"));
6767
}

Sources/JExtractSwiftLib/Common/TypeAnnotations.swift renamed to Sources/JExtractSwiftLib/Common/JavaTypeAnnotations.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,27 @@ import SwiftJavaJNICore
1717

1818
/// Determine if the given type needs any extra annotations that should be included
1919
/// in Java sources when the corresponding Java type is rendered.
20-
func getTypeAnnotations(swiftType: SwiftType, config: Configuration) -> [JavaAnnotation] {
20+
func getJavaTypeAnnotations(swiftType: SwiftType, config: Configuration) -> [JavaAnnotation] {
2121
if swiftType.isUnsignedInteger {
2222
return [JavaAnnotation.unsigned]
2323
}
2424

2525
switch swiftType.asNominalType?.asKnownType {
2626
case .array(let element) where element.isUnsignedInteger:
2727
return [JavaAnnotation.unsigned]
28+
case .array(let element): // check recursively for [[UInt8]] etc
29+
return getJavaTypeAnnotations(swiftType: element, config: config)
30+
31+
case .set(let element) where element.isUnsignedInteger:
32+
return [JavaAnnotation.unsigned]
33+
case .set(let element):
34+
return getJavaTypeAnnotations(swiftType: element, config: config)
35+
36+
case .dictionary(let key, let value) where key.isUnsignedInteger || value.isUnsignedInteger:
37+
return [JavaAnnotation.unsigned]
38+
case .dictionary(let key, let value):
39+
return getJavaTypeAnnotations(swiftType: key, config: config) + getJavaTypeAnnotations(swiftType: value, config: config)
40+
2841
default:
2942
return []
3043
}

Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ extension JavaType {
121121
switch self {
122122
case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString:
123123
true
124+
case .array(let element):
125+
element.implementsJavaValue
124126
default:
125127
false
126128
}

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ extension FFMSwift2JavaGenerator {
358358
genericRequirements: [SwiftGenericRequirement]
359359
) throws -> TranslatedParameter {
360360
// If the result type should cause any annotations on the method, include them here.
361-
let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config)
361+
let parameterAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config)
362362

363363
// If there is a 1:1 mapping between this Swift type and a C type, that can
364364
// be expressed as a Java primitive type.
@@ -694,7 +694,7 @@ extension FFMSwift2JavaGenerator {
694694
) throws -> TranslatedResult {
695695
let swiftType = swiftResult.type
696696
// If the result type should cause any annotations on the method, include them here.
697-
let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config)
697+
let resultAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config)
698698

699699
// If there is a 1:1 mapping between this Swift type and a C type, that can
700700
// be expressed as a Java primitive type.

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ extension JNISwift2JavaGenerator {
463463
) throws -> TranslatedParameter {
464464

465465
// If the result type should cause any annotations on the method, include them here.
466-
let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config)
466+
let parameterAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config)
467467

468468
switch swiftType {
469469
case .nominal(let nominalType):
@@ -804,7 +804,7 @@ extension JNISwift2JavaGenerator {
804804
genericParameters: [SwiftGenericParameterDeclaration],
805805
genericRequirements: [SwiftGenericRequirement],
806806
) throws -> TranslatedParameter {
807-
let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config)
807+
let parameterAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config)
808808

809809
switch swiftType {
810810
case .nominal(let nominalType):
@@ -896,7 +896,7 @@ extension JNISwift2JavaGenerator {
896896
let swiftType = swiftResult.type
897897

898898
// If the result type should cause any annotations on the method, include them here.
899-
let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config)
899+
let resultAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config)
900900

901901
switch swiftType {
902902
case .nominal(let nominalType):
@@ -1251,7 +1251,7 @@ extension JNISwift2JavaGenerator {
12511251
) throws -> TranslatedResult {
12521252
let discriminatorName = "\(resultName)$_discriminator$"
12531253

1254-
let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config)
1254+
let parameterAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config)
12551255

12561256
switch swiftType {
12571257
case .nominal(let nominalType):
@@ -1365,9 +1365,30 @@ extension JNISwift2JavaGenerator {
13651365
genericParameters: [SwiftGenericParameterDeclaration],
13661366
genericRequirements: [SwiftGenericRequirement],
13671367
) throws -> TranslatedParameter {
1368-
let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config)
1368+
let parameterAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: elementType, config: config)
13691369

13701370
switch elementType {
1371+
case .nominal(let nominalType) where nominalType.nominalTypeDecl.knownTypeKind == .array:
1372+
guard let fullKnownType = nominalType.asKnownType else {
1373+
throw JavaTranslationError.unsupportedSwiftType(elementType)
1374+
}
1375+
1376+
guard case .array(let innerElement) = fullKnownType else {
1377+
throw JavaTranslationError.unsupportedSwiftType(elementType)
1378+
}
1379+
1380+
let innerParam = try translateArrayParameter(
1381+
elementType: innerElement,
1382+
parameterName: parameterName,
1383+
genericParameters: genericParameters,
1384+
genericRequirements: genericRequirements
1385+
)
1386+
let innerJavaType = innerParam.parameter.type.javaType
1387+
return TranslatedParameter(
1388+
parameter: JavaParameter(name: parameterName, type: .array(innerJavaType), annotations: parameterAnnotations),
1389+
conversion: .requireNonNull(.placeholder, message: "\(parameterName) must not be null")
1390+
)
1391+
13711392
case .nominal(let nominalType):
13721393
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
13731394
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else {
@@ -1417,9 +1438,30 @@ extension JNISwift2JavaGenerator {
14171438
genericParameters: [SwiftGenericParameterDeclaration],
14181439
genericRequirements: [SwiftGenericRequirement],
14191440
) throws -> TranslatedResult {
1420-
let annotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config)
1441+
let annotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: elementType, config: config)
14211442

14221443
switch elementType {
1444+
case .nominal(let nominalType) where nominalType.nominalTypeDecl.knownTypeKind == .array:
1445+
guard let fullKnownType = nominalType.asKnownType else {
1446+
throw JavaTranslationError.unsupportedSwiftType(elementType)
1447+
}
1448+
1449+
guard case .array(let innerElement) = fullKnownType else {
1450+
throw JavaTranslationError.unsupportedSwiftType(elementType)
1451+
}
1452+
1453+
let innerResult = try translateArrayResult(
1454+
elementType: innerElement,
1455+
genericParameters: genericParameters,
1456+
genericRequirements: genericRequirements
1457+
)
1458+
return TranslatedResult(
1459+
javaType: .array(innerResult.javaType),
1460+
annotations: annotations,
1461+
outParameters: [],
1462+
conversion: .placeholder
1463+
)
1464+
14231465
case .nominal(let nominalType):
14241466
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
14251467
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else {

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,22 @@ extension JNISwift2JavaGenerator {
846846
resultName: String
847847
) throws -> NativeResult {
848848
switch elementType {
849+
case .nominal(let nominalType) where nominalType.nominalTypeDecl.knownTypeKind == .array:
850+
guard let fullKnownType = nominalType.asKnownType else {
851+
throw JavaTranslationError.unsupportedSwiftType(known: .array(elementType))
852+
}
853+
854+
guard case .array(let innerElement) = fullKnownType else {
855+
throw JavaTranslationError.unsupportedSwiftType(known: .array(elementType))
856+
}
857+
858+
let innerResult = try translateArrayResult(elementType: innerElement, resultName: resultName)
859+
return NativeResult(
860+
javaType: .array(innerResult.javaType),
861+
conversion: .getJNIValue(.placeholder),
862+
outParameters: []
863+
)
864+
849865
case .nominal(let nominalType):
850866
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
851867
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config),
@@ -897,6 +913,28 @@ extension JNISwift2JavaGenerator {
897913
parameterName: String
898914
) throws -> NativeParameter {
899915
switch elementType {
916+
case .nominal(let nominalType) where nominalType.nominalTypeDecl.knownTypeKind == .array:
917+
guard let fullKnownType = nominalType.asKnownType else {
918+
throw JavaTranslationError.unsupportedSwiftType(elementType)
919+
}
920+
921+
guard case .array(let innerElement) = fullKnownType else {
922+
throw JavaTranslationError.unsupportedSwiftType(elementType)
923+
}
924+
925+
let innerParam = try translateArrayParameter(elementType: innerElement, parameterName: parameterName)
926+
guard case .concrete(let innerJavaType) = innerParam.parameters.first?.type else {
927+
throw JavaTranslationError.unsupportedSwiftType(elementType)
928+
}
929+
return NativeParameter(
930+
parameters: [
931+
JavaParameter(name: parameterName, type: .array(innerJavaType))
932+
],
933+
conversion: .initFromJNI(.placeholder, swiftType: knownTypes.arraySugar(elementType)),
934+
indirectConversion: nil,
935+
conversionCheck: nil
936+
)
937+
900938
case .nominal(let nominalType):
901939
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
902940
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config),

0 commit comments

Comments
 (0)