diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CancellationProbe.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CancellationProbe.swift new file mode 100644 index 000000000..9d2c89f0a --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CancellationProbe.swift @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public actor CancellationProbe { + private var cancelled = false + + public init() {} + + public func waitForCancellation() async throws -> Int64 { + try await suspend() + return 1 + } + + public func waitForOptionalCancellation() async throws -> Int64? { + try await suspend() + return 1 + } + + public func wasCancelled() -> Bool { cancelled } + + private func suspend() async throws { + do { + try await Task.sleep(for: .seconds(60)) + } catch { + cancelled = true + throw error + } + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncCancellationTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncCancellationTest.java new file mode 100644 index 000000000..566e74822 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncCancellationTest.java @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; +import java.util.concurrent.*; +import static org.junit.jupiter.api.Assertions.*; + +public class AsyncCancellationTest { + @Test + void cancellingAConvertedScalarFutureCancelsTheSwiftTask() throws Exception { + assertSwiftCancellation(false); + } + + @Test + void cancellingAnOutParameterResultCancelsTheSwiftTask() throws Exception { + assertSwiftCancellation(true); + } + + private void assertSwiftCancellation(boolean optional) throws Exception { + try (var arena = SwiftArena.ofConfined()) { + var probe = CancellationProbe.init(arena); + CompletableFuture future = optional + ? probe.waitForOptionalCancellation() : probe.waitForCancellation(); + assertTrue(future.cancel(true)); + assertThrows(CancellationException.class, future::join); + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5); + boolean observed = false; + while (System.nanoTime() < deadline && !observed) { + observed = probe.wasCancelled().get(1, TimeUnit.SECONDS); + if (!observed) Thread.sleep(10); + } + assertTrue(observed, "Swift must observe cancellation of the returned future"); + assertTrue(future.cancel(false)); + } + } + + @Test + void completedTasksCanBeBoundAndReleasedImmediately() throws Exception { + for (int i = 0; i < 100; i++) { + var future = MySwiftLibrary.asyncSum(i, 1); + assertEquals(i + 1L, future.get(5, TimeUnit.SECONDS)); + assertFalse(future.cancel(false)); + } + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 9dc93d507..98c0940c1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -704,7 +704,7 @@ extension JNISwift2JavaGenerator { switch mode { case .completableFuture: - nativeFutureType = .completableFuture(nativeFunctionSignature.result.javaType) + nativeFutureType = .class(package: "org.swift.swiftkit.core", name: "SwiftCompletableFuture", typeParameters: [nativeFunctionSignature.result.javaType.boxedType]) translatedFutureType = .completableFuture(translatedFunctionSignature.result.javaType) completeMethodID = "_JNIMethodIDCache.CompletableFuture.complete" completeExceptionallyMethodID = "_JNIMethodIDCache.CompletableFuture.completeExceptionally" @@ -730,7 +730,7 @@ extension JNISwift2JavaGenerator { outParameters: result.outParameters + [futureOutParameter], conversion: .method( .constant("future$"), - function: "thenApply", + function: mode == .completableFuture ? "mapSwiftResult" : "thenApply", arguments: [ .lambda( args: ["futureResult$"], diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 6490586d7..4c87fa6a2 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -2064,6 +2064,10 @@ extension JNISwift2JavaGenerator { } } + if completeMethodID == "_JNIMethodIDCache.CompletableFuture.complete" { + printer.print("SwiftJavaRuntimeSupport._JNIAsyncTask.bind(task!, to: result_future, in: environment)") + } + return "" case .closure(let args, let body): diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/FeaturesJextract.md b/Sources/SwiftJavaDocumentation/Documentation.docc/FeaturesJextract.md index a359f7db2..01fe67514 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/FeaturesJextract.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/FeaturesJextract.md @@ -243,6 +243,19 @@ There are two modes of extracting them, configurable using the `asyncFuncMode` s } } +#### Cancelling JNI async calls + +In `completableFuture` mode, cancelling the future returned by a JNI async call +also cancels its Swift task, including when the generated bridge converts the +result. Swift cancellation is cooperative: the operation must check cancellation +or use a cancellation-aware suspension such as `Task.sleep`. Cancellation does +not interrupt an operating-system thread or wait for the Swift task to finish. +The `mayInterruptIfRunning` argument does not change this behavior. + +This forwarding applies to the future returned by the generated API. Additional +Java future stages created by callers follow the usual `CompletableFuture` +cancellation rules. The `legacyFuture` mode keeps its existing behavior. + #### Asynchronous functions and Kotlin async/await Because Kotlin offers extension `suspending fun` methods on Future types, it is possible to `.await()` diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIAsyncTask.swift b/Sources/SwiftJavaRuntimeSupport/_JNIAsyncTask.swift new file mode 100644 index 000000000..0c67fce0c --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/_JNIAsyncTask.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava + +/// Owns the Swift task until its Java future completes or is cancelled. +public final class _JNIAsyncTask: Sendable { + let task: Task + + init(_ task: Task) { self.task = task } + + private static let setTask = _JNIMethodIDCache.Method(name: "setSwiftTask", signature: "(J)V") + private static let methods = _JNIMethodIDCache( + className: "org/swift/swiftkit/core/SwiftCompletableFuture", + methods: [setTask] + ) + + public static func bind(_ task: Task, to future: jobject?, in environment: JNIEnvironment) { + let handle = Unmanaged.passRetained(_JNIAsyncTask(task)).toOpaque() + _ = environment.interface.CallVoidMethodA( + environment, + future, + methods.methods[setTask]!, + [jvalue(j: Int64(Int(bitPattern: handle)))] + ) + } +} + +#if compiler(>=6.3) +@used +#endif +@_cdecl("Java_org_swift_swiftkit_core_SwiftCompletableFuture_cancelSwiftTask") +public func cancelSwiftAsyncTask(environment: UnsafeMutablePointer!, type: jclass, handle: jlong) { + Unmanaged<_JNIAsyncTask>.fromOpaque(UnsafeRawPointer(bitPattern: Int(handle))!).takeUnretainedValue().task.cancel() +} + +#if compiler(>=6.3) +@used +#endif +@_cdecl("Java_org_swift_swiftkit_core_SwiftCompletableFuture_releaseSwiftTask") +public func releaseSwiftAsyncTask(environment: UnsafeMutablePointer!, type: jclass, handle: jlong) { + Unmanaged<_JNIAsyncTask>.fromOpaque(UnsafeRawPointer(bitPattern: Int(handle))!).release() +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftCompletableFuture.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftCompletableFuture.java new file mode 100644 index 000000000..798596cde --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftCompletableFuture.java @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +/** A JNI task future whose cancellation reaches the Swift task, including after result conversion. */ +public final class SwiftCompletableFuture extends CompletableFuture { + private final Object taskLock = new Object(); + private long taskHandle; + private final Runnable cancelSource; + + public SwiftCompletableFuture() { this(null); } + + private SwiftCompletableFuture(Runnable cancelSource) { this.cancelSource = cancelSource; } + + public void setSwiftTask(long handle) { + synchronized (taskLock) { + if (!isDone()) { taskHandle = handle; return; } + } + if (isCancelled()) cancelSwiftTask(handle); + releaseSwiftTask(handle); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + boolean cancelled = super.cancel(mayInterruptIfRunning); + if (cancelled) { + finishTask(true); + if (cancelSource != null) cancelSource.run(); + } + return cancelled; + } + + @Override + public boolean complete(T value) { + boolean completed = super.complete(value); + if (completed) finishTask(false); + return completed; + } + + @Override + public boolean completeExceptionally(Throwable error) { + boolean completed = super.completeExceptionally(error); + if (completed) finishTask(false); + return completed; + } + + /** Unlike thenApply, cancelling this result also cancels its source task. */ + public CompletableFuture mapSwiftResult(Function transform) { + SwiftCompletableFuture result = new SwiftCompletableFuture<>(() -> cancel(false)); + whenComplete((value, error) -> { + if (error != null) result.completeExceptionally(error); + else { + try { result.complete(transform.apply(value)); } + catch (Throwable failure) { result.completeExceptionally(failure); } + } + }); + return result; + } + + private void finishTask(boolean cancel) { + long handle; + synchronized (taskLock) { + handle = taskHandle; + taskHandle = 0; + } + if (handle != 0) { + if (cancel) cancelSwiftTask(handle); + releaseSwiftTask(handle); + } + } + + private static native void cancelSwiftTask(long handle); + private static native void releaseSwiftTask(long handle); +} diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/SwiftCompletableFutureTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/SwiftCompletableFutureTest.java new file mode 100644 index 000000000..8379f08bc --- /dev/null +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/SwiftCompletableFutureTest.java @@ -0,0 +1,68 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +import org.junit.jupiter.api.Test; +import java.util.concurrent.*; +import static org.junit.jupiter.api.Assertions.*; + +public class SwiftCompletableFutureTest { + @Test + void mappingPreservesResultsAndAlreadyCompletedSources() { + var source = new SwiftCompletableFuture(); + var result = source.mapSwiftResult(String::length); + source.complete("hello"); + assertEquals(5, result.join()); + assertEquals(5, source.mapSwiftResult(String::length).join()); + assertFalse(result.cancel(false)); + } + + @Test + void mappingPreservesFailures() { + var source = new SwiftCompletableFuture(); + var result = source.mapSwiftResult(String::length); + var error = new IllegalStateException("source failed"); + source.completeExceptionally(error); + assertSame(error, assertThrows(CompletionException.class, result::join).getCause()); + } + + @Test + void mappingPreservesConversionFailures() { + var source = new SwiftCompletableFuture(); + var error = new IllegalArgumentException("conversion failed"); + var result = source.mapSwiftResult(value -> { throw error; }); + source.complete("hello"); + assertSame(error, assertThrows(CompletionException.class, result::join).getCause()); + } + + @Test + void cancellingMappedResultCancelsTheSource() { + var source = new SwiftCompletableFuture(); + var result = source.mapSwiftResult(String::length); + assertTrue(result.cancel(false)); + assertTrue(result.isCancelled()); + assertTrue(source.isCancelled()); + assertTrue(result.cancel(true)); + } + + @Test + void cancellingTheSourceCancelsTheMappedResult() { + var source = new SwiftCompletableFuture(); + var result = source.mapSwiftResult(String::length); + source.cancel(false); + assertTrue(result.isCancelled()); + assertThrows(CancellationException.class, result::join); + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIActorTests.swift b/Tests/JExtractSwiftTests/JNI/JNIActorTests.swift index aca720352..4ea21e379 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIActorTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIActorTests.swift @@ -43,16 +43,16 @@ struct JNIActor1MethodsTests { expectedChunks: [ """ public java.util.concurrent.CompletableFuture hello() { - java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + org.swift.swiftkit.core.SwiftCompletableFuture future$ = new org.swift.swiftkit.core.SwiftCompletableFuture(); K.$hello(this.$memoryAddress(), future$); - return future$.thenApply((futureResult$) -> { + return future$.mapSwiftResult((futureResult$) -> { return futureResult$; } ); } """, """ - private static native void $hello(long selfPointer, java.util.concurrent.CompletableFuture result_future); + private static native void $hello(long selfPointer, org.swift.swiftkit.core.SwiftCompletableFuture result_future); """, ], notExpectedChunks: [ @@ -71,7 +71,7 @@ struct JNIActor1MethodsTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_K__00024hello__JLjava_util_concurrent_CompletableFuture_2") + @_cdecl("Java_com_example_swift_K__00024hello__JLorg_swift_swiftkit_core_SwiftCompletableFuture_2") ... task = Task.immediate { ... @@ -91,16 +91,16 @@ struct JNIActor1MethodsTests { expectedChunks: [ """ public java.util.concurrent.CompletableFuture hi() { - java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + org.swift.swiftkit.core.SwiftCompletableFuture future$ = new org.swift.swiftkit.core.SwiftCompletableFuture(); K.$hi(this.$memoryAddress(), future$); - return future$.thenApply((futureResult$) -> { + return future$.mapSwiftResult((futureResult$) -> { return futureResult$; } ); } """, """ - private static native void $hi(long selfPointer, java.util.concurrent.CompletableFuture result_future); + private static native void $hi(long selfPointer, org.swift.swiftkit.core.SwiftCompletableFuture result_future); """, ], notExpectedChunks: [ @@ -119,7 +119,7 @@ struct JNIActor1MethodsTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_K__00024hi__JLjava_util_concurrent_CompletableFuture_2") + @_cdecl("Java_com_example_swift_K__00024hi__JLorg_swift_swiftkit_core_SwiftCompletableFuture_2") ... task = Task.immediate { ... @@ -235,16 +235,16 @@ struct JNIActor1MethodsTests { expectedChunks: [ """ public java.util.concurrent.CompletableFuture concurrentlyAsync() { - java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + org.swift.swiftkit.core.SwiftCompletableFuture future$ = new org.swift.swiftkit.core.SwiftCompletableFuture(); K.$concurrentlyAsync(this.$memoryAddress(), future$); - return future$.thenApply((futureResult$) -> { + return future$.mapSwiftResult((futureResult$) -> { return futureResult$; } ); } """, """ - private static native void $concurrentlyAsync(long selfPointer, java.util.concurrent.CompletableFuture result_future); + private static native void $concurrentlyAsync(long selfPointer, org.swift.swiftkit.core.SwiftCompletableFuture result_future); """, ], notExpectedChunks: [ @@ -263,7 +263,7 @@ struct JNIActor1MethodsTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_K__00024concurrentlyAsync__JLjava_util_concurrent_CompletableFuture_2") + @_cdecl("Java_com_example_swift_K__00024concurrentlyAsync__JLorg_swift_swiftkit_core_SwiftCompletableFuture_2") ... task = Task.immediate { ... diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncCancellationTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncCancellationTests.swift new file mode 100644 index 000000000..12e482946 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncCancellationTests.swift @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import SwiftJavaConfigurationShared +import Testing + +@Suite +struct JNIAsyncCancellationTests { + @Test("CompletableFuture result conversion preserves Swift cancellation") + func cancellationBridge_java() throws { + try assertOutput( + input: "public func wait() async throws -> Int64?", + .jni, + .java, + expectedChunks: [ + "org.swift.swiftkit.core.SwiftCompletableFuture future$ = new org.swift.swiftkit.core.SwiftCompletableFuture();", + "return future$.mapSwiftResult((futureResult$) -> {", + ] + ) + } + + @Test("The native future is bound to its Swift task") + func cancellationBridge_swift() throws { + try assertOutput( + input: "public func wait() async throws -> Int64?", + .jni, + .swift, + expectedChunks: [ + "SwiftJavaRuntimeSupport._JNIAsyncTask.bind(task!, to: result_future, in: environment)" + ] + ) + } + + @Test("Legacy Future mode retains its existing bridge") + func legacyBridgeIsUnchanged() throws { + var config = Configuration() + config.asyncFuncMode = .legacyFuture + try assertOutput( + input: "public func wait() async -> Int64", + config: config, + .jni, + .java, + expectedChunks: ["return future$.thenApply((futureResult$) -> {"], + notExpectedChunks: ["mapSwiftResult", "core.SwiftCompletableFuture"] + ) + try assertOutput( + input: "public func wait() async -> Int64", + config: config, + .jni, + .swift, + expectedChunks: ["_JNIMethodIDCache.SimpleCompletableFuture.complete"], + notExpectedChunks: ["_JNIAsyncTask.bind"] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 866c542d4..dfdc1c08d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -35,16 +35,16 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture asyncVoid() { - java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + org.swift.swiftkit.core.SwiftCompletableFuture future$ = new org.swift.swiftkit.core.SwiftCompletableFuture(); SwiftModule.$asyncVoid(future$); - return future$.thenApply((futureResult$) -> { + return future$.mapSwiftResult((futureResult$) -> { return futureResult$; } ); } """, """ - private static native void $asyncVoid(java.util.concurrent.CompletableFuture result_future); + private static native void $asyncVoid(org.swift.swiftkit.core.SwiftCompletableFuture result_future); """, ] ) @@ -59,8 +59,8 @@ struct JNIAsyncTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2") - public func Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { + @_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__Lorg_swift_swiftkit_core_SwiftCompletableFuture_2") + public func Java_com_example_swift_SwiftModule__00024asyncVoid__Lorg_swift_swiftkit_core_SwiftCompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future) var task: Task? = nil #if swift(>=6.2) @@ -89,6 +89,7 @@ struct JNIAsyncTests { _ = environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } } + SwiftJavaRuntimeSupport._JNIAsyncTask.bind(task!, to: result_future, in: environment) } """ ] @@ -111,16 +112,16 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture async() { - java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + org.swift.swiftkit.core.SwiftCompletableFuture future$ = new org.swift.swiftkit.core.SwiftCompletableFuture(); SwiftModule.$async(future$); - return future$.thenApply((futureResult$) -> { + return future$.mapSwiftResult((futureResult$) -> { return futureResult$; } ); } """, """ - private static native void $async(java.util.concurrent.CompletableFuture result_future); + private static native void $async(org.swift.swiftkit.core.SwiftCompletableFuture result_future); """, ] ) @@ -135,8 +136,8 @@ struct JNIAsyncTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2") - public func Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { + @_cdecl("Java_com_example_swift_SwiftModule__00024async__Lorg_swift_swiftkit_core_SwiftCompletableFuture_2") + public func Java_com_example_swift_SwiftModule__00024async__Lorg_swift_swiftkit_core_SwiftCompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future) var task: Task? = nil #if swift(>=6.2) @@ -179,6 +180,7 @@ struct JNIAsyncTests { } } } + SwiftJavaRuntimeSupport._JNIAsyncTask.bind(task!, to: result_future, in: environment) } """ ] @@ -201,16 +203,16 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture async(long i) { - java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + org.swift.swiftkit.core.SwiftCompletableFuture future$ = new org.swift.swiftkit.core.SwiftCompletableFuture(); SwiftModule.$async(i, future$); - return future$.thenApply((futureResult$) -> { + return future$.mapSwiftResult((futureResult$) -> { return futureResult$; } ); } """, """ - private static native void $async(long i, java.util.concurrent.CompletableFuture result_future); + private static native void $async(long i, org.swift.swiftkit.core.SwiftCompletableFuture result_future); """, ] ) @@ -225,8 +227,8 @@ struct JNIAsyncTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") - public func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, i: jlong, result_future: jobject?) { + @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLorg_swift_swiftkit_core_SwiftCompletableFuture_2") + public func Java_com_example_swift_SwiftModule__00024async__JLorg_swift_swiftkit_core_SwiftCompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, i: jlong, result_future: jobject?) { nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future) var task: Task? = nil #if swift(>=6.2) @@ -257,6 +259,7 @@ struct JNIAsyncTests { _ = environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } + SwiftJavaRuntimeSupport._JNIAsyncTask.bind(task!, to: result_future, in: environment) return } """ @@ -284,16 +287,16 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture async(MyClass c, SwiftArena swiftArena) { - java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + org.swift.swiftkit.core.SwiftCompletableFuture future$ = new org.swift.swiftkit.core.SwiftCompletableFuture(); SwiftModule.$async(c.$memoryAddress(), future$); - return future$.thenApply((futureResult$) -> { + return future$.mapSwiftResult((futureResult$) -> { return MyClass.wrapMemoryAddressUnsafe(futureResult$, swiftArena); } ); } """, """ - private static native void $async(long c, java.util.concurrent.CompletableFuture result_future); + private static native void $async(long c, org.swift.swiftkit.core.SwiftCompletableFuture result_future); """, ] ) @@ -312,8 +315,8 @@ struct JNIAsyncTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") - public func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, c: jlong, result_future: jobject?) { + @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLorg_swift_swiftkit_core_SwiftCompletableFuture_2") + public func Java_com_example_swift_SwiftModule__00024async__JLorg_swift_swiftkit_core_SwiftCompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, c: jlong, result_future: jobject?) { assert(c != 0, "c memory address was null") let cBits$ = Int(Int64(fromJNI: c, in: environment)) let c$ = UnsafeMutablePointer(bitPattern: cBits$) @@ -356,6 +359,7 @@ struct JNIAsyncTests { _ = environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } + SwiftJavaRuntimeSupport._JNIAsyncTask.bind(task!, to: result_future, in: environment) return } """ @@ -375,16 +379,16 @@ struct JNIAsyncTests { expectedChunks: [ """ public static java.util.concurrent.CompletableFuture async(java.lang.String s) { - java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + org.swift.swiftkit.core.SwiftCompletableFuture future$ = new org.swift.swiftkit.core.SwiftCompletableFuture(); SwiftModule.$async(s, future$); - return future$.thenApply((futureResult$) -> { + return future$.mapSwiftResult((futureResult$) -> { return futureResult$; } ); } """, """ - private static native void $async(java.lang.String s, java.util.concurrent.CompletableFuture result_future); + private static native void $async(java.lang.String s, org.swift.swiftkit.core.SwiftCompletableFuture result_future); """, ] ) @@ -401,8 +405,8 @@ struct JNIAsyncTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2") - public func Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, s: jstring?, result_future: jobject?) { + @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Lorg_swift_swiftkit_core_SwiftCompletableFuture_2") + public func Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Lorg_swift_swiftkit_core_SwiftCompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, s: jstring?, result_future: jobject?) { nonisolated(unsafe) let s = environment.interface.NewGlobalRef(environment, s) nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future) ... diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassAsyncSelfCaptureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassAsyncSelfCaptureTests.swift index af9575f52..00ce76246 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassAsyncSelfCaptureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassAsyncSelfCaptureTests.swift @@ -32,7 +32,7 @@ struct JNIAsyncSelfCaptureTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_MyClass__00024compute__JLjava_util_concurrent_CompletableFuture_2") + @_cdecl("Java_com_example_swift_MyClass__00024compute__JLorg_swift_swiftkit_core_SwiftCompletableFuture_2") ... nonisolated(unsafe) let selfPointerUnsafeSendable$ = selfPointer$ ... @@ -59,7 +59,7 @@ struct JNIAsyncSelfCaptureTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_WorkerBox__00024work__JJLjava_util_concurrent_CompletableFuture_2") + @_cdecl("Java_com_example_swift_WorkerBox__00024work__JJLorg_swift_swiftkit_core_SwiftCompletableFuture_2") ... nonisolated(unsafe) let selfPointerExistentialUnsafeSendable$ = selfPointerExistential$ ... diff --git a/Tests/JExtractSwiftTests/JNI/JNIDistributedActorTests.swift b/Tests/JExtractSwiftTests/JNI/JNIDistributedActorTests.swift index 6b16459de..7f184f8ca 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIDistributedActorTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIDistributedActorTests.swift @@ -34,16 +34,16 @@ struct JNIDistributedActorTests { expectedChunks: [ """ public java.util.concurrent.CompletableFuture hi() { - java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + org.swift.swiftkit.core.SwiftCompletableFuture future$ = new org.swift.swiftkit.core.SwiftCompletableFuture(); D.$hi(this.$memoryAddress(), future$); - return future$.thenApply((futureResult$) -> { + return future$.mapSwiftResult((futureResult$) -> { return futureResult$; } ); } """, """ - private static native void $hi(long selfPointer, java.util.concurrent.CompletableFuture result_future); + private static native void $hi(long selfPointer, org.swift.swiftkit.core.SwiftCompletableFuture result_future); """, ], notExpectedChunks: [ @@ -62,7 +62,7 @@ struct JNIDistributedActorTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_D__00024hi__JLjava_util_concurrent_CompletableFuture_2") + @_cdecl("Java_com_example_swift_D__00024hi__JLorg_swift_swiftkit_core_SwiftCompletableFuture_2") ... task = Task.immediate { ... diff --git a/Tests/JExtractSwiftTests/JNI/JNIIsolatedParameterTests.swift b/Tests/JExtractSwiftTests/JNI/JNIIsolatedParameterTests.swift index 714d23462..476d52bc4 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIIsolatedParameterTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIIsolatedParameterTests.swift @@ -41,7 +41,7 @@ struct JNIIsolatedParameterTests { public java.util.concurrent.CompletableFuture run(MyActor actor) { """, """ - private static native void $run(long actor, long selfPointer, java.util.concurrent.CompletableFuture result_future); + private static native void $run(long actor, long selfPointer, org.swift.swiftkit.core.SwiftCompletableFuture result_future); """, ] )