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,57 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 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 DefaultOperations {
func seed() -> Int
func add(_ value: Int) -> Int
func total() -> Int
func checked(_ shouldThrow: Bool) throws -> Int64
}

extension DefaultOperations {
public func add(_ renamed: Int) -> Int { seed() + renamed }
public func total() -> Int { add(2) }
public func checked(_ shouldThrow: Bool) throws -> Int64 {
if shouldThrow { throw GreeterError() }
return Int64(total())
}
}

public func callDefaultOperations(_ operations: some DefaultOperations) -> Int {
operations.total()
}

public struct DefaultNamespace {
public protocol Parent { func seed() -> Int }
public protocol Child: Parent { func extra() -> Int }
}

extension DefaultNamespace.Child {
public func extra() -> Int { seed() + 3 }
}

public func callNestedDefaults(_ child: some DefaultNamespace.Child) -> Int {
child.extra()
}

public protocol DefaultParent { func value() -> Int64 }
public protocol RefinedDefault: DefaultParent {}

extension RefinedDefault {
public func value() -> Int64 { 7 }
}

public func callRefinedDefault(_ child: some RefinedDefault) -> Int64 {
child.value()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 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 static org.junit.jupiter.api.Assertions.*;

public class ProtocolDefaultsTest {
@Test
void javaCallbacksInheritSwiftDefaults() throws Exception {
DefaultOperations operations = () -> 10;
assertEquals(12, operations.add(2));
assertEquals(12, operations.total());
assertEquals(12, MySwiftLibrary.callDefaultOperations(operations));
assertEquals(12, operations.checked(false));
assertThrows(GreeterError.class, () -> operations.checked(true));
}

@Test
void defaultsPreserveJavaOverridesOfOtherDefaultedRequirements() throws Exception {
DefaultOperations operations = new DefaultOperations() {
public long seed() { return 10; }
public long add(long value) { return 100 + value; }
};
assertEquals(102, operations.total());
assertEquals(102, operations.checked(false));
assertEquals(102, MySwiftLibrary.callDefaultOperations(operations));
}

@Test
void nestedProtocolsUseTheirInheritedJavaCallback() {
DefaultNamespace.Child child = () -> 4;
assertEquals(7, child.extra());
assertEquals(7, MySwiftLibrary.callNestedDefaults(child));
DefaultNamespace.Parent parent = child;
assertEquals(4, parent.seed());
}

@Test
void refinedProtocolsCanDefaultAnInheritedRequirement() {
RefinedDefault child = new RefinedDefault() {};
assertEquals(7, child.value());
assertEquals(7, MySwiftLibrary.callRefinedDefault(child));
DefaultParent parent = child;
assertEquals(7, parent.value());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
public class ProtocolRequirementNamesTest {
@Test
void javaCallbackOverridesTheDefaultImplementation() {
NamingCallback callback = () -> "Java";
NamingCallback callback = new NamingCallback() {
public String message() { return "Java"; }
};
assertEquals("Java", MySwiftLibrary.callNamingCallback(callback));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ extension JNISwift2JavaGenerator {
}

private func printProtocol(_ printer: inout JavaPrinter, _ decl: ExtractedNominalType) {
var extends = self.inheritedProtocols(of: decl).map(\.effectiveJavaSimpleName)
var extends = self.inheritedProtocols(of: decl).map(\.effectiveJavaName)

// If we cannot generate Swift wrappers
// that allows the user to implement the wrapped interface in Java
Expand All @@ -210,11 +210,52 @@ extension JNISwift2JavaGenerator {
// (`supportedProtocolRequirements(of:)`) so the interface never
// declares a requirement (e.g. a property setter) that the box
// doesn't implement.
for requirement in self.supportedProtocolRequirements(of: decl) {
printFunctionDowncallMethods(&printer, requirement, skipMethodBody: true)
let defaults = protocolDefaultMethods(of: decl)
var requirements = self.supportedProtocolRequirements(of: decl)
// A refined protocol may supply a default for an inherited requirement.
for method in defaults where !requirements.contains(where: { sameProtocolSignature($0, method.requirement) }) {
requirements.append(method.requirement)
}
for requirement in requirements {
if let method = defaults.first(where: { sameProtocolSignature($0.requirement, requirement) }),
let target = translatedDecl(for: method.helper), let original = translatedDecl(for: requirement)
{
printJavaBindingWrapperMethod(
&printer,
original,
importedFunc: requirement,
skipMethodBody: false,
protocolDefaultTarget: target
)
} else {
printFunctionDowncallMethods(&printer, requirement, skipMethodBody: true)
}
printer.println()
}
}

let defaults = protocolDefaultMethods(of: decl)
if !defaults.isEmpty {
// Java interfaces cannot declare native methods. Keep the native entry
// points in a package-private companion, initialized on first default call.
printer.println()
let staticModifier = decl.parent == nil ? "" : "static "
printer.printBraceBlock("\(staticModifier)final class \(decl.effectiveJavaSimpleName)$SwiftDefaults") { printer in
printer.printBraceBlock("static") { printer in
if let loading = config.overrideStaticBlockLibraryLoading {
for line in loading { printer.print(line) }
} else {
printer.print("SwiftLibraries.loadLibraryWithFallbacks(SwiftLibraries.LIB_NAME_SWIFT_JAVA);")
printer.print("SwiftLibraries.loadLibraryWithFallbacks(\"\(config.nativeLibraryName ?? swiftModuleName)\");")
}
}
for method in defaults {
if let translated = translatedDecl(for: method.helper) {
printJavaBindingWrapperMethod(&printer, translated, skipMethodBody: false)
}
}
}
}
}

/// Prints the full `.java` file for a protocol's existential box — the
Expand All @@ -241,7 +282,7 @@ extension JNISwift2JavaGenerator {
/// (dynamic) value pointer and the concrete (dynamic) type-metadata pointer.
private func printExistentialBox(_ printer: inout JavaPrinter, _ decl: ExtractedNominalType) {
let boxName = decl.swiftNominal.javaExistentialBoxName
let implementsClause = "JNISwiftInstance, \(decl.effectiveJavaSimpleName)"
let implementsClause = "JNISwiftInstance, \(decl.effectiveJavaName)"

printer.printBraceBlock("final class \(boxName) implements \(implementsClause)") { printer in
printDesignatedConstructor(&printer, javaName: boxName, pointerParams: ["selfPointer", "selfTypePointer"])
Expand Down Expand Up @@ -353,8 +394,19 @@ extension JNISwift2JavaGenerator {
type.parent == decl.swiftNominal
}

for nestedType in nestedTypes {
printConcreteType(&printer, nestedType.value)
for (_, nestedType) in nestedTypes.sorted(by: { $0.key < $1.key }) {
let identifiers = currentJavaIdentifiers
resetForNewOutputFile(
nestedType.swiftNominal.kind == .protocol
? allProtocolRequirementMethods(of: nestedType)
: nestedType.initializers + nestedType.variables + nestedType.methods
)
if nestedType.swiftNominal.kind == .protocol {
printProtocol(&printer, nestedType)
} else {
printConcreteType(&printer, nestedType)
}
currentJavaIdentifiers = identifiers
printer.println()
}

Expand Down Expand Up @@ -880,8 +932,10 @@ extension JNISwift2JavaGenerator {
_ translatedDecl: TranslatedFunctionDecl,
importedFunc: ExtractedFunc? = nil,
skipMethodBody: Bool,
protocolDefaultTarget: TranslatedFunctionDecl? = nil,
) {
var modifiers = ["public"]
if protocolDefaultTarget != nil { modifiers.append("default") }
if translatedDecl.isStatic {
modifiers.append("static")
}
Expand Down Expand Up @@ -931,7 +985,7 @@ extension JNISwift2JavaGenerator {
var modifiers = modifiers

// If we are a protocol, we emit this as default method
if isParentProtocol {
if isParentProtocol && !modifiers.contains("default") {
modifiers.insert("default", at: 1)
}

Expand Down Expand Up @@ -967,10 +1021,17 @@ extension JNISwift2JavaGenerator {
printer.print("\(signature);")
} else {
printer.printBraceBlock(signature) { printer in
printDowncall(&printer, translatedDecl)
if let target = protocolDefaultTarget {
var arguments = ["this"] + translatedSignature.parameters.map(\.parameter.name)
if translatedSignature.requiresSwiftArena { arguments.append("swiftArena") }
let call = "\(target.parentName).\(target.name)(\(arguments.joined(separator: .comma)))"
printer.print((resultType.isVoid ? "" : "return ") + call + ";")
} else {
printDowncall(&printer, translatedDecl)
}
}

printNativeFunction(&printer, translatedDecl)
if protocolDefaultTarget == nil { printNativeFunction(&printer, translatedDecl) }
}
}

Expand Down
Loading
Loading