Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ public func takeGenericProtocol<First: ProtocolA, Second: ProtocolB>(_ proto1: F
public func takeCombinedGenericProtocol<T: ProtocolA & ProtocolB>(_ proto: T) -> Int64 {
proto.constantA + proto.constantB
}

public func takeProtocolB(_ proto: some ProtocolB) -> Int64 {
proto.constantB
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -110,4 +118,4 @@ void useStorage() {
assertEquals(5, MySwiftLibrary.loadWithStorage(storage, arena).getValue());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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: ", "))"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
9 changes: 9 additions & 0 deletions Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,13 @@ 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]
}
}
}
70 changes: 70 additions & 0 deletions Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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() {
...
}
}
""",
]
)
}
}
Loading