From d55d7f3a1b4d15953bb72165c62a08a9c3294e40 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Tue, 7 Jul 2026 19:42:34 +0200 Subject: [PATCH 1/5] jextract: Translate Swift () -> () to java Runnable --- ...MSwift2JavaGenerator+JavaTranslation.swift | 34 +++++++++++++++---- .../FuncCallbackImportTests.swift | 4 +-- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 0b4f52695..479cdf034 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -489,13 +489,11 @@ extension FFMSwift2JavaGenerator { genericRequirements: genericRequirements ) - case .function: - return TranslatedParameter( - parameter: JavaParameter( - name: parameterName, - type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)") - ), - conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) + case .function(let functionType): + return translateFunctionParameter( + functionType: functionType, + parameterName: parameterName, + methodName: methodName ) case .existential, .opaque, .genericParameter: @@ -568,6 +566,28 @@ extension FFMSwift2JavaGenerator { ) } + /// Translate a Swift Function parameter to the Java interface. + func translateFunctionParameter( + functionType: SwiftFunctionType, + parameterName: String, + methodName: String, + ) -> TranslatedParameter { + let parameterType = + if functionType.parameters.count == 0 && functionType.resultType.isVoid { + JavaType.javaLangRunnable + } else { + JavaType.class(package: nil, name: "\(methodName).\(parameterName)") + } + + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: parameterType + ), + conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) + ) + } + /// Translate an Optional Swift API parameter to the user-facing Java API parameter. func translateOptionalParameter( wrappedType swiftType: SwiftType, diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 39bd94311..9a11a6dde 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -119,7 +119,7 @@ final class FuncCallbackImportTests { * public func callMe(callback: () -> Void) * } */ - public static void callMe(callMe.callback callback) { + public static void callMe(java.lang.Runnable callback) { try(var arena$ = Arena.ofConfined()) { swiftjava___FakeModule_callMe_callback.call(callMe.$toUpcallStub(callback, arena$)); } @@ -234,7 +234,7 @@ final class FuncCallbackImportTests { * public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ()) * } */ - public static void callMeMore(callMeMore.callback callback, callMeMore.fn fn) { + public static void callMeMore(callMeMore.callback callback, java.lang.Runnable fn) { try(var arena$ = Arena.ofConfined()) { swiftjava___FakeModule_callMeMore_callback_fn.call(callMeMore.$toUpcallStub(callback, arena$), callMeMore.$toUpcallStub(fn, arena$)); } From 9b4f4c4bfde005a4020f6fd6424321a2296803ff Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Thu, 9 Jul 2026 17:41:59 +0200 Subject: [PATCH 2/5] Create extractKnownJavaFunctionalInterfaceType to handle known interfaces --- ...FFMSwift2JavaGenerator+JavaTranslation.swift | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 479cdf034..4afcdd508 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -566,6 +566,17 @@ extension FFMSwift2JavaGenerator { ) } + // Extract Java known functional interface type or null if not exists + func extractKnownJavaFunctionalInterfaceType( + functionType: SwiftFunctionType, + ) -> JavaType? { + if functionType.parameters.isEmpty && functionType.resultType.isVoid { + JavaType.javaLangRunnable + } else { + nil + } + } + /// Translate a Swift Function parameter to the Java interface. func translateFunctionParameter( functionType: SwiftFunctionType, @@ -573,11 +584,7 @@ extension FFMSwift2JavaGenerator { methodName: String, ) -> TranslatedParameter { let parameterType = - if functionType.parameters.count == 0 && functionType.resultType.isVoid { - JavaType.javaLangRunnable - } else { - JavaType.class(package: nil, name: "\(methodName).\(parameterName)") - } + extractKnownJavaFunctionalInterfaceType(functionType: functionType) ?? JavaType.class(package: nil, name: "\(methodName).\(parameterName)") return TranslatedParameter( parameter: JavaParameter( From b5e39bbc9edc194cabdd6112ef2aac6bf9b25ca2 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Thu, 9 Jul 2026 19:14:47 +0200 Subject: [PATCH 3/5] Support JNI and Native translation --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 21 ++ ...ISwift2JavaGenerator+JavaTranslation.swift | 65 +++++- ...wift2JavaGenerator+NativeTranslation.swift | 216 +++++++++++------- .../FuncCallbackImportTests.swift | 16 +- .../JNI/JNIClosureTests.swift | 10 +- 5 files changed, 220 insertions(+), 108 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 5093dad61..784f021a9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -255,6 +255,17 @@ extension FFMSwift2JavaGenerator { } printFunctionDescriptorDefinition(&printer, cResultType, cParams) + if cParameterTypes.isEmpty && cResultType.isVoid { + printer.print( + """ + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Runnable.class, "run", DESC); + private static MemorySegment toUpcallStub(Runnable fi, Arena arena) { + return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + } + """ + ) + return + } printer.print( """ private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); @@ -296,6 +307,16 @@ extension FFMSwift2JavaGenerator { ) { let cdeclDescriptor = "\(bindingDescriptorName).$\(functionType.name)" if functionType.isCompatibleWithC { + if !functionType.swiftType.isEscaping && functionType.parameters.isEmpty && functionType.swiftType.resultType.isVoid { + printer.print( + """ + private static MemorySegment $toUpcallStub(java.lang.Runnable fi, Arena arena) { + return \(bindingDescriptorName).$\(functionType.name).toUpcallStub(fi, arena); + } + """ + ) + return + } // If the user-facing functional interface is C ABI compatible, just extend // the lowered function pointer parameter interface. printer.print( diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 1e30455a5..81db978da 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -516,17 +516,14 @@ extension JNISwift2JavaGenerator { conversion: .placeholder, ) - case .function: - return TranslatedParameter( - parameter: JavaParameter( - name: parameterName, - type: .class( - package: javaPackage, - name: String.javaQualifiedName(parentName.fullName, methodName, parameterName) - ), - annotations: parameterAnnotations, - ), - conversion: .placeholder, + case .function(let functionType): + return translateFunctionParameter( + swiftType: swiftType, + functionType: functionType, + parameterName: parameterName, + methodName: methodName, + parentName: parentName, + parameterAnnotations: parameterAnnotations ) case .opaque(let proto), .existential(let proto): @@ -699,6 +696,52 @@ extension JNISwift2JavaGenerator { nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType)) } + func extractKnownJavaFunctionalInterfaceType( + swiftType: SwiftType, + functionType: SwiftFunctionType, + parameterName: String, + parameterAnnotations: [JavaAnnotation] + ) -> TranslatedParameter? { + if !functionType.isEscaping && functionType.parameters.isEmpty && functionType.resultType.isVoid { + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: JavaType.javaLangRunnable, + annotations: parameterAnnotations, + ), + conversion: .placeholder, + ) + } + return nil + } + + func translateFunctionParameter( + swiftType: SwiftType, + functionType: SwiftFunctionType, + parameterName: String, + methodName: String, + parentName: SwiftQualifiedTypeName, + parameterAnnotations: [JavaAnnotation] + ) -> TranslatedParameter { + extractKnownJavaFunctionalInterfaceType( + swiftType: swiftType, + functionType: functionType, + parameterName: parameterName, + parameterAnnotations: parameterAnnotations + ) + ?? TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .class( + package: javaPackage, + name: String.javaQualifiedName(parentName.fullName, methodName, parameterName) + ), + annotations: parameterAnnotations, + ), + conversion: .placeholder, + ) + } + func translateProtocolParameter( protocolType: SwiftType, parameterName: String, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 9f025bbdf..f3fa65512 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -265,87 +265,13 @@ extension JNISwift2JavaGenerator { conversionCheck: nil ) - case .function(let fn): - - // @Sendable is not supported yet as "environment" is later captured inside the closure. - if fn.isEscaping { - // For escaping closures we e need to create a Swift wrapper around - // the passed down Java functional interface because we must keep it - // alive with a global ref, that will remain around for as long as the - // escaping closure is. - // - // Prepare the name and shapes of the Java side functional interface - // and Swift side @JavaInterface wrapper we'll use to implement that - // interface (and keep the Java object reachable). - - // Name it: interface MyClass_myMethod_theClosure { ... } - let javaClosureInterfaceName = String.flatName( - prefix: "Java", - parent: parentName, - method: methodName, - parameter: parameterName, - ) - let javaBinaryName = String.javaBinaryName( - package: self.javaPackage, - parent: parentName, - method: methodName, - qualifier: parameterName, - ) - let generator = JavaInterfaceProtocolWrapperGenerator() - let syntheticFunction = try generator.generateSyntheticClosureFunction( - functionType: fn, - javaInterfaceName: javaClosureInterfaceName, - javaBinaryName: javaBinaryName, - ) - - return NativeParameter( - parameters: [ - JavaParameter( - name: parameterName, - type: .class( - package: javaPackage, - name: String.javaQualifiedName(parentName.fullName, methodName, parameterName) - ) - ) - ], - conversion: .escapingClosureLowering( - syntheticFunction: syntheticFunction - ), - indirectConversion: nil, - conversionCheck: nil, - syntheticClosure: syntheticFunction - ) - } - - // Non-escaping closures use the legacy translation - var parameters = [NativeParameter]() - for (i, parameter) in fn.parameters.enumerated() { - let closureParamName = parameter.parameterName ?? "_\(i)" - let closureParameter = try translateClosureParameter( - parameter.type, - parameterName: closureParamName - ) - parameters.append(closureParameter) - } - - let result = try translateClosureResult(fn.resultType) - - return NativeParameter( - parameters: [ - JavaParameter( - name: parameterName, - type: .class( - package: javaPackage, - name: String.javaQualifiedName(parentName.fullName, methodName, parameterName) - ) - ) - ], - conversion: .closureLowering( - parameters: parameters, - result: result - ), - indirectConversion: nil, - conversionCheck: nil + case .function(let functionType): + return try translateFunctionParameter( + swiftType: type, + functionType: functionType, + parameterName: parameterName, + methodName: methodName, + parentName: parentName ) case .opaque(let proto), .existential(let proto): @@ -436,6 +362,125 @@ extension JNISwift2JavaGenerator { ) } + func extractKnownJavaFunctionalInterfaceType( + functionType: SwiftFunctionType, + parameterName: String, + parameters: [JNISwift2JavaGenerator.NativeParameter], + result: JNISwift2JavaGenerator.NativeResult + ) -> NativeParameter? { + if !functionType.isEscaping && functionType.parameters.isEmpty && functionType.resultType.isVoid { + return NativeParameter( + parameters: [ + JavaParameter( + name: parameterName, + type: JavaType.javaLangRunnable + ) + ], + conversion: .closureLowering( + parameters: parameters, + result: result + ), + indirectConversion: nil, + conversionCheck: nil + ) + } + return nil + } + + func translateFunctionParameter( + swiftType: SwiftType, + functionType: SwiftFunctionType, + parameterName: String, + methodName: String, + parentName: SwiftQualifiedTypeName + ) throws -> NativeParameter { + // @Sendable is not supported yet as "environment" is later captured inside the closure. + if functionType.isEscaping { + // For escaping closures we e need to create a Swift wrapper around + // the passed down Java functional interface because we must keep it + // alive with a global ref, that will remain around for as long as the + // escaping closure is. + // + // Prepare the name and shapes of the Java side functional interface + // and Swift side @JavaInterface wrapper we'll use to implement that + // interface (and keep the Java object reachable). + + // Name it: interface MyClass_myMethod_theClosure { ... } + let javaClosureInterfaceName = String.flatName( + prefix: "Java", + parent: parentName, + method: methodName, + parameter: parameterName, + ) + let javaBinaryName = String.javaBinaryName( + package: self.javaPackage, + parent: parentName, + method: methodName, + qualifier: parameterName, + ) + let generator = JavaInterfaceProtocolWrapperGenerator() + let syntheticFunction = try generator.generateSyntheticClosureFunction( + functionType: functionType, + javaInterfaceName: javaClosureInterfaceName, + javaBinaryName: javaBinaryName, + ) + + return NativeParameter( + parameters: [ + JavaParameter( + name: parameterName, + type: .class( + package: javaPackage, + name: String.javaQualifiedName(parentName.fullName, methodName, parameterName) + ) + ) + ], + conversion: .escapingClosureLowering( + syntheticFunction: syntheticFunction + ), + indirectConversion: nil, + conversionCheck: nil, + syntheticClosure: syntheticFunction + ) + } + + // Non-escaping closures use the legacy translation + var parameters = [NativeParameter]() + for (i, parameter) in functionType.parameters.enumerated() { + let closureParamName = parameter.parameterName ?? "_\(i)" + let closureParameter = try translateClosureParameter( + parameter.type, + parameterName: closureParamName + ) + parameters.append(closureParameter) + } + + let result = try translateClosureResult(functionType.resultType) + return extractKnownJavaFunctionalInterfaceType( + functionType: functionType, + parameterName: parameterName, + parameters: parameters, + result: result + ) + ?? NativeParameter( + parameters: [ + JavaParameter( + name: parameterName, + type: .class( + package: javaPackage, + name: String.javaQualifiedName(parentName.fullName, methodName, parameterName) + ) + ) + ], + conversion: .closureLowering( + parameters: parameters, + result: result + ), + indirectConversion: nil, + conversionCheck: nil + ) + } + func translateProtocolParameter( protocolType: SwiftType, methodName: String, @@ -1644,10 +1689,17 @@ extension JNISwift2JavaGenerator { $0.conversion.render(&printer, $0.parameters.first!.name) } + let methodName = + if parameters.isEmpty && nativeResult.javaType.isVoid { + "run" + } else { + "apply" + } + printer.print( """ let class$ = environment.interface.GetObjectClass(environment, \(placeholder)) - let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")! + let methodID$ = environment.interface.GetMethodID(environment, class$, "\(methodName)", "\(methodSignature.mangledName)")! environment.interface.DeleteLocalRef(environment, class$) let arguments$: [jvalue] = [\(arguments.joined(separator: .comma))] """ diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 9a11a6dde..f6633c8d9 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -100,16 +100,14 @@ final class FuncCallbackImportTests { void apply(); } private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); - private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); - private static MemorySegment toUpcallStub(Function fi, Arena arena) { + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Runnable.class, "run", DESC); + private static MemorySegment toUpcallStub(Runnable fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } } } public static class callMe { - @FunctionalInterface - public interface callback extends swiftjava___FakeModule_callMe_callback.$callback.Function {} - private static MemorySegment $toUpcallStub(callback fi, Arena arena) { + private static MemorySegment $toUpcallStub(java.lang.Runnable fi, Arena arena) { return swiftjava___FakeModule_callMe_callback.$callback.toUpcallStub(fi, arena); } } @@ -210,8 +208,8 @@ final class FuncCallbackImportTests { void apply(); } private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); - private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); - private static MemorySegment toUpcallStub(Function fi, Arena arena) { + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Runnable.class, "run", DESC); + private static MemorySegment toUpcallStub(Runnable fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } } @@ -222,9 +220,7 @@ final class FuncCallbackImportTests { private static MemorySegment $toUpcallStub(callback fi, Arena arena) { return swiftjava___FakeModule_callMeMore_callback_fn.$callback.toUpcallStub(fi, arena); } - @FunctionalInterface - public interface fn extends swiftjava___FakeModule_callMeMore_callback_fn.$fn.Function {} - private static MemorySegment $toUpcallStub(fn fi, Arena arena) { + private static MemorySegment $toUpcallStub(java.lang.Runnable fi, Arena arena) { return swiftjava___FakeModule_callMeMore_callback_fn.$fn.toUpcallStub(fi, arena); } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index 65dd28997..451a30a11 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -46,12 +46,12 @@ struct JNIClosureTests { * public func emptyClosure(closure: () -> ()) * } */ - public static void emptyClosure(com.example.swift.SwiftModule.emptyClosure.closure closure) { + public static void emptyClosure(java.lang.Runnable closure) { SwiftModule.$emptyClosure(closure); } """, """ - private static native void $emptyClosure(com.example.swift.SwiftModule.emptyClosure.closure closure); + private static native void $emptyClosure(java.lang.Runnable closure); """, ] ) @@ -66,11 +66,11 @@ struct JNIClosureTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2") - public func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { + @_cdecl("Java_com_example_swift_SwiftModule__00024emptyClosure__Ljava_lang_Runnable_2") + public func Java_com_example_swift_SwiftModule__00024emptyClosure__Ljava_lang_Runnable_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.emptyClosure(closure: { let class$ = environment.interface.GetObjectClass(environment, closure) - let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "()V")! + let methodID$ = environment.interface.GetMethodID(environment, class$, "run", "()V")! environment.interface.DeleteLocalRef(environment, class$) let arguments$: [jvalue] = [] environment.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) From 97a2fed9a54726da997f96900a5ca9fc4f0cb488 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Thu, 9 Jul 2026 21:06:12 +0200 Subject: [PATCH 4/5] Use full class name for java.lang.Runnable --- .../FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift | 4 ++-- Tests/JExtractSwiftTests/FuncCallbackImportTests.swift | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 784f021a9..37ce8ce9c 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -258,8 +258,8 @@ extension FFMSwift2JavaGenerator { if cParameterTypes.isEmpty && cResultType.isVoid { printer.print( """ - private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Runnable.class, "run", DESC); - private static MemorySegment toUpcallStub(Runnable fi, Arena arena) { + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(java.lang.Runnable.class, "run", DESC); + private static MemorySegment toUpcallStub(java.lang.Runnable fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } """ diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index f6633c8d9..e3086dfba 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -100,8 +100,8 @@ final class FuncCallbackImportTests { void apply(); } private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); - private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Runnable.class, "run", DESC); - private static MemorySegment toUpcallStub(Runnable fi, Arena arena) { + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(java.lang.Runnable.class, "run", DESC); + private static MemorySegment toUpcallStub(java.lang.Runnable fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } } @@ -208,8 +208,8 @@ final class FuncCallbackImportTests { void apply(); } private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); - private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Runnable.class, "run", DESC); - private static MemorySegment toUpcallStub(Runnable fi, Arena arena) { + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(java.lang.Runnable.class, "run", DESC); + private static MemorySegment toUpcallStub(java.lang.Runnable fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } } From 39d6dbe0a4a0cdd94bf677cc2824e76030acba56 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Fri, 10 Jul 2026 17:35:39 +0200 Subject: [PATCH 5/5] Use Runnable in the sample app --- .../src/test/java/com/example/swift/MySwiftLibraryTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 e0decfb3e..478b24c34 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -81,9 +81,9 @@ void call_globalStringIdentity_empty() { void call_globalCallMeRunnable() { CountDownLatch countDownLatch = new CountDownLatch(3); - MySwiftLibrary.globalCallMeRunnable(new MySwiftLibrary.globalCallMeRunnable.run() { + MySwiftLibrary.globalCallMeRunnable(new java.lang.Runnable() { @Override - public void apply() { + public void run() { countDownLatch.countDown(); } });