From 468d63f9433bf39d3d905b9a39506b1c80e54117 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Tue, 1 Sep 2026 18:36:07 +0200 Subject: [PATCH] jextract: Support Java Int & Double to Long func interface --- .../MySwiftLibrary/MySwiftLibrary.swift | 8 ++ .../com/example/swift/MySwiftLibraryTest.java | 12 ++ .../MySwiftLibrary/MySwiftLibrary.swift | 8 ++ .../com/example/swift/MySwiftLibraryTest.java | 12 ++ .../Sources/MySwiftLibrary/Closures.swift | 8 ++ .../java/com/example/swift/ClosuresTest.java | 12 ++ .../ExampleSwiftLibrary/MySwiftLibrary.swift | 8 ++ .../JavaTypes/JavaType+JDK.swift | 10 ++ .../KnownFunctionalInterfaces.swift | 29 +++++ .../FuncCallbackImportTests.swift | 89 +++++++++++++++ .../JNI/JNIClosureTests.swift | 104 ++++++++++++++++++ 11 files changed, 300 insertions(+) diff --git a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift index 827f23190..f9fca0ca5 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -108,6 +108,14 @@ public func globalCallMeLongToIntFunction(run: (Int64) -> Int32) -> Int32 { run(1) } +public func globalCallMeDoubleToLongFunction(run: (Double) -> Int64) -> Int64 { + run(1.0) +} + +public func globalCallMeIntToLongFunction(run: (Int32) -> Int64) -> Int64 { + run(1) +} + // ==== 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 3365c950a..aac0e6239 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -158,4 +158,16 @@ void call_globalCallMeLongToIntFunction_noThrow() { int result = MySwiftLibrary.globalCallMeLongToIntFunction((long a) -> { return (int) a; }); assertEquals(1, result); } + + @Test + void call_globalCallMeDoubleToLongFunction_noThrow() { + long result = MySwiftLibrary.globalCallMeDoubleToLongFunction((double a) -> { return (long) a; }); + assertEquals(1L, result); + } + + @Test + void call_globalCallMeIntToLongFunction_noThrow() { + long result = MySwiftLibrary.globalCallMeIntToLongFunction((int a) -> { return (long) a; }); + assertEquals(1L, result); + } } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index afb0466f6..a1a7e2fbd 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -120,6 +120,14 @@ public func globalCallMeLongToIntFunction(run: (Int64) -> Int32) -> Int32 { run(1) } +public func globalCallMeDoubleToLongFunction(run: (Double) -> Int64) -> Int64 { + run(1.0) +} + +public func globalCallMeIntToLongFunction(run: (Int32) -> Int64) -> Int64 { + run(1) +} + 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 763862f41..61ce80ffd 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -270,4 +270,16 @@ void call_globalCallMeLongToIntFunction_noThrow() { int result = MySwiftLibrary.globalCallMeLongToIntFunction((long a) -> { return (int) a; }); assertEquals(1, result); } + + @Test + void call_globalCallMeDoubleToLongFunction_noThrow() { + long result = MySwiftLibrary.globalCallMeDoubleToLongFunction((double a) -> { return (long) a; }); + assertEquals(1L, result); + } + + @Test + void call_globalCallMeIntToLongFunction_noThrow() { + long result = MySwiftLibrary.globalCallMeIntToLongFunction((int a) -> { return (long) a; }); + assertEquals(1L, result); + } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift index c10321b53..6eb23399d 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift @@ -93,6 +93,14 @@ public func globalCallMeLongToIntFunction(run: (Int64) -> Int32) -> Int32 { run(1) } +public func globalCallMeDoubleToLongFunction(run: (Double) -> Int64) -> Int64 { + run(1.0) +} + +public func globalCallMeIntToLongFunction(run: (Int32) -> Int64) -> Int64 { + run(1) +} + 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 f69e3375c..47d83eeac 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java @@ -152,4 +152,16 @@ void globalCallMeLongToIntFunction() { int result = MySwiftLibrary.globalCallMeLongToIntFunction((long a) -> { return (int) a; }); assertEquals(1, result); } + + @Test + void globalCallMeDoubleToLongFunction() { + long result = MySwiftLibrary.globalCallMeDoubleToLongFunction((double a) -> { return (long) a; }); + assertEquals(1L, result); + } + + @Test + void globalCallMeIntToLongFunction() { + long result = MySwiftLibrary.globalCallMeIntToLongFunction((int a) -> { return (long) a; }); + assertEquals(1L, result); + } } diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index a23937019..1f37898b9 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -115,6 +115,14 @@ public func globalCallMeLongToIntFunction(run: (Int64) -> Int32) -> Int32 { run(1) } +public func globalCallMeDoubleToLongFunction(run: (Double) -> Int64) -> Int64 { + run(1.0) +} + +public func globalCallMeIntToLongFunction(run: (Int32) -> Int64) -> Int64 { + run(1) +} + 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 5fe0d2d7f..7c094df4f 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -120,6 +120,16 @@ extension JavaType { .class(package: "java.util.function", name: "LongToIntFunction") } + /// The description of the type java.util.function.DoubleToLongFunction. + static var javaUtilFunctionDoubleToLongFunction: JavaType { + .class(package: "java.util.function", name: "DoubleToLongFunction") + } + + /// The description of the type java.util.function.IntToLongFunction. + static var javaUtilFunctionIntToLongFunction: JavaType { + .class(package: "java.util.function", name: "IntToLongFunction") + } + /// 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 ca77ff0ef..fe4b0b04e 100644 --- a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift +++ b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift @@ -155,6 +155,20 @@ struct KnownJavaFunctionalInterface: Sendable { result: .int ) + static let doubleToLongFunction = KnownJavaFunctionalInterface( + JavaType.javaUtilFunctionDoubleToLongFunction, + method: "applyAsLong", + parameters: [.double], + result: .long + ) + + static let intToLongFunction = KnownJavaFunctionalInterface( + JavaType.javaUtilFunctionIntToLongFunction, + method: "applyAsLong", + parameters: [.int], + result: .long + ) + static let all: [KnownJavaFunctionalInterface] = [ .runnable, .booleanSupplier, @@ -175,6 +189,8 @@ struct KnownJavaFunctionalInterface: Sendable { .doubleBinaryOperator, .doubleToIntFunction, .longToIntFunction, + .doubleToLongFunction, + .intToLongFunction, ] static func find(parameters: [JavaType], result: JavaType) -> KnownJavaFunctionalInterface? { @@ -277,6 +293,19 @@ struct KnownJavaFunctionalInterface: Sendable { } } + // To long functions + if parameters.count == 1 && result.isInt64 { + let parameter = parameters[0].type + return switch () { + case _ where parameter.isInt32: + intToLongFunction + case _ where parameter.isDouble: + doubleToLongFunction + default: + nil + } + } + // Binary operators if parameters.count == 2 && parameters[0].type == result && parameters[1].type == result { let parameter = parameters[0].type diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index fe969c833..6e555b635 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -50,6 +50,9 @@ final class FuncCallbackImportTests { public func callMeDoubleToIntFunction(callback: (Double) -> Int32) public func callMeLongToIntFunction(callback: (Int64) -> Int32) + public func callMeDoubleToLongFunction(callback: (Double) -> Int64) + public func callMeIntToLongFunction(callback: (Int32) -> Int64) + public func callMeIntUnaryOperator(callback: (Int32) -> Int32) public func callMeLongUnaryOperator(callback: (Int64) -> Int64) public func callMeDoubleUnaryOperator(callback: (Double) -> Double) @@ -943,6 +946,92 @@ final class FuncCallbackImportTests { ) } + @Test("Import: public func callMeDoubleToLongFunction(callback: (Double) -> Int64)") + func func_callMecallMeDoubleToLongFunctionFunc_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 == "callMeDoubleToLongFunction" }! + + 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 callMeDoubleToLongFunction(callback: (Double) -> Int64) + * } + */ + public static void callMeDoubleToLongFunction(java.util.function.DoubleToLongFunction callback) { + try(var arena$ = Arena.ofConfined()) { + swiftjava___FakeModule_callMeDoubleToLongFunction_callback.call(callMeDoubleToLongFunction.$toUpcallStub(callback, arena$)); + } + } + """ + ] + ) + } + + @Test("Import: public func callMeLongToIntFunction(callback: (Int32) -> Int64)") + func func_callMecallMeIntToLongFunctionFunc_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 == "callMeIntToLongFunction" }! + + 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 callMeIntToLongFunction(callback: (Int32) -> Int64) + * } + */ + public static void callMeIntToLongFunction(java.util.function.IntToLongFunction callback) { + try(var arena$ = Arena.ofConfined()) { + swiftjava___FakeModule_callMeIntToLongFunction_callback.call(callMeIntToLongFunction.$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 b0bede469..4ef90684c 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -40,6 +40,9 @@ struct JNIClosureTests { public func closureDoubleToIntFunction(closure: (Double) -> Int32) {} public func closureLongToIntFunction(closure: (Int64) -> Int32) {} + public func closureDoubleToLongFunction(closure: (Double) -> Int64) {} + public func closureIntToLongFunction(closure: (Int32) -> Int64) {} + public func closureIntBinaryOperator(closure: (Int32, Int32) -> Int32) {} public func closureLongBinaryOperator(closure: (Int64, Int64) -> Int64) {} public func closureDoubleBinaryOperator(closure: (Double, Double) -> Double) {} @@ -447,6 +450,56 @@ struct JNIClosureTests { ) } + @Test + func closureDoubleToLongFunction_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func closureDoubleToLongFunction(closure: (Double) -> Int64) + * } + */ + public static void closureDoubleToLongFunction(java.util.function.DoubleToLongFunction closure) { + SwiftModule.$closureDoubleToLongFunction(closure); + } + """, + """ + private static native void $closureDoubleToLongFunction(java.util.function.DoubleToLongFunction closure); + """, + ] + ) + } + + @Test + func closureIntToLongFunction_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func closureIntToLongFunction(closure: (Int32) -> Int64) + * } + */ + public static void closureIntToLongFunction(java.util.function.IntToLongFunction closure) { + SwiftModule.$closureIntToLongFunction(closure); + } + """, + """ + private static native void $closureIntToLongFunction(java.util.function.IntToLongFunction closure); + """, + ] + ) + } + @Test func closureIntBinaryOperator_javaBindings() throws { try assertOutput( @@ -897,6 +950,57 @@ struct JNIClosureTests { ) } + @Test + func closureDoubleToLongFunction_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024closureDoubleToLongFunction__Ljava_util_function_DoubleToLongFunction_2") + public func Java_com_example_swift_SwiftModule__00024closureDoubleToLongFunction__Ljava_util_function_DoubleToLongFunction_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { + SwiftModule.closureDoubleToLongFunction(closure: { + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "applyAsLong", "(D)J")! + environment.interface.DeleteLocalRef(environment, class$) + let arguments$: [jvalue] = [_0.getJValue(in: environment)] + return Int64(fromJNI: environment.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment) + } + ) + } + """ + ] + ) + } + + @Test + func closureIntToLongFunction_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024closureIntToLongFunction__Ljava_util_function_IntToLongFunction_2") + public func Java_com_example_swift_SwiftModule__00024closureIntToLongFunction__Ljava_util_function_IntToLongFunction_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { + SwiftModule.closureIntToLongFunction(closure: { + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "applyAsLong", "(I)J")! + environment.interface.DeleteLocalRef(environment, class$) + let arguments$: [jvalue] = [_0.getJValue(in: environment)] + return Int64(fromJNI: environment.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment) + } + ) + } + """ + ] + ) + } + + @Test func closureDoubleUnaryOperator_swiftThunks() throws { try assertOutput(