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
@@ -0,0 +1,48 @@
//===----------------------------------------------------------------------===//
//
// 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 NamingCallback {
func message() -> String
}

extension NamingCallback {
public func message() -> String { "default" }
}

struct DefaultNamingCallback: NamingCallback {}

public func callNamingCallback(_ callback: some NamingCallback) -> String {
callback.message()
}

public func makeNamingCallback() -> any NamingCallback { DefaultNamingCallback() }

public protocol NamingParent {
var id: Int64 { get }
func value(number: Int64) -> Int64
}

public protocol NamingChild: NamingParent {
var tag: Int64 { get }
func value(text: Int64) -> Int64
}

struct NamingValue: NamingChild {
var id: Int64 { 1 }
var tag: Int64 { 2 }
func value(number: Int64) -> Int64 { number + 10 }
func value(text: Int64) -> Int64 { text + 20 }
}

public func makeNamingChild() -> any NamingChild { NamingValue() }
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

package com.example.swift;

import org.junit.jupiter.api.Test;
import org.swift.swiftkit.core.SwiftArena;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class ProtocolRequirementNamesTest {
@Test
void javaCallbackOverridesTheDefaultImplementation() {
NamingCallback callback = () -> "Java";
assertEquals("Java", MySwiftLibrary.callNamingCallback(callback));
}

@Test
void returnedBoxUsesTheDefaultImplementation() {
try (var arena = SwiftArena.ofConfined()) {
assertEquals("default", MySwiftLibrary.makeNamingCallback(arena).message());
}
}

@Test
void inheritedOverloadsDispatchToTheCorrectWitness() {
try (var arena = SwiftArena.ofConfined()) {
NamingChild child = MySwiftLibrary.makeNamingChild(arena);
assertEquals(1, child.getId());
assertEquals(2, child.getTag());
assertEquals(13, child.value(3));
assertEquals(23, child.valueText(3));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ extension JNISwift2JavaGenerator {
let filename = "\(decl.effectiveJavaSimpleName).java"
logger.debug("Printing contents: \(filename)")

resetForNewOutputFile(decl.initializers + decl.variables + decl.methods)
resetForNewOutputFile(decl.swiftNominal.kind == .protocol ? self.allProtocolRequirementMethods(of: decl) : decl.initializers + decl.variables + decl.methods)

printHeader(&printer)
printPackage(&printer)
Expand Down Expand Up @@ -283,6 +283,7 @@ extension JNISwift2JavaGenerator {
printer.println()

for method in self.allProtocolRequirementMethods(of: decl) {
self.currentJavaIdentifiers = identifiers(forProtocolRequirement: method)
printExistentialBoxMethod(&printer, decl, method)
printer.println()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ extension JNISwift2JavaGenerator {
printer.println()

self.currentJavaIdentifiers = JavaIdentifierFactory(
type.initializers + type.variables + type.methods
type.swiftNominal.kind == .protocol ? self.allProtocolRequirementMethods(of: type) : type.initializers + type.variables + type.methods
)

switch type.swiftNominal.kind {
Expand Down Expand Up @@ -396,6 +396,7 @@ extension JNISwift2JavaGenerator {
let boxParentName = SwiftQualifiedTypeName(type.swiftNominal.javaExistentialBoxName)

for method in self.allProtocolRequirementMethods(of: type) {
self.currentJavaIdentifiers = identifiers(forProtocolRequirement: method)
guard var translated = try? self.javaTranslator.translate(method) else {
self.logger.debug("Failed to translate protocol requirement for existential box dispatch thunk: \(method)")
continue
Expand Down
16 changes: 15 additions & 1 deletion Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,25 @@ extension JNISwift2JavaGenerator {
/// (getters/setters), excluding statics and anything whose signature
/// doesn't translate (e.g. referencing `Self`/associated types).
func supportedProtocolRequirements(of type: ExtractedNominalType) -> [ExtractedFunc] {
// Probe support without caching names before the declaring interface's
// complete requirement set has been selected for overload disambiguation.
(type.methods + type.variables).filter { requirement in
requirement.isProtocolRequirement && !requirement.isStatic && !requirement.isClass && self.translatedDecl(for: requirement) != nil
requirement.isProtocolRequirement && !requirement.isStatic && !requirement.isClass
&& (try? self.javaTranslator.translate(requirement)) != nil
}
}

/// Use the declaring interface's names when implementing an inherited requirement.
/// A refined protocol may introduce overloads, but cannot rename a parent's API.
func identifiers(forProtocolRequirement requirement: ExtractedFunc) -> JavaIdentifierFactory {
guard let name = requirement.parentType?.asNominalType?.nominalTypeDecl.qualifiedName,
let owner = analysis.extractedTypes[name]
else {
fatalError("Missing declaring protocol for requirement: \(requirement)")
}
return JavaIdentifierFactory(allProtocolRequirementMethods(of: owner))
}

/// All wrappable requirements for `type` (a protocol), including those
/// inherited from refined protocols — the transitive closure of
/// `supportedProtocolRequirements(of:)`. Used to build an existential
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

import JExtractSwiftLib
import SwiftJavaConfigurationShared
import Testing

@Suite
struct JNIProtocolRequirementNamesTests {
@Test("Default implementations do not rename Java requirements")
func defaultImplementation_java() throws {
var config = Configuration()
config.enableJavaCallbacks = true
try assertOutput(
input: """
public protocol Callback { func message() -> String }
extension Callback { public func message() -> String { "default" } }
public func makeCallback() -> any Callback
""",
config: config,
.jni,
.java,
expectedChunks: ["public java.lang.String message();", "public java.lang.String message() {"],
notExpectedChunks: ["message_()"]
)
}

private let inheritedSource = """
public protocol Parent {
var id: Int64 { get }
func value(number: Int64) -> Int64
}
public protocol Child: Parent {
var tag: Int64 { get }
func value(text: Int64) -> Int64
}
public func makeChild() -> any Child
"""

@Test("Inherited box methods retain the declaring interface's names")
func inheritedRequirements_java() throws {
try assertOutput(
input: inheritedSource,
.jni,
.java,
expectedChunks: [
"public long value(long number);",
"public long valueText(long text);",
"public long value(long number) {",
"public long valueText(long text) {",
],
notExpectedChunks: ["valueNumber("]
)
}

@Test("Inherited box dispatch thunks use the same names as Java")
func inheritedRequirements_swift() throws {
try assertOutput(
input: inheritedSource,
.jni,
.swift,
expectedChunks: [
"@_cdecl(\"Java_com_example_swift_ChildBox__00024value__JJJ\")",
"@_cdecl(\"Java_com_example_swift_ChildBox__00024valueText__JJJ\")",
],
notExpectedChunks: ["ChildBox__00024valueNumber__"]
)
}
}
Loading