Skip to content

Commit e7e9ba0

Browse files
authored
jextract/jni: Support optional tuple return values 2 (#723)
* Add generic type in tuple pattern to example code * Add optional tuple example * Restructure example code * Add unit test for tuples * Update handling of Optional result * Return to strict switch branching * Fix test fixtures
1 parent de257ec commit e7e9ba0

13 files changed

Lines changed: 215 additions & 116 deletions

File tree

Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,11 @@ public func multipleOptionals(
9191
) -> Int64? {
9292
1
9393
}
94+
95+
public func optionalTuple() -> (Int64, String)? {
96+
(42, "hello")
97+
}
98+
99+
public func optionalTuple2() -> (Int64?, Alignment?)? {
100+
(42, .horizontal)
101+
}

Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,7 @@ public func makeBigTuple() -> (
4545
public func namedByteArrayTuple() -> (name: [UInt8], another: [UInt8]) {
4646
(name: [1, 2, 3], another: [4, 5])
4747
}
48+
49+
public func genericTypeTuple() -> (MyID<Double>, Alignment) {
50+
(MyID(1.23), .horizontal)
51+
}

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,30 @@ void optionalThrows() {
143143
assertEquals("swiftError", exception.getMessage());
144144
}
145145
}
146-
}
146+
147+
@Test
148+
void optionalTuple() {
149+
var result = MySwiftLibrary.optionalTuple();
150+
assertDoesNotThrow(() -> {
151+
var resultUnwrapped = result.orElseThrow();
152+
assertEquals(42, resultUnwrapped.$0);
153+
assertEquals("hello", resultUnwrapped.$1);
154+
});
155+
}
156+
157+
@Test
158+
void optionalTuple2() {
159+
try (var arena = SwiftArena.ofConfined()) {
160+
var result = MySwiftLibrary.optionalTuple2(arena);
161+
assertDoesNotThrow(() -> {
162+
var resultUnwrapped = result.orElseThrow();
163+
assertDoesNotThrow(() -> {
164+
assertEquals(42, resultUnwrapped.$0.orElseThrow());
165+
});
166+
assertDoesNotThrow(() -> {
167+
assertEquals(Alignment.Discriminator.HORIZONTAL, resultUnwrapped.$1.orElseThrow().getDiscriminator());
168+
});
169+
});
170+
}
171+
}
172+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.example.swift;
1616

1717
import org.junit.jupiter.api.Test;
18+
import org.swift.swiftkit.core.SwiftArena;
1819
import org.swift.swiftkit.core.tuple.Tuple2;
1920
import org.swift.swiftkit.core.tuple.Tuple3;
2021
import org.swift.swiftkit.core.tuple.Tuple16;
@@ -95,4 +96,13 @@ void namedByteArrayTuple() {
9596
assertArrayEquals(new byte[] { 1, 2, 3 }, (byte[]) result.$0);
9697
assertArrayEquals(new byte[] { 4, 5 }, (byte[]) result.$1);
9798
}
99+
100+
@Test
101+
void genericTypeTuple() {
102+
try (var arena = SwiftArena.ofConfined()) {
103+
var result = MySwiftLibrary.genericTypeTuple(arena);
104+
assertEquals("1.23", result.$0.getDescription());
105+
assertEquals(Alignment.Discriminator.HORIZONTAL, result.$1.getDiscriminator());
106+
}
107+
}
98108
}

Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ extension JavaType {
8888
}
8989
}
9090

