diff --git a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift index 530c821d5..1ad278fd8 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftAndJavaJarFFMSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -76,6 +76,10 @@ public func globalCallMeDoublePredicate(run: (Double) -> Bool) -> Bool { run(1.0) } +public func globalCallMeIntBinaryOperator(run: (Int32, Int32) -> Int32) -> Int32 { + run(1, 2) +} + // ==== 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 a809d299c..15c484071 100644 --- a/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftAndJavaJarFFMSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -110,4 +110,10 @@ void call_globalCallMeDoublePredicate_noThrow() { boolean result = MySwiftLibrary.globalCallMeDoublePredicate((double a) -> { return true; }); assertEquals(true, result); } + + @Test + void call_globalCallMeIntBinaryOperator_noThrow() { + int result = MySwiftLibrary.globalCallMeIntBinaryOperator((int a, int b) -> { return a + b; }); + assertEquals(3, result); + } } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index cd2ceefd7..34317dacf 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -88,6 +88,10 @@ public func globalCallMeDoublePredicate(run: (Double) -> Bool) -> Bool { run(1.0) } +public func globalCallMeIntBinaryOperator(run: (Int32, Int32) -> Int32) -> Int32 { + run(1, 2) +} + 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 70658e008..019a56606 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -222,4 +222,10 @@ void call_globalCallMeDoublePredicate_noThrow() { boolean result = MySwiftLibrary.globalCallMeDoublePredicate((double a) -> { return true; }); assertEquals(true, result); } + + @Test + void call_globalCallMeIntBinaryOperator_noThrow() { + int result = MySwiftLibrary.globalCallMeIntBinaryOperator((int a, int b) -> { return a + b; }); + assertEquals(3, result); + } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift index 91350a839..d2e66c428 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift @@ -61,6 +61,10 @@ public func globalCallMeDoublePredicate(run: (Double) -> Bool) -> Bool { run(1.0) } +public func globalCallMeIntBinaryOperator(run: (Int32, Int32) -> Int32) -> Int32 { + run(1, 2) +} + 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 473afbcf0..8a9662b83 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java @@ -104,4 +104,10 @@ void globalCallMeDoublePredicate() { boolean result = MySwiftLibrary.globalCallMeDoublePredicate((double a) -> { return true; }); assertEquals(true, result); } + + @Test + void globalCallMeIntBinaryOperator() { + int result = MySwiftLibrary.globalCallMeIntBinaryOperator((int a, int b) -> { return a + b; }); + assertEquals(3, result); + } } diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index 495fd41f1..8ef7fd07b 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -83,6 +83,10 @@ public func globalCallMeDoublePredicate(run: (Double) -> Bool) -> Bool { run(1.0) } +public func globalCallMeIntBinaryOperator(run: (Int32, Int32) -> Int32) -> Int32 { + run(1, 2) +} + 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 cbeb4b3dd..020ece79a 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -80,6 +80,11 @@ extension JavaType { .class(package: "java.util.function", name: "DoublePredicate") } + /// The description of the type java.util.function.IntBinaryOperator. + static var javaUtilFunctionIntBinaryOperator: JavaType { + .class(package: "java.util.function", name: "IntBinaryOperator") + } + /// 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 2a2a66fa0..43d29cf73 100644 --- a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift +++ b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift @@ -99,6 +99,13 @@ struct KnownJavaFunctionalInterface: Sendable { result: .boolean ) + static let intBinaryOperator = KnownJavaFunctionalInterface( + JavaType.javaUtilFunctionIntBinaryOperator, + method: "applyAsInt", + parameters: [.int, .int], + result: .int + ) + static let all: [KnownJavaFunctionalInterface] = [ .runnable, .booleanSupplier, @@ -111,6 +118,7 @@ struct KnownJavaFunctionalInterface: Sendable { .intPredicate, .longPredicate, .doublePredicate, + .intBinaryOperator, ] static func find(parameters: [JavaType], result: JavaType) -> KnownJavaFunctionalInterface? { @@ -185,6 +193,17 @@ struct KnownJavaFunctionalInterface: Sendable { } } + // Binary operators + if parameters.count == 2 && parameters[0].type == result && parameters[1].type == result { + let parameter = parameters[0].type + return switch () { + case _ where parameter.isInt32: + intBinaryOperator + default: + nil + } + } + return nil } diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 9add79130..f66110358 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -47,6 +47,8 @@ final class FuncCallbackImportTests { public func callMeLongPredicate(callback: (Int64) -> Bool) public func callMeDoublePredicate(callback: (Double) -> Bool) + public func callMeIntBinaryOperator(callback: (Int32, Int32) -> Int32) + public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ()) public func withBuffer(body: (UnsafeRawBufferPointer) -> Int) """ @@ -717,6 +719,49 @@ final class FuncCallbackImportTests { ) } + @Test("Import: public func callMecallMeIntBinaryOperatorFunc(callback: (Int32, Int32) -> Int32)") + func func_callMecallMeIntBinaryOperatorFunc_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 == "callMeIntBinaryOperator" }! + + 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 callMeIntBinaryOperator(callback: (Int32, Int32) -> Int32) + * } + */ + public static void callMeIntBinaryOperator(java.util.function.IntBinaryOperator callback) { + try(var arena$ = Arena.ofConfined()) { + swiftjava___FakeModule_callMeIntBinaryOperator_callback.call(callMeIntBinaryOperator.$toUpcallStub(callback, arena$)); + } + } + """ + ] + ) + } + @Test("Import: public func withBuffer(body: (UnsafeRawBufferPointer) -> Int)") func func_withBuffer_body() throws { var config = Configuration() diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index c91e08eea..e1aae0040 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -33,6 +33,8 @@ struct JNIClosureTests { public func closureLongPredicate(closure: (Int64) -> Bool) {} public func closureDoublePredicate(closure: (Double) -> Bool) {} + public func closureIntBinaryOperator(closure: (Int32, Int32) -> Int32) {} + public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) {} """ @@ -311,6 +313,31 @@ struct JNIClosureTests { ) } + @Test + func closureIntBinaryOperator_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func closureIntBinaryOperator(closure: (Int32, Int32) -> Int32) + * } + */ + public static void closureIntBinaryOperator(java.util.function.IntBinaryOperator closure) { + SwiftModule.$closureIntBinaryOperator(closure); + } + """, + """ + private static native void $closureIntBinaryOperator(java.util.function.IntBinaryOperator closure); + """, + ] + ) + } + @Test func emptyClosure_swiftThunks() throws { try assertOutput( @@ -586,6 +613,31 @@ struct JNIClosureTests { ) } + @Test + func closureIntBinaryOperator_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024closureIntBinaryOperator__Ljava_util_function_IntBinaryOperator_2") + public func Java_com_example_swift_SwiftModule__00024closureIntBinaryOperator__Ljava_util_function_IntBinaryOperator_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { + SwiftModule.closureIntBinaryOperator(closure: { + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "applyAsInt", "(II)I")! + environment.interface.DeleteLocalRef(environment, class$) + let arguments$: [jvalue] = [_0.getJValue(in: environment), _1.getJValue(in: environment)] + return Int32(fromJNI: environment.interface.CallIntMethodA(environment, closure, methodID$, arguments$), in: environment) + } + ) + } + """ + ] + ) + } + @Test func closureWithArgumentsAndReturn_javaBindings() throws { try assertOutput(