From e91fd2b7fde3f8de8f62fa4336b268c8a7e240f8 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 3 Dec 2025 15:28:53 +0100 Subject: [PATCH 1/4] fix protocols --- Package.swift | 1 + .../MySwiftLibrary/ConcreteProtocolAB.swift | 4 ++ .../Sources/MySwiftLibrary/ProtocolA.swift | 1 + .../example/swift/ProtocolCallbacksTest.java | 4 +- .../java/com/example/swift/ProtocolTest.java | 10 ++- ...t2JavaGenerator+JavaBindingsPrinting.swift | 63 ++++++++++++++----- .../Configuration.swift | 5 +- .../JNI/JNIProtocolTests.swift | 34 +++++++++- .../MemoryManagementModeTests.swift | 2 +- 9 files changed, 102 insertions(+), 22 deletions(-) diff --git a/Package.swift b/Package.swift index d877ae696..2c56a2870 100644 --- a/Package.swift +++ b/Package.swift @@ -460,6 +460,7 @@ let package = Package( .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "OrderedCollections", package: "swift-collections"), "JavaTypes", "SwiftJavaShared", "SwiftJavaConfigurationShared", diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift index ed55d0398..d83fbb28d 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift @@ -21,6 +21,10 @@ public class ConcreteProtocolAB: ProtocolA, ProtocolB { return "ConcreteProtocolAB" } + public func makeClass() -> MySwiftClass { + return MySwiftClass(x: 10, y: 50) + } + public init(constantA: Int64, constantB: Int64) { self.constantA = constantA self.constantB = constantB diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift index d5281b81e..6e19596f9 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift @@ -17,6 +17,7 @@ public protocol ProtocolA { var mutable: Int64 { get set } func name() -> String + func makeClass() -> MySwiftClass } public func takeProtocol(_ proto1: any ProtocolA, _ proto2: some ProtocolA) -> Int64 { diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java index e79fd4a3b..b4ebe8532 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java @@ -74,7 +74,7 @@ public String withString(String input) { public void withVoid() {} @Override - public MySwiftClass withObject(MySwiftClass input) { + public MySwiftClass withObject(MySwiftClass input, SwiftArena swiftArena$) { return input; } @@ -84,7 +84,7 @@ public OptionalLong withOptionalInt64(OptionalLong input) { } @Override - public Optional withOptionalObject(Optional input) { + public Optional withOptionalObject(Optional input, SwiftArena swiftArena$) { return input; } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java index f5d1ffcf7..b8159b8ad 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java @@ -73,6 +73,14 @@ void protocolMethod() { } } + @Test + void protocolClassMethod() { + try (var arena = SwiftArena.ofConfined()) { + ProtocolA proto1 = ConcreteProtocolAB.init(10, 5, arena); + assertEquals(10, proto1.makeClass().getX()); + } + } + static class JavaStorage implements Storage { StorageItem item; @@ -81,7 +89,7 @@ static class JavaStorage implements Storage { } @Override - public StorageItem load() { + public StorageItem load(SwiftArena swiftArena$) { return item; } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 78cf7d418..75a4aa556 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -144,17 +144,17 @@ extension JNISwift2JavaGenerator { printer.printBraceBlock("public interface \(decl.swiftNominal.name)\(extendsString)") { printer in for initializer in decl.initializers { - printFunctionDowncallMethods(&printer, initializer, skipMethodBody: true, skipArenas: true) + printFunctionDowncallMethods(&printer, initializer, skipMethodBody: true) printer.println() } for method in decl.methods { - printFunctionDowncallMethods(&printer, method, skipMethodBody: true, skipArenas: true) + printFunctionDowncallMethods(&printer, method, skipMethodBody: true) printer.println() } for variable in decl.variables { - printFunctionDowncallMethods(&printer, variable, skipMethodBody: true, skipArenas: true) + printFunctionDowncallMethods(&printer, variable, skipMethodBody: true) printer.println() } } @@ -420,7 +420,7 @@ extension JNISwift2JavaGenerator { printer.print("record _NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") } - self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction, skipMethodBody: false, skipArenas: false) + self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction, skipMethodBody: false) printer.println() } } @@ -428,8 +428,7 @@ extension JNISwift2JavaGenerator { private func printFunctionDowncallMethods( _ printer: inout CodePrinter, _ decl: ImportedFunc, - skipMethodBody: Bool = false, - skipArenas: Bool = false + skipMethodBody: Bool = false ) { guard translatedDecl(for: decl) != nil else { // Failed to translate. Skip. @@ -440,7 +439,7 @@ extension JNISwift2JavaGenerator { printJavaBindingWrapperHelperClass(&printer, decl) - printJavaBindingWrapperMethod(&printer, decl, skipMethodBody: skipMethodBody, skipArenas: skipArenas) + printJavaBindingWrapperMethod(&printer, decl, skipMethodBody: skipMethodBody) } /// Print the helper type container for a user-facing Java API. @@ -486,21 +485,19 @@ extension JNISwift2JavaGenerator { private func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, _ decl: ImportedFunc, - skipMethodBody: Bool, - skipArenas: Bool + skipMethodBody: Bool ) { guard let translatedDecl = translatedDecl(for: decl) else { fatalError("Decl was not translated, \(decl)") } - printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl, skipMethodBody: skipMethodBody, skipArenas: skipArenas) + printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl, skipMethodBody: skipMethodBody) } private func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl, importedFunc: ImportedFunc? = nil, - skipMethodBody: Bool, - skipArenas: Bool + skipMethodBody: Bool ) { var modifiers = ["public"] if translatedDecl.isStatic { @@ -531,14 +528,52 @@ extension JNISwift2JavaGenerator { let parametersStr = parameters.joined(separator: ", ") // Print default global arena variation + // If we have enabled Java callbacks, we do not want to emit these + // for protocols as we already disable arenas for these methods, + // which would then lead to duplicate methods. + let shouldGenerateGlobalArenaVariation: Bool + let isParentProtocol = importedFunc?.parentType?.asNominalType?.isProtocol ?? false + if config.effectiveMemoryManagementMode.requiresGlobalArena && translatedSignature.requiresSwiftArena { + shouldGenerateGlobalArenaVariation = true + } else if isParentProtocol, translatedSignature.requiresSwiftArena, config.effectiveEnableJavaCallbacks { + shouldGenerateGlobalArenaVariation = true + } else { + shouldGenerateGlobalArenaVariation = false + } + +// let importedProtocolType = self.analysis.importedTypes.first { _, type in +// guard let parentType = importedFunc?.parentType?.asNominalTypeDeclaration else { +// return false +// } +// return type.swiftNominal == parentType && (parentType.kind == .protocol) +// }?.value +// +// if config.effectiveMemoryManagementMode.requiresGlobalArena && translatedSignature.requiresSwiftArena { +// if let importedProtocolType { +// // If this is a method from a protocol we are implementing +// // we must generate +// // the auto arena version, since that is what the wrappers from Swift +// // will use. +// let isProtocolMethodOrVariable = importedProtocolType.methods.contains { +// $0.name == importedFunc?.name && $0.functionSignature == importedFunc?.functionSignature +// } +// shouldGenerateGlobalArenaVariation = isProtocolMethodOrVariable +// } else { +// shouldGenerateGlobalArenaVariation = false +// } +// } else { +// shouldGenerateGlobalArenaVariation = false +// } + + if shouldGenerateGlobalArenaVariation { if let importedFunc { printDeclDocumentation(&printer, importedFunc) } var modifiers = modifiers // If we are a protocol, we emit this as default method - if importedFunc?.parentType?.asNominalTypeDeclaration?.kind == .protocol { + if importedFunc?.parentType?.asNominalType?.isProtocol ?? false { modifiers.insert("default", at: 1) } @@ -555,7 +590,7 @@ extension JNISwift2JavaGenerator { printer.println() } - if translatedSignature.requiresSwiftArena, !skipArenas { + if translatedSignature.requiresSwiftArena { parameters.append("SwiftArena swiftArena$") } if let importedFunc { diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 37d1e1e79..e0c40f1c5 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -65,7 +65,10 @@ public struct Configuration: Codable { asyncFuncMode ?? .default } - public var enableJavaCallbacks: Bool? // FIXME: default it to false, but that plays not nice with Codable + public var enableJavaCallbacks: Bool? + public var effectiveEnableJavaCallbacks: Bool { + enableJavaCallbacks ?? false + } public var generatedJavaSourcesListFileOutput: String? diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index 231c4d25d..3b47fd100 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -32,7 +32,9 @@ struct JNIProtocolTests { public protocol B {} - public class SomeClass: SomeProtocol {} + public class SomeClass: SomeProtocol { + public func makeClass() -> SomeClass {} + } public func takeProtocol(x: some SomeProtocol, y: any SomeProtocol) public func takeGeneric(s: S) @@ -61,7 +63,29 @@ struct JNIProtocolTests { ... public void method(); ... - public SomeClass withObject(SomeClass c); + public SomeClass withObject(SomeClass c, SwiftArena swiftArena$); + ... + } + """ + ]) + } + + @Test + func emitsDefault() throws { + try assertOutput( + input: source, + config: config, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public interface SomeProtocol { + ... + public default SomeClass withObject(SomeClass c) { + return withObject(c, SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA); + } + ... + public SomeClass withObject(SomeClass c, SwiftArena swiftArena$); ... } """ @@ -78,7 +102,11 @@ struct JNIProtocolTests { expectedChunks: [ """ public final class SomeClass implements JNISwiftInstance, SomeProtocol { - """ + ... + public SomeClass makeClass(SwiftArena swiftArena$) { + ... + } + """, ]) } diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift index 7e78434d1..2228aad88 100644 --- a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift @@ -99,7 +99,7 @@ struct MemoryManagementModeTests { } """, """ - public MyClass f(); + public MyClass f(SwiftArena swiftArena$); """ ] ) From 2adf7de9a1b283292b3c85ada93f5a50085a477f Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 3 Dec 2025 15:29:53 +0100 Subject: [PATCH 2/4] remove comments --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 75a4aa556..cebb3f1b5 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -542,30 +542,6 @@ extension JNISwift2JavaGenerator { shouldGenerateGlobalArenaVariation = false } -// let importedProtocolType = self.analysis.importedTypes.first { _, type in -// guard let parentType = importedFunc?.parentType?.asNominalTypeDeclaration else { -// return false -// } -// return type.swiftNominal == parentType && (parentType.kind == .protocol) -// }?.value -// -// if config.effectiveMemoryManagementMode.requiresGlobalArena && translatedSignature.requiresSwiftArena { -// if let importedProtocolType { -// // If this is a method from a protocol we are implementing -// // we must generate -// // the auto arena version, since that is what the wrappers from Swift -// // will use. -// let isProtocolMethodOrVariable = importedProtocolType.methods.contains { -// $0.name == importedFunc?.name && $0.functionSignature == importedFunc?.functionSignature -// } -// shouldGenerateGlobalArenaVariation = isProtocolMethodOrVariable -// } else { -// shouldGenerateGlobalArenaVariation = false -// } -// } else { -// shouldGenerateGlobalArenaVariation = false -// } - if shouldGenerateGlobalArenaVariation { if let importedFunc { printDeclDocumentation(&printer, importedFunc) From 1aaebf5a5d1107243d773ec09cea277ace412a78 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 3 Dec 2025 15:31:01 +0100 Subject: [PATCH 3/4] comments --- .../JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index cebb3f1b5..07f8b2f07 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -528,9 +528,9 @@ extension JNISwift2JavaGenerator { let parametersStr = parameters.joined(separator: ", ") // Print default global arena variation - // If we have enabled Java callbacks, we do not want to emit these - // for protocols as we already disable arenas for these methods, - // which would then lead to duplicate methods. + // If we have enabled javaCallbacks we must emit default + // arena methods for protocols, as this is what + // Swift will call into, when you call a interface from Swift. let shouldGenerateGlobalArenaVariation: Bool let isParentProtocol = importedFunc?.parentType?.asNominalType?.isProtocol ?? false From 83f84b7ecf5e87e939769fe6e14011ffe8aff4e9 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 3 Dec 2025 15:31:23 +0100 Subject: [PATCH 4/4] cleanup --- .../JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 07f8b2f07..bfa2ff12d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -549,7 +549,7 @@ extension JNISwift2JavaGenerator { var modifiers = modifiers // If we are a protocol, we emit this as default method - if importedFunc?.parentType?.asNominalType?.isProtocol ?? false { + if isParentProtocol { modifiers.insert("default", at: 1) }