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")) + } + }