From 0869e5509f21a392fafbd0fe9465a2996656ecfe Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 26 Oct 2025 11:41:55 +0100 Subject: [PATCH 01/26] introduce async mode --- .../SwiftTypes/SwiftEffectSpecifier.swift | 1 + .../SwiftTypes/SwiftFunctionSignature.swift | 4 +- .../Configuration.swift | 6 +++ .../JExtract/JExtractAsyncMode.swift | 34 ++++++++++++++ .../JExtract/JExtractGenerationMode.swift | 22 +++++++++ .../JExtractMemoryManagementMode.swift | 34 ++++++++++++++ .../JExtractMinimumAccessLevelMode.swift | 26 +++++++++++ .../JExtractUnsignedIntegerMode.swift} | 45 +------------------ 8 files changed, 126 insertions(+), 46 deletions(-) create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncMode.swift create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift rename Sources/SwiftJavaConfigurationShared/{GenerationMode.swift => JExtract/JExtractUnsignedIntegerMode.swift} (68%) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift index 9ba4d7ad..6ffdbff6 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift @@ -14,4 +14,5 @@ enum SwiftEffectSpecifier: Equatable { case `throws` + case async } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 7e0f4885..58e29d49 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -246,7 +246,7 @@ extension SwiftFunctionSignature { effectSpecifiers.append(.throws) } if let asyncSpecifier = signature.effectSpecifiers?.asyncSpecifier { - throw SwiftFunctionTranslationError.async(asyncSpecifier) + effectSpecifiers.append(.async) } let parameters = try signature.parameterClause.parameters.map { param in @@ -331,7 +331,7 @@ extension SwiftFunctionSignature { effectSpecifiers.append(.throws) } if let asyncSpecifier = decl.effectSpecifiers?.asyncSpecifier { - throw SwiftFunctionTranslationError.async(asyncSpecifier) + effectSpecifiers.append(.async) } return effectSpecifiers } diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 2d9b4311..58bfb3aa 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -46,6 +46,7 @@ public struct Configuration: Codable { public var effectiveUnsignedNumbersMode: JExtractUnsignedIntegerMode { unsignedNumbersMode ?? .default } + public var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? public var effectiveMinimumInputAccessLevelMode: JExtractMinimumAccessLevelMode { minimumInputAccessLevelMode ?? .default @@ -56,6 +57,11 @@ public struct Configuration: Codable { memoryManagementMode ?? .default } + public var asyncMode: JExtractAsyncMode? + public var effectiveAsyncMode: JExtractAsyncMode { + asyncMode ?? .default + } + // ==== java 2 swift --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncMode.swift new file mode 100644 index 00000000..0db90dc3 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncMode.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Configures how Swift `async` functions should be extracted by jextract. +public enum JExtractAsyncMode: String, Codable { + /// Extract Swift `async` APIs as Java functions that return `CompletableFuture`s. + case completableFuture + + /// Extract Swift `async` APIs as Java functions that return `Future`s. + /// + /// This mode is useful for platforms that do not have `CompletableFuture` support, such as + /// Android 23 and below. + /// + /// - Note: Prefer using the `completableFuture` mode instead, if possible. + case future +} + + +extension JExtractAsyncMode { + public static var `default`: Self { + .completableFuture + } +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift new file mode 100644 index 00000000..8df00cf0 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. +public enum JExtractGenerationMode: String, Codable { + /// Foreign Value and Memory API + case ffm + + /// Java Native Interface + case jni +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift new file mode 100644 index 00000000..be77d27d --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Configures how memory should be managed by the user +public enum JExtractMemoryManagementMode: String, Codable { + /// Force users to provide an explicit `SwiftArena` to all calls that require them. + case explicit + + /// Provide both explicit `SwiftArena` support + /// and a default global automatic `SwiftArena` that will deallocate memory when the GC decides to. + case allowGlobalAutomatic + + public static var `default`: Self { + .explicit + } + + public var requiresGlobalArena: Bool { + switch self { + case .explicit: false + case .allowGlobalAutomatic: true + } + } +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift new file mode 100644 index 00000000..22fead57 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// The minimum access level which +public enum JExtractMinimumAccessLevelMode: String, Codable { + case `public` + case `package` + case `internal` +} + +extension JExtractMinimumAccessLevelMode { + public static var `default`: Self { + .public + } +} diff --git a/Sources/SwiftJavaConfigurationShared/GenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift similarity index 68% rename from Sources/SwiftJavaConfigurationShared/GenerationMode.swift rename to Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift index 22fdd5f5..d7a94070 100644 --- a/Sources/SwiftJavaConfigurationShared/GenerationMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift @@ -12,15 +12,6 @@ // //===----------------------------------------------------------------------===// -/// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. -public enum JExtractGenerationMode: String, Codable { - /// Foreign Value and Memory API - case ffm - - /// Java Native Interface - case jni -} - /// Configures how Swift unsigned integers should be extracted by jextract. public enum JExtractUnsignedIntegerMode: String, Codable { /// Treat unsigned Swift integers as their signed equivalents in Java signatures, @@ -51,6 +42,7 @@ public enum JExtractUnsignedIntegerMode: String, Codable { // case widenOrAnnotate } + extension JExtractUnsignedIntegerMode { public var needsConversion: Bool { switch self { @@ -63,38 +55,3 @@ extension JExtractUnsignedIntegerMode { .annotate } } - -/// The minimum access level which -public enum JExtractMinimumAccessLevelMode: String, Codable { - case `public` - case `package` - case `internal` -} - -extension JExtractMinimumAccessLevelMode { - public static var `default`: Self { - .public - } -} - - -/// Configures how memory should be managed by the user -public enum JExtractMemoryManagementMode: String, Codable { - /// Force users to provide an explicit `SwiftArena` to all calls that require them. - case explicit - - /// Provide both explicit `SwiftArena` support - /// and a default global automatic `SwiftArena` that will deallocate memory when the GC decides to. - case allowGlobalAutomatic - - public static var `default`: Self { - .explicit - } - - public var requiresGlobalArena: Bool { - switch self { - case .explicit: false - case .allowGlobalAutomatic: true - } - } -} From 7a75d45c1951de15249c62af2cd233d85b4979e9 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 26 Oct 2025 15:54:56 +0100 Subject: [PATCH 02/26] import async functions using semaphores and completablefuture --- .../Sources/MySwiftLibrary/Async.swift | 26 ++++ .../java/com/example/swift/AsyncTest.java | 52 +++++++ .../com/example/swift/MySwiftClassTest.java | 8 + .../Convenience/JavaType+Extensions.swift | 25 ++++ Sources/JExtractSwiftLib/ImportedDecls.swift | 4 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 3 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 85 ++++++++++- ...wift2JavaGenerator+NativeTranslation.swift | 57 +++++++- Sources/SwiftJava/Helpers/_Semaphore.swift | 76 ++++++++++ .../JNI/JNIAsyncTests.swift | 138 ++++++++++++++++++ 10 files changed, 468 insertions(+), 6 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java create mode 100644 Sources/SwiftJava/Helpers/_Semaphore.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift new file mode 100644 index 00000000..3d7c5ac6 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +public func asyncSum(i1: Int64, i2: Int64) async -> Int64 { + try? await Task.sleep(for: .milliseconds(500)) + return i1 + i2 +} + +public func asyncCopy(myClass: MySwiftClass) async -> MySwiftClass { + let new = MySwiftClass(x: myClass.x, y: myClass.y) + try? await Task.sleep(for: .milliseconds(500)) + return new +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java new file mode 100644 index 00000000..36c2bdf7 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// 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 com.example.swift.MySwiftClass; +import com.example.swift.MySwiftLibrary; +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import java.time.Duration; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.*; + +public class AsyncTest { + @Test + void asyncSum() { + CompletableFuture future = MySwiftLibrary.asyncSum(10, 12); + + Long result = future.join(); + assertEquals(22, result); + } + + @Test + void asyncCopy() throws Exception { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass obj = MySwiftClass.init(10, 5, arena); + CompletableFuture future = MySwiftLibrary.asyncCopy(obj, arena); + + MySwiftClass result = future.join(); + assertEquals(10, result.getX()); + assertEquals(5, result.getY()); + } + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 2e9a7e62..fba8f13f 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -152,4 +152,12 @@ void addXWithJavaLong() { assertEquals(70, c1.addXWithJavaLong(javaLong)); } } + + @Test + void getAsyncVariable() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + assertEquals(42, c1.getGetAsync().join()); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index cb849e79..373fec17 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -111,4 +111,29 @@ extension JavaType { false } } + + var wrapperClassIfNeeded: JavaType { + switch self { + case .boolean: + return .class(package: nil, name: "Boolean") + case .byte(let parameterAnnotations): + return .class(package: nil, name: "Byte") + case .char(let parameterAnnotations): + return .class(package: nil, name: "Character") + case .short(let parameterAnnotations): + return .class(package: nil, name: "Short") + case .int(let parameterAnnotations): + return .class(package: nil, name: "Integer") + case .long(let parameterAnnotations): + return .class(package: nil, name: "Long") + case .float: + return .class(package: nil, name: "Float") + case .double: + return .class(package: nil, name: "Double") + case .void: + return .class(package: nil, name: "Void") + default: + return self + } + } } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 1f0d3efc..5ccdf3ad 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -192,6 +192,10 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { self.functionSignature.effectSpecifiers.contains(.throws) } + var isAsync: Bool { + self.functionSignature.effectSpecifiers.contains(.async) + } + init( module: String, swiftDecl: any DeclSyntaxProtocol, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 258b537e..6becc806 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -429,7 +429,7 @@ extension JNISwift2JavaGenerator { let translatedSignature = translatedDecl.translatedFunctionSignature let resultType = translatedSignature.resultType.javaType var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() } - let throwsClause = translatedDecl.isThrowing ? " throws Exception" : "" + let throwsClause = translatedDecl.isThrowing && !translatedDecl.isAsync ? " throws Exception" : "" let generics = translatedDecl.translatedFunctionSignature.parameters.reduce(into: [(String, [JavaType])]()) { generics, parameter in guard case .generic(let name, let extends) = parameter.parameter.type else { @@ -483,7 +483,6 @@ extension JNISwift2JavaGenerator { printNativeFunction(&printer, translatedDecl) } - } private func printNativeFunction(_ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl) { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index a0178d3c..3e1a3c21 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -118,6 +118,7 @@ extension JNISwift2JavaGenerator { name: getAsCaseName, isStatic: false, isThrowing: false, + isAsync: false, nativeFunctionName: "$\(getAsCaseName)", parentName: enumName, functionTypes: [], @@ -216,6 +217,7 @@ extension JNISwift2JavaGenerator { name: javaName, isStatic: decl.isStatic || !decl.hasParent || decl.isInitializer, isThrowing: decl.isThrowing, + isAsync: decl.isAsync, nativeFunctionName: "$\(javaName)", parentName: parentName, functionTypes: funcTypes, @@ -279,7 +281,11 @@ extension JNISwift2JavaGenerator { genericRequirements: functionSignature.genericRequirements ) - let resultType = try translate(swiftResult: functionSignature.result) + var resultType = try translate(swiftResult: functionSignature.result) + + if functionSignature.effectSpecifiers.contains(.async) { + resultType = asyncResultConversion(result: resultType, mode: config.effectiveAsyncMode) + } return TranslatedFunctionSignature( selfParameter: selfParameter, @@ -470,6 +476,35 @@ extension JNISwift2JavaGenerator { } } + func asyncResultConversion( + result: TranslatedResult, + mode: JExtractAsyncMode + ) -> TranslatedResult { + switch mode { + case .completableFuture: + let supplyAsyncBodyConversion: JavaNativeConversionStep = if result.javaType.isVoid { + .aggregate([ + .print(result.conversion), + .null + ]) + } else { + result.conversion + } + + return TranslatedResult( + javaType: .class(package: "java.util.concurrent", name: "CompletableFuture<\(result.javaType.wrapperClassIfNeeded)>"), + annotations: result.annotations, + outParameters: result.outParameters, + conversion: .method(.constant("java.util.concurrent.CompletableFuture"), function: "supplyAsync", arguments: [ + .lambda(body: supplyAsyncBodyConversion) + ]) + ) + + case .future: + fatalError("TODO") + } + } + func translateProtocolParameter( protocolType: SwiftType, parameterName: String, @@ -753,6 +788,8 @@ extension JNISwift2JavaGenerator { let isThrowing: Bool + let isAsync: Bool + /// The name of the native function let nativeFunctionName: String @@ -912,6 +949,17 @@ extension JNISwift2JavaGenerator { /// Access a member of the value indirect case replacingPlaceholder(JavaNativeConversionStep, placeholder: String) + /// `return value` + indirect case `return`(JavaNativeConversionStep) + + /// `() -> { return body; }` + indirect case lambda(body: JavaNativeConversionStep) + + case null + + /// Prints the conversion step, ignoring the output. + indirect case print(JavaNativeConversionStep) + /// 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. @@ -1024,13 +1072,37 @@ extension JNISwift2JavaGenerator { case .replacingPlaceholder(let inner, let placeholder): return inner.render(&printer, placeholder) + + case .return(let inner): + let inner = inner.render(&printer, placeholder) + return "return \(inner);" + + case .lambda(let body): + var printer = CodePrinter() + printer.printBraceBlock("() ->") { printer in + let body = body.render(&printer, placeholder) + if !body.isEmpty { + printer.print("return \(body);") + } else { + printer.print("return;") + } + } + return printer.finalize() + + case .null: + return "null" + + case .print(let inner): + let inner = inner.render(&printer, placeholder) + printer.print("\(inner);") + return "" } } /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder, .constant, .isOptionalPresent, .combinedName: + case .placeholder, .constant, .isOptionalPresent, .combinedName, .null: return false case .constructSwiftValue, .wrapMemoryAddressUnsafe: @@ -1074,6 +1146,15 @@ extension JNISwift2JavaGenerator { case .replacingPlaceholder(let inner, _): return inner.requiresSwiftArena + + case .return(let inner): + return inner.requiresSwiftArena + + case .lambda(let body): + return body.requiresSwiftArena + + case .print(let inner): + return inner.requiresSwiftArena } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index bdbfe2f1..74d400f4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -59,10 +59,20 @@ extension JNISwift2JavaGenerator { nil } - return try NativeFunctionSignature( + var result = try translate(swiftResult: functionSignature.result) + + if functionSignature.effectSpecifiers.contains(.async) { + result = asyncResultConversion( + result: result, + functionSignature: functionSignature, + mode: config.effectiveAsyncMode + ) + } + + return NativeFunctionSignature( selfParameter: nativeSelf, parameters: parameters, - result: translate(swiftResult: functionSignature.result) + result: result ) } @@ -504,6 +514,27 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } + + func asyncResultConversion( + result: NativeResult, + functionSignature: SwiftFunctionSignature, + mode: JExtractAsyncMode + ) -> NativeResult { + switch mode { + case .completableFuture: + return NativeResult( + javaType: result.javaType, + conversion: .asyncBlocking( + result.conversion, + swiftFunctionResultType: functionSignature.result.type + ), + outParameters: result.outParameters + ) + + case .future: + fatalError("TODO") + } + } } struct NativeFunctionSignature { @@ -588,6 +619,8 @@ extension JNISwift2JavaGenerator { indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String) + indirect case asyncBlocking(NativeSwiftConversionStep, swiftFunctionResultType: SwiftType) + /// 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. @@ -815,6 +848,26 @@ extension JNISwift2JavaGenerator { """ ) return unwrappedName + + case .asyncBlocking(let inner, let swiftFunctionResultType): + printer.print("let _semaphore$ = SwiftJava._Semaphore(value: 0)") + printer.print("var swiftResult$: \(swiftFunctionResultType)!") + + printer.printBraceBlock("Task") { printer in + printer.print( + """ + swiftResult$ = await \(placeholder) + _semaphore$.signal() + """ + ) + } + printer.print( + """ + _semaphore$.wait() + """ + ) + let inner = inner.render(&printer, "swiftResult$") + return inner } } } diff --git a/Sources/SwiftJava/Helpers/_Semaphore.swift b/Sources/SwiftJava/Helpers/_Semaphore.swift new file mode 100644 index 00000000..0f693b76 --- /dev/null +++ b/Sources/SwiftJava/Helpers/_Semaphore.swift @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#if canImport(Dispatch) +import Dispatch +#elseif canImport(Glibc) +@preconcurrency import Glibc +#elseif canImport(Musl) +@preconcurrency import Musl +#elseif canImport(Bionic) +@preconcurrency import Bionic +#elseif canImport(WASILibc) +@preconcurrency import WASILibc +#if canImport(wasi_pthread) +import wasi_pthread +#endif +#else +#error("The module was unable to identify your C library.") +#endif + +public final class _Semaphore: @unchecked Sendable { + #if canImport(Darwin) + private let sem: DispatchSemaphore + #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) + private let sem: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1) + #endif + + /// Creates new counting semaphore with an initial value. + public init(value: Int) { + #if canImport(Dispatch) + self.sem = DispatchSemaphore(value: value) + #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) + let err = sem_init(self.sem, 0, UInt32(value)) + precondition(err == 0, "\(#function) failed in sem with error \(err)") + #endif + } + + deinit { + #if !canImport(Dispatch) && ((compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))) + let err = sem_destroy(self.sem) + precondition(err == 0, "\(#function) failed in sem with error \(err)") + self.sem.deallocate() + #endif + } + + /// Waits for, or decrements, a semaphore. + public func wait() { + #if canImport(Dispatch) + sem.wait() + #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) + let err = sem_wait(self.sem) + precondition(err == 0, "\(#function) failed in sem with error \(err)") + #endif + } + + /// Signals (increments) a semaphore. + public func signal() { + #if canImport(Dispatch) + _ = sem.signal() + #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) + let err = sem_post(self.sem) + precondition(err == 0, "\(#function) failed in sem with error \(err)") + #endif + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift new file mode 100644 index 00000000..a6c771ed --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -0,0 +1,138 @@ +//===----------------------------------------------------------------------===// +// +// 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 Testing + +@Suite +struct JNIAsyncTests { + + @Test("Import: async -> Void (CompletableFuture)") + func completableFuture_asyncVoid_java() throws { + try assertOutput( + input: "public func asyncVoid() async", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func asyncVoid() async + * } + */ + public static java.util.concurrent.CompletableFuture asyncVoid() { + return CompletableFuture.supplyAsync(() -> { + SwiftModule.$asyncVoid(); + return null; + } + ); + } + """, + """ + private static native void $asyncVoid(); + """, + ] + ) + } + + @Test("Import: async throws -> Void (CompletableFuture)") + func completableFuture_asyncThrowsVoid_java() throws { + try assertOutput( + input: "public func async() async throws", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async() async throws + * } + */ + public static java.util.concurrent.CompletableFuture async() { + return CompletableFuture.supplyAsync(() -> { + SwiftModule.$async(); + return null; + } + ); + } + """, + """ + private static native void $async(); + """, + ] + ) + } + + @Test("Import: (Int64) async -> Int64 (CompletableFuture)") + func completableFuture_asyncIntToInt_java() throws { + try assertOutput( + input: "public func async(i: Int64) async -> Int64", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async(i: Int64) async -> Int64 + * } + */ + public static java.util.concurrent.CompletableFuture async(long i) { + return CompletableFuture.supplyAsync(() -> { + return SwiftModule.$async(i); + } + ); + } + """, + """ + private static native long $async(long i); + """, + ] + ) + } + + @Test("Import: (MyClass) async -> MyClass (CompletableFuture)") + func completableFuture_asyncMyClassToMyClass_java() throws { + try assertOutput( + input: """ + class MyClass { } + + public func async(c: MyClass) async -> MyClass + """, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async(c: MyClass) async -> MyClass + * } + */ + public static java.util.concurrent.CompletableFuture async(MyClass c, SwiftArena swiftArena$) { + return CompletableFuture.supplyAsync(() -> { + return MyClass.wrapMemoryAddressUnsafe(SwiftModule.$async(c.$memoryAddress()), swiftArena$); + } + ); + } + """, + """ + private static native long $async(long c); + """, + ] + ) + } +} From d8db270faa65f68f917450d725f20459f7b64399 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 26 Oct 2025 16:50:26 +0100 Subject: [PATCH 03/26] add support for async throws --- .../Sources/MySwiftLibrary/Async.swift | 4 +- .../Sources/MySwiftLibrary/MySwiftError.swift | 17 +++++++ .../Sources/MySwiftLibrary/Optionals.swift | 4 ++ .../java/com/example/swift/OptionalsTest.java | 13 +++++- ...wift2JavaGenerator+NativeTranslation.swift | 27 +++++++---- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 46 ++++++++++++------- .../SwiftTypes/SwiftFunctionSignature.swift | 2 +- 7 files changed, 81 insertions(+), 32 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift index 3d7c5ac6..3d81aa88 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -19,8 +19,8 @@ public func asyncSum(i1: Int64, i2: Int64) async -> Int64 { return i1 + i2 } -public func asyncCopy(myClass: MySwiftClass) async -> MySwiftClass { +public func asyncCopy(myClass: MySwiftClass) async throws -> MySwiftClass { let new = MySwiftClass(x: myClass.x, y: myClass.y) - try? await Task.sleep(for: .milliseconds(500)) + try await Task.sleep(for: .milliseconds(500)) return new } diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift new file mode 100644 index 00000000..d9d77d38 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +enum MySwiftError: Error { + case swiftError +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift index 3e122102..ca1d7458 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift @@ -62,6 +62,10 @@ public func optionalJavaKitLong(input: Optional) -> Int64? { } } +public func optionalThrowing() throws -> Int64? { + throw MySwiftError.swiftError +} + public func multipleOptionals( input1: Optional, input2: Optional, diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java index d60ff6d5..d26de1d1 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java @@ -14,6 +14,7 @@ package com.example.swift; +import com.example.swift.MySwiftLibrary; import org.junit.jupiter.api.Test; import org.swift.swiftkit.core.SwiftArena; @@ -22,8 +23,7 @@ import java.util.OptionalInt; import java.util.OptionalLong; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class OptionalsTest { @Test @@ -113,4 +113,13 @@ void multipleOptionals() { assertEquals(result, OptionalLong.of(1L)); } } + + @Test + void optionalThrows() { + try (var arena = SwiftArena.ofConfined()) { + Exception exception = assertThrows(Exception.class, () -> MySwiftLibrary.optionalThrowing()); + + assertEquals("swiftError", exception.getMessage()); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 74d400f4..067a304b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -526,6 +526,7 @@ extension JNISwift2JavaGenerator { javaType: result.javaType, conversion: .asyncBlocking( result.conversion, + isThrowing: functionSignature.effectSpecifiers.contains(.throws), swiftFunctionResultType: functionSignature.result.type ), outParameters: result.outParameters @@ -619,7 +620,7 @@ extension JNISwift2JavaGenerator { indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String) - indirect case asyncBlocking(NativeSwiftConversionStep, swiftFunctionResultType: SwiftType) + indirect case asyncBlocking(NativeSwiftConversionStep, isThrowing: Bool, swiftFunctionResultType: SwiftType) /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { @@ -849,24 +850,30 @@ extension JNISwift2JavaGenerator { ) return unwrappedName - case .asyncBlocking(let inner, let swiftFunctionResultType): + case .asyncBlocking(let inner, let isThrowing, let swiftFunctionResultType): printer.print("let _semaphore$ = SwiftJava._Semaphore(value: 0)") - printer.print("var swiftResult$: \(swiftFunctionResultType)!") + let resultType = isThrowing ? "Result<\(swiftFunctionResultType), any Error>" : swiftFunctionResultType.description + printer.print("var swiftResult$: \(resultType)!") printer.printBraceBlock("Task") { printer in - printer.print( - """ - swiftResult$ = await \(placeholder) - _semaphore$.signal() - """ - ) + if isThrowing { + printer.printBraceBlock("do") { printer in + printer.print("swiftResult$ = await Result.success(\(placeholder))") + } + printer.printBraceBlock("catch") { printer in + printer.print("swiftResult$ = Result.failure(error)") + } + } else { + printer.print("swiftResult$ = await \(placeholder)") + } + printer.print("_semaphore$.signal()") } printer.print( """ _semaphore$.wait() """ ) - let inner = inner.render(&printer, "swiftResult$") + let inner = inner.render(&printer, isThrowing ? "try swiftResult$.get()" : "swiftResult$") return inner } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 1d4f81dc..359f60e7 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -353,29 +353,41 @@ extension JNISwift2JavaGenerator { } // Lower the result. - let innerBody: String - if !decl.functionSignature.result.type.isVoid { - let loweredResult = nativeSignature.result.conversion.render(&printer, result) - innerBody = "return \(loweredResult)" - } else { - innerBody = result + func innerBody(in printer: inout CodePrinter) -> String { + if !decl.functionSignature.result.type.isVoid { + let loweredResult = nativeSignature.result.conversion.render(&printer, result) + return "return \(loweredResult)" + } else { + return result + } } if decl.isThrowing { - // TODO: Handle classes for dummy value - let dummyReturn = !nativeSignature.result.javaType.isVoid ? "return \(decl.functionSignature.result.type).jniPlaceholderValue" : "" + let dummyReturn: String + + if nativeSignature.result.javaType.isVoid { + dummyReturn = "" + } else { + // We assume it is something that implements JavaValue + dummyReturn = "return \(nativeSignature.result.javaType.swiftTypeName(resolver: { _ in "" })).jniPlaceholderValue" + } + + printer.print("do {") + printer.indent() + printer.print(innerBody(in: &printer)) + printer.outdent() + printer.print("} catch {") + printer.indent() printer.print( - """ - do { - \(innerBody) - } catch { - environment.throwAsException(error) - \(dummyReturn) - } - """ + """ + environment.throwAsException(error) + \(dummyReturn) + """ ) + printer.outdent() + printer.print("}") } else { - printer.print(innerBody) + printer.print(innerBody(in: &printer)) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 58e29d49..cfd09267 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -245,7 +245,7 @@ extension SwiftFunctionSignature { if signature.effectSpecifiers?.throwsClause != nil { effectSpecifiers.append(.throws) } - if let asyncSpecifier = signature.effectSpecifiers?.asyncSpecifier { + if signature.effectSpecifiers?.asyncSpecifier != nil { effectSpecifiers.append(.async) } From 850225a42d41bc830c7516667a7e7ff97bb6dbfe Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 27 Oct 2025 15:50:03 +0100 Subject: [PATCH 04/26] rename SwiftKitSwift --- Package.swift | 8 ++++---- .../FFMSwift2JavaGenerator+SwiftThunkPrinting.swift | 4 ++-- .../JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift | 4 ++-- Sources/SwiftJava/Helpers/_Semaphore.swift | 2 +- .../SwiftRuntimeFunctions.swift} | 0 SwiftKitCore/build.gradle | 6 +++--- .../java/org/swift/swiftkit/core/SwiftLibraries.java | 10 +++++----- SwiftKitFFM/build.gradle | 6 +++--- .../main/java/org/swift/swiftkit/ffm/SwiftRuntime.java | 10 +++++----- 9 files changed, 25 insertions(+), 25 deletions(-) rename Sources/{SwiftKitSwift/SwiftKit.swift => SwiftJavaRuntimeSupport/SwiftRuntimeFunctions.swift} (100%) diff --git a/Package.swift b/Package.swift index f6cc83e7..337bb99a 100644 --- a/Package.swift +++ b/Package.swift @@ -169,9 +169,9 @@ let package = Package( // Support library written in Swift for SwiftKit "Java" .library( - name: "SwiftKitSwift", + name: "SwiftJavaRuntimeSupport", type: .dynamic, - targets: ["SwiftKitSwift"] + targets: ["SwiftJavaRuntimeSupport"] ), .library( @@ -213,7 +213,7 @@ let package = Package( name: "SwiftJavaDocumentation", dependencies: [ "SwiftJava", - "SwiftKitSwift", + "SwiftJavaRuntimeSupport", ] ), @@ -351,7 +351,7 @@ let package = Package( ] ), .target( - name: "SwiftKitSwift", + name: "SwiftJavaRuntimeSupport", dependencies: [], swiftSettings: [ .swiftLanguageMode(.v5), diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 5ef266c8..91bddb5f 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -108,7 +108,7 @@ extension FFMSwift2JavaGenerator { """ // Generated by swift-java - import SwiftKitSwift + import SwiftJavaRuntimeSupport """) @@ -136,7 +136,7 @@ extension FFMSwift2JavaGenerator { """ // Generated by swift-java - import SwiftKitSwift + import SwiftJavaRuntimeSupport """ ) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index f9ba88b9..475ea939 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -189,7 +189,7 @@ extension FFMSwift2JavaGenerator { private static final boolean INITIALIZED_LIBS = initializeLibs(); static boolean initializeLibs() { System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); - System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_JAVA_RUNTIME_SUPPORT); System.loadLibrary(LIB_NAME); return true; } @@ -339,7 +339,7 @@ extension FFMSwift2JavaGenerator { private static SymbolLookup getSymbolLookup() { if (SwiftLibraries.AUTO_LOAD_LIBS) { System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); - System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_JAVA_RUNTIME_SUPPORT); System.loadLibrary(LIB_NAME); } diff --git a/Sources/SwiftJava/Helpers/_Semaphore.swift b/Sources/SwiftJava/Helpers/_Semaphore.swift index 0f693b76..4db9e61b 100644 --- a/Sources/SwiftJava/Helpers/_Semaphore.swift +++ b/Sources/SwiftJava/Helpers/_Semaphore.swift @@ -30,7 +30,7 @@ import wasi_pthread #endif public final class _Semaphore: @unchecked Sendable { - #if canImport(Darwin) + #if canImport(Dispatch) private let sem: DispatchSemaphore #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) private let sem: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1) diff --git a/Sources/SwiftKitSwift/SwiftKit.swift b/Sources/SwiftJavaRuntimeSupport/SwiftRuntimeFunctions.swift similarity index 100% rename from Sources/SwiftKitSwift/SwiftKit.swift rename to Sources/SwiftJavaRuntimeSupport/SwiftRuntimeFunctions.swift diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 9e4891dc..11312cae 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -73,10 +73,10 @@ tasks.test { } } -// SwiftKit depends on SwiftKitSwift (Swift library that this Java library calls into) +// SwiftKit depends on SwiftJavaRuntimeSupport (Swift library that this Java library calls into) def compileSwift = tasks.register("compileSwift", Exec) { - description "Compile the swift-java SwiftKitSwift dynamic library that SwiftKit (Java) calls into" + description "Compile the swift-java SwiftJavaRuntimeSupport dynamic library that SwiftKit (Java) calls into" inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes @@ -84,7 +84,7 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftKitSwift") + args("build", "--target", "SwiftJavaRuntimeSupport") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java index a093cc50..e9609222 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -27,7 +27,7 @@ public final class SwiftLibraries { // Library names of core Swift and SwiftKit public static final String LIB_NAME_SWIFT_CORE = "swiftCore"; public static final String LIB_NAME_SWIFT_CONCURRENCY = "swift_Concurrency"; - public static final String LIB_NAME_SWIFTKITSWIFT = "SwiftKitSwift"; + public static final String LIB_NAME_SWIFT_JAVA_RUNTIME_SUPPORT = "SwiftJavaRuntimeSupport"; /** * Allows for configuration if jextracted types should automatically attempt to load swiftCore and the library type is from. @@ -42,10 +42,10 @@ public final class SwiftLibraries { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); - public static boolean loadLibraries(boolean loadSwiftKit) { - System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); - if (loadSwiftKit) { - System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); + public static boolean loadLibraries(boolean loadSwiftJavaRuntimeSupport) { + System.loadLibrary(LIB_NAME_SWIFT_JAVA_RUNTIME_SUPPORT); + if (loadSwiftJavaRuntimeSupport) { + System.loadLibrary(LIB_NAME_SWIFT_JAVA_RUNTIME_SUPPORT); } return true; } diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle index c04b1017..2967f1e2 100644 --- a/SwiftKitFFM/build.gradle +++ b/SwiftKitFFM/build.gradle @@ -75,10 +75,10 @@ tasks.withType(JavaCompile).configureEach { options.compilerArgs.add("-Xlint:preview") } -// SwiftKit depends on SwiftKitSwift (Swift library that this Java library calls into) +// SwiftKit depends on SwiftJavaRuntimeSupport (Swift library that this Java library calls into) def compileSwift = tasks.register("compileSwift", Exec) { - description "Compile the swift-java SwiftKitSwift dynamic library that SwiftKit (Java) calls into" + description "Compile the swift-java SwiftJavaRuntimeSupport dynamic library that SwiftKit (Java) calls into" inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes @@ -86,7 +86,7 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftKitSwift") + args("build", "--target", "SwiftJavaRuntimeSupport") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index e03b9bfe..b47a724c 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -33,7 +33,7 @@ public class SwiftRuntime { public static final String STDLIB_DYLIB_NAME = "swiftCore"; - public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; + public static final String SWIFT_JAVA_RUNTIME_SUPPORT_DYLIB_NAME = "SwiftJavaRuntimeSupport"; private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; @@ -42,10 +42,10 @@ public class SwiftRuntime { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); - public static boolean loadLibraries(boolean loadSwiftKit) { + public static boolean loadLibraries(boolean loadSwiftJavaRuntimeSupport) { System.loadLibrary(STDLIB_DYLIB_NAME); - if (loadSwiftKit) { - System.loadLibrary(SWIFTKITSWIFT_DYLIB_NAME); + if (loadSwiftJavaRuntimeSupport) { + System.loadLibrary(SWIFT_JAVA_RUNTIME_SUPPORT_DYLIB_NAME); } return true; } @@ -487,4 +487,4 @@ boolean isEnabled() { } throw new IllegalArgumentException("Not handled log group: " + this); } -} \ No newline at end of file +} From 3fe66367ca8f5c7d1da158395c373b046c2837d7 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 28 Oct 2025 08:31:14 +0100 Subject: [PATCH 05/26] move helper files to runtime support --- Package.swift | 5 ++++- .../JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 1 + .../_JNIMethodIDCache.swift | 3 +++ .../Helpers => SwiftJavaRuntimeSupport}/_Semaphore.swift | 0 4 files changed, 8 insertions(+), 1 deletion(-) rename Sources/{SwiftJava/Helpers => SwiftJavaRuntimeSupport}/_JNIMethodIDCache.swift (98%) rename Sources/{SwiftJava/Helpers => SwiftJavaRuntimeSupport}/_Semaphore.swift (100%) diff --git a/Package.swift b/Package.swift index 337bb99a..be84484f 100644 --- a/Package.swift +++ b/Package.swift @@ -352,7 +352,10 @@ let package = Package( ), .target( name: "SwiftJavaRuntimeSupport", - dependencies: [], + dependencies: [ + "CSwiftJavaJNI", + "SwiftJava" + ], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 359f60e7..b962341a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -465,6 +465,7 @@ extension JNISwift2JavaGenerator { import SwiftJava import CSwiftJavaJNI + import SwiftJavaRuntimeSupport """ ) diff --git a/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift similarity index 98% rename from Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift rename to Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index a67d225f..b813cba8 100644 --- a/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -12,6 +12,9 @@ // //===----------------------------------------------------------------------===// +import CSwiftJavaJNI +import SwiftJava + /// A cache used to hold references for JNI method and classes. /// /// This type is used internally in by the outputted JExtract wrappers diff --git a/Sources/SwiftJava/Helpers/_Semaphore.swift b/Sources/SwiftJavaRuntimeSupport/_Semaphore.swift similarity index 100% rename from Sources/SwiftJava/Helpers/_Semaphore.swift rename to Sources/SwiftJavaRuntimeSupport/_Semaphore.swift From 1117fafcbdaadf63b5acff92567c322f49e3e9f4 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 28 Oct 2025 09:16:19 +0100 Subject: [PATCH 06/26] make confined arena threadsafe --- Samples/SwiftAndJavaJarSampleLib/Package.swift | 2 +- Samples/SwiftJavaExtractFFMSampleApp/Package.swift | 2 +- Samples/SwiftJavaExtractJNISampleApp/Package.swift | 2 +- .../JNISwift2JavaGenerator+NativeTranslation.swift | 2 +- .../swiftkit/core/ConfinedSwiftMemorySession.java | 13 +++++-------- .../java/org/swift/swiftkit/core/SwiftArena.java | 2 +- .../swift/swiftkit/ffm/AllocatingSwiftArena.java | 2 +- .../swiftkit/ffm/FFMConfinedSwiftMemorySession.java | 4 ++-- 8 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Samples/SwiftAndJavaJarSampleLib/Package.swift b/Samples/SwiftAndJavaJarSampleLib/Package.swift index c350a0e7..71c2a5ed 100644 --- a/Samples/SwiftAndJavaJarSampleLib/Package.swift +++ b/Samples/SwiftAndJavaJarSampleLib/Package.swift @@ -63,7 +63,7 @@ let package = Package( .target( name: "MySwiftLibrary", dependencies: [ - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftJavaRuntimeSupport", package: "swift-java"), ], exclude: [ "swift-java.config", diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift index b9765c24..40a6bb7b 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift @@ -65,7 +65,7 @@ let package = Package( dependencies: [ .product(name: "SwiftJava", package: "swift-java"), .product(name: "CSwiftJavaJNI", package: "swift-java"), - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftJavaRuntimeSupport", package: "swift-java"), ], exclude: [ "swift-java.config", diff --git a/Samples/SwiftJavaExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift index c7d5d717..c72dd75d 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -62,7 +62,7 @@ let package = Package( dependencies: [ .product(name: "SwiftJava", package: "swift-java"), .product(name: "CSwiftJavaJNI", package: "swift-java"), - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftJavaRuntimeSupport", package: "swift-java"), ], exclude: [ "swift-java.config" diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 067a304b..971f2091 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -851,7 +851,7 @@ extension JNISwift2JavaGenerator { return unwrappedName case .asyncBlocking(let inner, let isThrowing, let swiftFunctionResultType): - printer.print("let _semaphore$ = SwiftJava._Semaphore(value: 0)") + printer.print("let _semaphore$ = _Semaphore(value: 0)") let resultType = isThrowing ? "Result<\(swiftFunctionResultType), any Error>" : swiftFunctionResultType.description printer.print("var swiftResult$: \(resultType)!") diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index 4383a6fe..3514d9c1 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -16,6 +16,8 @@ import java.util.LinkedList; import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; public class ConfinedSwiftMemorySession implements ClosableSwiftArena { @@ -23,21 +25,17 @@ public class ConfinedSwiftMemorySession implements ClosableSwiftArena { final static int CLOSED = 0; final static int ACTIVE = 1; - final Thread owner; final AtomicInteger state; final ConfinedResourceList resources; - public ConfinedSwiftMemorySession(Thread owner) { - this.owner = owner; + public ConfinedSwiftMemorySession() { this.state = new AtomicInteger(ACTIVE); this.resources = new ConfinedResourceList(); } void checkValid() throws RuntimeException { - if (this.owner != null && this.owner != Thread.currentThread()) { - throw new WrongThreadException(String.format("ConfinedSwift arena is confined to %s but was closed from %s!", this.owner, Thread.currentThread())); - } else if (this.state.get() < ACTIVE) { + if (this.state.get() < ACTIVE) { throw new RuntimeException("SwiftArena is already closed!"); } } @@ -61,8 +59,7 @@ public void register(SwiftInstance instance) { } static final class ConfinedResourceList implements SwiftResourceList { - // TODO: Could use intrusive linked list to avoid one indirection here - final List resourceCleanups = new LinkedList<>(); + final Queue resourceCleanups = new ConcurrentLinkedQueue<>(); void add(SwiftInstanceCleanup cleanup) { resourceCleanups.add(cleanup); diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java index 3b6c4626..96353c37 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java @@ -31,7 +31,7 @@ public interface SwiftArena { void register(SwiftInstance instance); static ClosableSwiftArena ofConfined() { - return new ConfinedSwiftMemorySession(Thread.currentThread()); + return new ConfinedSwiftMemorySession(); } static SwiftArena ofAuto() { diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java index 08fad15b..b72818a3 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java @@ -24,7 +24,7 @@ public interface AllocatingSwiftArena extends SwiftArena, SegmentAllocator { MemorySegment allocate(long byteSize, long byteAlignment); static ClosableAllocatingSwiftArena ofConfined() { - return new FFMConfinedSwiftMemorySession(Thread.currentThread()); + return new FFMConfinedSwiftMemorySession(); } static AllocatingSwiftArena ofAuto() { diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java index 424e54b6..05daebd7 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java @@ -22,8 +22,8 @@ final class FFMConfinedSwiftMemorySession extends ConfinedSwiftMemorySession implements AllocatingSwiftArena, ClosableAllocatingSwiftArena { final Arena arena; - public FFMConfinedSwiftMemorySession(Thread owner) { - super(owner); + public FFMConfinedSwiftMemorySession() { + super(); this.arena = Arena.ofConfined(); } From 4b04da2f28f5d1e8359b082c72382a738a901e5b Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 28 Oct 2025 09:20:46 +0100 Subject: [PATCH 07/26] update to Swift 6.2 --- .github/workflows/pull_request.yml | 20 ++++++++++---------- Package.swift | 2 +- README.md | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f2a1db71..5e5b767a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -26,11 +26,11 @@ jobs: name: Documentation check runs-on: ubuntu-latest container: - image: 'swift:6.1-noble' + image: 'swift:6.2-noble' strategy: fail-fast: true matrix: - swift_version: ['6.1.3'] + swift_version: ['6.2'] os_version: ['jammy'] jdk_vendor: ['corretto'] steps: @@ -48,7 +48,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -74,7 +74,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -98,7 +98,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -118,7 +118,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -140,7 +140,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -162,7 +162,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -182,7 +182,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names @@ -210,7 +210,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2.0'] # no nightly testing on macOS + swift_version: ['6.2'] # no nightly testing on macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names diff --git a/Package.swift b/Package.swift index be84484f..c87fdad8 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.2 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport diff --git a/README.md b/README.md index 2688f288..d21e3a73 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ This does require the use of the relatively recent [JEP-454: Foreign Function & This is the primary way we envision calling Swift code from server-side Java libraries and applications. Required language/runtime versions: -- **Swift 6.1**, because of dependence on rich swift interface files +- **Swift 6.2**, because of dependence on rich swift interface files - **JDK 25+** - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-25. @@ -85,7 +85,7 @@ This mode is more limited in some performance and flexibility that it can offer, We recommend this mode when FFM is not available, or wide ranging deployment compatibility is your priority. When performance is paramaunt, we recommend the FFM mode instead. Required language/runtime versions: -- **Swift 6.1**, because of dependence on rich swift interface files +- **Swift 6.2**, because of dependence on rich swift interface files - **Java 7+**, including @@ -94,7 +94,7 @@ Required language/runtime versions: This project contains multiple builds, living side by side together. You will need to have: -- Swift (6.1.x+) +- Swift (6.2.x+) - Java (25+ for FFM, even though we support lower JDK targets) - Gradle (installed by "Gradle wrapper" automatically when you run gradle through `./gradlew`) @@ -104,7 +104,7 @@ Install **Swift**, the easiest way to do this is to use **Swiftly**: [swift.org/ This should automatically install a recent Swift, but you can always make sure by running: ```bash -swiftly install 6.1.2 --use +swiftly install 6.2 --use ``` Install a recent enough Java distribution. We validate this project using Corretto so you can choose to use that as well, @@ -229,4 +229,4 @@ xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc **This project is under active development. We welcome feedback about any issues you encounter.** -There is no guarantee about API stability until the project reaches a 1.0 release. \ No newline at end of file +There is no guarantee about API stability until the project reaches a 1.0 release. From f187e2af2039ee26261327bb13616a789e9bbd40 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 28 Oct 2025 10:22:00 +0100 Subject: [PATCH 08/26] use immediate when available --- Samples/SwiftJavaExtractJNISampleApp/Package.swift | 2 +- .../JNISwift2JavaGenerator+NativeTranslation.swift | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift index c72dd75d..33a35c49 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.2 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 971f2091..eb239d0c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -855,7 +855,7 @@ extension JNISwift2JavaGenerator { let resultType = isThrowing ? "Result<\(swiftFunctionResultType), any Error>" : swiftFunctionResultType.description printer.print("var swiftResult$: \(resultType)!") - printer.printBraceBlock("Task") { printer in + func printInner(printer: inout CodePrinter) { if isThrowing { printer.printBraceBlock("do") { printer in printer.print("swiftResult$ = await Result.success(\(placeholder))") @@ -868,6 +868,17 @@ extension JNISwift2JavaGenerator { } printer.print("_semaphore$.signal()") } + + printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in + printer.printBraceBlock("Task.immediate") { printer in + printInner(printer: &printer) + } + } + printer.printBraceBlock("else") { printer in + printer.printBraceBlock("Task") { printer in + printInner(printer: &printer) + } + } printer.print( """ _semaphore$.wait() From 7ce00dddeae6a8a01d1e0ee9f72cfa2230b287a5 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 28 Oct 2025 11:00:49 +0100 Subject: [PATCH 09/26] add async tests --- .../Sources/MySwiftLibrary/Async.swift | 4 + .../java/com/example/swift/AsyncTest.java | 9 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 5 +- .../JNI/JNIAsyncTests.swift | 171 +++++++++++++++++- 4 files changed, 178 insertions(+), 11 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift index 3d81aa88..607a38d5 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -19,6 +19,10 @@ public func asyncSum(i1: Int64, i2: Int64) async -> Int64 { return i1 + i2 } +public func asyncSleep() async throws { + try await Task.sleep(for: .milliseconds(500)) +} + public func asyncCopy(myClass: MySwiftClass) async throws -> MySwiftClass { let new = MySwiftClass(x: myClass.x, y: myClass.y) try await Task.sleep(for: .milliseconds(500)) diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java index 36c2bdf7..9e039cec 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java @@ -39,12 +39,19 @@ void asyncSum() { } @Test - void asyncCopy() throws Exception { + void asyncSleep() { + CompletableFuture future = MySwiftLibrary.asyncSleep(); + future.join(); + } + + @Test + void asyncCopy() { try (var arena = SwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(10, 5, arena); CompletableFuture future = MySwiftLibrary.asyncCopy(obj, arena); MySwiftClass result = future.join(); + assertEquals(10, result.getX()); assertEquals(5, result.getY()); } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index b962341a..85e78a78 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -354,11 +354,12 @@ extension JNISwift2JavaGenerator { // Lower the result. func innerBody(in printer: inout CodePrinter) -> String { + let loweredResult = nativeSignature.result.conversion.render(&printer, result) + if !decl.functionSignature.result.type.isVoid { - let loweredResult = nativeSignature.result.conversion.render(&printer, result) return "return \(loweredResult)" } else { - return result + return loweredResult } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index a6c771ed..a20cce32 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -18,7 +18,7 @@ import Testing @Suite struct JNIAsyncTests { - @Test("Import: async -> Void (CompletableFuture)") + @Test("Import: async -> Void (Java, CompletableFuture)") func completableFuture_asyncVoid_java() throws { try assertOutput( input: "public func asyncVoid() async", @@ -33,7 +33,7 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture asyncVoid() { - return CompletableFuture.supplyAsync(() -> { + return java.util.concurrent.CompletableFuture.supplyAsync(() -> { SwiftModule.$asyncVoid(); return null; } @@ -47,7 +47,39 @@ struct JNIAsyncTests { ) } - @Test("Import: async throws -> Void (CompletableFuture)") + @Test("Import: async -> Void (Swift, CompletableFuture)") + func completableFuture_asyncVoid_swift() throws { + try assertOutput( + input: "public func asyncVoid() async", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__") + func Java_com_example_swift_SwiftModule__00024asyncVoid__(environment: UnsafeMutablePointer!, thisClass: jclass) { + let _semaphore$ = _Semaphore(value: 0) + var swiftResult$: ()! + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + swiftResult$ = await SwiftModule.asyncVoid() + _semaphore$.signal() + } + } + else { + Task { + swiftResult$ = await SwiftModule.asyncVoid() + _semaphore$.signal() + } + } + _semaphore$.wait() + swiftResult$ + } + """ + ] + ) + } + + @Test("Import: async throws -> Void (Java, CompletableFuture)") func completableFuture_asyncThrowsVoid_java() throws { try assertOutput( input: "public func async() async throws", @@ -62,7 +94,7 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture async() { - return CompletableFuture.supplyAsync(() -> { + return java.util.concurrent.CompletableFuture.supplyAsync(() -> { SwiftModule.$async(); return null; } @@ -76,7 +108,53 @@ struct JNIAsyncTests { ) } - @Test("Import: (Int64) async -> Int64 (CompletableFuture)") + @Test("Import: async throws -> Void (Swift, CompletableFuture)") + func completableFuture_asyncThrowsVoid_swift() throws { + try assertOutput( + input: "public func async() async throws", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__") + func Java_com_example_swift_SwiftModule__00024async__(environment: UnsafeMutablePointer!, thisClass: jclass) { + do { + let _semaphore$ = _Semaphore(value: 0) + var swiftResult$: Result<(), any Error>! + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + do { + swiftResult$ = await Result.success(try SwiftModule.async()) + } + catch { + swiftResult$ = Result.failure(error) + } + _semaphore$.signal() + } + } + else { + Task { + do { + swiftResult$ = await Result.success(try SwiftModule.async()) + } + catch { + swiftResult$ = Result.failure(error) + } + _semaphore$.signal() + } + } + _semaphore$.wait() + try swiftResult$.get() + } catch { + environment.throwAsException(error) + + } + """ + ] + ) + } + + @Test("Import: (Int64) async -> Int64 (Java, CompletableFuture)") func completableFuture_asyncIntToInt_java() throws { try assertOutput( input: "public func async(i: Int64) async -> Int64", @@ -91,7 +169,7 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture async(long i) { - return CompletableFuture.supplyAsync(() -> { + return java.util.concurrent.CompletableFuture.supplyAsync(() -> { return SwiftModule.$async(i); } ); @@ -104,7 +182,39 @@ struct JNIAsyncTests { ) } - @Test("Import: (MyClass) async -> MyClass (CompletableFuture)") + @Test("Import: (Int64) async -> Int64 (Swift, CompletableFuture)") + func completableFuture_asyncIntToInt_swift() throws { + try assertOutput( + input: "public func async(i: Int64) async -> Int64", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__J") + func Java_com_example_swift_SwiftModule__00024async__J(environment: UnsafeMutablePointer!, thisClass: jclass, i: jlong) -> jlong { + let _semaphore$ = _Semaphore(value: 0) + var swiftResult$: Int64! + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment!)) + _semaphore$.signal() + } + } + else { + Task { + swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment!)) + _semaphore$.signal() + } + } + _semaphore$.wait() + return swiftResult$.getJNIValue(in: environment!) + } + """ + ] + ) + } + + @Test("Import: (MyClass) async -> MyClass (Java, CompletableFuture)") func completableFuture_asyncMyClassToMyClass_java() throws { try assertOutput( input: """ @@ -123,7 +233,7 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture async(MyClass c, SwiftArena swiftArena$) { - return CompletableFuture.supplyAsync(() -> { + return java.util.concurrent.CompletableFuture.supplyAsync(() -> { return MyClass.wrapMemoryAddressUnsafe(SwiftModule.$async(c.$memoryAddress()), swiftArena$); } ); @@ -135,4 +245,49 @@ struct JNIAsyncTests { ] ) } + + @Test("Import: (MyClass) async -> MyClass (Swift, CompletableFuture)") + func completableFuture_asyncMyClassToMyClass_swift() throws { + try assertOutput( + input: """ + class MyClass { } + + public func async(c: MyClass) async -> MyClass + """, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__J") + func Java_com_example_swift_SwiftModule__00024async__J(environment: UnsafeMutablePointer!, thisClass: jclass, c: jlong) -> jlong { + assert(c != 0, "c memory address was null") + let cBits$ = Int(Int64(fromJNI: c, in: environment!)) + let c$ = UnsafeMutablePointer(bitPattern: cBits$) + guard let c$ else { + fatalError("c memory address was null in call to \\(#function)!") + } + let _semaphore$ = _Semaphore(value: 0) + var swiftResult$: MyClass! + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + swiftResult$ = await SwiftModule.async(c: c$.pointee) + _semaphore$.signal() + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:873 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:872 + else { + Task { + swiftResult$ = await SwiftModule.async(c: c$.pointee) + _semaphore$.signal() + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:878 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:877 + _semaphore$.wait() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) + } + """ + ] + ) + } } From adfdea8a046b1a52ffd2a77eaf1dc18f9c750daf Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 28 Oct 2025 11:05:01 +0100 Subject: [PATCH 10/26] add docs --- .../Documentation.docc/SupportedFeatures.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 997e680e..4f585ec7 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -328,3 +328,19 @@ which conform a to a given Swift protocol. #### Returning protocol types Protocols are not yet supported as return types. + +### `async` methods + +> Note: Importing `async` methods is currently only available in the JNI mode of jextract. + +Asynchronous methods in Swift can be extraced using different modes, which are explained in detail below. + +#### Async mode: completable-future (default) + +In this mode `async` methods in Swift are extracted as methods returning a `java.util.concurrent.CompletableFuture`. +This mode gives the most flexibility and should be prefered if your platform supports `CompletableFuture`. + +#### Async mode: future + +This is a mode for legacy platforms, where `CompletableFuture` is not available, such as Android 23 and below. +In this mode `async` methods in Swift are extracted as methods returning a `java.util.concurrent.Future`. From d693691d25a8e61930300e1fb6cda4621b19db42 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 28 Oct 2025 12:27:28 +0100 Subject: [PATCH 11/26] add "future" mode --- ...ISwift2JavaGenerator+JavaTranslation.swift | 21 ++++++++++++++++-- ...wift2JavaGenerator+NativeTranslation.swift | 5 +---- .../Commands/JExtractCommand.swift | 5 +++++ .../org/swift/swiftkit/core/SwiftAsync.java | 22 +++++++++++++++++++ 4 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 3e1a3c21..ed297b66 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -496,12 +496,29 @@ extension JNISwift2JavaGenerator { annotations: result.annotations, outParameters: result.outParameters, conversion: .method(.constant("java.util.concurrent.CompletableFuture"), function: "supplyAsync", arguments: [ - .lambda(body: supplyAsyncBodyConversion) + .lambda(body: supplyAsyncBodyConversion), + .constant("SwiftAsync.SWIFT_ASYNC_EXECUTOR") ]) ) case .future: - fatalError("TODO") + let asyncBodyConversion: JavaNativeConversionStep = if result.javaType.isVoid { + .aggregate([ + .print(result.conversion), + .null + ]) + } else { + result.conversion + } + + return TranslatedResult( + javaType: .class(package: "java.util.concurrent", name: "Future<\(result.javaType.wrapperClassIfNeeded)>"), + annotations: result.annotations, + outParameters: result.outParameters, + conversion: .method(.constant("SwiftAsync.SWIFT_ASYNC_EXECUTOR"), function: "submit", arguments: [ + .lambda(body: asyncBodyConversion) + ]) + ) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index eb239d0c..95884235 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -521,7 +521,7 @@ extension JNISwift2JavaGenerator { mode: JExtractAsyncMode ) -> NativeResult { switch mode { - case .completableFuture: + case .completableFuture, .future: return NativeResult( javaType: result.javaType, conversion: .asyncBlocking( @@ -531,9 +531,6 @@ extension JNISwift2JavaGenerator { ), outParameters: result.outParameters ) - - case .future: - fatalError("TODO") } } } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index c730cbf6..045526e4 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -78,6 +78,9 @@ extension SwiftJava { """ ) var dependsOn: [String] = [] + + @Option(help: "The mode to use for extracting asynchronous Swift code. By default async methods are extracted as Java functions returning CompletableFuture.") + var asyncMode: JExtractAsyncMode = .default } } @@ -98,6 +101,7 @@ extension SwiftJava.JExtractCommand { config.unsignedNumbersMode = unsignedNumbers config.minimumInputAccessLevelMode = minimumInputAccessLevel config.memoryManagementMode = memoryManagementMode + config.asyncMode = asyncMode try checkModeCompatibility() @@ -162,3 +166,4 @@ extension JExtractGenerationMode: ExpressibleByArgument {} extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} extension JExtractMinimumAccessLevelMode: ExpressibleByArgument {} extension JExtractMemoryManagementMode: ExpressibleByArgument {} +extension JExtractAsyncMode: ExpressibleByArgument {} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java new file mode 100644 index 00000000..339bfcca --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// 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.ExecutorService; +import java.util.concurrent.Executors; + +public final class SwiftAsync { + public static final ExecutorService SWIFT_ASYNC_EXECUTOR = Executors.newCachedThreadPool(); +} From f6db47e03d17e1ae54fb1f3209b2397ed8397b48 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 28 Oct 2025 12:28:36 +0100 Subject: [PATCH 12/26] remove space --- .../JExtract/JExtractAsyncMode.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncMode.swift index 0db90dc3..6347ef62 100644 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncMode.swift @@ -26,7 +26,6 @@ public enum JExtractAsyncMode: String, Codable { case future } - extension JExtractAsyncMode { public static var `default`: Self { .completableFuture From 12513076b70c2c352a9b8c71b009b5bccbb69df1 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 28 Oct 2025 15:15:57 +0100 Subject: [PATCH 13/26] add async benchmark --- .../Sources/MySwiftLibrary/Async.swift | 25 ++++++++++++ .../com/example/swift/AsyncBenchmark.java | 40 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift index 607a38d5..84825e6e 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -28,3 +28,28 @@ public func asyncCopy(myClass: MySwiftClass) async throws -> MySwiftClass { try await Task.sleep(for: .milliseconds(500)) return new } + +public func asyncRunningSum() async -> Int64 { + let totalSum = await withTaskGroup(of: Int64.self) { group in + // 1. Add child tasks to the group + for number in stride(from: Int64(1), through: 100, by: 1) { + group.addTask { + try? await Task.sleep(for: .milliseconds(number)) + return number + } + } + + var collectedSum: Int64 = 0 + + // `for await ... in group` loops as each child task completes, + // (not necessarily in the order they were added). + for await number in group { + collectedSum += number + } + + return collectedSum + } + + // This is the value returned by the `withTaskGroup` closure. + return totalSum +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java new file mode 100644 index 00000000..899b4c86 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java @@ -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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.swift.swiftkit.core.ClosableSwiftArena; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) +public class AsyncBenchmark { + + @Benchmark + public long asyncSum() { + return MySwiftLibrary.asyncRunningSum().join(); + } +} \ No newline at end of file From 2d0e79b7519d9c514ea8a402e85a1993cd6e1719 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 28 Oct 2025 15:28:53 +0100 Subject: [PATCH 14/26] use deamon threads --- .../org/swift/swiftkit/core/SwiftAsync.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java index 339bfcca..09d49fee 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java @@ -16,7 +16,22 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; public final class SwiftAsync { - public static final ExecutorService SWIFT_ASYNC_EXECUTOR = Executors.newCachedThreadPool(); + + private static final ThreadFactory SWIFT_ASYNC_THREAD_FACTORY = new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix = "swift-async-pool-"; + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement()); + t.setDaemon(true); + return t; + } + }; + + public static final ExecutorService SWIFT_ASYNC_EXECUTOR = Executors.newCachedThreadPool(SWIFT_ASYNC_THREAD_FACTORY); } From 26bb99229dc84e8dddfc5a1c5c5062950d11a605 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 28 Oct 2025 15:37:41 +0100 Subject: [PATCH 15/26] use forkjoin pool --- .../org/swift/swiftkit/core/SwiftAsync.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java index 09d49fee..25f49e78 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java @@ -16,22 +16,16 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; public final class SwiftAsync { - private static final ThreadFactory SWIFT_ASYNC_THREAD_FACTORY = new ThreadFactory() { - private final AtomicInteger threadNumber = new AtomicInteger(1); - private final String namePrefix = "swift-async-pool-"; - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement()); - t.setDaemon(true); - return t; - } - }; - - public static final ExecutorService SWIFT_ASYNC_EXECUTOR = Executors.newCachedThreadPool(SWIFT_ASYNC_THREAD_FACTORY); + public static final ExecutorService SWIFT_ASYNC_EXECUTOR = new ForkJoinPool( + Runtime.getRuntime().availableProcessors(), + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, + true + ); } From a7745585f519f76454a7516eea19f63924b6ed16 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 29 Oct 2025 21:33:02 +0100 Subject: [PATCH 16/26] work on upcall --- .../Sources/MySwiftLibrary/Async.swift | 6 +- .../com/example/swift/AsyncBenchmark.java | 36 +++- .../Convenience/JavaType+Extensions.swift | 33 ++-- ...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 2 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 115 +++++++----- ...wift2JavaGenerator+NativeTranslation.swift | 174 +++++++++--------- Sources/JExtractSwiftLib/JavaParameter.swift | 1 + .../JavaTypes/JavaType+JDK.swift | 5 + .../SwiftTypes/SwiftEffectSpecifier.swift | 2 +- .../SwiftTypes/SwiftFunctionSignature.swift | 4 + Sources/JavaTypes/JavaType+JNI.swift | 6 +- Sources/JavaTypes/JavaType+JavaSource.swift | 14 +- Sources/JavaTypes/JavaType+SwiftNames.swift | 6 +- Sources/JavaTypes/JavaType.swift | 2 +- Sources/JavaTypes/Mangling.swift | 2 +- .../Configuration.swift | 6 +- ...Mode.swift => JExtractAsyncFuncMode.swift} | 4 +- .../_JNIBoxedConversions.swift | 91 +++++++++ .../_JNIMethodIDCache.swift | 18 +- .../Commands/JExtractCommand.swift | 8 +- .../org/swift/swiftkit/core/SwiftAsync.java | 31 ---- 23 files changed, 349 insertions(+), 221 deletions(-) rename Sources/SwiftJavaConfigurationShared/JExtract/{JExtractAsyncMode.swift => JExtractAsyncFuncMode.swift} (92%) create mode 100644 Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift index 84825e6e..40310627 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -15,7 +15,6 @@ import SwiftJava public func asyncSum(i1: Int64, i2: Int64) async -> Int64 { - try? await Task.sleep(for: .milliseconds(500)) return i1 + i2 } @@ -29,6 +28,11 @@ public func asyncCopy(myClass: MySwiftClass) async throws -> MySwiftClass { return new } +public func asyncOptional(i: Int64) async throws -> Int64? { + try await Task.sleep(for: .milliseconds(100)) + return i +} + public func asyncRunningSum() async -> Int64 { let totalSum = await withTaskGroup(of: Int64.self) { group in // 1. Add child tasks to the group diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java index 899b4c86..9cc74246 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java @@ -20,21 +20,41 @@ import org.swift.swiftkit.core.ConfinedSwiftMemorySession; import org.swift.swiftkit.core.SwiftArena; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.OptionalInt; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) -@OutputTimeUnit(TimeUnit.SECONDS) -@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +@Fork(value = 1, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class AsyncBenchmark { + /** + * Parameter for the number of parallel tasks to launch. + */ + @Param({"100", "500", "1000"}) + public int taskCount; + + @Setup(Level.Trial) + public void beforeAll() {} + + @TearDown(Level.Trial) + public void afterAll() {} @Benchmark - public long asyncSum() { - return MySwiftLibrary.asyncRunningSum().join(); + public void asyncSum(Blackhole bh) { + CompletableFuture[] futures = new CompletableFuture[taskCount]; + + // Launch all tasks in parallel using supplyAsync on our custom executor + for (int i = 0; i < taskCount; i++) { + futures[i] = MySwiftLibrary.asyncSum(10, 5).thenAccept(bh::consume); + } + + // Wait for all futures to complete. + CompletableFuture.allOf(futures).join(); } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index 373fec17..3b29fcd3 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -25,7 +25,7 @@ extension JavaType { case .long: return "J" case .float: return "F" case .double: return "D" - case .class(let package, let name): + case .class(let package, let name, _): let nameWithInnerClasses = name.replacingOccurrences(of: ".", with: "$") if let package { return "L\(package.replacingOccurrences(of: ".", with: "/"))/\(nameWithInnerClasses);" @@ -112,26 +112,27 @@ extension JavaType { } } - var wrapperClassIfNeeded: JavaType { + /// Returns the boxed type, or self if the type is already a Java class. + var boxedType: JavaType { switch self { case .boolean: - return .class(package: nil, name: "Boolean") - case .byte(let parameterAnnotations): - return .class(package: nil, name: "Byte") - case .char(let parameterAnnotations): - return .class(package: nil, name: "Character") - case .short(let parameterAnnotations): - return .class(package: nil, name: "Short") - case .int(let parameterAnnotations): - return .class(package: nil, name: "Integer") - case .long(let parameterAnnotations): - return .class(package: nil, name: "Long") + return .class(package: "java.lang", name: "Boolean") + case .byte: + return .class(package: "java.lang", name: "Byte") + case .char: + return .class(package: "java.lang", name: "Character") + case .short: + return .class(package: "java.lang", name: "Short") + case .int: + return .class(package: "java.lang", name: "Integer") + case .long: + return .class(package: "java.lang", name: "Long") case .float: - return .class(package: nil, name: "Float") + return .class(package: "java.lang", name: "Float") case .double: - return .class(package: nil, name: "Double") + return .class(package: "java.lang", name: "Double") case .void: - return .class(package: nil, name: "Void") + return .class(package: "java.lang", name: "Void") default: return self } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 188f5e8b..a4485fff 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -453,7 +453,7 @@ extension FFMSwift2JavaGenerator { func renderMemoryLayoutValue(for javaType: JavaType) -> String { if let layout = ForeignValueLayout(javaType: javaType) { return layout.description - } else if case .class(package: _, name: let customClass) = javaType { + } else if case .class(package: _, name: let customClass, _) = javaType { return ForeignValueLayout(customType: customClass).description } else { fatalError("renderMemoryLayoutValue not supported for \(javaType)") diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 5ccdf3ad..84cc43c0 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -193,7 +193,7 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } var isAsync: Bool { - self.functionSignature.effectSpecifiers.contains(.async) + self.functionSignature.isAsync } init( diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 6becc806..ce6ec18e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -522,7 +522,7 @@ extension JNISwift2JavaGenerator { // Indirect return receivers for outParameter in translatedFunctionSignature.resultType.outParameters { - printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render());") + printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render(type: outParameter.type));") arguments.append(outParameter.name) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index ed297b66..3f07fad0 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -182,13 +182,13 @@ extension JNISwift2JavaGenerator { } // Swift -> Java - let translatedFunctionSignature = try translate( + var translatedFunctionSignature = try translate( functionSignature: decl.functionSignature, methodName: javaName, parentName: parentName ) // Java -> Java (native) - let nativeFunctionSignature = try nativeTranslation.translate( + var nativeFunctionSignature = try nativeTranslation.translate( functionSignature: decl.functionSignature, translatedFunctionSignature: translatedFunctionSignature, methodName: javaName, @@ -213,6 +213,16 @@ extension JNISwift2JavaGenerator { } } + // Handle async methods + if decl.functionSignature.isAsync { + self.convertToAsync( + translatedFunctionSignature: &translatedFunctionSignature, + nativeFunctionSignature: &nativeFunctionSignature, + originalFunctionSignature: decl.functionSignature, + mode: config.effectiveAsyncFuncMode + ) + } + return TranslatedFunctionDecl( name: javaName, isStatic: decl.isStatic || !decl.hasParent || decl.isInitializer, @@ -281,11 +291,7 @@ extension JNISwift2JavaGenerator { genericRequirements: functionSignature.genericRequirements ) - var resultType = try translate(swiftResult: functionSignature.result) - - if functionSignature.effectSpecifiers.contains(.async) { - resultType = asyncResultConversion(result: resultType, mode: config.effectiveAsyncMode) - } + let resultType = try translate(swiftResult: functionSignature.result) return TranslatedFunctionSignature( selfParameter: selfParameter, @@ -476,49 +482,58 @@ extension JNISwift2JavaGenerator { } } - func asyncResultConversion( - result: TranslatedResult, - mode: JExtractAsyncMode - ) -> TranslatedResult { + func convertToAsync( + translatedFunctionSignature: inout TranslatedFunctionSignature, + nativeFunctionSignature: inout NativeFunctionSignature, + originalFunctionSignature: SwiftFunctionSignature, + mode: JExtractAsyncFuncMode + ) { switch mode { case .completableFuture: - let supplyAsyncBodyConversion: JavaNativeConversionStep = if result.javaType.isVoid { - .aggregate([ - .print(result.conversion), - .null - ]) - } else { - result.conversion - } +// let supplyAsyncBodyConversion: JavaNativeConversionStep = if result.javaType.isVoid { +// .aggregate([ +// .print(result.conversion), +// .null +// ]) +// } else { +// result.conversion +// } + + // Update translated function + + let nativeFutureType = JavaType.completableFuture(nativeFunctionSignature.result.javaType) + + let futureOutParameter = OutParameter( + name: "$future", + type: nativeFutureType, + allocation: .new + ) - return TranslatedResult( - javaType: .class(package: "java.util.concurrent", name: "CompletableFuture<\(result.javaType.wrapperClassIfNeeded)>"), + let result = translatedFunctionSignature.resultType + translatedFunctionSignature.resultType = TranslatedResult( + javaType: .completableFuture(translatedFunctionSignature.resultType.javaType), annotations: result.annotations, - outParameters: result.outParameters, - conversion: .method(.constant("java.util.concurrent.CompletableFuture"), function: "supplyAsync", arguments: [ - .lambda(body: supplyAsyncBodyConversion), - .constant("SwiftAsync.SWIFT_ASYNC_EXECUTOR") + outParameters: result.outParameters + [futureOutParameter], + conversion: .aggregate(variable: nil, [ + .print(.placeholder), // Make the downcall + .method(.constant("$future"), function: "thenApply", arguments: [ + .lambda(args: ["futureResult$"], body: .replacingPlaceholder(result.conversion, placeholder: "futureResult$")) + ]) ]) ) - case .future: - let asyncBodyConversion: JavaNativeConversionStep = if result.javaType.isVoid { - .aggregate([ - .print(result.conversion), - .null - ]) - } else { - result.conversion - } - - return TranslatedResult( - javaType: .class(package: "java.util.concurrent", name: "Future<\(result.javaType.wrapperClassIfNeeded)>"), - annotations: result.annotations, - outParameters: result.outParameters, - conversion: .method(.constant("SwiftAsync.SWIFT_ASYNC_EXECUTOR"), function: "submit", arguments: [ - .lambda(body: asyncBodyConversion) - ]) + // Update native function + nativeFunctionSignature.result.javaType = .void + nativeFunctionSignature.result.conversion = .asyncCompleteFuture( + nativeFunctionSignature.result.conversion, + swiftFunctionResultType: originalFunctionSignature.result.type, + nativeReturnType: nativeFunctionSignature.result.javaType, + outParameters: nativeFunctionSignature.result.outParameters ) + nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType)) + + case .future: + fatalError() } } @@ -866,11 +881,15 @@ extension JNISwift2JavaGenerator { struct OutParameter { enum Allocation { case newArray(JavaType, size: Int) + case new - func render() -> String { + func render(type: JavaType) -> String { switch self { case .newArray(let javaType, let size): "new \(javaType)[\(size)]" + + case .new: + "new \(type)()" } } } @@ -969,8 +988,8 @@ extension JNISwift2JavaGenerator { /// `return value` indirect case `return`(JavaNativeConversionStep) - /// `() -> { return body; }` - indirect case lambda(body: JavaNativeConversionStep) + /// `(args) -> { return body; }` + indirect case lambda(args: [String] = [], body: JavaNativeConversionStep) case null @@ -1094,9 +1113,9 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "return \(inner);" - case .lambda(let body): + case .lambda(let args, let body): var printer = CodePrinter() - printer.printBraceBlock("() ->") { printer in + printer.printBraceBlock("(\(args.joined(separator: ", "))) ->") { printer in let body = body.render(&printer, placeholder) if !body.isEmpty { printer.print("return \(body);") @@ -1167,7 +1186,7 @@ extension JNISwift2JavaGenerator { case .return(let inner): return inner.requiresSwiftArena - case .lambda(let body): + case .lambda(_, let body): return body.requiresSwiftArena case .print(let inner): diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 95884235..f5888c4c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -59,15 +59,7 @@ extension JNISwift2JavaGenerator { nil } - var result = try translate(swiftResult: functionSignature.result) - - if functionSignature.effectSpecifiers.contains(.async) { - result = asyncResultConversion( - result: result, - functionSignature: functionSignature, - mode: config.effectiveAsyncMode - ) - } + let result = try translate(swiftResult: functionSignature.result) return NativeFunctionSignature( selfParameter: nativeSelf, @@ -76,27 +68,6 @@ extension JNISwift2JavaGenerator { ) } - func translateParameters( - _ parameters: [SwiftParameter], - translatedParameters: [TranslatedParameter], - methodName: String, - parentName: String, - genericParameters: [SwiftGenericParameterDeclaration], - genericRequirements: [SwiftGenericRequirement] - ) throws -> [NativeParameter] { - try zip(translatedParameters, parameters).map { translatedParameter, swiftParameter in - let parameterName = translatedParameter.parameter.name - return try translateParameter( - type: swiftParameter.type, - parameterName: parameterName, - methodName: methodName, - parentName: parentName, - genericParameters: genericParameters, - genericRequirements: genericRequirements - ) - } - } - func translateParameter( type: SwiftType, parameterName: String, @@ -514,31 +485,12 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } - - func asyncResultConversion( - result: NativeResult, - functionSignature: SwiftFunctionSignature, - mode: JExtractAsyncMode - ) -> NativeResult { - switch mode { - case .completableFuture, .future: - return NativeResult( - javaType: result.javaType, - conversion: .asyncBlocking( - result.conversion, - isThrowing: functionSignature.effectSpecifiers.contains(.throws), - swiftFunctionResultType: functionSignature.result.type - ), - outParameters: result.outParameters - ) - } - } } struct NativeFunctionSignature { let selfParameter: NativeParameter? - let parameters: [NativeParameter] - let result: NativeResult + var parameters: [NativeParameter] + var result: NativeResult } struct NativeParameter { @@ -551,8 +503,8 @@ extension JNISwift2JavaGenerator { } struct NativeResult { - let javaType: JavaType - let conversion: NativeSwiftConversionStep + var javaType: JavaType + var conversion: NativeSwiftConversionStep /// Out parameters for populating the indirect return values. var outParameters: [JavaParameter] @@ -617,7 +569,12 @@ extension JNISwift2JavaGenerator { indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String) - indirect case asyncBlocking(NativeSwiftConversionStep, isThrowing: Bool, swiftFunctionResultType: SwiftType) + indirect case asyncCompleteFuture( + NativeSwiftConversionStep, + swiftFunctionResultType: SwiftType, + nativeReturnType: JavaType, + outParameters: [JavaParameter] + ) /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { @@ -635,15 +592,15 @@ extension JNISwift2JavaGenerator { case .getJNIValue(let inner): let inner = inner.render(&printer, placeholder) - return "\(inner).getJNIValue(in: environment!)" + return "\(inner).getJNIValue(in: environment)" case .getJValue(let inner): let inner = inner.render(&printer, placeholder) - return "\(inner).getJValue(in: environment!)" + return "\(inner).getJValue(in: environment)" case .initFromJNI(let inner, let swiftType): let inner = inner.render(&printer, placeholder) - return "\(swiftType)(fromJNI: \(inner), in: environment!)" + return "\(swiftType)(fromJNI: \(inner), in: environment)" case .extractSwiftProtocolValue(let inner, let typeMetadataVariableName, let protocolNames): let inner = inner.render(&printer, placeholder) @@ -847,42 +804,85 @@ extension JNISwift2JavaGenerator { ) return unwrappedName - case .asyncBlocking(let inner, let isThrowing, let swiftFunctionResultType): - printer.print("let _semaphore$ = _Semaphore(value: 0)") - let resultType = isThrowing ? "Result<\(swiftFunctionResultType), any Error>" : swiftFunctionResultType.description - printer.print("var swiftResult$: \(resultType)!") - - func printInner(printer: inout CodePrinter) { - if isThrowing { - printer.printBraceBlock("do") { printer in - printer.print("swiftResult$ = await Result.success(\(placeholder))") - } - printer.printBraceBlock("catch") { printer in - printer.print("swiftResult$ = Result.failure(error)") - } - } else { - printer.print("swiftResult$ = await \(placeholder)") - } - printer.print("_semaphore$.signal()") + case .asyncCompleteFuture( + let inner, + let swiftFunctionResultType, + let nativeReturnType, + let outParameters + ): + // Global ref all indirect returns + for outParameter in outParameters { + printer.print("let \(outParameter.name) = environment.interface.NewGlobalRef(environment, \(outParameter.name))") } - printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in - printer.printBraceBlock("Task.immediate") { printer in - printInner(printer: &printer) - } - } - printer.printBraceBlock("else") { printer in - printer.printBraceBlock("Task") { printer in - printInner(printer: &printer) - } - } printer.print( """ - _semaphore$.wait() + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + let completableFutureClazz$ = environment.interface.FindClass(environment, "java/util/concurrent/CompletableFuture")! + let completeMethodID = environment.interface.GetMethodID(environment, completableFutureClazz$, "complete", "(Ljava/lang/Object;)Z")! """ ) - let inner = inner.render(&printer, isThrowing ? "try swiftResult$.get()" : "swiftResult$") - return inner + + printer.printBraceBlock("Task") { printer in + printer.print("let swiftResult$ = await \(placeholder)") + printer.print( + """ + let environment = try JavaVirtualMachine.shared().environment() + """ + ) + printer.printBraceBlock("defer") { printer in + printer.print("environment.interface.DeleteGlobalRef(environment, globalFuture)") + for outParameter in outParameters { + printer.print("environment.interface.DeleteGlobalRef(environment, \(outParameter.name))") + } + } + let inner = inner.render(&printer, "swiftResult$") + if swiftFunctionResultType.isVoid { + printer.print(inner) + } else { + printer.printBraceBlock("withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)])") { printer in + printer.print("environment.interface.CallBooleanMethodV(environment, globalFuture, completeMethodID, $0)") + } + } + } + + return "" + +// printer.print("let _semaphore$ = _Semaphore(value: 0)") +// let resultType = isThrowing ? "Result<\(swiftFunctionResultType), any Error>" : swiftFunctionResultType.description +// printer.print("var swiftResult$: \(resultType)!") +// +// func printInner(printer: inout CodePrinter) { +// if isThrowing { +// printer.printBraceBlock("do") { printer in +// printer.print("swiftResult$ = await Result.success(\(placeholder))") +// } +// printer.printBraceBlock("catch") { printer in +// printer.print("swiftResult$ = Result.failure(error)") +// } +// } else { +// printer.print("swiftResult$ = await \(placeholder)") +// } +// printer.print("_semaphore$.signal()") +// } +// +// printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in +// printer.printBraceBlock("Task.immediate") { printer in +// printInner(printer: &printer) +// } +// } +// printer.printBraceBlock("else") { printer in +// printer.printBraceBlock("Task") { printer in +// printInner(printer: &printer) +// } +// } +// printer.print( +// """ +// _semaphore$.wait() +// """ +// ) +// let inner = inner.render(&printer, isThrowing ? "try swiftResult$.get()" : "swiftResult$") +// return inner } } } diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index 43f5a2b4..2670e6ee 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -24,6 +24,7 @@ struct JavaParameter { switch self { case .concrete(let type): return type.jniTypeSignature + case .generic(_, let extends): guard !extends.isEmpty else { return "Ljava/lang/Object;" diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 02850801..5e5a7268 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -39,4 +39,9 @@ extension JavaType { static var javaLangThrowable: JavaType { .class(package: "java.lang", name: "Throwable") } + + /// The description of the type java.util.concurrent.CompletableFuture + static func completableFuture(_ T: JavaType) -> JavaType { + .class(package: "java.util.concurrent", name: "CompletableFuture", typeParameters: [T.boxedType]) + } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift index 6ffdbff6..fb28586b 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift @@ -14,5 +14,5 @@ enum SwiftEffectSpecifier: Equatable { case `throws` - case async + case `async` } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index cfd09267..7e291c1b 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -30,6 +30,10 @@ public struct SwiftFunctionSignature: Equatable { var genericParameters: [SwiftGenericParameterDeclaration] var genericRequirements: [SwiftGenericRequirement] + var isAsync: Bool { + effectSpecifiers.contains(.async) + } + init( selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], diff --git a/Sources/JavaTypes/JavaType+JNI.swift b/Sources/JavaTypes/JavaType+JNI.swift index 08361bac..b9b00ccb 100644 --- a/Sources/JavaTypes/JavaType+JNI.swift +++ b/Sources/JavaTypes/JavaType+JNI.swift @@ -34,9 +34,9 @@ extension JavaType { case .array(.float): "jfloatArray?" case .array(.double): "jdoubleArray?" case .array: "jobjectArray?" - case .class(package: "java.lang", name: "String"): "jstring?" - case .class(package: "java.lang", name: "Class"): "jclass?" - case .class(package: "java.lang", name: "Throwable"): "jthrowable?" + case .class(package: "java.lang", name: "String", _): "jstring?" + case .class(package: "java.lang", name: "Class", _): "jclass?" + case .class(package: "java.lang", name: "Throwable", _): "jthrowable?" case .class: "jobject?" } } diff --git a/Sources/JavaTypes/JavaType+JavaSource.swift b/Sources/JavaTypes/JavaType+JavaSource.swift index f9e2e0cd..f81f0c6d 100644 --- a/Sources/JavaTypes/JavaType+JavaSource.swift +++ b/Sources/JavaTypes/JavaType+JavaSource.swift @@ -51,9 +51,13 @@ extension JavaType: CustomStringConvertible { case .double: "double" case .void: "void" case .array(let elementType): "\(elementType.description)[]" - case .class(package: let package, name: let name): + case .class(package: let package, name: let name, let typeParameters): if let package { - "\(package).\(name)" + if !typeParameters.isEmpty { + "\(package).\(name)<\(typeParameters.map(\.description).joined(separator: ", "))>" + } else { + "\(package).\(name)" + } } else { name } @@ -64,7 +68,7 @@ extension JavaType: CustomStringConvertible { /// and nil otherwise. public var className: String? { switch self { - case .class(_, let name): + case .class(_, let name, _): return name default: return nil @@ -75,9 +79,9 @@ extension JavaType: CustomStringConvertible { /// and nil otherwise. public var fullyQualifiedClassName: String? { switch self { - case .class(.some(let package), let name): + case .class(.some(let package), let name, _): return "\(package).\(name)" - case .class(nil, let name): + case .class(nil, let name, _): return name default: return nil diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index 20de73fc..7015ef91 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -25,7 +25,7 @@ extension JavaType { .array: return false - case .class(package: "java.lang", name: "String"): + case .class(package: "java.lang", name: "String", _): return !stringIsValueType case .class: @@ -38,7 +38,7 @@ extension JavaType { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .array: return false - case .class(package: "java.lang", name: "Runnable"): + case .class(package: "java.lang", name: "Runnable", _): return true case .class: return false @@ -57,7 +57,7 @@ extension JavaType { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .array: return false - case .class(package: "java.lang", name: "String"): + case .class(package: "java.lang", name: "String", _): return true case .class: return false diff --git a/Sources/JavaTypes/JavaType.swift b/Sources/JavaTypes/JavaType.swift index 2a2d901f..ce7191ba 100644 --- a/Sources/JavaTypes/JavaType.swift +++ b/Sources/JavaTypes/JavaType.swift @@ -28,7 +28,7 @@ public enum JavaType: Equatable, Hashable { /// A Java class separated into its package (e.g., "java.lang") and class name /// (e.g., "Object") - case `class`(package: String?, name: String) + case `class`(package: String?, name: String, typeParameters: [JavaType] = []) /// A Java array. indirect case array(JavaType) diff --git a/Sources/JavaTypes/Mangling.swift b/Sources/JavaTypes/Mangling.swift index f0dbd484..1311b717 100644 --- a/Sources/JavaTypes/Mangling.swift +++ b/Sources/JavaTypes/Mangling.swift @@ -35,7 +35,7 @@ extension JavaType { case .short: "S" case .void: "V" case .array(let elementType): "[" + elementType.mangledName - case .class(package: let package, name: let name): + case .class(package: let package, name: let name, _): "L\(package!).\(name.replacingPeriodsWithDollars());".replacingPeriodsWithSlashes() } } diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 58bfb3aa..6d8d20e5 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -57,9 +57,9 @@ public struct Configuration: Codable { memoryManagementMode ?? .default } - public var asyncMode: JExtractAsyncMode? - public var effectiveAsyncMode: JExtractAsyncMode { - asyncMode ?? .default + public var asyncFuncMode: JExtractAsyncFuncMode? + public var effectiveAsyncFuncMode: JExtractAsyncFuncMode { + asyncFuncMode ?? .default } // ==== java 2 swift --------------------------------------------------------- diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift similarity index 92% rename from Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncMode.swift rename to Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift index 6347ef62..d3133fab 100644 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// /// Configures how Swift `async` functions should be extracted by jextract. -public enum JExtractAsyncMode: String, Codable { +public enum JExtractAsyncFuncMode: String, Codable { /// Extract Swift `async` APIs as Java functions that return `CompletableFuture`s. case completableFuture @@ -26,7 +26,7 @@ public enum JExtractAsyncMode: String, Codable { case future } -extension JExtractAsyncMode { +extension JExtractAsyncFuncMode { public static var `default`: Self { .completableFuture } diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift new file mode 100644 index 00000000..2c0db17d --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift @@ -0,0 +1,91 @@ +import CSwiftJavaJNI +import SwiftJava + +public enum _JNIBoxedConversions { + private static let booleanMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(Z)Ljava/lang/Boolean;", isStatic: true) + private static let byteMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(B)Ljava/lang/Byte;", isStatic: true) + private static let charMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(C)Ljava/lang/Character;", isStatic: true) + private static let shortMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(S)Ljava/lang/Short;", isStatic: true) + private static let intMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(I)Ljava/lang/Integer;", isStatic: true) + private static let longMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(J)Ljava/lang/Long;", isStatic: true) + private static let floatMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(F)Ljava/lang/Float;", isStatic: true) + private static let doubleMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(D)Ljava/lang/Double;", isStatic: true) + + private static let booleanCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Boolean", + methods: [booleanMethod] + ) + + private static let byteCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Byte", + methods: [byteMethod] + ) + private static let charCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Character", + methods: [charMethod] + ) + + private static let shortCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Short", + methods: [shortMethod] + ) + private static let intCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Integer", + methods: [intMethod] + ) + + private static let longCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Long", + methods: [longMethod] + ) + + private static let floatCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Float", + methods: [floatMethod] + ) + + private static let doubleCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Double", + methods: [doubleMethod] + ) + + public static func box(_ value: jboolean, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, booleanCache.javaClass, booleanCache.methods[booleanMethod]!, [jvalue(z: value)])! + } + + public static func box(_ value: jbyte, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, byteCache.javaClass, byteCache.methods[byteMethod]!, [jvalue(b: value)])! + } + + public static func box(_ value: jchar, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, charCache.javaClass, charCache.methods[charMethod]!, [jvalue(c: value)])! + } + + public static func box(_ value: jshort, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, shortCache.javaClass, shortCache.methods[shortMethod]!, [jvalue(s: value)])! + } + + public static func box(_ value: jint, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, intCache.javaClass, intCache.methods[intMethod]!, [jvalue(i: value)])! + } + + public static func box(_ value: jlong, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, longCache.javaClass, longCache.methods[longMethod]!, [jvalue(j: value)])! + } + + public static func box(_ value: jfloat, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, floatCache.javaClass, floatCache.methods[floatMethod]!, [jvalue(f: value)])! + } + + public static func box(_ value: jdouble, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, doubleCache.javaClass, doubleCache.methods[doubleMethod]!, [jvalue(d: value)])! + } +} diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index b813cba8..dd7eb5d1 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -23,10 +23,12 @@ public final class _JNIMethodIDCache: Sendable { public struct Method: Hashable { public let name: String public let signature: String + public let isStatic: Bool - public init(name: String, signature: String) { + public init(name: String, signature: String, isStatic: Bool = false) { self.name = name self.signature = signature + self.isStatic = isStatic } } @@ -43,10 +45,18 @@ public final class _JNIMethodIDCache: Sendable { } self._class = environment.interface.NewGlobalRef(environment, clazz)! self.methods = methods.reduce(into: [:]) { (result, method) in - if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { - result[method] = methodID + if method.isStatic { + if let methodID = environment.interface.GetStaticMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Static method \(method.signature) with signature \(method.signature) not found in class \(className)") + } } else { - fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + } } } } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 045526e4..aba79983 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -79,8 +79,8 @@ extension SwiftJava { ) var dependsOn: [String] = [] - @Option(help: "The mode to use for extracting asynchronous Swift code. By default async methods are extracted as Java functions returning CompletableFuture.") - var asyncMode: JExtractAsyncMode = .default + @Option(help: "The mode to use for extracting asynchronous Swift functions. By default async methods are extracted as Java functions returning CompletableFuture.") + var asyncFuncMode: JExtractAsyncFuncMode = .default } } @@ -101,7 +101,7 @@ extension SwiftJava.JExtractCommand { config.unsignedNumbersMode = unsignedNumbers config.minimumInputAccessLevelMode = minimumInputAccessLevel config.memoryManagementMode = memoryManagementMode - config.asyncMode = asyncMode + config.asyncFuncMode = asyncFuncMode try checkModeCompatibility() @@ -166,4 +166,4 @@ extension JExtractGenerationMode: ExpressibleByArgument {} extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} extension JExtractMinimumAccessLevelMode: ExpressibleByArgument {} extension JExtractMemoryManagementMode: ExpressibleByArgument {} -extension JExtractAsyncMode: ExpressibleByArgument {} +extension JExtractAsyncFuncMode: ExpressibleByArgument {} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java deleted file mode 100644 index 25f49e78..00000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftAsync.java +++ /dev/null @@ -1,31 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -public final class SwiftAsync { - - public static final ExecutorService SWIFT_ASYNC_EXECUTOR = new ForkJoinPool( - Runtime.getRuntime().availableProcessors(), - ForkJoinPool.defaultForkJoinWorkerThreadFactory, - null, - true - ); -} From e0b39303de7cc45e8c4390f68953b5b4d7769c60 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 30 Oct 2025 12:51:45 +0100 Subject: [PATCH 17/26] get upcall working --- Package.swift | 20 +++- .../Sources/MySwiftLibrary/Async.swift | 4 + .../java/com/example/swift/AsyncTest.java | 18 +++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 4 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 3 +- ...wift2JavaGenerator+NativeTranslation.swift | 105 +++++++++--------- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../SwiftTypes/SwiftFunctionSignature.swift | 4 + .../DefaultCaches.swift | 53 +++++++++ .../SwiftJavaRuntimeSupport/_Semaphore.swift | 76 ------------- .../SwiftRuntimeFunctions.swift | 0 SwiftKitCore/build.gradle | 6 +- .../swift/swiftkit/core/SwiftLibraries.java | 10 +- SwiftKitFFM/build.gradle | 6 +- .../org/swift/swiftkit/ffm/SwiftRuntime.java | 8 +- 15 files changed, 169 insertions(+), 150 deletions(-) create mode 100644 Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift delete mode 100644 Sources/SwiftJavaRuntimeSupport/_Semaphore.swift rename Sources/{SwiftJavaRuntimeSupport => SwiftRuntimeFunctions}/SwiftRuntimeFunctions.swift (100%) diff --git a/Package.swift b/Package.swift index c87fdad8..1171133b 100644 --- a/Package.swift +++ b/Package.swift @@ -170,10 +170,15 @@ let package = Package( // Support library written in Swift for SwiftKit "Java" .library( name: "SwiftJavaRuntimeSupport", - type: .dynamic, targets: ["SwiftJavaRuntimeSupport"] ), + .library( + name: "SwiftRuntimeFunctions", + type: .dynamic, + targets: ["SwiftRuntimeFunctions"] + ), + .library( name: "JExtractSwiftLib", targets: ["JExtractSwiftLib"] @@ -214,6 +219,7 @@ let package = Package( dependencies: [ "SwiftJava", "SwiftJavaRuntimeSupport", + "SwiftRuntimeFunctions", ] ), @@ -362,6 +368,18 @@ let package = Package( ] ), + .target( + name: "SwiftRuntimeFunctions", + dependencies: [ + "CSwiftJavaJNI", + "SwiftJava" + ], + swiftSettings: [ + .swiftLanguageMode(.v5), + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + ] + ), + .target( name: "CSwiftJavaJNI", swiftSettings: [ diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift index 40310627..5cedc686 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -33,6 +33,10 @@ public func asyncOptional(i: Int64) async throws -> Int64? { return i } +public func asyncThrows() async throws { + throw MySwiftError.swiftError +} + public func asyncRunningSum() async -> Int64 { let totalSum = await withTaskGroup(of: Int64.self) { group in // 1. Add child tasks to the group diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java index 9e039cec..ae6e7cc7 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java @@ -56,4 +56,22 @@ void asyncCopy() { assertEquals(5, result.getY()); } } + + @Test + void asyncThrows() { + CompletableFuture future = MySwiftLibrary.asyncThrows(); + + ExecutionException ex = assertThrows(ExecutionException.class, future::get); + + Throwable cause = ex.getCause(); + assertNotNull(cause); + assertEquals(Exception.class, cause.getClass()); + assertEquals("swiftError", cause.getMessage()); + } + + @Test + void asyncOptional() { + CompletableFuture future = MySwiftLibrary.asyncOptional(42); + assertEquals(OptionalLong.of(42), future.join()); + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 91bddb5f..f233ef00 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -108,7 +108,7 @@ extension FFMSwift2JavaGenerator { """ // Generated by swift-java - import SwiftJavaRuntimeSupport + import SwiftRuntimeFunctions """) @@ -136,7 +136,7 @@ extension FFMSwift2JavaGenerator { """ // Generated by swift-java - import SwiftJavaRuntimeSupport + import SwiftRuntimeFunctions """ ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 3f07fad0..31cd75a2 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -528,7 +528,8 @@ extension JNISwift2JavaGenerator { nativeFunctionSignature.result.conversion, swiftFunctionResultType: originalFunctionSignature.result.type, nativeReturnType: nativeFunctionSignature.result.javaType, - outParameters: nativeFunctionSignature.result.outParameters + outParameters: nativeFunctionSignature.result.outParameters, + isThrowing: originalFunctionSignature.isThrowing ) nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType)) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index f5888c4c..31c62394 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -573,7 +573,8 @@ extension JNISwift2JavaGenerator { NativeSwiftConversionStep, swiftFunctionResultType: SwiftType, nativeReturnType: JavaType, - outParameters: [JavaParameter] + outParameters: [JavaParameter], + isThrowing: Bool ) /// Returns the conversion string applied to the placeholder. @@ -808,7 +809,8 @@ extension JNISwift2JavaGenerator { let inner, let swiftFunctionResultType, let nativeReturnType, - let outParameters + let outParameters, + let isThrowing ): // Global ref all indirect returns for outParameter in outParameters { @@ -818,71 +820,66 @@ extension JNISwift2JavaGenerator { printer.print( """ let globalFuture = environment.interface.NewGlobalRef(environment, result_future) - let completableFutureClazz$ = environment.interface.FindClass(environment, "java/util/concurrent/CompletableFuture")! - let completeMethodID = environment.interface.GetMethodID(environment, completableFutureClazz$, "complete", "(Ljava/lang/Object;)Z")! """ ) - printer.printBraceBlock("Task") { printer in + func printDo(printer: inout CodePrinter) { printer.print("let swiftResult$ = await \(placeholder)") - printer.print( - """ - let environment = try JavaVirtualMachine.shared().environment() - """ - ) - printer.printBraceBlock("defer") { printer in - printer.print("environment.interface.DeleteGlobalRef(environment, globalFuture)") - for outParameter in outParameters { - printer.print("environment.interface.DeleteGlobalRef(environment, \(outParameter.name))") - } - } + printer.print("environment = try JavaVirtualMachine.shared().environment()") let inner = inner.render(&printer, "swiftResult$") if swiftFunctionResultType.isVoid { - printer.print(inner) + printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") } else { printer.printBraceBlock("withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)])") { printer in - printer.print("environment.interface.CallBooleanMethodV(environment, globalFuture, completeMethodID, $0)") + printer.print("environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0)") } } } - return "" + func printTask(printer: inout CodePrinter) { + printer.printBraceBlock("defer") { printer in + // Defer might on any thread, so we need to attach environment. + printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") + printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)") + for outParameter in outParameters { + printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(outParameter.name))") + } + } + if isThrowing { + printer.printBraceBlock("do") { printer in + printDo(printer: &printer) + } + printer.printBraceBlock("catch") { printer in + // We might not be on the same thread after the suspension, so we need to attach the thread again. + printer.print( + """ + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + """ + ) + } + } else { + printDo(printer: &printer) + } + } -// printer.print("let _semaphore$ = _Semaphore(value: 0)") -// let resultType = isThrowing ? "Result<\(swiftFunctionResultType), any Error>" : swiftFunctionResultType.description -// printer.print("var swiftResult$: \(resultType)!") -// -// func printInner(printer: inout CodePrinter) { -// if isThrowing { -// printer.printBraceBlock("do") { printer in -// printer.print("swiftResult$ = await Result.success(\(placeholder))") -// } -// printer.printBraceBlock("catch") { printer in -// printer.print("swiftResult$ = Result.failure(error)") -// } -// } else { -// printer.print("swiftResult$ = await \(placeholder)") -// } -// printer.print("_semaphore$.signal()") -// } -// -// printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in -// printer.printBraceBlock("Task.immediate") { printer in -// printInner(printer: &printer) -// } -// } -// printer.printBraceBlock("else") { printer in -// printer.printBraceBlock("Task") { printer in -// printInner(printer: &printer) -// } -// } -// printer.print( -// """ -// _semaphore$.wait() -// """ -// ) -// let inner = inner.render(&printer, isThrowing ? "try swiftResult$.get()" : "swiftResult$") -// return inner + printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in + printer.printBraceBlock("Task.immediate") { printer in + // Immediate runs on the caller thread, so we don't need to attach the environment again. + printer.print("var environment = environment!") // this is to ensure we always use the same environment name, even though we are rebinding it. + printTask(printer: &printer) + } + } + printer.printBraceBlock("else") { printer in + printer.printBraceBlock("Task") { printer in + // We can be on any thread, so we need to attach the thread. + printer.print("var environment = try! JavaVirtualMachine.shared().environment()") + printTask(printer: &printer) + } + } + + return "" } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 85e78a78..5403ae40 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -363,7 +363,7 @@ extension JNISwift2JavaGenerator { } } - if decl.isThrowing { + if decl.isThrowing, !decl.isAsync { let dummyReturn: String if nativeSignature.result.javaType.isVoid { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 7e291c1b..1f2fbd36 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -34,6 +34,10 @@ public struct SwiftFunctionSignature: Equatable { effectSpecifiers.contains(.async) } + var isThrowing: Bool { + effectSpecifiers.contains(.throws) + } + init( selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], diff --git a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift new file mode 100644 index 00000000..3f8e5a0b --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift @@ -0,0 +1,53 @@ +import SwiftJava + +extension _JNIMethodIDCache { + public enum CompletableFuture { + private static let completeMethod = Method( + name: "complete", + signature: "(Ljava/lang/Object;)Z" + ) + + private static let completeExceptionallyMethod = Method( + name: "completeExceptionally", + signature: "(Ljava/lang/Throwable;)Z" + ) + + private static let cache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/util/concurrent/CompletableFuture", + methods: [completeMethod, completeExceptionallyMethod] + ) + + public static var `class`: jclass { + cache.javaClass + } + + /// CompletableFuture.complete(T) + public static var complete: jmethodID { + cache.methods[completeMethod]! + } + + /// CompletableFuture.completeExceptionally(Throwable) + public static var completeExceptionally: jmethodID { + cache.methods[completeExceptionallyMethod]! + } + } + + public enum Exception { + private static let messageConstructor = Method(name: "", signature: "(Ljava/lang/String;)V") + + private static let cache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Exception", + methods: [messageConstructor] + ) + + public static var `class`: jclass { + cache.javaClass + } + + public static var constructWithMessage: jmethodID { + cache.methods[messageConstructor]! + } + } +} diff --git a/Sources/SwiftJavaRuntimeSupport/_Semaphore.swift b/Sources/SwiftJavaRuntimeSupport/_Semaphore.swift deleted file mode 100644 index 4db9e61b..00000000 --- a/Sources/SwiftJavaRuntimeSupport/_Semaphore.swift +++ /dev/null @@ -1,76 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#if canImport(Dispatch) -import Dispatch -#elseif canImport(Glibc) -@preconcurrency import Glibc -#elseif canImport(Musl) -@preconcurrency import Musl -#elseif canImport(Bionic) -@preconcurrency import Bionic -#elseif canImport(WASILibc) -@preconcurrency import WASILibc -#if canImport(wasi_pthread) -import wasi_pthread -#endif -#else -#error("The module was unable to identify your C library.") -#endif - -public final class _Semaphore: @unchecked Sendable { - #if canImport(Dispatch) - private let sem: DispatchSemaphore - #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) - private let sem: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1) - #endif - - /// Creates new counting semaphore with an initial value. - public init(value: Int) { - #if canImport(Dispatch) - self.sem = DispatchSemaphore(value: value) - #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) - let err = sem_init(self.sem, 0, UInt32(value)) - precondition(err == 0, "\(#function) failed in sem with error \(err)") - #endif - } - - deinit { - #if !canImport(Dispatch) && ((compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))) - let err = sem_destroy(self.sem) - precondition(err == 0, "\(#function) failed in sem with error \(err)") - self.sem.deallocate() - #endif - } - - /// Waits for, or decrements, a semaphore. - public func wait() { - #if canImport(Dispatch) - sem.wait() - #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) - let err = sem_wait(self.sem) - precondition(err == 0, "\(#function) failed in sem with error \(err)") - #endif - } - - /// Signals (increments) a semaphore. - public func signal() { - #if canImport(Dispatch) - _ = sem.signal() - #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) - let err = sem_post(self.sem) - precondition(err == 0, "\(#function) failed in sem with error \(err)") - #endif - } -} diff --git a/Sources/SwiftJavaRuntimeSupport/SwiftRuntimeFunctions.swift b/Sources/SwiftRuntimeFunctions/SwiftRuntimeFunctions.swift similarity index 100% rename from Sources/SwiftJavaRuntimeSupport/SwiftRuntimeFunctions.swift rename to Sources/SwiftRuntimeFunctions/SwiftRuntimeFunctions.swift diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 11312cae..f4376e94 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -73,10 +73,10 @@ tasks.test { } } -// SwiftKit depends on SwiftJavaRuntimeSupport (Swift library that this Java library calls into) +// SwiftKit depends on SwiftRuntimeFunctions (Swift library that this Java library calls into) def compileSwift = tasks.register("compileSwift", Exec) { - description "Compile the swift-java SwiftJavaRuntimeSupport dynamic library that SwiftKit (Java) calls into" + description "Compile the swift-java SwiftRuntimeFunctions dynamic library that SwiftKit (Java) calls into" inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes @@ -84,7 +84,7 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftJavaRuntimeSupport") + args("build", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java index e9609222..e045432c 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -27,7 +27,7 @@ public final class SwiftLibraries { // Library names of core Swift and SwiftKit public static final String LIB_NAME_SWIFT_CORE = "swiftCore"; public static final String LIB_NAME_SWIFT_CONCURRENCY = "swift_Concurrency"; - public static final String LIB_NAME_SWIFT_JAVA_RUNTIME_SUPPORT = "SwiftJavaRuntimeSupport"; + public static final String LIB_NAME_SWIFT_RUNTIME_FUNCTIONS = "SwiftRuntimeFunctions"; /** * Allows for configuration if jextracted types should automatically attempt to load swiftCore and the library type is from. @@ -42,10 +42,10 @@ public final class SwiftLibraries { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); - public static boolean loadLibraries(boolean loadSwiftJavaRuntimeSupport) { - System.loadLibrary(LIB_NAME_SWIFT_JAVA_RUNTIME_SUPPORT); - if (loadSwiftJavaRuntimeSupport) { - System.loadLibrary(LIB_NAME_SWIFT_JAVA_RUNTIME_SUPPORT); + public static boolean loadLibraries(boolean loadSwiftRuntimeFunctions) { + System.loadLibrary(LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); + if (loadSwiftRuntimeFunctions) { + System.loadLibrary(LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); } return true; } diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle index 2967f1e2..dcbbe8df 100644 --- a/SwiftKitFFM/build.gradle +++ b/SwiftKitFFM/build.gradle @@ -75,10 +75,10 @@ tasks.withType(JavaCompile).configureEach { options.compilerArgs.add("-Xlint:preview") } -// SwiftKit depends on SwiftJavaRuntimeSupport (Swift library that this Java library calls into) +// SwiftKit depends on SwiftRuntimeFunctions (Swift library that this Java library calls into) def compileSwift = tasks.register("compileSwift", Exec) { - description "Compile the swift-java SwiftJavaRuntimeSupport dynamic library that SwiftKit (Java) calls into" + description "Compile the swift-java SwiftRuntimeFunctions dynamic library that SwiftKit (Java) calls into" inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes @@ -86,7 +86,7 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftJavaRuntimeSupport") + args("build", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index b47a724c..74a270c0 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -33,7 +33,7 @@ public class SwiftRuntime { public static final String STDLIB_DYLIB_NAME = "swiftCore"; - public static final String SWIFT_JAVA_RUNTIME_SUPPORT_DYLIB_NAME = "SwiftJavaRuntimeSupport"; + public static final String SWIFT_RUNTIME_FUNCTIONS_DYLIB_NAME = "SwiftRuntimeFunctions"; private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; @@ -42,10 +42,10 @@ public class SwiftRuntime { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); - public static boolean loadLibraries(boolean loadSwiftJavaRuntimeSupport) { + public static boolean loadLibraries(boolean loadSwiftRuntimeFunctions) { System.loadLibrary(STDLIB_DYLIB_NAME); - if (loadSwiftJavaRuntimeSupport) { - System.loadLibrary(SWIFT_JAVA_RUNTIME_SUPPORT_DYLIB_NAME); + if (loadSwiftRuntimeFunctions) { + System.loadLibrary(SWIFT_RUNTIME_FUNCTIONS_DYLIB_NAME); } return true; } From e7c86195277bd57f89a941ea3f1df0410868f6f0 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 30 Oct 2025 13:02:46 +0100 Subject: [PATCH 18/26] remove force unwrap of JNI environment --- ...wift2JavaGenerator+NativeTranslation.swift | 16 +++++++------- .../JNI/JNIAsyncTests.swift | 10 ++++----- .../JNI/JNIClassTests.swift | 20 ++++++++--------- .../JNI/JNIClosureTests.swift | 14 ++++++------ .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 14 ++++++------ .../JNI/JNIJavaKitTests.swift | 2 +- .../JNI/JNIModuleTests.swift | 8 +++---- .../JNI/JNIOptionalTests.swift | 14 ++++++------ .../JNI/JNIProtocolTests.swift | 16 +++++++------- .../JNI/JNIStructTests.swift | 8 +++---- .../JNI/JNIVariablesTests.swift | 22 +++++++++---------- 11 files changed, 72 insertions(+), 72 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 31c62394..45208d7b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -526,7 +526,7 @@ extension JNISwift2JavaGenerator { /// `value.getJValue(in:)` indirect case getJValue(NativeSwiftConversionStep) - /// `SwiftType(from: value, in: environment!)` + /// `SwiftType(from: value, in: environment)` indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) indirect case extractSwiftProtocolValue( @@ -613,11 +613,11 @@ extension JNISwift2JavaGenerator { // TODO: Remove the _openExistential when we decide to only support language mode v6+ printer.print( """ - guard let \(inner)TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: \(typeMetadataVariableName), in: environment!))) else { + guard let \(inner)TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: \(typeMetadataVariableName), in: environment))) else { fatalError("\(typeMetadataVariableName) memory address was null") } let \(inner)DynamicType$: Any.Type = unsafeBitCast(\(inner)TypeMetadataPointer$, to: Any.Type.self) - guard let \(inner)RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: \(inner), in: environment!))) else { + guard let \(inner)RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: \(inner), in: environment))) else { fatalError("\(inner) memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -640,7 +640,7 @@ extension JNISwift2JavaGenerator { } printer.print( """ - let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment!)) + let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment)) let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) """ ) @@ -698,13 +698,13 @@ extension JNISwift2JavaGenerator { printer.print( """ - let class$ = environment!.interface.GetObjectClass(environment, \(placeholder)) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")! + let class$ = environment.interface.GetObjectClass(environment, \(placeholder)) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")! let arguments$: [jvalue] = [\(arguments.joined(separator: ", "))] """ ) - let upcall = "environment!.interface.\(nativeResult.javaType.jniCallMethodAName)(environment, \(placeholder), methodID$, arguments$)" + let upcall = "environment.interface.\(nativeResult.javaType.jniCallMethodAName)(environment, \(placeholder), methodID$, arguments$)" let result = nativeResult.conversion.render(&printer, upcall) if nativeResult.javaType.isVoid { @@ -720,7 +720,7 @@ extension JNISwift2JavaGenerator { case .initializeJavaKitWrapper(let inner, let wrapperName): let inner = inner.render(&printer, placeholder) - return "\(wrapperName)(javaThis: \(inner), environment: environment!)" + return "\(wrapperName)(javaThis: \(inner), environment: environment)" case .optionalLowering(let valueConversion, let discriminatorName, let valueName): let value = valueConversion.render(&printer, valueName) diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index a20cce32..e6f24797 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -196,18 +196,18 @@ struct JNIAsyncTests { var swiftResult$: Int64! if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { Task.immediate { - swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment!)) + swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) _semaphore$.signal() } } else { Task { - swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment!)) + swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) _semaphore$.signal() } } _semaphore$.wait() - return swiftResult$.getJNIValue(in: environment!) + return swiftResult$.getJNIValue(in: environment) } """ ] @@ -261,7 +261,7 @@ struct JNIAsyncTests { @_cdecl("Java_com_example_swift_SwiftModule__00024async__J") func Java_com_example_swift_SwiftModule__00024async__J(environment: UnsafeMutablePointer!, thisClass: jclass, c: jlong) -> jlong { assert(c != 0, "c memory address was null") - let cBits$ = Int(Int64(fromJNI: c, in: environment!)) + let cBits$ = Int(Int64(fromJNI: c, in: environment)) let c$ = UnsafeMutablePointer(bitPattern: cBits$) guard let c$ else { fatalError("c memory address was null in call to \\(#function)!") @@ -284,7 +284,7 @@ struct JNIAsyncTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: swiftResult$) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index a7527aa8..c3102a62 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -221,9 +221,9 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024init__JJ") func Java_com_example_swift_MyClass__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyClass.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + result$.initialize(to: MyClass.init(x: Int64(fromJNI: x, in: environment), y: Int64(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @@ -232,7 +232,7 @@ struct JNIClassTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: MyClass.init()) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, ] @@ -303,12 +303,12 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024doSomething__JJ") func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment)) } """ ] @@ -352,7 +352,7 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024copy__J") func Java_com_example_swift_MyClass__00024copy__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") @@ -360,7 +360,7 @@ struct JNIClassTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: self$.pointee.copy()) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ] @@ -404,18 +404,18 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024isEqual__JJ") func Java_com_example_swift_MyClass__00024isEqual__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, other: jlong, self: jlong) -> jboolean { assert(other != 0, "other memory address was null") - let otherBits$ = Int(Int64(fromJNI: other, in: environment!)) + let otherBits$ = Int(Int64(fromJNI: other, in: environment)) let other$ = UnsafeMutablePointer(bitPattern: otherBits$) guard let other$ else { fatalError("other memory address was null in call to \\(#function)!") } assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) + return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index b374d24e..b54749c1 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -63,10 +63,10 @@ struct JNIClosureTests { @_cdecl("Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2") func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.emptyClosure(closure: { - let class$ = environment!.interface.GetObjectClass(environment, closure) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "()V")! + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "()V")! let arguments$: [jvalue] = [] - environment!.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) + environment.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) } ) } @@ -115,10 +115,10 @@ struct JNIClosureTests { @_cdecl("Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2") func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.closureWithArgumentsAndReturn(closure: { _0, _1 in - let class$ = environment!.interface.GetObjectClass(environment, closure) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! - let arguments$: [jvalue] = [_0.getJValue(in: environment!), _1.getJValue(in: environment!)] - return Int64(fromJNI: environment!.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment!) + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! + let arguments$: [jvalue] = [_0.getJValue(in: environment), _1.getJValue(in: environment)] + return Int64(fromJNI: environment.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment) } ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index c6aaf923..ef422e42 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -224,25 +224,25 @@ struct JNIEnumTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: MyEnum.first) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2") func Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg0: jstring?) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyEnum.second(String(fromJNI: arg0, in: environment!))) + result$.initialize(to: MyEnum.second(String(fromJNI: arg0, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyEnum__00024third__JI") func Java_com_example_swift_MyEnum__00024third__JI(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jint) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment!), y: Int32(fromJNI: y, in: environment!))) + result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment), y: Int32(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ]) @@ -302,7 +302,7 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(Ljava/lang/String;)V") let constructorID$ = cache$[method$] - return withVaList([_0.getJNIValue(in: environment!) ?? 0]) { + return withVaList([_0.getJNIValue(in: environment) ?? 0]) { return environment.interface.NewObjectV(environment, class$, constructorID$, $0) } } @@ -318,7 +318,7 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JI)V") let constructorID$ = cache$[method$] - return withVaList([x.getJNIValue(in: environment!), y.getJNIValue(in: environment!)]) { + return withVaList([x.getJNIValue(in: environment), y.getJNIValue(in: environment)]) { return environment.interface.NewObjectV(environment, class$, constructorID$, $0) } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift index ac6b8384..69d77b73 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift @@ -71,7 +71,7 @@ struct JNIJavaKitTests { guard let javaInteger_unwrapped$ = javaInteger else { fatalError("javaInteger was null in call to \\(#function), but Swift requires non-optional!") } - SwiftModule.function(javaLong: JavaLong(javaThis: javaLong_unwrapped$, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger_unwrapped$, environment: environment!), int: Int64(fromJNI: int, in: environment!)) + SwiftModule.function(javaLong: JavaLong(javaThis: javaLong_unwrapped$, environment: environment), javaInteger: JavaInteger(javaThis: javaInteger_unwrapped$, environment: environment), int: Int64(fromJNI: int, in: environment)) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 27b0cdea..dddf1147 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -130,13 +130,13 @@ struct JNIModuleTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ") func Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { - return SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int64(fromJNI: i4, in: environment!)).getJNIValue(in: environment!) + return SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment), i2: Int16(fromJNI: i2, in: environment), i3: Int32(fromJNI: i3, in: environment), i4: Int64(fromJNI: i4, in: environment)).getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD") func Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) { - SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment!), f: Float(fromJNI: f, in: environment!), d: Double(fromJNI: d, in: environment!)) + SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment), f: Float(fromJNI: f, in: environment), d: Double(fromJNI: d, in: environment)) } """ ] @@ -179,7 +179,7 @@ struct JNIModuleTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2") func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { - return SwiftModule.copy(String(fromJNI: string, in: environment!)).getJNIValue(in: environment!) + return SwiftModule.copy(String(fromJNI: string, in: environment)).getJNIValue(in: environment) } """, ] @@ -247,7 +247,7 @@ struct JNIModuleTests { @_cdecl("Java_com_example_swift_SwiftModule__00024methodB__") func Java_com_example_swift_SwiftModule__00024methodB__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { do { - return try SwiftModule.methodB().getJNIValue(in: environment!) + return try SwiftModule.methodB().getJNIValue(in: environment) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index be2e0f6a..6c931d7b 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -72,10 +72,10 @@ struct JNIOptionalTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024optionalSugar__BJ") func Java_com_example_swift_SwiftModule__00024optionalSugar__BJ(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jlong) -> jlong { - let result_value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment!) : nil).map { + let result_value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment) : nil).map { Int64($0) << 32 | Int64(1) } ?? 0 - return result_value$.getJNIValue(in: environment!) + return result_value$.getJNIValue(in: environment) } """ ] @@ -123,8 +123,8 @@ struct JNIOptionalTests { @_cdecl("Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B") func Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jstring?, result_discriminator$: jbyteArray?) -> jstring? { let result$: jstring? - if let innerResult$ = SwiftModule.optionalExplicit(arg_discriminator == 1 ? String(fromJNI: arg_value, in: environment!) : nil) { - result$ = innerResult$.getJNIValue(in: environment!) + if let innerResult$ = SwiftModule.optionalExplicit(arg_discriminator == 1 ? String(fromJNI: arg_value, in: environment) : nil) { + result$ = innerResult$.getJNIValue(in: environment) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) } @@ -180,14 +180,14 @@ struct JNIOptionalTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024optionalClass__J_3B") func Java_com_example_swift_SwiftModule__00024optionalClass__J_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jlong, result_discriminator$: jbyteArray?) -> jlong { - let argBits$ = Int(Int64(fromJNI: arg, in: environment!)) + let argBits$ = Int(Int64(fromJNI: arg, in: environment)) let arg$ = UnsafeMutablePointer(bitPattern: argBits$) let result$: jlong if let innerResult$ = SwiftModule.optionalClass(arg$?.pointee) { let _result$ = UnsafeMutablePointer.allocate(capacity: 1) _result$.initialize(to: innerResult$) let _resultBits$ = Int64(Int(bitPattern: _result$)) - result$ = _resultBits$.getJNIValue(in: environment!) + result$ = _resultBits$.getJNIValue(in: environment) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) } @@ -242,7 +242,7 @@ struct JNIOptionalTests { @_cdecl("Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2") func Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jobject?) { SwiftModule.optionalJavaKitClass(arg.map { - return JavaLong(javaThis: $0, environment: environment!) + return JavaLong(javaThis: $0, environment: environment) } ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index b5a0fcdb..c5302ca6 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -107,11 +107,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ") func Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong, y: jlong, y_typeMetadataAddress: jlong) { - guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else { fatalError("x_typeMetadataAddress memory address was null") } let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) - guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else { fatalError("x memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -122,11 +122,11 @@ struct JNIProtocolTests { } let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad) #endif - guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment!))) else { + guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment))) else { fatalError("y_typeMetadataAddress memory address was null") } let yDynamicType$: Any.Type = unsafeBitCast(yTypeMetadataPointer$, to: Any.Type.self) - guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment!))) else { + guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment))) else { fatalError("y memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -172,11 +172,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeGeneric__JJ") func Java_com_example_swift_SwiftModule__00024takeGeneric__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, s: jlong, s_typeMetadataAddress: jlong) { - guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment!))) else { + guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment))) else { fatalError("s_typeMetadataAddress memory address was null") } let sDynamicType$: Any.Type = unsafeBitCast(sTypeMetadataPointer$, to: Any.Type.self) - guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment!))) else { + guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment))) else { fatalError("s memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -222,11 +222,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeComposite__JJ") func Java_com_example_swift_SwiftModule__00024takeComposite__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong) { - guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else { fatalError("x_typeMetadataAddress memory address was null") } let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) - guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else { fatalError("x memory address was null") } #if hasFeature(ImplicitOpenExistentials) diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index a7c689aa..f8830b64 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -142,9 +142,9 @@ struct JNIStructTests { @_cdecl("Java_com_example_swift_MyStruct__00024init__JJ") func Java_com_example_swift_MyStruct__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyStruct.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + result$.initialize(to: MyStruct.init(x: Int64(fromJNI: x, in: environment), y: Int64(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ] @@ -214,12 +214,12 @@ struct JNIStructTests { @_cdecl("Java_com_example_swift_MyStruct__00024doSomething__JJ") func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment)) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index c9d313a5..363c117d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -70,7 +70,7 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.constant.getJNIValue(in: environment!) + return self$.pointee.constant.getJNIValue(in: environment) } """ ] @@ -135,7 +135,7 @@ struct JNIVariablesTests { fatalError("self memory address was null in call to \\(#function)!") } ... - return self$.pointee.mutable.getJNIValue(in: environment!) + return self$.pointee.mutable.getJNIValue(in: environment) } """, """ @@ -143,7 +143,7 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") ... - self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) + self$.pointee.mutable = Int64(fromJNI: newValue, in: environment) } """ ] @@ -188,7 +188,7 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.computed.getJNIValue(in: environment!) + return self$.pointee.computed.getJNIValue(in: environment) } """, ] @@ -234,7 +234,7 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... do { - return try self$.pointee.computedThrowing.getJNIValue(in: environment!) + return try self$.pointee.computedThrowing.getJNIValue(in: environment) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue @@ -297,14 +297,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.getterAndSetter.getJNIValue(in: environment!) + return self$.pointee.getterAndSetter.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { ... - self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) + self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment) } """ ] @@ -363,14 +363,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { ... - return self$.pointee.someBoolean.getJNIValue(in: environment!) + return self$.pointee.someBoolean.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { ... - self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) + self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment) } """ ] @@ -429,14 +429,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024isBoolean__J") func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { ... - return self$.pointee.isBoolean.getJNIValue(in: environment!) + return self$.pointee.isBoolean.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setBoolean__ZJ") func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { ... - self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment!) + self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment) } """ ] From c94b250de037bcacd1942c5d245f7f71e7505526 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 30 Oct 2025 13:06:43 +0100 Subject: [PATCH 19/26] small cleanups --- ...ISwift2JavaGenerator+JavaTranslation.swift | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 31cd75a2..ee4af3be 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -490,15 +490,6 @@ extension JNISwift2JavaGenerator { ) { switch mode { case .completableFuture: -// let supplyAsyncBodyConversion: JavaNativeConversionStep = if result.javaType.isVoid { -// .aggregate([ -// .print(result.conversion), -// .null -// ]) -// } else { -// result.conversion -// } - // Update translated function let nativeFutureType = JavaType.completableFuture(nativeFunctionSignature.result.javaType) @@ -986,14 +977,9 @@ extension JNISwift2JavaGenerator { /// Access a member of the value indirect case replacingPlaceholder(JavaNativeConversionStep, placeholder: String) - /// `return value` - indirect case `return`(JavaNativeConversionStep) - /// `(args) -> { return body; }` indirect case lambda(args: [String] = [], body: JavaNativeConversionStep) - case null - /// Prints the conversion step, ignoring the output. indirect case print(JavaNativeConversionStep) @@ -1139,7 +1125,7 @@ extension JNISwift2JavaGenerator { /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder, .constant, .isOptionalPresent, .combinedName, .null: + case .placeholder, .constant, .isOptionalPresent, .combinedName: return false case .constructSwiftValue, .wrapMemoryAddressUnsafe: @@ -1184,9 +1170,6 @@ extension JNISwift2JavaGenerator { case .replacingPlaceholder(let inner, _): return inner.requiresSwiftArena - case .return(let inner): - return inner.requiresSwiftArena - case .lambda(_, let body): return body.requiresSwiftArena From 736a48d3a7af387ee0c6a7d74d8894fa450ae74b Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 30 Oct 2025 14:13:09 +0100 Subject: [PATCH 20/26] update docs --- .../Documentation.docc/SupportedFeatures.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 4f585ec7..e97ed03e 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -57,7 +57,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Typed throws: `func x() throws(E)` | ❌ | ❌ | | Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | ✅ | | Computed properties: `var` (incl. `throws`) | ✅ / TODO | ✅ | -| Async functions `func async` and properties: `var { get async {} }` | ❌ | ❌ | +| Async functions `func async` and properties: `var { get async {} }` | ❌ | ✅ | | Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | | Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | | Generic parameters in functions: `func f(x: T)` | ❌ | ✅ | @@ -97,7 +97,6 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Result builders | ❌ | ❌ | | Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | | Value semantic types (e.g. struct copying) | ❌ | ❌ | -| Swift concurrency: `func async`, `actor`, `distribued actor` | ❌ | ❌ | | | | | | | | | @@ -329,18 +328,20 @@ which conform a to a given Swift protocol. #### Returning protocol types Protocols are not yet supported as return types. -### `async` methods +### `async` functions -> Note: Importing `async` methods is currently only available in the JNI mode of jextract. +> Note: Importing `async` functions is currently only available in the JNI mode of jextract. -Asynchronous methods in Swift can be extraced using different modes, which are explained in detail below. +Asynchronous functions in Swift can be extraced using different modes, which are explained below. -#### Async mode: completable-future (default) +#### Async function mode: completable-future (default) -In this mode `async` methods in Swift are extracted as methods returning a `java.util.concurrent.CompletableFuture`. +In this mode `async` functions in Swift are extracted as Java methods returning a `java.util.concurrent.CompletableFuture`. This mode gives the most flexibility and should be prefered if your platform supports `CompletableFuture`. #### Async mode: future This is a mode for legacy platforms, where `CompletableFuture` is not available, such as Android 23 and below. -In this mode `async` methods in Swift are extracted as methods returning a `java.util.concurrent.Future`. +In this mode `async` functions in Swift are extracted as Java methods returning a `java.util.concurrent.Future`. +To enable this mode pass the `--async-func-mode future` command line option, +or set the `asyncFuncMode` configuration value in `swift-java.config` From 23b837e3f35b19ab3e441f3fe779c57c1fa4c2c2 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 30 Oct 2025 16:52:05 +0100 Subject: [PATCH 21/26] fix codegen tests --- .../Sources/MySwiftLibrary/Async.swift | 25 -- ...ISwift2JavaGenerator+JavaTranslation.swift | 7 - .../DefaultCaches.swift | 14 ++ .../_JNIBoxedConversions.swift | 14 ++ .../JNI/JNIAsyncTests.swift | 219 +++++++++++------- 5 files changed, 165 insertions(+), 114 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift index 5cedc686..ebef1892 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -36,28 +36,3 @@ public func asyncOptional(i: Int64) async throws -> Int64? { public func asyncThrows() async throws { throw MySwiftError.swiftError } - -public func asyncRunningSum() async -> Int64 { - let totalSum = await withTaskGroup(of: Int64.self) { group in - // 1. Add child tasks to the group - for number in stride(from: Int64(1), through: 100, by: 1) { - group.addTask { - try? await Task.sleep(for: .milliseconds(number)) - return number - } - } - - var collectedSum: Int64 = 0 - - // `for await ... in group` loops as each child task completes, - // (not necessarily in the order they were added). - for await number in group { - collectedSum += number - } - - return collectedSum - } - - // This is the value returned by the `withTaskGroup` closure. - return totalSum -} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index ee4af3be..500b9b0f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -1096,10 +1096,6 @@ extension JNISwift2JavaGenerator { case .replacingPlaceholder(let inner, let placeholder): return inner.render(&printer, placeholder) - case .return(let inner): - let inner = inner.render(&printer, placeholder) - return "return \(inner);" - case .lambda(let args, let body): var printer = CodePrinter() printer.printBraceBlock("(\(args.joined(separator: ", "))) ->") { printer in @@ -1112,9 +1108,6 @@ extension JNISwift2JavaGenerator { } return printer.finalize() - case .null: - return "null" - case .print(let inner): let inner = inner.render(&printer, placeholder) printer.print("\(inner);") diff --git a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift index 3f8e5a0b..1c3079bc 100644 --- a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift +++ b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift @@ -1,3 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// 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 extension _JNIMethodIDCache { diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift index 2c0db17d..68d98ffc 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift @@ -1,3 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// 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 CSwiftJavaJNI import SwiftJava diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index e6f24797..5f488192 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -32,16 +32,17 @@ struct JNIAsyncTests { * public func asyncVoid() async * } */ - public static java.util.concurrent.CompletableFuture asyncVoid() { - return java.util.concurrent.CompletableFuture.supplyAsync(() -> { - SwiftModule.$asyncVoid(); - return null; + public static java.util.concurrent.CompletableFuture asyncVoid() { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$asyncVoid($future); + return $future.thenApply((futureResult$) -> { + return futureResult$; } ); } """, """ - private static native void $asyncVoid(); + private static native void $asyncVoid(java.util.concurrent.CompletableFuture result_future); """, ] ) @@ -55,24 +56,33 @@ struct JNIAsyncTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__") - func Java_com_example_swift_SwiftModule__00024asyncVoid__(environment: UnsafeMutablePointer!, thisClass: jclass) { - let _semaphore$ = _Semaphore(value: 0) - var swiftResult$: ()! + @_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { Task.immediate { - swiftResult$ = await SwiftModule.asyncVoid() - _semaphore$.signal() + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.asyncVoid() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } } else { Task { - swiftResult$ = await SwiftModule.asyncVoid() - _semaphore$.signal() + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.asyncVoid() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } } - _semaphore$.wait() - swiftResult$ } """ ] @@ -93,16 +103,17 @@ struct JNIAsyncTests { * public func async() async throws * } */ - public static java.util.concurrent.CompletableFuture async() { - return java.util.concurrent.CompletableFuture.supplyAsync(() -> { - SwiftModule.$async(); - return null; + public static java.util.concurrent.CompletableFuture async() { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async($future); + return $future.thenApply((futureResult$) -> { + return futureResult$; } ); } """, """ - private static native void $async(); + private static native void $async(java.util.concurrent.CompletableFuture result_future); """, ] ) @@ -116,39 +127,48 @@ struct JNIAsyncTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024async__") - func Java_com_example_swift_SwiftModule__00024async__(environment: UnsafeMutablePointer!, thisClass: jclass) { - do { - let _semaphore$ = _Semaphore(value: 0) - var swiftResult$: Result<(), any Error>! - if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { - do { - swiftResult$ = await Result.success(try SwiftModule.async()) - } - catch { - swiftResult$ = Result.failure(error) - } - _semaphore$.signal() + @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + do { + let swiftResult$ = await try SwiftModule.async() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + catch { + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) } } - else { - Task { - do { - swiftResult$ = await Result.success(try SwiftModule.async()) - } - catch { - swiftResult$ = Result.failure(error) - } - _semaphore$.signal() + } + else { + Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + do { + let swiftResult$ = await try SwiftModule.async() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + catch { + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) } } - _semaphore$.wait() - try swiftResult$.get() - } catch { - environment.throwAsException(error) - } + } """ ] ) @@ -168,15 +188,17 @@ struct JNIAsyncTests { * public func async(i: Int64) async -> Int64 * } */ - public static java.util.concurrent.CompletableFuture async(long i) { - return java.util.concurrent.CompletableFuture.supplyAsync(() -> { - return SwiftModule.$async(i); + public static java.util.concurrent.CompletableFuture async(long i) { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(i, $future); + return $future.thenApply((futureResult$) -> { + return futureResult$; } ); } """, """ - private static native long $async(long i); + private static native void $async(long i, java.util.concurrent.CompletableFuture result_future); """, ] ) @@ -190,24 +212,38 @@ struct JNIAsyncTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024async__J") - func Java_com_example_swift_SwiftModule__00024async__J(environment: UnsafeMutablePointer!, thisClass: jclass, i: jlong) -> jlong { - let _semaphore$ = _Semaphore(value: 0) - var swiftResult$: Int64! + @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, i: jlong, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { Task.immediate { - swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) - _semaphore$.signal() + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) + environment = try JavaVirtualMachine.shared().environment() + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } } } else { Task { - swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) - _semaphore$.signal() + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) + environment = try JavaVirtualMachine.shared().environment() + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } } } - _semaphore$.wait() - return swiftResult$.getJNIValue(in: environment) + return } """ ] @@ -233,14 +269,16 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture async(MyClass c, SwiftArena swiftArena$) { - return java.util.concurrent.CompletableFuture.supplyAsync(() -> { - return MyClass.wrapMemoryAddressUnsafe(SwiftModule.$async(c.$memoryAddress()), swiftArena$); + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(c.$memoryAddress(), $future); + return $future.thenApply((futureResult$) -> { + return MyClass.wrapMemoryAddressUnsafe(futureResult$, swiftArena$); } ); } """, """ - private static native long $async(long c); + private static native void $async(long c, java.util.concurrent.CompletableFuture result_future); """, ] ) @@ -258,33 +296,50 @@ struct JNIAsyncTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024async__J") - func Java_com_example_swift_SwiftModule__00024async__J(environment: UnsafeMutablePointer!, thisClass: jclass, c: jlong) -> jlong { + @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_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$) guard let c$ else { fatalError("c memory address was null in call to \\(#function)!") } - let _semaphore$ = _Semaphore(value: 0) - var swiftResult$: MyClass! + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { Task.immediate { - swiftResult$ = await SwiftModule.async(c: c$.pointee) - _semaphore$.signal() - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:873 - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:872 + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(c: c$.pointee) + environment = try JavaVirtualMachine.shared().environment() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } + } + } else { Task { - swiftResult$ = await SwiftModule.async(c: c$.pointee) - _semaphore$.signal() - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:878 - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:877 - _semaphore$.wait() - let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: swiftResult$) - let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment) + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(c: c$.pointee) + environment = try JavaVirtualMachine.shared().environment() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } + } + } + return } """ ] From cd5011f7b16d47a51971c61e9af12c4600355330 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 30 Oct 2025 16:54:08 +0100 Subject: [PATCH 22/26] remove legacy mode for now --- .../JNI/JNISwift2JavaGenerator+JavaTranslation.swift | 3 --- .../JExtract/JExtractAsyncFuncMode.swift | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 500b9b0f..56174e3e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -523,9 +523,6 @@ extension JNISwift2JavaGenerator { isThrowing: originalFunctionSignature.isThrowing ) nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType)) - - case .future: - fatalError() } } diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift index d3133fab..221649c5 100644 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift @@ -23,7 +23,7 @@ public enum JExtractAsyncFuncMode: String, Codable { /// Android 23 and below. /// /// - Note: Prefer using the `completableFuture` mode instead, if possible. - case future +// case future } extension JExtractAsyncFuncMode { From 136bd1412127b756483b3dd34c220d8ce0ed90f1 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 30 Oct 2025 17:12:44 +0100 Subject: [PATCH 23/26] fix dependencies --- Samples/SwiftAndJavaJarSampleLib/Package.swift | 2 +- Samples/SwiftJavaExtractFFMSampleApp/Package.swift | 2 +- .../src/main/java/org/swift/swiftkit/core/SwiftLibraries.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Samples/SwiftAndJavaJarSampleLib/Package.swift b/Samples/SwiftAndJavaJarSampleLib/Package.swift index 71c2a5ed..32ffbb28 100644 --- a/Samples/SwiftAndJavaJarSampleLib/Package.swift +++ b/Samples/SwiftAndJavaJarSampleLib/Package.swift @@ -63,7 +63,7 @@ let package = Package( .target( name: "MySwiftLibrary", dependencies: [ - .product(name: "SwiftJavaRuntimeSupport", package: "swift-java"), + .product(name: "SwiftRuntimeFunctions", package: "swift-java"), ], exclude: [ "swift-java.config", diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift index 40a6bb7b..98d1bd33 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift @@ -65,7 +65,7 @@ let package = Package( dependencies: [ .product(name: "SwiftJava", package: "swift-java"), .product(name: "CSwiftJavaJNI", package: "swift-java"), - .product(name: "SwiftJavaRuntimeSupport", package: "swift-java"), + .product(name: "SwiftRuntimeFunctions", package: "swift-java"), ], exclude: [ "swift-java.config", diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java index e045432c..7eccde74 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -24,7 +24,7 @@ public final class SwiftLibraries { - // Library names of core Swift and SwiftKit + // Library names of core Swift and SwiftRuntimeFunctions public static final String LIB_NAME_SWIFT_CORE = "swiftCore"; public static final String LIB_NAME_SWIFT_CONCURRENCY = "swift_Concurrency"; public static final String LIB_NAME_SWIFT_RUNTIME_FUNCTIONS = "SwiftRuntimeFunctions"; @@ -43,7 +43,7 @@ public final class SwiftLibraries { private static final boolean INITIALIZED_LIBS = loadLibraries(false); public static boolean loadLibraries(boolean loadSwiftRuntimeFunctions) { - System.loadLibrary(LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); + System.loadLibrary(LIB_NAME_SWIFT_CORE); if (loadSwiftRuntimeFunctions) { System.loadLibrary(LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); } From d64ceca446adfde277218551fabc258c968578b9 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 30 Oct 2025 17:17:11 +0100 Subject: [PATCH 24/26] output correct lib name for FFM --- Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 475ea939..c445d5c9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -189,7 +189,7 @@ extension FFMSwift2JavaGenerator { private static final boolean INITIALIZED_LIBS = initializeLibs(); static boolean initializeLibs() { System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); - System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_JAVA_RUNTIME_SUPPORT); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); System.loadLibrary(LIB_NAME); return true; } @@ -339,7 +339,7 @@ extension FFMSwift2JavaGenerator { private static SymbolLookup getSymbolLookup() { if (SwiftLibraries.AUTO_LOAD_LIBS) { System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); - System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_JAVA_RUNTIME_SUPPORT); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); System.loadLibrary(LIB_NAME); } From 997f14bcdbc6774119e1847874de74af64ce8c69 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 31 Oct 2025 08:05:08 +0100 Subject: [PATCH 25/26] remove dependencies from SwiftRuntimeFunctions --- Package.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Package.swift b/Package.swift index 1171133b..ecd82b39 100644 --- a/Package.swift +++ b/Package.swift @@ -370,10 +370,6 @@ let package = Package( .target( name: "SwiftRuntimeFunctions", - dependencies: [ - "CSwiftJavaJNI", - "SwiftJava" - ], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) From 49f7788e968b4446ac000a6b0d2872353cb8fd04 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 31 Oct 2025 08:36:12 +0100 Subject: [PATCH 26/26] update swift-syntax --- Package.swift | 2 +- README.md | 2 +- .../Convenience/SwiftSyntax+Extensions.swift | 4 +++- .../JExtractSwiftLib/SwiftTypes/SwiftType.swift | 2 +- .../SwiftTypes/SwiftTypeLookupContext.swift | 16 ++++++---------- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Package.swift b/Package.swift index ecd82b39..09743135 100644 --- a/Package.swift +++ b/Package.swift @@ -202,7 +202,7 @@ let package = Package( ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax", from: "601.0.1"), + .package(url: "https://github.com/swiftlang/swift-syntax", from: "602.0.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), diff --git a/README.md b/README.md index d21e3a73..6f9a5fde 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ It is possible to generate Swift bindings to Java libraries using SwiftJava by u Required language/runtime versions: - **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integratio -- **Swift 6.0.x**, because the library uses modern Swift macros +- **Swift 6.2.x**, because the library uses modern Swift macros **swift-java jextract** diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index 8a58f42a..d3902aa4 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import SwiftDiagnostics -import SwiftSyntax +@_spi(ExperimentalLanguageFeatures) import SwiftSyntax extension WithModifiersSyntax { var accessControlModifiers: DeclModifierListSyntax { @@ -218,6 +218,8 @@ extension DeclSyntaxProtocol { } else { "var" } + case .usingDecl(let node): + node.nameForDebug } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 81afe637..3840b3e1 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -215,7 +215,7 @@ extension SwiftType { switch type.as(TypeSyntaxEnum.self) { case .arrayType, .classRestrictionType, .dictionaryType, .missingType, .namedOpaqueReturnType, - .packElementType, .packExpansionType, .suppressedType: + .packElementType, .packExpansionType, .suppressedType, .inlineArrayType: throw TypeTranslationError.unimplementedType(type) case .attributedType(let attributedType): diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index f47669b5..13f42cfc 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -42,24 +42,19 @@ class SwiftTypeLookupContext { return typeDeclaration(for: names) } - case .fromFileScope(_, let names): - if !names.isEmpty { - return typeDeclaration(for: names) - } - - case .lookInMembers(let scopeNode): + case .lookForMembers(let scopeNode): if let nominalDecl = try typeDeclaration(for: scopeNode, sourceFilePath: "FIXME.swift") { // FIXME: no path here // implement some node -> file if let found = symbolTable.lookupNestedType(name.name, parent: nominalDecl as! SwiftNominalTypeDeclaration) { return found } } - case .lookInGenericParametersOfExtendedType(let extensionNode): + case .lookForGenericParameters(let extensionNode): // TODO: Implement _ = extensionNode break - case .mightIntroduceDollarIdentifiers: + case .lookForImplicitClosureParameters: // Dollar identifier can't be a type, ignore. break } @@ -81,8 +76,9 @@ class SwiftTypeLookupContext { // TODO: Implement _ = implicitDecl break - case .dollarIdentifier: - break + case .equivalentNames(let equivalentNames): + // TODO: Implement + _ = equivalentNames } } return nil