From b06790e5a2e9d545c2378fea62d614d1d6d1de34 Mon Sep 17 00:00:00 2001 From: Himeshi Date: Tue, 27 Oct 2026 14:06:22 +0900 Subject: [PATCH] JNI: align protocol parameter wrapper names Use the same wrapper name in generated declarations and JNI conversions when Java method names differ from Swift names. Preserve shared adapters for initializer overloads, and cover protocol property setters and Java callbacks. --- .../ProtocolParameterOverloads.swift | 12 +++++ .../swift/ProtocolParameterOverloadsTest.java | 11 +++++ ...ISwift2JavaGenerator+JavaTranslation.swift | 9 ++++ ...wift2JavaGenerator+NativeTranslation.swift | 13 ++++- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 15 +++--- .../JNIProtocolParameterOverloadTests.swift | 48 +++++++++++++++++++ 6 files changed, 100 insertions(+), 8 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolParameterOverloads.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolParameterOverloads.swift index 802e6c330..c85e34d1d 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolParameterOverloads.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolParameterOverloads.swift @@ -35,3 +35,15 @@ public final class ProtocolParameterOverloads { result + callback.value() + extra } } + +public final class ProtocolPropertyService { + public var callback: any OverloadedCallback + + public init(callback: some OverloadedCallback) { + self.callback = callback + } + + public func read() -> Int64 { + callback.value() + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolParameterOverloadsTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolParameterOverloadsTest.java index f14d8d428..f88f4ff85 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolParameterOverloadsTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolParameterOverloadsTest.java @@ -31,4 +31,15 @@ void overloadedInitializersAndMethodsAcceptJavaCallbacks() { assertEquals(27, second.calculate(callback, 2)); } } + + @Test + void propertySetterAcceptsAndRetainsJavaCallback() { + try (var arena = SwiftArena.ofConfined()) { + var service = ProtocolPropertyService.init(() -> 10, arena); + assertEquals(10, service.read()); + service.setCallback(() -> 42); + assertEquals(42, service.read()); + } + } + } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index d2ddfc994..15eac2356 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -194,6 +194,14 @@ extension JNISwift2JavaGenerator { ) } + /// Initializer overloads can share a protocol adapter even when their Java + /// names are disambiguated. Other declarations use the Java name, which is + /// also a valid Swift identifier for operators and Java keywords. + static func protocolWrapperMethodName(for decl: ExtractedFunc, javaName: String) -> String { + if case .initializer = decl.apiKind { return decl.name } + return javaName + } + func translate(_ decl: ExtractedFunc) throws -> TranslatedFunctionDecl { let nativeTranslation = NativeJavaTranslation( config: self.config, @@ -226,6 +234,7 @@ extension JNISwift2JavaGenerator { functionSignature: decl.functionSignature, translatedFunctionSignature: translatedFunctionSignature, methodName: javaName, + protocolWrapperMethodName: Self.protocolWrapperMethodName(for: decl, javaName: javaName), parentName: parentName, ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 4c87fa6a2..5d8f80fe1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -32,6 +32,7 @@ extension JNISwift2JavaGenerator { functionSignature: SwiftFunctionSignature, translatedFunctionSignature: TranslatedFunctionSignature, methodName: String, + protocolWrapperMethodName: String, parentName: SwiftQualifiedTypeName ) throws -> NativeFunctionSignature { let parameters = try zip(translatedFunctionSignature.parameters, functionSignature.parameters).map { @@ -42,6 +43,7 @@ extension JNISwift2JavaGenerator { type: swiftParameter.type, parameterName: parameterName, methodName: methodName, + protocolWrapperMethodName: protocolWrapperMethodName, parentName: parentName, genericParameters: functionSignature.genericParameters, genericRequirements: functionSignature.genericRequirements @@ -74,6 +76,7 @@ extension JNISwift2JavaGenerator { type: swiftType, parameterName: "selfPointer", methodName: methodName, + protocolWrapperMethodName: protocolWrapperMethodName, parentName: parentName, genericParameters: functionSignature.genericParameters, genericRequirements: functionSignature.genericRequirements @@ -91,6 +94,7 @@ extension JNISwift2JavaGenerator { type: .metatype(selfType), parameterName: "selfTypePointer", methodName: methodName, + protocolWrapperMethodName: protocolWrapperMethodName, parentName: parentName, genericParameters: functionSignature.genericParameters, genericRequirements: functionSignature.genericRequirements @@ -121,6 +125,7 @@ extension JNISwift2JavaGenerator { type: SwiftType, parameterName: String, methodName: String, + protocolWrapperMethodName: String, parentName: SwiftQualifiedTypeName, genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] @@ -277,7 +282,7 @@ extension JNISwift2JavaGenerator { case .opaque(let proto), .existential(let proto): return try translateProtocolParameter( protocolType: proto, - methodName: methodName, + methodName: protocolWrapperMethodName, parameterName: parameterName, parentName: parentName ) @@ -286,7 +291,7 @@ extension JNISwift2JavaGenerator { if let concreteTy = type.typeIn(genericParameters: genericParameters, genericRequirements: genericRequirements) { return try translateProtocolParameter( protocolType: concreteTy, - methodName: methodName, + methodName: protocolWrapperMethodName, parameterName: parameterName, parentName: parentName ) @@ -309,6 +314,7 @@ extension JNISwift2JavaGenerator { type: elements[0].type, parameterName: parameterName, methodName: methodName, + protocolWrapperMethodName: protocolWrapperMethodName, parentName: parentName, genericParameters: genericParameters, genericRequirements: genericRequirements @@ -319,6 +325,7 @@ extension JNISwift2JavaGenerator { elements: elements, parameterName: parameterName, methodName: methodName, + protocolWrapperMethodName: protocolWrapperMethodName, parentName: parentName, genericParameters: genericParameters, genericRequirements: genericRequirements @@ -333,6 +340,7 @@ extension JNISwift2JavaGenerator { elements: [SwiftTupleElement], parameterName: String, methodName: String, + protocolWrapperMethodName: String, parentName: SwiftQualifiedTypeName, genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] @@ -346,6 +354,7 @@ extension JNISwift2JavaGenerator { type: element.type, parameterName: elementParamName, methodName: methodName, + protocolWrapperMethodName: protocolWrapperMethodName, parentName: parentName, genericParameters: genericParameters, genericRequirements: genericRequirements diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index efad8cc35..8f402d565 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -532,7 +532,7 @@ extension JNISwift2JavaGenerator { return } - printAllSwiftFunctionHelperClasses(&printer, decl) + printAllSwiftFunctionHelperClasses(&printer, decl, translatedDecl) printCDecl( &printer, @@ -550,14 +550,13 @@ extension JNISwift2JavaGenerator { private func printAllSwiftFunctionHelperClasses( _ printer: inout SwiftPrinter, _ decl: ExtractedFunc, + _ translatedDecl: TranslatedFunctionDecl, ) { // Emit the `@JavaInterface` Swift wrappers for any `@escaping` closure parameters. - if let translatedDecl = translatedDecl(for: decl) { - printEscapingClosureSwiftFunctionHelperClasses(&printer, translatedDecl) - } + printEscapingClosureSwiftFunctionHelperClasses(&printer, translatedDecl) // Emit the `@JavaInterface` Swift wrappers any existential / protocol parameters. - printProtocolParameterSwiftFunctionHelperClasses(&printer, decl) + printProtocolParameterSwiftFunctionHelperClasses(&printer, decl, javaName: translatedDecl.name) } /// Emits Swift wrapper types for any parameter type that is a protocol @@ -565,6 +564,7 @@ extension JNISwift2JavaGenerator { private func printProtocolParameterSwiftFunctionHelperClasses( _ printer: inout SwiftPrinter, _ decl: ExtractedFunc, + javaName: String, ) { let protocolParameters = decl.functionSignature.parameters.compactMap { parameter in if let concreteType = parameter.type.typeIn( @@ -618,7 +618,10 @@ extension JNISwift2JavaGenerator { fatalError() } let swiftClassName = JNISwift2JavaGenerator.protocolParameterWrapperClassName( - methodName: decl.name, + methodName: JavaTranslation.protocolWrapperMethodName( + for: decl, + javaName: javaName + ), parameterName: parameterName, parentName: decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedTypeName ?? SwiftQualifiedTypeName(swiftModuleName), ) diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolParameterOverloadTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolParameterOverloadTests.swift index a0e074aeb..360b6fab9 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolParameterOverloadTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolParameterOverloadTests.swift @@ -55,4 +55,52 @@ struct JNIProtocolParameterOverloadTests { #expect(output.components(separatedBy: "final class \(name):").count - 1 == 1) } } + + @Test("Protocol adapters use the same names in declarations and conversions") + func renamedMethodsAndSetter() throws { + var config = Configuration() + config.swiftModule = "SwiftModule" + config.enableJavaCallbacks = true + let analyzer = makeSwiftJavaAnalyzer(config: config) + try analyzer.analyze( + path: "/fake/Adapters.swiftinterface", + text: """ + public protocol Store { func load() -> String } + public struct Service { + public var store: any Store + public init(store: some Store) + public init(store: some Store, extra: Int64) + public init(first store: some Store) + public init(second store: some Store) + public func use(first value: some Store) + public func use(second value: some Store) + public func `default`(_ value: some Store) + } + """ + ) + let generator = JNISwift2JavaGenerator( + config: config, + translator: analyzer, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake", + javaClassLookupTable: [:], + moduleJavaPackages: [:] + ) + var printer = SwiftPrinter(mode: .accumulateAll) + try generator.writeSwiftThunkSources(&printer) + let output = printer.finalize() + for (name, references) in [ + ("_Service_setStore_newValue_Wrapper", 1), + ("_Service_init_store_Wrapper", 4), + ("_Service_useFirst_value_Wrapper", 1), + ("_Service_useSecond_value_Wrapper", 1), + ("_Service_default__value_Wrapper", 1), + ] { + #expect(output.components(separatedBy: "final class \(name):").count - 1 == 1) + #expect(output.components(separatedBy: "= \(name)(").count - 1 == references) + } + #expect(!output.contains("_Service_store_newValue_Wrapper")) + } + }