From c545dda3168e6b8e87f4f03f07df0d058ae68d92 Mon Sep 17 00:00:00 2001 From: Leonid L Date: Sun, 23 Aug 2026 17:18:25 +0200 Subject: [PATCH] JNI: fix non-compiling async thunks for protocol boxes --- ...wift2JavaGenerator+NativeTranslation.swift | 28 +++-- .../JNI/JNIClassAsyncSelfCaptureTests.swift | 109 ++++++++++++++++++ 2 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 Tests/JExtractSwiftTests/JNI/JNIClassAsyncSelfCaptureTests.swift diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 8e644a7c7..079f0b5e4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -1958,15 +1958,20 @@ extension JNISwift2JavaGenerator { """ ) + var selfCaptures: [(sendable: String, original: String)] = [] if let selfParameter = nativeFunctionSignature.selfParameter { - for parameter in selfParameter.parameters { - printer.print("nonisolated(unsafe) let \(parameter.name)Sendable$ = \(parameter.name)$") + if case .extractSwiftProtocolValue = selfParameter.conversion { + for parameter in selfParameter.parameters { + selfCaptures.append(("\(parameter.name)ExistentialSendable$", "\(parameter.name)Existential$")) + } + } else { + for parameter in selfParameter.parameters { + selfCaptures.append(("\(parameter.name)Sendable$", "\(parameter.name)$")) + } } } - if let selfTypeParameter = nativeFunctionSignature.selfTypeParameter { - for parameter in selfTypeParameter.parameters { - printer.print("nonisolated(unsafe) let \(parameter.name)Sendable$ = \(parameter.name)$") - } + for capture in selfCaptures { + printer.print("nonisolated(unsafe) let \(capture.sendable) = \(capture.original)") } func printDo(printer: inout SwiftPrinter) { @@ -2011,15 +2016,8 @@ extension JNISwift2JavaGenerator { } func printTaskBody(printer: inout SwiftPrinter) { - if let selfParameter = nativeFunctionSignature.selfParameter { - for parameter in selfParameter.parameters { - printer.print("let \(parameter.name)$ = \(parameter.name)Sendable$") - } - } - if let selfTypeParameter = nativeFunctionSignature.selfTypeParameter { - for parameter in selfTypeParameter.parameters { - printer.print("let \(parameter.name)$ = \(parameter.name)Sendable$") - } + for capture in selfCaptures { + printer.print("let \(capture.original) = \(capture.sendable)") } printer.printBraceBlock("defer") { printer in // Defer might on any thread, so we need to attach environment. diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassAsyncSelfCaptureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassAsyncSelfCaptureTests.swift new file mode 100644 index 000000000..167cf028c --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIClassAsyncSelfCaptureTests.swift @@ -0,0 +1,109 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 JNIAsyncSelfCaptureTests { + + @Test("Import: class async method captures converted self pointer (Swift)") + func classAsyncMethod_swift() throws { + try assertOutput( + input: """ + public class MyClass { + public func compute() async -> Int64 { 42 } + } + """, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024compute__JLjava_util_concurrent_CompletableFuture_2") + ... + nonisolated(unsafe) let selfPointerSendable$ = selfPointer$ + ... + task = Task.immediate { + ... + let selfPointer$ = selfPointerSendable$ + ... + let swiftResult$ = await selfPointer$.pointee.compute() + """ + ] + ) + } + + @Test("Import: protocol box async method captures loaded existential (Swift)") + func protocolBoxAsyncMethod_swift() throws { + try assertOutput( + input: """ + public protocol Worker { + func work() async -> Int64 + } + """, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_WorkerBox__00024work__JJLjava_util_concurrent_CompletableFuture_2") + ... + nonisolated(unsafe) let selfPointerExistentialSendable$ = selfPointerExistential$ + ... + task = Task.immediate { + ... + let selfPointerExistential$ = selfPointerExistentialSendable$ + ... + let swiftResult$ = await selfPointerExistential$.work() + """ + ], + notExpectedChunks: [ + "nonisolated(unsafe) let selfPointerSendable$", + "nonisolated(unsafe) let selfTypePointerSendable$", + ] + ) + } + + @Test("Import: generic class async method captures only self pointer (Swift)") + func genericClassAsyncMethod_swift() throws { + try assertOutput( + input: """ + public class Box { + public func compute() async -> Int64 { 42 } + } + """, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + extension Box: _SwiftModule_Box_opener { + ... + nonisolated(unsafe) let selfPointerSendable$ = selfPointer$ + ... + task = Task.immediate { + ... + let selfPointer$ = selfPointerSendable$ + ... + let swiftResult$ = await selfPointer$.pointee.compute() + """ + ], + notExpectedChunks: [ + "selfTypePointerSendable$", + ] + ) + } +}