91+
var swiftJniPlaceholderExpr: String {
92+
switch self {
93+
case .boolean, .byte, .char, .short, .int, .long: "0"
94+
case .float, .double: "0.0"
95+
case .array, .class: "nil"
96+
case .void: "()"
97+
}
98+
}
99+
91100
var jniCallMethodAName: String {
92101
switch self {
93102
case .boolean: "CallBooleanMethodA"

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,13 @@ extension JNISwift2JavaGenerator {
10491049
if let knownType = nominalType.asKnownType {
10501050
switch knownType {
10511051
case .optional(let wrapped):
1052+
if let wrappedKnownKind = wrapped.asNominalTypeDeclaration?.knownTypeKind,
1053+
let javaType = JNIJavaTypeTranslator.translate(knownType: wrappedKnownKind, config: self.config),
1054+
let optionalType = javaType.optionalType
1055+
{
1056+
return .class(package: nil, name: optionalType)
1057+
}
1058+
10521059
let wrappedType = try translateGenericTypeParameter(
10531060
wrapped,
10541061
genericParameters: genericParameters,
@@ -1141,7 +1148,17 @@ extension JNISwift2JavaGenerator {
11411148
}
11421149
return .class(package: nil, name: generic.name)
11431150

1144-
case .metatype, .tuple, .function, .existential, .opaque, .composite:
1151+
case .tuple(let elements):
1152+
let elementJavaTypes = try elements.map { element in
1153+
try translateGenericTypeParameter(
1154+
element.type,
1155+
genericParameters: genericParameters,
1156+
genericRequirements: genericRequirements
1157+
)
1158+
}
1159+
return .tuple(elementTypes: elementJavaTypes)
1160+
1161+
case .metatype, .function, .existential, .opaque, .composite:
11451162
throw JavaTranslationError.unsupportedSwiftType(swiftType)
11461163
}
11471164
}
@@ -1259,22 +1276,11 @@ extension JNISwift2JavaGenerator {
12591276
switch swiftType {
12601277
case .nominal(let nominalType):
12611278
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
1262-
switch knownType {
1263-
case .foundationDate, .essentialsDate:
1264-
// Handled as wrapped struct
1265-
break
1266-
1267-
case .foundationData, .essentialsData:
1268-
// Handled as wrapped struct
1269-
break
1270-
1271-
default:
1272-
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else {
1273-
throw JavaTranslationError.unsupportedSwiftType(swiftType)
1274-
}
1275-
1279+
if let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config),
1280+
javaType.implementsJavaValue
1281+
{
12761282
guard let returnType = javaType.optionalType, let optionalClass = javaType.optionalWrapperType else {
1277-
throw JavaTranslationError.unsupportedSwiftType(swiftType)
1283+
break
12781284
}
12791285

12801286
// Check if we can fit the value and a discriminator byte in a primitive.
@@ -1320,41 +1326,44 @@ extension JNISwift2JavaGenerator {
13201326
throw JavaTranslationError.unsupportedSwiftType(swiftType)
13211327
}
13221328

1323-
// We assume this is a JExtract class.
1324-
let javaType = try translateGenericTypeParameter(
1325-
swiftType,
1326-
genericParameters: genericParameters,
1327-
genericRequirements: genericRequirements,
1328-
)
1329-
1330-
let wrappedValueResult = try translateResult(
1331-
swiftType: swiftType,
1332-
methodName: methodName,
1333-
resultName: resultName + "Wrapped$",
1334-
genericParameters: genericParameters,
1335-
genericRequirements: genericRequirements,
1336-
)
1337-
1338-
let returnType = JavaType.optional(javaType)
1339-
return TranslatedResult(
1340-
javaType: returnType,
1341-
nativeJavaType: wrappedValueResult.nativeJavaType,
1342-
annotations: parameterAnnotations,
1343-
outParameters: [
1344-
OutParameter(name: discriminatorName, type: .array(.byte), allocation: .newArray(.byte, size: 1))
1345-
] + wrappedValueResult.outParameters,
1346-
conversion: .toOptionalFromIndirectReturn(
1347-
discriminatorName: .constant(discriminatorName),
1348-
optionalClass: "Optional",
1349-
nativeResultJavaType: wrappedValueResult.nativeJavaType,
1350-
toValue: wrappedValueResult.conversion,
1351-
resultName: resultName
1352-
)
1353-
)
1329+
case .tuple:
1330+
break
13541331

13551332
default:
13561333
throw JavaTranslationError.unsupportedSwiftType(swiftType)
13571334
}
1335+
1336+
// Common indirect conversion
1337+
let javaType = try translateGenericTypeParameter(
1338+
swiftType,
1339+
genericParameters: genericParameters,
1340+
genericRequirements: genericRequirements,
1341+
)
1342+
1343+
let wrappedValueResult = try translateResult(
1344+
swiftType: swiftType,
1345+
methodName: methodName,
1346+
resultName: resultName + "Wrapped$",
1347+
genericParameters: genericParameters,
1348+
genericRequirements: genericRequirements,
1349+
)
1350+
1351+
let returnType = JavaType.optional(javaType)
1352+
return TranslatedResult(
1353+
javaType: returnType,
1354+
nativeJavaType: wrappedValueResult.nativeJavaType,
1355+
annotations: parameterAnnotations,
1356+
outParameters: [
1357+
OutParameter(name: discriminatorName, type: .array(.byte), allocation: .newArray(.byte, size: 1))
1358+
] + wrappedValueResult.outParameters,
1359+
conversion: .toOptionalFromIndirectReturn(
1360+
discriminatorName: .constant(discriminatorName),
1361+
optionalClass: "Optional",
1362+
nativeResultJavaType: wrappedValueResult.nativeJavaType,
1363+
toValue: wrappedValueResult.conversion,
1364+
resultName: resultName
1365+
)
1366+
)
13581367
}
13591368

13601369
func translateArrayParameter(

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift

Lines changed: 34 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -531,23 +531,9 @@ extension JNISwift2JavaGenerator {
531531
switch swiftType {
532532
case .nominal(let nominalType):
533533
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
534-
switch knownType {
535-
case .foundationDate, .essentialsDate:
536-
// Handled as wrapped struct
537-
break
538-
539-
case .foundationData, .essentialsData:
540-
// Handled as wrapped struct
541-
break
542-
543-
default:
544-
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config),
545-
javaType.implementsJavaValue
546-
else {
547-
self.logger.debug("Known type \(knownType) is not supported for optional results, skipping.")
548-
throw JavaTranslationError.unsupportedSwiftType(swiftType)
549-
}
550-
534+
if let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config),
535+
javaType.implementsJavaValue
536+
{
551537
// Check if we can fit the value and a discriminator byte in a primitive.
552538
// so the return JNI value will be (value, discriminator)
553539
if let nextIntergralTypeWithSpaceForByte = javaType.nextIntergralTypeWithSpaceForByte {
@@ -571,12 +557,9 @@ extension JNISwift2JavaGenerator {
571557
javaType: javaType,
572558
conversion: .optionalRaisingIndirectReturn(
573559
.getJNIValue(.placeholder),
560+
resultName: "\(resultName)$",
574561
returnType: javaType,
575-
discriminatorParameterName: discriminatorName,
576-
placeholderValue: .member(
577-
.constant("\(swiftType)"),
578-
member: "jniPlaceholderValue"
579-
)
562+
discriminatorParameterName: discriminatorName
580563
),
581564
outParameters: [
582565
JavaParameter(name: discriminatorName, type: .array(.byte))
@@ -591,29 +574,31 @@ extension JNISwift2JavaGenerator {
591574
throw JavaTranslationError.unsupportedSwiftType(swiftType)
592575
}
593576

594-
let wrappedValueResult = try translateResult(
595-
swiftType: swiftType,
596-
methodName: methodName,
597-
resultName: resultName + "Wrapped"
598-
)
599-
600-
// Assume JExtract imported class
601-
return NativeResult(
602-
javaType: wrappedValueResult.javaType,
603-
conversion: .optionalRaisingIndirectReturn(
604-
wrappedValueResult.conversion,
605-
returnType: wrappedValueResult.javaType,
606-
discriminatorParameterName: discriminatorName,
607-
placeholderValue: .constant("0")
608-
),
609-
outParameters: [
610-
JavaParameter(name: discriminatorName, type: .array(.byte))
611-
] + wrappedValueResult.outParameters
612-
)
577+
case .tuple:
578+
break
613579

614580
default:
615581
throw JavaTranslationError.unsupportedSwiftType(swiftType)
616582
}
583+
584+
// Common indirect conversion
585+
let wrappedValueResult = try translateResult(
586+
swiftType: swiftType,
587+
methodName: methodName,
588+
resultName: resultName + "Wrapped"
589+
)
590+
return NativeResult(
591+
javaType: wrappedValueResult.javaType,
592+
conversion: .optionalRaisingIndirectReturn(
593+
wrappedValueResult.conversion,
594+
resultName: "\(resultName)$",
595+
returnType: wrappedValueResult.javaType,
596+
discriminatorParameterName: discriminatorName
597+
),
598+
outParameters: [
599+
JavaParameter(name: discriminatorName, type: .array(.byte))
600+
] + wrappedValueResult.outParameters
601+
)
617602
}
618603

619604
func translateClosureResult(
@@ -1162,9 +1147,9 @@ extension JNISwift2JavaGenerator {
11621147

11631148
indirect case optionalRaisingIndirectReturn(
11641149
NativeSwiftConversionStep,
1150+
resultName: String,
11651151
returnType: JavaType,
1166-
discriminatorParameterName: String,
1167-
placeholderValue: NativeSwiftConversionStep
1152+
discriminatorParameterName: String
11681153
)
11691154

11701155
indirect case genericValueIndirectReturn(
@@ -1530,17 +1515,17 @@ extension JNISwift2JavaGenerator {
15301515

15311516
case .optionalRaisingIndirectReturn(
15321517
let inner,
1518+
let resultName,
15331519
let returnType,
1534-
let discriminatorParameterName,
1535-
let placeholderValue
1520+
let discriminatorParameterName
15361521
):
15371522
if !returnType.isVoid {
1538-
printer.print("let result$: \(returnType.jniTypeName)")
1523+
printer.print("let \(resultName): \(returnType.jniTypeName)")
15391524
}
15401525
printer.printBraceBlock("if let innerResult$ = \(placeholder)") { printer in
15411526
let inner = inner.render(&printer, "innerResult$")
15421527
if !returnType.isVoid {
1543-
printer.print("result$ = \(inner)")
1528+
printer.print("\(resultName) = \(inner)")
15441529
}
15451530
printer.print(
15461531
"""
@@ -1550,9 +1535,8 @@ extension JNISwift2JavaGenerator {
15501535
)
15511536
}
15521537
printer.printBraceBlock("else") { printer in
1553-
let placeholderValue = placeholderValue.render(&printer, placeholder)
15541538
if !returnType.isVoid {
1555-
printer.print("result$ = \(placeholderValue)")
1539+
printer.print("\(resultName) = \(returnType.swiftJniPlaceholderExpr)")
15561540
}
15571541
printer.print(
15581542
"""
@@ -1562,7 +1546,7 @@ extension JNISwift2JavaGenerator {
15621546
)
15631547
}
15641548
if !returnType.isVoid {
1565-
return "result$"
1549+
return resultName
15661550
} else {
15671551
return ""
15681552
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -751,14 +751,7 @@ extension JNISwift2JavaGenerator {
751751
}
752752

753753
private func dummyReturn(for nativeSignature: NativeFunctionSignature) -> String {
754-
if nativeSignature.result.javaType.isVoid {
755-
"return"
756-
} else if nativeSignature.result.javaType.isString {
757-
"return String.jniPlaceholderValue"
758-
} else {
759-
// We assume it is something that implements JavaValue
760-
"return \(nativeSignature.result.javaType.swiftTypeName(resolver: { _ in "" })).jniPlaceholderValue"
761-
}
754+
"return \(nativeSignature.result.javaType.swiftJniPlaceholderExpr)"
762755
}
763756

764757
private func printCDecl(

0 commit comments

Comments
 (0)