From 4daa74d572ba8cc89f74fda4ce84b326f57d3976 Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Wed, 15 Jul 2026 19:28:52 +0200 Subject: [PATCH 1/4] jextract: Remove emitting interface decl for known func interface --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 42 ++++++++++--------- ...t2JavaGenerator+JavaBindingsPrinting.swift | 9 ++++ .../KnownFunctionalInterfaces.swift | 11 +++++ .../FuncCallbackImportTests.swift | 8 ---- .../JNI/JNIClosureTests.swift | 9 ---- .../JNI/JNIEscapingClosureTests.swift | 23 ---------- 6 files changed, 42 insertions(+), 60 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index fc7d25620..b6bec40fd 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -232,36 +232,38 @@ extension FFMSwift2JavaGenerator { private static class \(name) """ ) { printer in - printer.print( - """ - @FunctionalInterface - public interface Function { - \(cResultType.javaType) apply(\(paramDecls.joined(separator: .comma))); + let (interfaceName, methodName, isKnownFuncInterface) = + if let known = KnownJavaFunctionalInterface.find(parameters: cParameterTypes, result: cResultType) { + (known.javaType.description, known.method, true) + } else { + ("Function", "apply", false) } - """ - ) - if let impl { + if !isKnownFuncInterface { printer.print( """ - public final static class Function$Impl implements Function { - \(impl.members.joinedJavaStatements(indent: 2)) - public \(cResultType.javaType) apply(\(paramDecls.joined(separator: .comma))) { - \(impl.body) - } + @FunctionalInterface + public interface Function { + \(cResultType.javaType) apply(\(paramDecls.joined(separator: .comma))); } """ ) - } - printFunctionDescriptorDefinition(&printer, cResultType, cParams) - let (interfaceName, methodName) = - if let known = KnownJavaFunctionalInterface.find(parameters: cParameterTypes, result: cResultType) { - (known.javaType.description, known.method) - } else { - ("Function", "apply") + if let impl { + printer.print( + """ + public final static class Function$Impl implements Function { + \(impl.members.joinedJavaStatements(indent: 2)) + public \(cResultType.javaType) apply(\(paramDecls.joined(separator: .comma))) { + \(impl.body) + } + } + """ + ) } + } + printFunctionDescriptorDefinition(&printer, cResultType, cParams) printer.print( """ private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(\(interfaceName).class, "\(methodName)", DESC); diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 5d61aafce..b37f09d32 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -791,12 +791,21 @@ extension JNISwift2JavaGenerator { return } + // If it's contain one function type and it's known java functional interface type, we don't need to print the whole class + if translated.functionTypes.count == 1 && KnownJavaFunctionalInterface.find(translated.functionTypes[0]) != nil { + return + } + printer.printBraceBlock( """ public static class \(translated.name) """ ) { printer in for functionType in translated.functionTypes { + if KnownJavaFunctionalInterface.find(functionType) != nil { + continue + } + printJavaBindingWrapperFunctionTypeHelper(&printer, functionType) } } diff --git a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift index fb0c784b0..616ee0852 100644 --- a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift +++ b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift @@ -40,10 +40,21 @@ struct KnownJavaFunctionalInterface: Sendable { find(parameters: parameters.map(\.javaType), result: result.javaType) } + static func find(parameters: [JNISwift2JavaGenerator.TranslatedParameter], result: JNISwift2JavaGenerator.TranslatedResult) -> KnownJavaFunctionalInterface? { + find(parameters: parameters.map(\.parameter.type.javaType), result: result.javaType) + } + static func find(_ methodSignature: MethodSignature) -> KnownJavaFunctionalInterface? { find(parameters: methodSignature.parameterTypes, result: methodSignature.resultType) } + static func find(_ functionType: JNISwift2JavaGenerator.TranslatedFunctionType) -> KnownJavaFunctionalInterface? { + if functionType.isEscaping { + return nil + } + return find(parameters: functionType.parameters, result: functionType.result) + } + init(_ javaType: JavaType, method: String, parameters: [JavaType], result: JavaType) { self.javaType = javaType self.method = method diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index e3086dfba..b16aa3de5 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -95,10 +95,6 @@ final class FuncCallbackImportTests { * } */ private static class $callback { - @FunctionalInterface - public interface Function { - void apply(); - } private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(java.lang.Runnable.class, "run", DESC); private static MemorySegment toUpcallStub(java.lang.Runnable fi, Arena arena) { @@ -203,10 +199,6 @@ final class FuncCallbackImportTests { * } */ private static class $fn { - @FunctionalInterface - public interface Function { - void apply(); - } private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(java.lang.Runnable.class, "run", DESC); private static MemorySegment toUpcallStub(java.lang.Runnable fi, Arena arena) { diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index 451a30a11..7a265442f 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -30,15 +30,6 @@ struct JNIClosureTests { .jni, .java, expectedChunks: [ - """ - public static class emptyClosure { - /** Corresponds to the Swift closure parameter of type {@code () -> ()}. */ - @FunctionalInterface - public interface closure { - void apply(); - } - } - """, """ /** * Downcall to Swift: diff --git a/Tests/JExtractSwiftTests/JNI/JNIEscapingClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEscapingClosureTests.swift index c5e4dceca..b6ca93af7 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEscapingClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEscapingClosureTests.swift @@ -173,29 +173,6 @@ struct JNIEscapingClosureTests { ) } - @Test - func nonEscapingClosure_stillWorks() throws { - let source = - """ - public func call(closure: () -> Void) {} - """ - - try assertOutput( - input: source, - .jni, - .java, - expectedChunks: [ - """ - /** Corresponds to the Swift closure parameter of type {@code () -> Void}. */ - @FunctionalInterface - public interface closure { - void apply(); - } - """ - ] - ) - } - @Test func escapingClosureWithParametersAndResult_swiftThunks() throws { let source = From fadc46da4a377ddce38da696063d7c3feef3e87c Mon Sep 17 00:00:00 2001 From: Amr Hesham Date: Thu, 16 Jul 2026 20:02:26 +0200 Subject: [PATCH 2/4] Use KnownJavaFunctionalInterface in JNI java bindings --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 24 +++++++++++-------- .../KnownFunctionalInterfaces.swift | 7 ++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index b6bec40fd..46ddc38bb 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -305,23 +305,27 @@ extension FFMSwift2JavaGenerator { ) { let cdeclDescriptor = "\(bindingDescriptorName).$\(functionType.name)" if functionType.isCompatibleWithC { - if !functionType.swiftType.isEscaping && functionType.parameters.isEmpty && functionType.swiftType.resultType.isVoid { + let (interfaceName, isKnownFuncInterface) = + if let known = KnownJavaFunctionalInterface.find(functionType) { + (known.javaType.description, true) + } else { + (functionType.name, false) + } + + if !isKnownFuncInterface { + // If the user-facing functional interface is C ABI compatible, just extend + // the lowered function pointer parameter interface. printer.print( """ - private static MemorySegment $toUpcallStub(java.lang.Runnable fi, Arena arena) { - return \(bindingDescriptorName).$\(functionType.name).toUpcallStub(fi, arena); - } + @FunctionalInterface + public interface \(interfaceName) extends \(cdeclDescriptor).Function {} """ ) - return } - // If the user-facing functional interface is C ABI compatible, just extend - // the lowered function pointer parameter interface. + printer.print( """ - @FunctionalInterface - public interface \(functionType.name) extends \(cdeclDescriptor).Function {} - private static MemorySegment $toUpcallStub(\(functionType.name) fi, Arena arena) { + private static MemorySegment $toUpcallStub(\(interfaceName) fi, Arena arena) { return \(bindingDescriptorName).$\(functionType.name).toUpcallStub(fi, arena); } """ diff --git a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift index 616ee0852..7baa30645 100644 --- a/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift +++ b/Sources/JExtractSwiftLib/KnownFunctionalInterfaces.swift @@ -55,6 +55,13 @@ struct KnownJavaFunctionalInterface: Sendable { return find(parameters: functionType.parameters, result: functionType.result) } + static func find(_ functionType: FFMSwift2JavaGenerator.TranslatedFunctionType) -> KnownJavaFunctionalInterface? { + if functionType.swiftType.isEscaping { + return nil + } + return find(parameters: functionType.parameters.map(\.parameter.type.javaType), result: functionType.result.javaResultType) + } + init(_ javaType: JavaType, method: String, parameters: [JavaType], result: JavaType) { self.javaType = javaType self.method = method From 59d9a272339e1dd3ddf524842742bd3f7fd07e64 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sat, 18 Jul 2026 10:47:12 +0900 Subject: [PATCH 3/4] Apply suggestions from code review --- .../JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index b37f09d32..73774bb6f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -791,8 +791,10 @@ extension JNISwift2JavaGenerator { return } - // If it's contain one function type and it's known java functional interface type, we don't need to print the whole class - if translated.functionTypes.count == 1 && KnownJavaFunctionalInterface.find(translated.functionTypes[0]) != nil { + // If contains one function type and it's known java functional interface type, we don't need to print the whole class + if translated.functionTypes.count == 1, + let ty = translated.functionTypes.first, + KnownJavaFunctionalInterface.find(ty) != nil { return } From e2c7fec6566799b08083ef83614de9e89a25ad4c Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Sat, 18 Jul 2026 10:59:43 +0900 Subject: [PATCH 4/4] fix formatting --- .../JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 73774bb6f..c4cd6bb04 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -792,9 +792,10 @@ extension JNISwift2JavaGenerator { } // If contains one function type and it's known java functional interface type, we don't need to print the whole class - if translated.functionTypes.count == 1, - let ty = translated.functionTypes.first, - KnownJavaFunctionalInterface.find(ty) != nil { + if translated.functionTypes.count == 1, + let ty = translated.functionTypes.first, + KnownJavaFunctionalInterface.find(ty) != nil + { return }