From c8169a34799c6fc6693b75d6d83bf34cb51b67ab Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Tue, 18 Aug 2026 11:39:38 +0200 Subject: [PATCH] jextract: Support Java DoubleUnaryOperator func interface --- .../MySwiftLibrary/MySwiftLibrary.swift | 4 ++ .../com/example/swift/MySwiftLibraryTest.java | 6 +++ .../MySwiftLibrary/MySwiftLibrary.swift | 4 ++ .../com/example/swift/MySwiftLibraryTest.java | 6 +++ .../Sources/MySwiftLibrary/Closures.swift | 4 ++ .../java/com/example/swift/ClosuresTest.java | 6 +++ .../ExampleSwiftLibrary/MySwiftLibrary.swift | 4 ++ .../JavaTypes/JavaType+JDK.swift | 5 ++ .../KnownFunctionalInterfaces.swift | 10 ++++ .../FuncCallbackImportTests.swift | 44 ++++++++++++++++ .../JNI/JNIClosureTests.swift | 52 +++++++++++++++++++ 11 files changed, 145 insertions(+) diff --git a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift index c2b43c04c..0bbf84769 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -84,6 +84,10 @@ public func globalCallMeLongUnaryOperator(run: (Int64) -> Int64) -> Int64 { run(1) } +public func globalCallMeDoubleUnaryOperator(run: (Double) -> Double) -> Double { + run(1.0) +} + public func globalCallMeIntBinaryOperator(run: (Int32, Int32) -> Int32) -> Int32 { run(1, 2) } 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 f99891d03..3dd923e2d 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -123,6 +123,12 @@ void call_globalCallMeLongUnaryOperator_noThrow() { assertEquals(1L, result); } + @Test + void call_globalCallMeDoubleUnaryOperator_noThrow() { + double result = MySwiftLibrary.globalCallMeDoubleBinaryOperator((double a) -> { return a; }); + assertEquals(1.0, result); + } + @Test void call_globalCallMeIntBinaryOperator_noThrow() { int result = MySwiftLibrary.globalCallMeIntBinaryOperator((int a, int b) -> { return a + b; }); diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 902daa781..bc207d425 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -96,6 +96,10 @@ public func globalCallMeLongUnaryOperator(run: (Int64) -> Int64) -> Int64 { run(1) } +public func globalCallMeDoubleUnaryOperator(run: (Double) -> Double) -> Double { + run(1.0) +} + public func globalCallMeIntBinaryOperator(run: (Int32, Int32) -> Int32) -> Int32 { run(1, 2) } 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 663f59b94..7a098c17c 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -235,6 +235,12 @@ void call_globalCallMeLongUnaryOperator_noThrow() { assertEquals(1L, result); } + @Test + void call_globalCallMeDoubleUnaryOperator_noThrow() { + double result = MySwiftLibrary.globalCallMeDoubleUnaryOperator((double a) -> { return a; }); + assertEquals(1.0, result); + } + @Test void call_globalCallMeIntBinaryOperator_noThrow() { int result = MySwiftLibrary.globalCallMeIntBinaryOperator((int a, int b) -> { return a + b; }); diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift index 7767b4f16..58785b252 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift @@ -69,6 +69,10 @@ public func globalCallMeLongUnaryOperator(run: (Int64) -> Int64) -> Int64 { run(1) } +public func globalCallMeDoubleUnaryOperator(run: (Double) -> Double) -> Double { + run(1.0) +} + public func globalCallMeIntBinaryOperator(run: (Int32, Int32) -> Int32) -> Int32 { run(1, 2) } 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 c41a830e6..05f2909a6 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java @@ -117,6 +117,12 @@ void globalCallMeLongUnaryOperator() { assertEquals(1L, result); } + @Test + void globalCallMeDoubleUnaryOperator() { + double result = MySwiftLibrary.globalCallMeDoubleUnaryOperator((double a) -> { return a; }); + assertEquals(1.0, result); + } + @Test void globalCallMeIntBinaryOperator() { int result = MySwiftLibrary.globalCallMeIntBinaryOperator((int a, int b) -> { return a + b; }); diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index accae2ebd..642080cdb 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -91,6 +91,10 @@ public func globalCallMeLongUnaryOperator(run: (Int64) -> Int64) -> Int64 { run(1) } +public func globalCallMeDoubleUnaryOperator(run: (Double) -> Double) -> Double { + run(1.0) +} + public func globalCallMeIntBinaryOperator(run: (Int32, Int32) -> Int32) -> Int32 { run(1, 2) } diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index b1ffc28c7..686968d87 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -90,6 +90,11 @@ extension JavaType { .class(package: "java.util.function", name: "LongUnaryOperator") } + /// The description of the type java.util.function.DoubleUnaryOperator. + static var javaUtilFunctionDoubleUnaryOperator: JavaType { + .class(package: "java.util.function", name: "DoubleUnaryOperator") + } + /// The description of the type java.util.function.IntBinaryOperator. static var javaUtilFunctionIntBinaryOperator: JavaType { .class(package: "java.util.function", name: "IntBinaryOperator") diff --git a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift index 95e759046..405c5938c 100644 --- a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift +++ b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift @@ -113,6 +113,13 @@ struct KnownJavaFunctionalInterface: Sendable { result: .long ) + static let doubleUnaryOperator = KnownJavaFunctionalInterface( + JavaType.javaUtilFunctionDoubleUnaryOperator, + method: "applyAsDouble", + parameters: [.double], + result: .double + ) + static let intBinaryOperator = KnownJavaFunctionalInterface( JavaType.javaUtilFunctionIntBinaryOperator, method: "applyAsInt", @@ -148,6 +155,7 @@ struct KnownJavaFunctionalInterface: Sendable { .doublePredicate, .intUnaryOperator, .longUnaryOperator, + .doubleUnaryOperator, .intBinaryOperator, .longBinaryOperator, .doubleBinaryOperator, @@ -233,6 +241,8 @@ struct KnownJavaFunctionalInterface: Sendable { intUnaryOperator case _ where parameter.isInt64: longUnaryOperator + case _ where parameter.isDouble: + doubleUnaryOperator default: nil } diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index cd1adc03b..4a40dfcf6 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -49,6 +49,7 @@ final class FuncCallbackImportTests { public func callMeIntUnaryOperator(callback: (Int32) -> Int32) public func callMeLongUnaryOperator(callback: (Int64) -> Int64) + public func callMeDoubleUnaryOperator(callback: (Double) -> Double) public func callMeIntBinaryOperator(callback: (Int32, Int32) -> Int32) public func callMeLongBinaryOperator(callback: (Int64, Int64) -> Int64) @@ -810,6 +811,49 @@ final class FuncCallbackImportTests { ) } + @Test("Import: public func callMecallMeDoubleUnaryOperatorFunc(callback: (Double) -> Double)") + func func_callMecallMeDoubleUnaryOperatorFunc_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 == "callMeDoubleUnaryOperator" }! + + 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, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func callMeDoubleUnaryOperator(callback: (Double) -> Double) + * } + */ + public static void callMeDoubleUnaryOperator(java.util.function.DoubleUnaryOperator callback) { + try(var arena$ = Arena.ofConfined()) { + swiftjava___FakeModule_callMeDoubleUnaryOperator_callback.call(callMeDoubleUnaryOperator.$toUpcallStub(callback, arena$)); + } + } + """ + ] + ) + } + @Test("Import: public func callMecallMeIntBinaryOperatorFunc(callback: (Int32, Int32) -> Int32)") func func_callMecallMeIntBinaryOperatorFunc_callback() throws { var config = Configuration() diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index a133f3e08..1796d2e45 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -35,6 +35,7 @@ struct JNIClosureTests { public func closureIntUnaryOperator(closure: (Int32) -> Int32) {} public func closureLongUnaryOperator(closure: (Int64) -> Int64) {} + public func closureDoubleUnaryOperator(closure: (Double) -> Double) {} public func closureIntBinaryOperator(closure: (Int32, Int32) -> Int32) {} public func closureLongBinaryOperator(closure: (Int64, Int64) -> Int64) {} @@ -368,6 +369,32 @@ struct JNIClosureTests { ) } + + @Test + func closureDoubleUnaryOperator_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func closureDoubleUnaryOperator(closure: (Double) -> Double) + * } + */ + public static void closureDoubleUnaryOperator(java.util.function.DoubleUnaryOperator closure) { + SwiftModule.$closureDoubleUnaryOperator(closure); + } + """, + """ + private static native void $closureDoubleUnaryOperator(java.util.function.DoubleUnaryOperator closure); + """, + ] + ) + } + @Test func closureIntBinaryOperator_javaBindings() throws { try assertOutput( @@ -768,6 +795,31 @@ struct JNIClosureTests { ) } + @Test + func closureDoubleUnaryOperator_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024closureDoubleUnaryOperator__Ljava_util_function_DoubleUnaryOperator_2") + public func Java_com_example_swift_SwiftModule__00024closureDoubleUnaryOperator__Ljava_util_function_DoubleUnaryOperator_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { + SwiftModule.closureDoubleUnaryOperator(closure: { + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "applyAsDouble", "(D)D")! + environment.interface.DeleteLocalRef(environment, class$) + let arguments$: [jvalue] = [_0.getJValue(in: environment)] + return Double(fromJNI: environment.interface.CallDoubleMethodA(environment, closure, methodID$, arguments$), in: environment) + } + ) + } + """ + ] + ) + } + @Test func closureIntBinaryOperator_swiftThunks() throws { try assertOutput(