From 70525ac65ff1ea796766594ca3cb8672b482a854 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 27 May 2026 14:53:02 +0900 Subject: [PATCH 1/2] Add inherited protocol as extended interface --- .../Sources/MySwiftLibrary/ProtocolB.swift | 4 ++ .../Sources/MySwiftLibrary/ProtocolC.swift | 26 +++++++ .../java/com/example/swift/ProtocolTest.java | 10 ++- .../Convenience/Collection+Extensions.swift | 13 ++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 4 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 12 +++- .../JNI/JNISwift2JavaGenerator.swift | 10 +++ .../JNI/JNIProtocolTests.swift | 70 +++++++++++++++++++ 8 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolC.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.swift index 6f20cf0fc..129cd4fb9 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.swift @@ -27,3 +27,7 @@ public func takeGenericProtocol(_ proto1: F public func takeCombinedGenericProtocol(_ proto: T) -> Int64 { proto.constantA + proto.constantB } + +public func takeProtocolB(_ proto: some ProtocolB) -> Int64 { + proto.constantB +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolC.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolC.swift new file mode 100644 index 000000000..8bcacdd11 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolC.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public protocol ProtocolC: ProtocolB { + var constantC: Int64 { get } +} + +public struct ConcreteProtocolC: ProtocolC { + public var constantB: Int64 + public var constantC: Int64 + public init(b: Int64, c: Int64) { + constantB = b + constantC = c + } +} 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 098babf16..885dbf6f7 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java @@ -81,6 +81,14 @@ void protocolClassMethod() { } } + @Test + void useChildProtocolAsParentProtocol() { + try (var arena = SwiftArena.ofConfined()) { + ProtocolC protoC = ConcreteProtocolC.init(3, 5, arena); + assertEquals(3, MySwiftLibrary.takeProtocolB(protoC)); + } + } + static class JavaStorage implements Storage { StorageItem item; @@ -110,4 +118,4 @@ void useStorage() { assertEquals(5, MySwiftLibrary.loadWithStorage(storage, arena).getValue()); } } -} \ No newline at end of file +} diff --git a/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift index 4286d9e16..f554a7a12 100644 --- a/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift @@ -49,3 +49,16 @@ extension Collection where Element == Int { return s } } + +extension Sequence where Element: Hashable { + func uniqued() -> [Element] { + var seen: Set = [] + var result: [Element] = [] + for element in self { + if seen.insert(element).inserted { + result.append(element) + } + } + return result + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index e02a7f767..ec7b1448c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -171,12 +171,12 @@ extension JNISwift2JavaGenerator { } private func printProtocol(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { - var extends = [String]() + var extends = self.inheritedProtocols(of: decl).map(\.effectiveJavaSimpleName) // If we cannot generate Swift wrappers // that allows the user to implement the wrapped interface in Java // then we require only JExtracted types can conform to this. - if !self.interfaceProtocolWrappers.keys.contains(decl) { + if !self.interfaceProtocolWrappers.keys.contains(decl) && !extends.contains("JNISwiftInstance") { extends.append("JNISwiftInstance") } let extendsString = extends.isEmpty ? "" : " extends \(extends.joined(separator: ", "))" diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 3f6797f1b..823fe7485 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -181,13 +181,23 @@ extension JNISwift2JavaGenerator { _ printer: inout CodePrinter, _ translatedWrapper: JavaInterfaceSwiftWrapper, ) throws { - printer.printBraceBlock("protocol \(translatedWrapper.wrapperName): \(translatedWrapper.swiftName)") { printer in + let inheritedWrappers = self.inheritedProtocols(of: translatedWrapper.importedType).compactMap { self.interfaceProtocolWrappers[$0] } + let inheritedTypes = [translatedWrapper.swiftName] + inheritedWrappers.map(\.wrapperName) + + printer.printBraceBlock("protocol \(translatedWrapper.wrapperName): \(inheritedTypes.joined(separator: ", "))") { printer in printer.print( "var \(translatedWrapper.javaInterfaceVariableName): \(translatedWrapper.javaInterfaceName) { get }" ) } printer.println() try printer.printBraceBlock("extension \(translatedWrapper.wrapperName)") { printer in + for inherited in inheritedWrappers { + printer.printBraceBlock("var \(inherited.javaInterfaceVariableName): \(inherited.javaInterfaceName)") { printer in + printer.print("\(translatedWrapper.javaInterfaceVariableName)") + } + printer.println() + } + for function in translatedWrapper.functions { try printInterfaceWrapperFunctionImpl(&printer, function, inside: translatedWrapper) printer.println() diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index adcef96fd..c63475efa 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -141,4 +141,14 @@ extension JNISwift2JavaGenerator { static func indirectVariableName(for parameterName: String) -> String { "\(parameterName)$indirect" } + + func inheritedProtocols(of type: ImportedNominalType) -> [ImportedNominalType] { + type.inheritedTypes + .compactMap(\.asNominalTypeDeclaration) + .filter { $0.kind == .protocol } + .compactMap { + self.analysis.importedTypes[$0.qualifiedName] + } + .uniqued() + } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index 6e763172b..43f627a8a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -41,6 +41,16 @@ struct JNIProtocolTests { public func takeComposite(x: any SomeProtocol & B) """ + let protocolInheritanceSource = """ + public protocol ParentProtocol { + public func parentMethod() + } + + public protocol ChildProtocol: ParentProtocol { + public func childMethod() + } + """ + @Test func generatesJavaInterface() throws { try assertOutput( @@ -72,6 +82,33 @@ struct JNIProtocolTests { ) } + @Test + func generatesJavaInterfaceWithInheritedProtocol() throws { + try assertOutput( + input: protocolInheritanceSource, + config: config, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public interface ParentProtocol { + ... + public void parentMethod(); + ... + } + """, + """ + public interface ChildProtocol extends ParentProtocol { + ... + public void childMethod(); + ... + } + """, + ] + ) + } + @Test func generatesJavaClassWithExtends() throws { try assertOutput( @@ -348,4 +385,37 @@ struct JNIProtocolTests { ] ) } + + @Test + func generatesProtocolWrappersWithInheritedProtocol() throws { + try assertOutput( + input: protocolInheritanceSource, + config: config, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + protocol SwiftJavaParentProtocolWrapper: ParentProtocol { + var _javaParentProtocolInterface: JavaParentProtocol { get } + } + """, + """ + protocol SwiftJavaChildProtocolWrapper: ChildProtocol, SwiftJavaParentProtocolWrapper { + var _javaChildProtocolInterface: JavaChildProtocol { get } + } + """, + """ + extension SwiftJavaChildProtocolWrapper { + var _javaParentProtocolInterface: JavaParentProtocol { + _javaChildProtocolInterface + } + public func childMethod() { + ... + } + } + """, + ] + ) + } } From 027b7582ac90fdb01608113608e7ccd14f4fd1c2 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 27 May 2026 15:01:41 +0900 Subject: [PATCH 2/2] Remove maybe unnecessary unique logic --- .../Convenience/Collection+Extensions.swift | 13 ------------- .../JNI/JNISwift2JavaGenerator.swift | 1 - 2 files changed, 14 deletions(-) diff --git a/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift index f554a7a12..4286d9e16 100644 --- a/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift @@ -49,16 +49,3 @@ extension Collection where Element == Int { return s } } - -extension Sequence where Element: Hashable { - func uniqued() -> [Element] { - var seen: Set = [] - var result: [Element] = [] - for element in self { - if seen.insert(element).inserted { - result.append(element) - } - } - return result - } -} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index c63475efa..208a3228f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -149,6 +149,5 @@ extension JNISwift2JavaGenerator { .compactMap { self.analysis.importedTypes[$0.qualifiedName] } - .uniqued() } }