diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 5335b81b1..32f52138a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -230,7 +230,12 @@ extension JNISwift2JavaGenerator { parameters: [ JavaParameter(name: parameterName, type: .long) ], - conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: type)), + conversion: .pointee( + .asyncTaskCapture( + .extractSwiftValue(.placeholder, swiftType: type), + name: NativeSwiftConversionStep.extractedSwiftValueName(for: parameterName) + ) + ), indirectConversion: nil, conversionCheck: nil ) @@ -327,7 +332,10 @@ extension JNISwift2JavaGenerator { parameters: [ JavaParameter(name: parameterName, type: .long) ], - conversion: .extractMetatypeValue(.placeholder), + conversion: .asyncTaskCapture( + .extractMetatypeValue(.placeholder), + name: NativeSwiftConversionStep.extractedSwiftValueName(for: parameterName) + ), indirectConversion: nil, conversionCheck: nil ) @@ -441,15 +449,18 @@ extension JNISwift2JavaGenerator { parameters: [ JavaParameter(name: parameterName, type: .javaLangObject) ], - conversion: .interfaceToSwiftObject( - .placeholder, - swiftWrapperClassName: JNISwift2JavaGenerator.protocolParameterWrapperClassName( - methodName: methodName, - parameterName: parameterName, - parentName: parentName + conversion: .asyncTaskCapture( + .interfaceToSwiftObject( + .placeholder, + swiftWrapperClassName: JNISwift2JavaGenerator.protocolParameterWrapperClassName( + methodName: methodName, + parameterName: parameterName, + parentName: parentName + ), + protocolTypes: protocolTypes, + allowsJavaImplementations: allowsJavaImplementations ), - protocolTypes: protocolTypes, - allowsJavaImplementations: allowsJavaImplementations + name: NativeSwiftConversionStep.swiftObjectName(for: parameterName) ), indirectConversion: nil, conversionCheck: nil @@ -521,10 +532,13 @@ extension JNISwift2JavaGenerator { parameters: [JavaParameter(name: parameterName, type: .long)], conversion: .pointee( .optionalChain( - .extractSwiftValue( - .placeholder, - swiftType: swiftType, - allowNil: true + .asyncTaskCapture( + .extractSwiftValue( + .placeholder, + swiftType: swiftType, + allowNil: true + ), + name: NativeSwiftConversionStep.extractedSwiftValueName(for: parameterName) ) ) ), @@ -1219,6 +1233,18 @@ extension JNISwift2JavaGenerator { /// Destructures a Swift tuple result and writes each element to an out-parameter. indirect case tupleDestructure(elements: [(index: Int, label: String?, conversion: NativeSwiftConversionStep, outParamName: String, javaType: JavaType)]) + /// Marks a temporary value produced by the inner conversion as captured by + /// the async task body. + indirect case asyncTaskCapture(NativeSwiftConversionStep, name: String) + + static func extractedSwiftValueName(for name: String) -> String { + "\(name)$" + } + + static func swiftObjectName(for name: String) -> String { + "\(name)swiftObject$" + } + /// Promotes the outermost `.getJNIValue` to `.getJNILocalRefValue`. /// Used for `@_cdecl` return positions to ensure the local ref survives /// ARC destruction of temporary `JavaObject`s. @@ -1231,6 +1257,22 @@ extension JNISwift2JavaGenerator { } } + var asyncTaskCaptureNames: [String] { + switch self { + case .asyncTaskCapture(let inner, let name): + return [name] + inner.asyncTaskCaptureNames + + case .pointee(let inner), .optionalChain(let inner): + return inner.asyncTaskCaptureNames + + case .tupleConstruct(let elements): + return elements.flatMap { $0.conversion.asyncTaskCaptureNames } + + default: + return [] + } + } + /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. @@ -1268,7 +1310,7 @@ extension JNISwift2JavaGenerator { let allowsJavaImplementations ): let inner = inner.render(&printer, placeholder) - let variableName = "\(inner)swiftObject$" + let variableName = Self.swiftObjectName(for: inner) let existentialType = SwiftKitPrinting.renderExistentialType(protocolTypes) printer.print("let \(variableName): \(existentialType)") @@ -1344,7 +1386,7 @@ extension JNISwift2JavaGenerator { case .extractSwiftValue(let inner, let swiftType, let allowNil, let convertLongFromJNI): let inner = inner.render(&printer, placeholder) - let pointerName = "\(inner)$" + let pointerName = Self.extractedSwiftValueName(for: inner) if !allowNil { printer.print(#"assert(\#(inner) != 0, "\#(inner) memory address was null")"#) } @@ -1367,7 +1409,7 @@ extension JNISwift2JavaGenerator { case .extractMetatypeValue(let inner): let inner = inner.render(&printer, placeholder) - let pointerName = "\(inner)$" + let pointerName = Self.extractedSwiftValueName(for: inner) printer.print( """ let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment)) @@ -1629,36 +1671,52 @@ extension JNISwift2JavaGenerator { let completeExceptionallyMethodID ): var globalRefs: [String] = ["globalFuture"] + var asyncTaskCaptureNames: [String] = [] + + func appendAsyncTaskCaptureNames(_ names: [String]) { + for name in names where !asyncTaskCaptureNames.contains(name) { + asyncTaskCaptureNames.append(name) + } + } + + appendAsyncTaskCaptureNames(nativeFunctionSignature.selfParameter?.conversion.asyncTaskCaptureNames ?? []) + appendAsyncTaskCaptureNames(nativeFunctionSignature.selfTypeParameter?.conversion.asyncTaskCaptureNames ?? []) + for parameter in nativeFunctionSignature.parameters { + appendAsyncTaskCaptureNames(parameter.conversion.asyncTaskCaptureNames) + } + + printer.print( + """ + struct _SwiftJavaUncheckedSendable: @unchecked Sendable { + let value: T + } + """ + ) // Global ref all indirect returns for outParameter in nativeFunctionSignature.result.outParameters { printer.print( - "nonisolated(unsafe) let \(outParameter.name) = environment.interface.NewGlobalRef(environment, \(outParameter.name))" + "let \(outParameter.name)Sendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, \(outParameter.name)))" ) globalRefs.append(outParameter.name) } // We also need to global ref any objects passed in for parameter in nativeFunctionSignature.parameters.flatMap(\.parameters) where !parameter.type.isPrimitive { - printer.print("nonisolated(unsafe) let \(parameter.name) = environment.interface.NewGlobalRef(environment, \(parameter.name))") + printer.print( + "let \(parameter.name)Sendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, \(parameter.name)))" + ) globalRefs.append(parameter.name) } printer.print( """ - nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future)) """ ) - if let selfParameter = nativeFunctionSignature.selfParameter { - for parameter in selfParameter.parameters { - printer.print("nonisolated(unsafe) let \(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 name in asyncTaskCaptureNames { + printer.print("let \(name)Sendable$ = _SwiftJavaUncheckedSendable(value: \(name))") } func printDo(printer: inout CodePrinter) { @@ -1703,15 +1761,11 @@ extension JNISwift2JavaGenerator { } func printTaskBody(printer: inout CodePrinter) { - if let selfParameter = nativeFunctionSignature.selfParameter { - for parameter in selfParameter.parameters { - printer.print("let \(parameter.name)$ = \(parameter.name)Sendable$") - } + for globalRef in globalRefs { + printer.print("let \(globalRef) = \(globalRef)Sendable$.value") } - if let selfTypeParameter = nativeFunctionSignature.selfTypeParameter { - for parameter in selfTypeParameter.parameters { - printer.print("let \(parameter.name)$ = \(parameter.name)Sendable$") - } + for name in asyncTaskCaptureNames { + printer.print("let \(name) = \(name)Sendable$.value") } printer.printBraceBlock("defer") { printer in // Defer might on any thread, so we need to attach environment. @@ -1839,6 +1893,9 @@ extension JNISwift2JavaGenerator { } } return "" + + case .asyncTaskCapture(let inner, _): + return inner.render(&printer, placeholder) } } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 7c968e419..492f10fcf 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -61,12 +61,16 @@ struct JNIAsyncTests { """ @_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?) { - nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + struct _SwiftJavaUncheckedSendable: @unchecked Sendable { + let value: T + } + let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future)) var task: Task? = nil #if swift(>=6.2) if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { task = Task.immediate { var environment = try! JavaVirtualMachine.shared().environment() + let globalFuture = globalFutureSendable$.value defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) @@ -80,6 +84,7 @@ struct JNIAsyncTests { if task == nil { task = Task { var environment = try! JavaVirtualMachine.shared().environment() + let globalFuture = globalFutureSendable$.value defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) @@ -137,12 +142,16 @@ struct JNIAsyncTests { """ @_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?) { - nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + struct _SwiftJavaUncheckedSendable: @unchecked Sendable { + let value: T + } + let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future)) var task: Task? = nil #if swift(>=6.2) if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { task = Task.immediate { var environment = try! JavaVirtualMachine.shared().environment() + let globalFuture = globalFutureSendable$.value defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) @@ -163,6 +172,7 @@ struct JNIAsyncTests { if task == nil { task = Task { var environment = try! JavaVirtualMachine.shared().environment() + let globalFuture = globalFutureSendable$.value defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) @@ -227,12 +237,16 @@ struct JNIAsyncTests { """ @_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?) { - nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + struct _SwiftJavaUncheckedSendable: @unchecked Sendable { + let value: T + } + let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future)) var task: Task? = nil #if swift(>=6.2) if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { task = Task.immediate { var environment = try! JavaVirtualMachine.shared().environment() + let globalFuture = globalFutureSendable$.value defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) @@ -247,6 +261,7 @@ struct JNIAsyncTests { if task == nil { task = Task { var environment = try! JavaVirtualMachine.shared().environment() + let globalFuture = globalFutureSendable$.value defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) @@ -320,12 +335,18 @@ struct JNIAsyncTests { guard let c$ else { fatalError("c memory address was null in call to \\(#function)!") } - nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + struct _SwiftJavaUncheckedSendable: @unchecked Sendable { + let value: T + } + let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future)) + let c$Sendable$ = _SwiftJavaUncheckedSendable(value: c$) var task: Task? = nil #if swift(>=6.2) if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { task = Task.immediate { var environment = try! JavaVirtualMachine.shared().environment() + let globalFuture = globalFutureSendable$.value + let c$ = c$Sendable$.value defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) @@ -343,6 +364,8 @@ struct JNIAsyncTests { if task == nil { task = Task { var environment = try! JavaVirtualMachine.shared().environment() + let globalFuture = globalFutureSendable$.value + let c$ = c$Sendable$.value defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) @@ -403,9 +426,14 @@ struct JNIAsyncTests { """ @_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?) { - nonisolated(unsafe) let s = environment.interface.NewGlobalRef(environment, s) - nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + struct _SwiftJavaUncheckedSendable: @unchecked Sendable { + let value: T + } + let sSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, s)) + let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future)) ... + let globalFuture = globalFutureSendable$.value + let s = sSendable$.value defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)