From 4d1859129c1562f36b3930da47dfc0c249e3026b Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 11 Nov 2026 10:53:46 +0900 Subject: [PATCH 1/2] Add representation code --- .../Sources/MySwiftLibrary/CallbackProtcol.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift index e1add48f5..a274a0194 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift @@ -111,5 +111,9 @@ public struct CallbackHolder { self.callback = callback } + public init(callback: any CallbackProtocol, _ extra: Int) { + self.callback = callback + } + public var callback: any CallbackProtocol } From 4fbe4367595cbf4388204bc492c234ce4a755137 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 11 Nov 2026 11:49:22 +0900 Subject: [PATCH 2/2] Use composited protocol name for protocol parameter wrapper type. --- ...wift2JavaGenerator+NativeTranslation.swift | 30 +++------- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 57 +++++++++---------- .../JNI/JNISwift2JavaGenerator.swift | 2 + .../JNI/JNIProtocolTests.swift | 49 +++++++++++----- 4 files changed, 72 insertions(+), 66 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 4ca6a03ca..431b69262 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -277,18 +277,14 @@ extension JNISwift2JavaGenerator { case .opaque(let proto), .existential(let proto): return try translateProtocolParameter( protocolType: proto, - methodName: methodName, - parameterName: parameterName, - parentName: parentName + parameterName: parameterName ) case .genericParameter: if let concreteTy = type.typeIn(genericParameters: genericParameters, genericRequirements: genericRequirements) { return try translateProtocolParameter( protocolType: concreteTy, - methodName: methodName, - parameterName: parameterName, - parentName: parentName + parameterName: parameterName ) } @@ -459,17 +455,13 @@ extension JNISwift2JavaGenerator { func translateProtocolParameter( protocolType: SwiftType, - methodName: String, - parameterName: String, - parentName: SwiftQualifiedTypeName? + parameterName: String ) throws -> NativeParameter { switch protocolType { case .nominal(let nominalType): return try translateProtocolParameter( protocolTypes: [nominalType], - methodName: methodName, - parameterName: parameterName, - parentName: parentName + parameterName: parameterName ) case .composite(let types): @@ -482,9 +474,7 @@ extension JNISwift2JavaGenerator { return try translateProtocolParameter( protocolTypes: protocolTypes, - methodName: methodName, - parameterName: parameterName, - parentName: parentName + parameterName: parameterName ) default: @@ -494,9 +484,7 @@ extension JNISwift2JavaGenerator { private func translateProtocolParameter( protocolTypes: [SwiftNominalType], - methodName: String, - parameterName: String, - parentName: SwiftQualifiedTypeName? + parameterName: String ) throws -> NativeParameter { // We allow Java implementations if we are able to generate the needed // Swift wrappers for all the protocol types. @@ -510,10 +498,8 @@ extension JNISwift2JavaGenerator { ], conversion: .interfaceToSwiftObject( .placeholder, - swiftWrapperClassName: JNISwift2JavaGenerator.protocolParameterWrapperClassName( - methodName: methodName, - parameterName: parameterName, - parentName: parentName + swiftWrapperClassName: JNISwift2JavaGenerator.protocolParameterBoxClassName( + protocolTypes: protocolTypes ), protocolTypes: protocolTypes, allowsJavaImplementations: allowsJavaImplementations diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index f5ec477be..4a372dad0 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -90,6 +90,7 @@ extension JNISwift2JavaGenerator { grouping: filteredTypes, by: { $0.value.sourceFilePath }, ) { + emittedProtocolParameterWrappers.removeAll() logger.warning("Writing types in file group: \(group.key): \(group.value.map(\.key))") let extractedTypesForThisFile = group.value @@ -503,7 +504,7 @@ extension JNISwift2JavaGenerator { printEscapingClosureSwiftFunctionHelperClasses(&printer, translatedDecl) // Emit the `@JavaInterface` Swift wrappers any existential / protocol parameters. - printProtocolParameterSwiftFunctionHelperClasses(&printer, decl, translatedDecl) + printProtocolParameterSwiftFunctionHelperClasses(&printer, decl) } /// Emits Swift wrapper types for any parameter type that is a protocol @@ -511,39 +512,41 @@ extension JNISwift2JavaGenerator { private func printProtocolParameterSwiftFunctionHelperClasses( _ printer: inout SwiftPrinter, _ decl: ExtractedFunc, - _ translatedDecl: TranslatedFunctionDecl, ) { - let parameters = zip(decl.functionSignature.parameters, translatedDecl.translatedFunctionSignature.parameters) - let protocolParameters = parameters.compactMap { parameter, translatedParameter in - if let concreteType = parameter.type.typeIn( + func protocolParameters(in type: SwiftType) -> [SwiftType] { + if let concreteType = type.typeIn( genericParameters: decl.functionSignature.genericParameters, genericRequirements: decl.functionSignature.genericRequirements, ) { - return (translatedParameter.parameter.name, concreteType) + return [concreteType] } - - switch parameter.type { + switch type { case .opaque(let protocolType), .existential(let protocolType): - return (translatedParameter.parameter.name, protocolType) - + return [protocolType] + case .tuple(let elements): + return elements.flatMap { protocolParameters(in: $0.type) } default: - return nil + return [] } - }.map { parameterName, protocolType in + } + + let protocolParameters = decl.functionSignature.parameters.flatMap { parameter in + protocolParameters(in: parameter.type) + }.map { protocolType in // We flatten any composite types switch protocolType { case .composite(let protocols): - return (parameterName, protocols) + return protocols default: - return (parameterName, [protocolType]) + return [protocolType] } } // For each parameter that is a generic or a protocol, // we generate a Swift class that conforms to all of those. - for (parameterName, protocolTypes) in protocolParameters { + for protocolTypes in protocolParameters { let protocolWrappers: [JavaInterfaceSwiftWrapper] = protocolTypes.compactMap { protocolType in guard let importedType = self.asExtractedNominalTypeDecl(protocolType), let wrapper = self.interfaceProtocolWrappers[importedType] @@ -561,14 +564,13 @@ extension JNISwift2JavaGenerator { continue } - let swiftClassName = JNISwift2JavaGenerator.protocolParameterWrapperClassName( - methodName: translatedDecl.name, - parameterName: parameterName, - parentName: translatedDecl.parentName, + let swiftClassName = JNISwift2JavaGenerator.protocolParameterBoxClassName( + protocolTypes: protocolWrappers.map(\.protocolType) ) + guard emittedProtocolParameterWrappers.insert(swiftClassName).inserted else { continue } let implementingProtocols = protocolWrappers.map(\.wrapperName).joined(separator: .comma) - printer.printBraceBlock("final class \(swiftClassName): \(implementingProtocols)") { printer in + printer.printBraceBlock("fileprivate final class \(swiftClassName): \(implementingProtocols)") { printer in let variables: [(String, String)] = protocolWrappers.map { wrapper in (wrapper.javaInterfaceVariableName, wrapper.javaInterfaceName) } @@ -1229,18 +1231,11 @@ extension JNISwift2JavaGenerator { return newSelfParamName } - static func protocolParameterWrapperClassName( - methodName: String, - parameterName: String, - parentName: SwiftQualifiedTypeName?, + static func protocolParameterBoxClassName( + protocolTypes: [SwiftNominalType] ) -> String { - let parent = - if let parentName { - "\(parentName.fullFlatName)_" - } else { - "" - } - return "_\(parent)\(methodName)_\(parameterName)_Wrapper" + let protocols = protocolTypes.map { $0.nominalTypeDecl.flatName } + return "_Java_\(protocols.joined(separator: "$"))_Box" } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index dbb6e23ab..9f9cee0b1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -60,6 +60,8 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { .map(\.swiftNominal.identity) ) var interfaceProtocolWrappers: [ExtractedNominalType: JavaInterfaceSwiftWrapper] = [:] + /// Protocol parameter adapter class names already emitted in the current output file. + var emittedProtocolParameterWrappers: Set = [] /// Protocols that should be boxed to support returning them as `any P / some P` private(set) var existentialProtocolBoxes: [ExtractedNominalType] = [] diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index 1549abf20..a33db28c0 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -191,7 +191,7 @@ struct JNIProtocolTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - final class _SwiftModule_takeGeneric_s_Wrapper: SwiftJavaSomeProtocolWrapper { + fileprivate final class _Java_SomeProtocol_Box: SwiftJavaSomeProtocolWrapper { let _javaSomeProtocolInterface: JavaSomeProtocol init(_javaSomeProtocolInterface: JavaSomeProtocol) { self._javaSomeProtocolInterface = _javaSomeProtocolInterface @@ -219,7 +219,7 @@ struct JNIProtocolTests { xswiftObject$ = xpointer$Existential$ } else { - xswiftObject$ = _SwiftModule_takeProtocol_x_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment)) + xswiftObject$ = _Java_SomeProtocol_Box(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment)) } let yswiftObject$: (any SomeProtocol) if environment.interface.IsInstanceOf(environment, y, _JNIMethodIDCache.JNISwiftInstance.class) != 0 { @@ -227,7 +227,7 @@ struct JNIProtocolTests { yswiftObject$ = ypointer$Existential$ } else { - yswiftObject$ = _SwiftModule_takeProtocol_y_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: y!, environment: environment)) + yswiftObject$ = _Java_SomeProtocol_Box(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: y!, environment: environment)) } SwiftModule.takeProtocol(x: xswiftObject$, y: yswiftObject$) } @@ -267,7 +267,7 @@ struct JNIProtocolTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - final class _SwiftModule_takeGeneric_s_Wrapper: SwiftJavaSomeProtocolWrapper { + fileprivate final class _Java_SomeProtocol_Box: SwiftJavaSomeProtocolWrapper { let _javaSomeProtocolInterface: JavaSomeProtocol init(_javaSomeProtocolInterface: JavaSomeProtocol) { self._javaSomeProtocolInterface = _javaSomeProtocolInterface @@ -283,7 +283,7 @@ struct JNIProtocolTests { sswiftObject$ = spointer$Existential$ } else { - sswiftObject$ = _SwiftModule_takeGeneric_s_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: s!, environment: environment)) + sswiftObject$ = _Java_SomeProtocol_Box(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: s!, environment: environment)) } SwiftModule.takeGeneric(s: sswiftObject$) } @@ -323,7 +323,7 @@ struct JNIProtocolTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - final class _SwiftModule_takeComposite_x_Wrapper: SwiftJavaSomeProtocolWrapper, SwiftJavaBWrapper { + fileprivate final class _Java_SomeProtocol$B_Box: SwiftJavaSomeProtocolWrapper, SwiftJavaBWrapper { let _javaSomeProtocolInterface: JavaSomeProtocol let _javaBInterface: JavaB init(_javaSomeProtocolInterface: JavaSomeProtocol, _javaBInterface: JavaB) { @@ -357,7 +357,7 @@ struct JNIProtocolTests { xswiftObject$ = xpointer$Existential$ } else { - xswiftObject$ = _SwiftModule_takeComposite_x_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment), _javaBInterface: JavaB(javaThis: x!, environment: environment)) + xswiftObject$ = _Java_SomeProtocol$B_Box(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment), _javaBInterface: JavaB(javaThis: x!, environment: environment)) } SwiftModule.takeComposite(x: xswiftObject$) } @@ -404,7 +404,7 @@ struct JNIProtocolTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - final class _Owner_setAnimal_newValue_Wrapper: SwiftJavaAnimalWrapper { + fileprivate final class _Java_Animal_Box: SwiftJavaAnimalWrapper { ... } """, @@ -413,12 +413,35 @@ struct JNIProtocolTests { public func Java_com_example_swift_Owner__00024setAnimal__Ljava_lang_Object_2J(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jobject?, selfPointer: jlong) { """, """ - newValueswiftObject$ = _Owner_setAnimal_newValue_Wrapper(_javaAnimalInterface: JavaAnimal(javaThis: newValue!, environment: environment)) + newValueswiftObject$ = _Java_Animal_Box(_javaAnimalInterface: JavaAnimal(javaThis: newValue!, environment: environment)) """, ] ) } + @Test + func takeAsTuple() throws { + let source = """ + public protocol Store { + func value() -> Int64 + } + public func take(pair: (any Store, some Store)) {} + """ + + try assertOutput( + input: source, + config: config, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + "fileprivate final class _Java_Store_Box: SwiftJavaStoreWrapper {", + "pair_0swiftObject$ = _Java_Store_Box(_javaStoreInterface: JavaStore(javaThis: pair_0!, environment: environment))", + "pair_1swiftObject$ = _Java_Store_Box(_javaStoreInterface: JavaStore(javaThis: pair_1!, environment: environment))", + ] + ) + } + @Test func takeAsOverloadedParameter() throws { let source = """ @@ -429,6 +452,7 @@ struct JNIProtocolTests { public struct Owner { public func take(first: any Animal) {} public func take(second: any Animal) {} + public func take(second: any Animal, option: Bool) {} } """ @@ -439,10 +463,9 @@ struct JNIProtocolTests { .swift, detectChunkByInitialLines: 1, expectedChunks: [ - "final class _Owner_takeFirst_first_Wrapper: SwiftJavaAnimalWrapper {", - "firstswiftObject$ = _Owner_takeFirst_first_Wrapper(_javaAnimalInterface: JavaAnimal(javaThis: first!, environment: environment))", - "final class _Owner_takeSecond_second_Wrapper: SwiftJavaAnimalWrapper {", - "secondswiftObject$ = _Owner_takeSecond_second_Wrapper(_javaAnimalInterface: JavaAnimal(javaThis: second!, environment: environment))", + "fileprivate final class _Java_Animal_Box: SwiftJavaAnimalWrapper {", + "firstswiftObject$ = _Java_Animal_Box(_javaAnimalInterface: JavaAnimal(javaThis: first!, environment: environment))", + "secondswiftObject$ = _Java_Animal_Box(_javaAnimalInterface: JavaAnimal(javaThis: second!, environment: environment))", ] ) }