From e1339562fa18c0364f68a67199e9374670180ca2 Mon Sep 17 00:00:00 2001 From: Himeshi Date: Fri, 23 Oct 2026 16:58:53 +0900 Subject: [PATCH] JNI: reuse protocol parameter adapters across overloads --- .../ProtocolParameterOverloads.swift | 37 ++++++++++++ .../swift/ProtocolParameterOverloadsTest.java | 34 +++++++++++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 6 ++ .../JNI/JNISwift2JavaGenerator.swift | 1 + .../JNIProtocolParameterOverloadTests.swift | 58 +++++++++++++++++++ 5 files changed, 136 insertions(+) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolParameterOverloads.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolParameterOverloadsTest.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNIProtocolParameterOverloadTests.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolParameterOverloads.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolParameterOverloads.swift new file mode 100644 index 000000000..802e6c330 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolParameterOverloads.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 OverloadedCallback { + func value() -> Int64 +} + +public final class ProtocolParameterOverloads { + public let result: Int64 + + public init(callback: some OverloadedCallback) { + result = callback.value() + } + + public init(callback: some OverloadedCallback, extra: Int64) { + result = callback.value() + extra + } + + public func calculate(_ callback: some OverloadedCallback) -> Int64 { + result + callback.value() + } + + public func calculate(_ callback: some OverloadedCallback, extra: Int64) -> Int64 { + result + callback.value() + extra + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolParameterOverloadsTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolParameterOverloadsTest.java new file mode 100644 index 000000000..f14d8d428 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolParameterOverloadsTest.java @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 org.swift.swiftkit.core.SwiftArena; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ProtocolParameterOverloadsTest { + @Test + void overloadedInitializersAndMethodsAcceptJavaCallbacks() { + OverloadedCallback callback = () -> 10; + try (var arena = SwiftArena.ofConfined()) { + var first = ProtocolParameterOverloads.init(callback, arena); + var second = ProtocolParameterOverloads.init(callback, 5, arena); + assertEquals(10, first.getResult()); + assertEquals(15, second.getResult()); + assertEquals(20, first.calculate(callback)); + assertEquals(27, second.calculate(callback, 2)); + } + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index cb9a31f40..5fbf89c70 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -572,6 +572,12 @@ extension JNISwift2JavaGenerator { parentName: decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedTypeName ?? SwiftQualifiedTypeName(swiftModuleName), ) let implementingProtocols = protocolWrappers.map(\.wrapperName).joined(separator: .comma) + // Overloads with the same protocol parameter share an identical adapter. + if let existing = emittedProtocolParameterWrappers[swiftClassName] { + precondition(existing == implementingProtocols, "Conflicting protocol adapters for \(swiftClassName)") + continue + } + emittedProtocolParameterWrappers[swiftClassName] = implementingProtocols printer.printBraceBlock("final class \(swiftClassName): \(implementingProtocols)") { printer in let variables: [(String, String)] = protocolWrappers.map { wrapper in diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 1320bf29f..fe478f16a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -60,6 +60,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { .map(\.swiftNominal.identity) ) var interfaceProtocolWrappers: [ExtractedNominalType: JavaInterfaceSwiftWrapper] = [:] + var emittedProtocolParameterWrappers: [String: String] = [:] /// 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/JNIProtocolParameterOverloadTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolParameterOverloadTests.swift new file mode 100644 index 000000000..a0e074aeb --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolParameterOverloadTests.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import CodePrinting +import Foundation +import JExtractSwiftLib +import SwiftJavaConfigurationShared +import Testing + +@Suite +struct JNIProtocolParameterOverloadTests { + @Test("Overloads share one adapter per protocol parameter") + func identicalAdaptersAreEmittedOnce() throws { + var config = Configuration() + config.swiftModule = "SwiftModule" + config.enableJavaCallbacks = true + let analyzer = makeSwiftJavaAnalyzer(config: config) + try analyzer.analyze( + path: "/fake/Overloads.swiftinterface", + text: """ + public protocol Callback { func value() -> Int64 } + public struct Client { + public init(store: some Callback) + public init(store: some Callback, extra: Int64) + } + public func use(_ callback: some Callback) + public func use(_ callback: some Callback, extra: Int64) + public func other(_ callback: some Callback) + """ + ) + let generator = JNISwift2JavaGenerator( + config: config, + translator: analyzer, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake", + javaClassLookupTable: [:], + moduleJavaPackages: [:] + ) + var printer = SwiftPrinter(mode: .accumulateAll) + try generator.writeSwiftThunkSources(&printer) + let output = printer.finalize() + for name in ["_Client_init_store_Wrapper", "_SwiftModule_use_callback_Wrapper", "_SwiftModule_other_callback_Wrapper"] { + #expect(output.components(separatedBy: "final class \(name):").count - 1 == 1) + } + } +}