From a26b909a53ec76b5c2a3521a1e0f9534fedd8ece Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Sat, 1 Aug 2026 17:36:57 +0200 Subject: [PATCH 1/3] jextract: Support Java BooleanSupplier functional interface --- .../MySwiftLibrary/MySwiftLibrary.swift | 4 + .../com/example/swift/MySwiftLibraryTest.java | 5 ++ .../MySwiftLibrary/MySwiftLibrary.swift | 4 + .../com/example/swift/MySwiftLibraryTest.java | 7 +- .../Sources/MySwiftLibrary/Closures.swift | 4 + .../java/com/example/swift/ClosuresTest.java | 8 +- .../ExampleSwiftLibrary/MySwiftLibrary.swift | 4 + .../JavaTypes/JavaType+JDK.swift | 5 ++ .../KnownFunctionalInterfaces.swift | 12 ++- .../SwiftExtract/SwiftTypes/SwiftType.swift | 7 ++ .../FuncCallbackImportTests.swift | 87 +++++++++++++++++++ .../JNI/JNIClosureTests.swift | 51 +++++++++++ 12 files changed, 194 insertions(+), 4 deletions(-) diff --git a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift index 4a06c91fc..a5f2e7c91 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -36,6 +36,10 @@ public func globalCallMeRunnable(run: () -> Void) { run() } +public func globalCallMeBooleanSupplier(run: () -> Bool) -> Bool { + return run() +} + // ==== Internal helpers func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { diff --git a/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java index 13ebb1b01..142ab11bd 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -54,4 +54,9 @@ public void apply() { assertEquals(0, countDownLatch.getCount()); } + @Test + void call_globalCallMeBooleanSupplier_noThrow() { + boolean result = MySwiftLibrary.globalCallMeBooleanSupplier(() -> { return true; }); + assertEquals(true, result); + } } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 0d547febe..3a65086df 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -48,6 +48,10 @@ public func globalCallMeRunnable(run: () -> Void) { run() } +public func globalCallMeBooleanSupplier(run: () -> Bool) -> Bool { + return run() +} + public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { buf.count } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java index 478b24c34..9b7373c27 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -73,8 +73,6 @@ void call_globalStringIdentity_empty() { assertEquals("", result); } - - @Test @Disabled("Upcalls not yet implemented in new scheme") @SuppressWarnings({"Convert2Lambda", "Convert2MethodRef"}) @@ -148,4 +146,9 @@ void call_globalThrowingString_throws_checkMessage() { ); } + @Test + void call_globalCallMeBooleanSupplier_noThrow() { + boolean result = MySwiftLibrary.globalCallMeBooleanSupplier(() -> { return true; }); + assertEquals(true, result); + } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift index 4ee87109f..dd646d004 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift @@ -20,6 +20,10 @@ public func closureWithInt(input: Int64, closure: (Int64) -> Int64) -> Int64 { closure(input) } +public func globalCallMeBooleanSupplier(run: () -> Bool) -> Bool { + return run() +} + public func closureMultipleArguments( input1: Int64, input2: Int64, diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java index b8389d419..e1d06f891 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java @@ -41,4 +41,10 @@ void closureMultipleArguments() { long result = MySwiftLibrary.closureMultipleArguments(5, 10, (a, b) -> a + b); assertEquals(15, result); } -} \ No newline at end of file + + @Test + void globalCallMeBooleanSupplier() { + boolean result = MySwiftLibrary.globalCallMeBooleanSupplier(() -> true); + assertEquals(true, result); + } +} diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index 518266e34..b8c2a0541 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -43,6 +43,10 @@ public func globalCallMeRunnable(run: () -> Void) { run() } +public func globalCallMeBooleanSupplier(run: () -> Bool) -> Bool { + return run() +} + public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { buf.count } diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 59f72e3ba..5797a196e 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -30,6 +30,11 @@ extension JavaType { .class(package: "java.lang", name: "Runnable") } + /// The description of the type java.util.function.BooleanSupplier. + static var javaFunctionBooleanSupplier: JavaType { + .class(package: "java.util.function", name: "BooleanSupplier") + } + /// The description of the type java.lang.Class. static var javaLangClass: JavaType { .class(package: "java.lang", name: "Class") diff --git a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift index ddafc8a4a..9d55a0dd9 100644 --- a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift +++ b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift @@ -29,8 +29,16 @@ struct KnownJavaFunctionalInterface: Sendable { result: .void ) + static let booleanSupplier = KnownJavaFunctionalInterface( + JavaType.javaFunctionBooleanSupplier, + method: "getAsBoolean", + parameters: [], + result: .boolean + ) + static let all: [KnownJavaFunctionalInterface] = [ - .runnable + .runnable, + .booleanSupplier, ] static func find(parameters: [JavaType], result: JavaType) -> KnownJavaFunctionalInterface? { @@ -59,6 +67,8 @@ struct KnownJavaFunctionalInterface: Sendable { return switch (parameters, result) { case ([], _) where result.isVoid: runnable + case ([], _) where result.isBoolean: + booleanSupplier default: nil } diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftType.swift b/Sources/SwiftExtract/SwiftTypes/SwiftType.swift index 092a10944..e265ccf98 100644 --- a/Sources/SwiftExtract/SwiftTypes/SwiftType.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftType.swift @@ -98,6 +98,13 @@ public enum SwiftType: Equatable { } } + public var isBoolean: Bool { + if case let .nominal(nominal) = self { + return nominal.nominalTypeDecl.knownTypeKind == .bool + } + return false + } + /// Whether this is a pointer type. I.e 'Unsafe[Mutable][Raw]Pointer' public var isPointer: Bool { switch self { diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index b16aa3de5..96c30b8ff 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -34,6 +34,7 @@ final class FuncCallbackImportTests { import _SwiftConcurrencyShims public func callMe(callback: () -> Void) + public func callMeBoolSupplier(callback: () -> Bool) public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ()) public func withBuffer(body: (UnsafeRawBufferPointer) -> Int) """ @@ -122,6 +123,92 @@ final class FuncCallbackImportTests { ) } + @Test("Import: public func callMeBoolSupplier(callback: () -> Bool)") + func func_callMeBoolSupplierFunc_callback() throws { + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = makeSwiftJavaAnalyzer(config: config) + st.log.logLevel = .error + + try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) + + let funcDecl = st.extractedGlobalFuncs.first { $0.name == "callMeBoolSupplier" }! + + let generator = FFMSwift2JavaGenerator( + config: config, + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + let output = JavaPrinter.toString { printer in + generator.printFunctionDowncallMethods(&printer, funcDecl) + } + + assertOutput( + output, + expected: + """ + // ==== -------------------------------------------------- + // callMeBoolSupplier + /** + * {@snippet lang=c : + * void swiftjava___FakeModule_callMeBoolSupplier_callback(_Bool (*callback)(void)) + * } + */ + private static class swiftjava___FakeModule_callMeBoolSupplier_callback { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* callback: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + __FakeModule.findOrThrow("swiftjava___FakeModule_callMeBoolSupplier_callback"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment callback) { + try { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(callback); + } + HANDLE.invokeExact(callback); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + /** + * {snippet lang=c : + * _Bool (*)(void) + * } + */ + private static class $callback { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_BOOL + ); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(java.util.function.BooleanSupplier.class, "getAsBoolean", DESC); + private static MemorySegment toUpcallStub(java.util.function.BooleanSupplier fi, Arena arena) { + return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + } + } + } + public static class callMeBoolSupplier { + private static MemorySegment $toUpcallStub(java.util.function.BooleanSupplier fi, Arena arena) { + return swiftjava___FakeModule_callMeBoolSupplier_callback.$callback.toUpcallStub(fi, arena); + } + } + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func callMeBoolSupplier(callback: () -> Bool) + * } + */ + public static void callMeBoolSupplier(java.util.function.BooleanSupplier callback) { + try(var arena$ = Arena.ofConfined()) { + swiftjava___FakeModule_callMeBoolSupplier_callback.call(callMeBoolSupplier.$toUpcallStub(callback, arena$)); + } + } + """ + ) + } + @Test("Import: public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ())") func func_callMeMoreFunc_callback() throws { var config = Configuration() diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index 7a265442f..aeba39a39 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -20,6 +20,7 @@ struct JNIClosureTests { let source = """ public func emptyClosure(closure: () -> ()) {} + public func closureBoolSupplier(closure: () -> Bool) {} public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) {} """ @@ -48,6 +49,31 @@ struct JNIClosureTests { ) } + @Test + func closureBoolSupplier_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func closureBoolSupplier(closure: () -> Bool) + * } + */ + public static void closureBoolSupplier(java.util.function.BooleanSupplier closure) { + SwiftModule.$closureBoolSupplier(closure); + } + """, + """ + private static native void $closureBoolSupplier(java.util.function.BooleanSupplier closure); + """, + ] + ) + } + @Test func emptyClosure_swiftThunks() throws { try assertOutput( @@ -73,6 +99,31 @@ struct JNIClosureTests { ) } + @Test + func closureBoolSupplier_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024closureBoolSupplier__Ljava_util_function_BooleanSupplier_2") + public func Java_com_example_swift_SwiftModule__00024closureBoolSupplier__Ljava_util_function_BooleanSupplier_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { + SwiftModule.closureBoolSupplier(closure: { + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "getAsBoolean", "()Z")! + environment.interface.DeleteLocalRef(environment, class$) + let arguments$: [jvalue] = [] + return Bool(fromJNI: environment.interface.CallBooleanMethodA(environment, closure, methodID$, arguments$), in: environment) + } + ) + } + """ + ] + ) + } + @Test func closureWithArgumentsAndReturn_javaBindings() throws { try assertOutput( From 43b4c214d8f6659e28e3fb84d534b4203251c2f8 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Sat, 1 Aug 2026 20:05:10 +0200 Subject: [PATCH 2/3] Fromat the code to simplify return statements --- .../Sources/MySwiftLibrary/MySwiftLibrary.swift | 2 +- .../Sources/MySwiftLibrary/MySwiftLibrary.swift | 2 +- .../Sources/MySwiftLibrary/Closures.swift | 2 +- Sources/ExampleSwiftLibrary/MySwiftLibrary.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift index a5f2e7c91..e92d7de48 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -37,7 +37,7 @@ public func globalCallMeRunnable(run: () -> Void) { } public func globalCallMeBooleanSupplier(run: () -> Bool) -> Bool { - return run() + run() } // ==== Internal helpers diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 3a65086df..305372eb4 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -49,7 +49,7 @@ public func globalCallMeRunnable(run: () -> Void) { } public func globalCallMeBooleanSupplier(run: () -> Bool) -> Bool { - return run() + run() } public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift index dd646d004..a70566fab 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift @@ -21,7 +21,7 @@ public func closureWithInt(input: Int64, closure: (Int64) -> Int64) -> Int64 { } public func globalCallMeBooleanSupplier(run: () -> Bool) -> Bool { - return run() + run() } public func closureMultipleArguments( diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index b8c2a0541..799f2e0f3 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -44,7 +44,7 @@ public func globalCallMeRunnable(run: () -> Void) { } public func globalCallMeBooleanSupplier(run: () -> Bool) -> Bool { - return run() + run() } public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { From 6b75cb6eae0b0ba077307174a2e31e452fc1cb9d Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Sun, 2 Aug 2026 05:38:05 +0200 Subject: [PATCH 3/3] Address code review comments --- Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift | 2 +- Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 5797a196e..3a5197c3e 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -31,7 +31,7 @@ extension JavaType { } /// The description of the type java.util.function.BooleanSupplier. - static var javaFunctionBooleanSupplier: JavaType { + static var javaUtilFunctionBooleanSupplier: JavaType { .class(package: "java.util.function", name: "BooleanSupplier") } diff --git a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift index 9d55a0dd9..cc812ef6b 100644 --- a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift +++ b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift @@ -30,7 +30,7 @@ struct KnownJavaFunctionalInterface: Sendable { ) static let booleanSupplier = KnownJavaFunctionalInterface( - JavaType.javaFunctionBooleanSupplier, + JavaType.javaUtilFunctionBooleanSupplier, method: "getAsBoolean", parameters: [], result: .boolean