Skip to content

Commit cc72839

Browse files
madsodgaardktoso
andauthored
[jextract] fix support for objects in async (#428)
Co-authored-by: Konrad `ktoso` Malawski <[email protected]>
1 parent 79f1f8e commit cc72839

File tree

7 files changed

+109
-21
lines changed

7 files changed

+109
-21
lines changed

Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ public func asyncOptional(i: Int64) async throws -> Int64? {
3636
public func asyncThrows() async throws {
3737
throw MySwiftError.swiftError
3838
}
39+
40+
public func asyncString(input: String) async -> String {
41+
return input
42+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,10 @@ void asyncOptional() {
7474
CompletableFuture<OptionalLong> future = MySwiftLibrary.asyncOptional(42);
7575
assertEquals(OptionalLong.of(42), future.join());
7676
}
77+
78+
@Test
79+
void asyncString() {
80+
CompletableFuture<String> future = MySwiftLibrary.asyncString("hey");
81+
assertEquals("hey", future.join());
82+
}
7783
}

Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,13 @@ extension JavaType {
137137
return self
138138
}
139139
}
140+
141+
var requiresBoxing: Bool {
142+
switch self {
143+
case .boolean, .byte, .char, .short, .int, .long, .float, .double:
144+
true
145+
default:
146+
false
147+
}
148+
}
140149
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -514,14 +514,12 @@ extension JNISwift2JavaGenerator {
514514
)
515515

516516
// Update native function
517-
nativeFunctionSignature.result.javaType = .void
518517
nativeFunctionSignature.result.conversion = .asyncCompleteFuture(
519-
nativeFunctionSignature.result.conversion,
520518
swiftFunctionResultType: originalFunctionSignature.result.type,
521-
nativeReturnType: nativeFunctionSignature.result.javaType,
522-
outParameters: nativeFunctionSignature.result.outParameters,
519+
nativeFunctionSignature: nativeFunctionSignature,
523520
isThrowing: originalFunctionSignature.isThrowing
524521
)
522+
nativeFunctionSignature.result.javaType = .void
525523
nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType))
526524
}
527525
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -570,10 +570,8 @@ extension JNISwift2JavaGenerator {
570570
indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String)
571571

572572
indirect case asyncCompleteFuture(
573-
NativeSwiftConversionStep,
574573
swiftFunctionResultType: SwiftType,
575-
nativeReturnType: JavaType,
576-
outParameters: [JavaParameter],
574+
nativeFunctionSignature: NativeFunctionSignature,
577575
isThrowing: Bool
578576
)
579577

@@ -806,15 +804,22 @@ extension JNISwift2JavaGenerator {
806804
return unwrappedName
807805

808806
case .asyncCompleteFuture(
809-
let inner,
810807
let swiftFunctionResultType,
811-
let nativeReturnType,
812-
let outParameters,
808+
let nativeFunctionSignature,
813809
let isThrowing
814810
):
811+
var globalRefs: [String] = ["globalFuture"]
812+
815813
// Global ref all indirect returns
816-
for outParameter in outParameters {
814+
for outParameter in nativeFunctionSignature.result.outParameters {
817815
printer.print("let \(outParameter.name) = environment.interface.NewGlobalRef(environment, \(outParameter.name))")
816+
globalRefs.append(outParameter.name)
817+
}
818+
819+
// We also need to global ref any objects passed in
820+
for parameter in nativeFunctionSignature.parameters.flatMap(\.parameters) where !parameter.type.isPrimitive {
821+
printer.print("let \(parameter.name) = environment.interface.NewGlobalRef(environment, \(parameter.name))")
822+
globalRefs.append(parameter.name)
818823
}
819824

820825
printer.print(
@@ -826,26 +831,28 @@ extension JNISwift2JavaGenerator {
826831
func printDo(printer: inout CodePrinter) {
827832
printer.print("let swiftResult$ = await \(placeholder)")
828833
printer.print("environment = try! JavaVirtualMachine.shared().environment()")
829-
let inner = inner.render(&printer, "swiftResult$")
834+
let inner = nativeFunctionSignature.result.conversion.render(&printer, "swiftResult$")
830835
if swiftFunctionResultType.isVoid {
831836
printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])")
832837
} else {
833-
printer.print(
834-
"""
835-
let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)
836-
environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)])
837-
"""
838-
)
838+
let result: String
839+
if nativeFunctionSignature.result.javaType.requiresBoxing {
840+
printer.print("let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)")
841+
result = "boxedResult$"
842+
} else {
843+
result = inner
844+
}
845+
846+
printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: \(result))])")
839847
}
840848
}
841849

842850
func printTaskBody(printer: inout CodePrinter) {
843851
printer.printBraceBlock("defer") { printer in
844852
// Defer might on any thread, so we need to attach environment.
845853
printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()")
846-
printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)")
847-
for outParameter in outParameters {
848-
printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(outParameter.name))")
854+
for globalRef in globalRefs {
855+
printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(globalRef))")
849856
}
850857
}
851858
if isThrowing {

Sources/JExtractSwiftLib/JavaParameter.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ struct JavaParameter {
3535
}
3636
}
3737

38+
var isPrimitive: Bool {
39+
switch self {
40+
case .concrete(let javaType):
41+
javaType.isPrimitive
42+
case .generic(let name, let extends):
43+
false
44+
}
45+
}
46+
3847
var jniTypeName: String {
3948
switch self {
4049
case .concrete(let type): type.jniTypeName

Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,59 @@ struct JNIAsyncTests {
353353
]
354354
)
355355
}
356+
357+
@Test("Import: (String) async -> String (Java, CompletableFuture)")
358+
func completableFuture_asyncStringToString_java() throws {
359+
try assertOutput(
360+
input: """
361+
public func async(s: String) async -> String
362+
""",
363+
.jni, .java,
364+
detectChunkByInitialLines: 2,
365+
expectedChunks: [
366+
"""
367+
public static java.util.concurrent.CompletableFuture<java.lang.String> async(java.lang.String s) {
368+
java.util.concurrent.CompletableFuture<java.lang.String> $future = new java.util.concurrent.CompletableFuture<java.lang.String>();
369+
SwiftModule.$async(s, $future);
370+
return $future.thenApply((futureResult$) -> {
371+
return futureResult$;
372+
}
373+
);
374+
}
375+
""",
376+
"""
377+
private static native void $async(java.lang.String s, java.util.concurrent.CompletableFuture<java.lang.String> result_future);
378+
""",
379+
]
380+
)
381+
}
382+
383+
@Test("Import: (String) async -> String (Swift, CompletableFuture)")
384+
func completableFuture_asyncStringToString_swift() throws {
385+
try assertOutput(
386+
input: """
387+
public func async(s: String) async -> String
388+
""",
389+
.jni, .swift,
390+
detectChunkByInitialLines: 1,
391+
expectedChunks: [
392+
"""
393+
@_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2")
394+
func Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, s: jstring?, result_future: jobject?) {
395+
let s = environment.interface.NewGlobalRef(environment, s)
396+
let globalFuture = environment.interface.NewGlobalRef(environment, result_future)
397+
...
398+
defer {
399+
let deferEnvironment = try! JavaVirtualMachine.shared().environment()
400+
environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)
401+
environment.interface.DeleteGlobalRef(deferEnvironment, s)
402+
}
403+
...
404+
environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: swiftResult$.getJNIValue(in: environment))])
405+
...
406+
}
407+
"""
408+
]
409+
)
410+
}
356411
}

0 commit comments

Comments
 (0)