From efba216a62836a3d673202bb09cdb2641a62f0a9 Mon Sep 17 00:00:00 2001 From: Om Singhal Date: Wed, 30 Sep 2026 17:26:40 -0400 Subject: [PATCH] JNI: fix async thunks for results returned through out parameters Async functions returning a generic type, an existential or a tuple write their result into out parameters and give Java a future of Void. The completion call rendered the empty result conversion instead, which produced `[jvalue(l: )]` and failed to compile. Complete the future with nil in that case. Fixes #752 --- ...wift2JavaGenerator+NativeTranslation.swift | 5 +- .../JNI/JNIAsyncTests.swift | 47 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index a262e5fc9..229e48f55 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -2000,7 +2000,10 @@ extension JNISwift2JavaGenerator { printer.print("environment = try! JavaVirtualMachine.shared().environment()") let inner = nativeFunctionSignature.result.conversion.render(&printer, "swiftResult$") let result: String - if nativeFunctionSignature.result.javaType.requiresBoxing { + if nativeFunctionSignature.result.javaType.isVoid { + // The result was written to the out parameters, so the Java future is completed with Void + result = "nil" + } else if nativeFunctionSignature.result.javaType.requiresBoxing { printer.print( "let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)" ) diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 7c968e419..825204af7 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -482,4 +482,51 @@ struct JNIAsyncTests { ] ) } + + @Test( + "Import: async with result in out parameters completes future with nil (Swift, CompletableFuture)", + arguments: [ + ( + """ + public struct MyID { + public var rawValue: T + } + public func asyncGeneric() async -> MyID + """, + "asyncGeneric" + ), + ( + """ + public protocol Shape { + func area() -> Double + } + public func asyncExistential() async -> any Shape + """, + "asyncExistential" + ), + ( + "public func asyncTuple() async -> (Int64, String)", + "asyncTuple" + ), + ] + ) + func completableFuture_asyncOutParameterResult_swift(input: String, functionName: String) throws { + try assertOutput( + input: input, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + let swiftResult$ = await SwiftModule.\(functionName)() + environment = try! JavaVirtualMachine.shared().environment() + ... + _ = environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + """ + ], + notExpectedChunks: [ + "[jvalue(l: )]" + ] + ) + } }