diff --git a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift index 88a277b4a..9fad48d20 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -52,6 +52,10 @@ public func globalCallMeDoubleSupplier(run: () -> Double) -> Double { run() } +public func globalCallMeIntConsumer(run: (Int32) -> Void) { + 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 9eb8ff147..503da60f3 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -77,4 +77,9 @@ void call_globalCallMeDoubleSupplier_noThrow() { double result = MySwiftLibrary.globalCallMeBooleanSupplier(() -> { return 2.0; }); assertEquals(2.0, result); } + + @Test + void call_globalCallMeIntConsumer_noThrow() { + MySwiftLibrary.globalCallMeIntConsumer((int a) -> { }); + } } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index f2dc75125..a0fd01681 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -64,6 +64,10 @@ public func globalCallMeDoubleSupplier(run: () -> Double) -> Double { run() } +public func globalCallMeIntConsumer(run: (Int32) -> Void) { + 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 5375017d5..cb60b1c62 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -189,4 +189,9 @@ void call_globalCallMeDoubleSupplier_noThrow() { double result = MySwiftLibrary.globalCallMeDoubleSupplier(() -> { return 2.0; }); assertEquals(2.0, result); } + + @Test + void call_globalCallMeIntConsumer_noThrow() { + MySwiftLibrary.globalCallMeIntConsumer((int a) -> { }); + } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift index 17d61d506..48d117554 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift @@ -37,6 +37,10 @@ public func globalCallMeDoubleSupplier(run: () -> Double) -> Double { run() } +public func globalCallMeIntConsumer(run: (Int32) -> Void) { + 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 bb414e33b..75b9f3a0e 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java @@ -71,4 +71,9 @@ void globalCallMeDoubleSupplier() { double result = MySwiftLibrary.globalCallMeDoubleSupplier(() -> 2.0); assertEquals(2.0, result); } + + @Test + void globalCallMeIntConsumer() { + MySwiftLibrary.globalCallMeIntConsumer((int a) -> {}); + } } diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index 132da25a8..b34d4135e 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -59,6 +59,10 @@ public func globalCallMeDoubleSupplier(run: () -> Double) -> Double { run() } +public func globalCallMeIntConsumer(run: (Int32) -> Void) { + 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 1859c970e..a9bb11818 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -50,6 +50,11 @@ extension JavaType { .class(package: "java.util.function", name: "DoubleSupplier") } + /// The description of the type java.util.function.IntConsumer. + static var javaUtilFunctionIntConsumer: JavaType { + .class(package: "java.util.function", name: "IntConsumer") + } + /// 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 dc276c68a..b6ce713be 100644 --- a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift +++ b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift @@ -57,12 +57,20 @@ struct KnownJavaFunctionalInterface: Sendable { result: .double ) + static let intConsumer = KnownJavaFunctionalInterface( + JavaType.javaUtilFunctionIntConsumer, + method: "accept", + parameters: [.int], + result: .void + ) + static let all: [KnownJavaFunctionalInterface] = [ .runnable, .booleanSupplier, .intSupplier, .longSupplier, .doubleSupplier, + .intConsumer, ] static func find(parameters: [JavaType], result: JavaType) -> KnownJavaFunctionalInterface? { @@ -88,20 +96,37 @@ struct KnownJavaFunctionalInterface: Sendable { let parameters = functionType.parameters let result = functionType.resultType - return switch (parameters, result) { - case ([], _) where result.isVoid: - runnable - case ([], _) where result.isBoolean: - booleanSupplier - case ([], _) where result.isInt32: - intSupplier - case ([], _) where result.isInt64: - longSupplier - case ([], _) where result.isDouble: - doubleSupplier - default: - nil + + // Runnable & Suppliers + if parameters == [] { + return switch () { + case _ where result.isVoid: + runnable + case _ where result.isBoolean: + booleanSupplier + case _ where result.isInt32: + intSupplier + case _ where result.isInt64: + longSupplier + case _ where result.isDouble: + doubleSupplier + default: + nil + } } + + // Consumers + if parameters.count == 1 && result.isVoid { + let parameter = parameters[0].type + return switch () { + case _ where parameter.isInt32: + intConsumer + default: + nil + } + } + + return nil } static func find(_ functionType: JNISwift2JavaGenerator.TranslatedFunctionType) -> KnownJavaFunctionalInterface? { diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 2d552fe6f..a39feea48 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -38,6 +38,9 @@ final class FuncCallbackImportTests { public func callMeIntSupplier(callback: () -> Int32) public func callMeLongSupplier(callback: () -> Int64) public func callMeDoubleSupplier(callback: () -> Double) + + public func callMeIntConsumer(callback: (Int32) -> Void) + public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ()) public func withBuffer(body: (UnsafeRawBufferPointer) -> Int) """ @@ -341,6 +344,50 @@ final class FuncCallbackImportTests { ) } + @Test("Import: public func callMeDoubleSupplier(callback: (Int32) -> Void)") + func func_callMeIntConsumerFunc_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 == "callMeIntConsumer" }! + + 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 callMeIntConsumer(callback: (Int32) -> Void) + * } + */ + public static void callMeIntConsumer(java.util.function.IntConsumer callback) { + try(var arena$ = Arena.ofConfined()) { + swiftjava___FakeModule_callMeIntConsumer_callback.call(callMeIntConsumer.$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 c5bfcd3ef..1635f1a9b 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -24,6 +24,9 @@ struct JNIClosureTests { public func closureIntSupplier(closure: () -> Int32) {} public func closureLongSupplier(closure: () -> Int64) {} public func closureDoubleSupplier(closure: () -> Double) {} + + public func closureIntConsumer(closure: (Int32) -> Void) {} + public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) {} """ @@ -152,6 +155,31 @@ struct JNIClosureTests { ) } + @Test + func closureIntConsumer_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func closureIntConsumer(closure: (Int32) -> Void) + * } + */ + public static void closureIntConsumer(java.util.function.IntConsumer closure) { + SwiftModule.$closureIntConsumer(closure); + } + """, + """ + private static native void $closureIntConsumer(java.util.function.IntConsumer closure); + """, + ] + ) + } + @Test func emptyClosure_swiftThunks() throws { try assertOutput( @@ -277,6 +305,31 @@ struct JNIClosureTests { ) } + @Test + func closureIntConsumer_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024closureIntConsumer__Ljava_util_function_IntConsumer_2") + public func Java_com_example_swift_SwiftModule__00024closureIntConsumer__Ljava_util_function_IntConsumer_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { + SwiftModule.closureIntConsumer(closure: { + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "accept", "(I)V")! + environment.interface.DeleteLocalRef(environment, class$) + let arguments$: [jvalue] = [_0.getJValue(in: environment)] + environment.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) + } + ) + } + """ + ] + ) + } + @Test func closureWithArgumentsAndReturn_javaBindings() throws { try assertOutput(