Skip to content

Commit b0d1e43

Browse files
piercifaniclaude
andcommitted
JNI: closures returning arrays of jextracted types, optional or not
A closure implemented in Java that returns `[Item]` generated a wrapper method declared `-> [Item]`, which doesn't compile (`Item` isn't a Java value), and one returning `[Item]?` was skipped. The wrapper method now returns the elements' Java objects, `[JavaItem?]`, for an array, and the Java `Optional` holding the array, `JavaOptional<JavaObject>`, for an optional array, which Swift reads back as `[JavaItem?]`. Each element is then converted to the Swift value it wraps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 2c28a44 commit b0d1e43

3 files changed

Lines changed: 64 additions & 1 deletion

File tree

‎Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+EscapingClosureWrapJava.swift‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,17 @@ extension JNISwift2JavaGenerator {
104104
}
105105
}
106106
// Arrays of jextracted values cross as arrays of their Java objects.
107-
if !isResult, case .nominal(let array) = type, case .array(let element) = array.asKnownType,
107+
if case .nominal(let array) = type, case .array(let element) = array.asKnownType,
108108
let nominal = jextractedNominal(element)
109109
{
110110
return "[\(nominal.generatedJavaClassMacroName)?]"
111111
}
112+
// An optional array is returned in a Java `Optional`, which holds the array as an object.
113+
if isResult, let wrapped = optionalWrapped(type), case .nominal(let array) = wrapped,
114+
case .array(let element) = array.asKnownType, jextractedNominal(element) != nil
115+
{
116+
return "JavaOptional<JavaObject>!"
117+
}
112118
return type.description
113119
}
114120

‎Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,19 @@ extension JNISwift2JavaGenerator {
352352
}
353353

354354
private func translateOptionalResult(wrappedType: SwiftType, methodName: String) throws -> UpcallConversionStep {
355+
// The Java `Optional` holds an array as an object: read it back as an array of the elements' Java objects.
356+
if case .nominal(let nominal) = wrappedType, case .array(let element) = nominal.asKnownType {
357+
guard case .nominal(let elementType) = element, elementType.nominalTypeDecl.knownTypeKind == nil,
358+
case .map(_, let elementConversion) = try translateArrayResult(elementType: element)
359+
else {
360+
throw JavaTranslationError.unsupportedSwiftType(wrappedType)
361+
}
362+
let javaArray = UpcallConversionStep.javaObjectArray(
363+
.placeholder,
364+
elementJavaClass: elementType.nominalTypeDecl.generatedJavaClassMacroName
365+
)
366+
return .map(.fromJavaOptional(.placeholder), body: .map(javaArray, body: elementConversion))
367+
}
355368
// The `fromJavaOptional` will handle the nullability
356369
let wrappedConversion = try translateResult(
357370
type: wrappedType,
@@ -392,6 +405,10 @@ enum UpcallConversionStep {
392405

393406
indirect case map(UpcallConversionStep, body: UpcallConversionStep)
394407

408+
/// A Java array held as a plain Java object, as the Swift array of its elements' Java objects.
409+
indirect case javaObjectArray(UpcallConversionStep, elementJavaClass: String)
410+
411+
395412
/// Returns the conversion string applied to the placeholder.
396413
func render(_ printer: inout SwiftPrinter, _ placeholder: String) -> String {
397414
switch self {
@@ -447,6 +464,10 @@ enum UpcallConversionStep {
447464
let inner = inner.render(&printer, placeholder)
448465
return "Optional(javaOptional: \(inner))"
449466

467+
case .javaObjectArray(let inner, let elementJavaClass):
468+
let inner = inner.render(&printer, placeholder)
469+
return "[\(elementJavaClass)?](fromJNI: \(inner).javaThis, in: \(inner).javaEnvironment)"
470+
450471
case .map(let inner, let body):
451472
let inner = inner.render(&printer, placeholder)
452473
var printer = SwiftPrinter()

‎Tests/JExtractSwiftTests/JNI/JNIEscapingClosureJExtractedTypesTests.swift‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,4 +240,40 @@ struct JNIEscapingClosureJExtractedTypesTests {
240240
]
241241
)
242242
}
243+
244+
@Test
245+
func closureReturningArrayOfJextractedType_returnsJavaObjects() throws {
246+
try assertOutput(
247+
input: itemSource + "\npublic func setSeeds(_ cb: @escaping (String) -> [Item]) {}",
248+
.jni,
249+
.swift,
250+
expectedChunks: [
251+
#"@JavaClass("com.example.swift.Item")"#,
252+
"public func apply(_ _0: String) -> [JavaItem?]",
253+
"let arrayElementPointer = UnsafeMutablePointer<Item>(bitPattern: Int(arrayElementMemoryAddress$))!",
254+
]
255+
)
256+
}
257+
258+
@Test
259+
func closureReturningOptionalArrayOfJextractedType_readsTheJavaOptional() throws {
260+
try assertOutput(
261+
input: itemSource + "\npublic func setSeeds(_ cb: @escaping (String) -> [Item]?) {}",
262+
.jni,
263+
.swift,
264+
expectedChunks: [
265+
"public func apply(_ _0: String) -> JavaOptional<JavaObject>!",
266+
"return Optional(javaOptional: javaInterface_cb$.apply(_0)).map {",
267+
"return [JavaItem?](fromJNI: $0.javaThis, in: $0.javaEnvironment).map {",
268+
]
269+
)
270+
try assertOutput(
271+
input: itemSource + "\npublic func setSeeds(_ cb: @escaping (String) -> [Item]?) {}",
272+
.jni,
273+
.java,
274+
expectedChunks: [
275+
"java.util.Optional<Item[]> apply(java.lang.String _0);"
276+
]
277+
)
278+
}
243279
}

0 commit comments

Comments
 (0)