From 433ae800c6ac8ae1e17929b446ced62ba2e053e1 Mon Sep 17 00:00:00 2001 From: clemo97 Date: Mon, 26 Jan 2026 19:43:23 +0300 Subject: [PATCH 1/2] Append parameter labels to Java method names for overload disambiguation --- .../com/example/swift/HelloJava2Swift.java | 28 +++++++++---------- ...MSwift2JavaGenerator+JavaTranslation.swift | 15 +++++++++- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index a13125478..9bcecb3ff 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -40,13 +40,13 @@ public static void main(String[] args) { static void examples() { MySwiftLibrary.helloWorld(); - MySwiftLibrary.globalTakeInt(1337); + MySwiftLibrary.globalTakeInt_i(1337); - long cnt = MySwiftLibrary.globalWriteString("String from Java"); + long cnt = MySwiftLibrary.globalWriteString_string("String from Java"); CallTraces.trace("count = " + cnt); - MySwiftLibrary.globalCallMeRunnable(() -> { + MySwiftLibrary.globalCallMeRunnable_run(() -> { CallTraces.trace("running runnable"); }); @@ -55,7 +55,7 @@ static void examples() { // Example of using an arena; MyClass.deinit is run at end of scope try (var arena = AllocatingSwiftArena.ofConfined()) { - MySwiftClass obj = MySwiftClass.init(2222, 7777, arena); + MySwiftClass obj = MySwiftClass.init_len_cap(2222, 7777, arena); // just checking retains/releases work CallTraces.trace("retainCount = " + SwiftRuntime.retainCount(obj)); @@ -68,14 +68,14 @@ static void examples() { CallTraces.trace("obj.counter = " + obj.getCounter()); obj.voidMethod(); - obj.takeIntMethod(42); + obj.takeIntMethod_i(42); - MySwiftClass otherObj = MySwiftClass.factory(12, 42, arena); + MySwiftClass otherObj = MySwiftClass.factory_len_cap(12, 42, arena); otherObj.voidMethod(); - MySwiftStruct swiftValue = MySwiftStruct.init(2222, 1111, arena); + MySwiftStruct swiftValue = MySwiftStruct.init_cap_len(2222, 1111, arena); CallTraces.trace("swiftValue.capacity = " + swiftValue.getCapacity()); - swiftValue.withCapLen((cap, len) -> { + swiftValue.withCapLen__((cap, len) -> { CallTraces.trace("withCapLenCallback: cap=" + cap + ", len=" + len); }); } @@ -83,11 +83,11 @@ static void examples() { // Example of using 'Data'. try (var arena = AllocatingSwiftArena.ofConfined()) { var origBytes = arena.allocateFrom("foobar"); - var origDat = Data.init(origBytes, origBytes.byteSize(), arena); + var origDat = Data.init_bytes_count(origBytes, origBytes.byteSize(), arena); CallTraces.trace("origDat.count = " + origDat.getCount()); - var retDat = MySwiftLibrary.globalReceiveReturnData(origDat, arena); - retDat.withUnsafeBytes((retBytes) -> { + var retDat = MySwiftLibrary.globalReceiveReturnData_data(origDat, arena); + retDat.withUnsafeBytes__((retBytes) -> { var str = retBytes.getString(0); CallTraces.trace("retStr=" + str); }); @@ -95,9 +95,9 @@ static void examples() { try (var arena = AllocatingSwiftArena.ofConfined()) { var bytes = arena.allocateFrom("hello"); - var dat = Data.init(bytes, bytes.byteSize(), arena); - MySwiftLibrary.globalReceiveSomeDataProtocol(dat); - MySwiftLibrary.globalReceiveOptional(OptionalLong.of(12), Optional.of(dat)); + var dat = Data.init_bytes_count(bytes, bytes.byteSize(), arena); + MySwiftLibrary.globalReceiveSomeDataProtocol_data(dat); + MySwiftLibrary.globalReceiveOptional_o1_o2(OptionalLong.of(12), Optional.of(dat)); } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index da1914d45..8eaef5f8a 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -156,11 +156,24 @@ extension FFMSwift2JavaGenerator { let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. - let javaName = switch decl.apiKind { + let baseName = switch decl.apiKind { case .getter, .subscriptGetter: decl.javaGetterName case .setter, .subscriptSetter: decl.javaSetterName case .function, .initializer, .enumCase: decl.name } + + // Add parameter labels to make method names unique (for overloading support) + let suffix: String + switch decl.apiKind { + case .getter, .subscriptGetter, .setter, .subscriptSetter: + suffix = "" + default: + suffix = decl.functionSignature.parameters + .map { "_" + ($0.argumentLabel ?? "_") } + .joined() + } + + let javaName = baseName + suffix // Signature. let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName) From cc77cc5a88d2f1ccd5c385146b66c231593358b8 Mon Sep 17 00:00:00 2001 From: clemo97 Date: Mon, 2 Mar 2026 22:55:52 +0300 Subject: [PATCH 2/2] Fix Swift overloads producing uncompilable Java wrappers - Add DuplicateNames struct for two-pass conflict detection - Only apply parameter label suffixes when base names conflict - Extract makeJavaMethodName and makeMethodNameWithParamsSuffix helpers - Fix FoundationData.fromByteArray to use generated init__ name - Add globalMethodOverloadingInt overload test methods to sample - Add OverloadSample.swift demonstrating conflict/non-conflict cases --- .../MySwiftLibrary/MySwiftLibrary.swift | 10 ++ .../MySwiftLibrary/OverloadSample.swift | 38 ++++++++ .../com/example/swift/HelloJava2Swift.java | 28 +++--- ...FMSwift2JavaGenerator+FoundationData.swift | 2 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 91 +++++++++++++------ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 3 + .../FFM/FFMSwift2JavaGenerator.swift | 5 + 7 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/OverloadSample.swift diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index b8b19d902..a2bbcb834 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -108,6 +108,16 @@ func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: Stri fflush(stdout) } +// ==== Overload test methods (same base name, different parameter labels → conflict → suffix needed) + +public func globalMethodOverloadingInt(a: Int) { + p("globalMethodOverloadingInt(a: \(a))") +} + +public func globalMethodOverloadingInt(b: Int) { + p("globalMethodOverloadingInt(b: \(b))") +} + #if os(Linux) // FIXME: why do we need this workaround? @_silgen_name("_objc_autoreleaseReturnValue") diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/OverloadSample.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/OverloadSample.swift new file mode 100644 index 000000000..337847d3d --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/OverloadSample.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 class OverloadSample { + public func takeValue(a: String) {} + public func takeValue(b: String) {} +} + +public class OverloadSample2 { + public func takeArgument(a: String) { + print("Got: \(a)") + } + + public func takeArgument(b: String) { + print("Got: \(b)") + } +} + +public class OverloadSample3 { + public func takeChair(a: String) { + print("Got: \(a)") + } + + public func takeTable(b: String) { + print("Got: \(b)") + } +} diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index 9bcecb3ff..6b835a05a 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -40,13 +40,13 @@ public static void main(String[] args) { static void examples() { MySwiftLibrary.helloWorld(); - MySwiftLibrary.globalTakeInt_i(1337); + MySwiftLibrary.globalTakeInt(1337); - long cnt = MySwiftLibrary.globalWriteString_string("String from Java"); + long cnt = MySwiftLibrary.globalWriteString("String from Java"); CallTraces.trace("count = " + cnt); - MySwiftLibrary.globalCallMeRunnable_run(() -> { + MySwiftLibrary.globalCallMeRunnable(() -> { CallTraces.trace("running runnable"); }); @@ -55,7 +55,7 @@ static void examples() { // Example of using an arena; MyClass.deinit is run at end of scope try (var arena = AllocatingSwiftArena.ofConfined()) { - MySwiftClass obj = MySwiftClass.init_len_cap(2222, 7777, arena); + MySwiftClass obj = MySwiftClass.init(2222, 7777, arena); // just checking retains/releases work CallTraces.trace("retainCount = " + SwiftRuntime.retainCount(obj)); @@ -68,14 +68,14 @@ static void examples() { CallTraces.trace("obj.counter = " + obj.getCounter()); obj.voidMethod(); - obj.takeIntMethod_i(42); + obj.takeIntMethod(42); - MySwiftClass otherObj = MySwiftClass.factory_len_cap(12, 42, arena); + MySwiftClass otherObj = MySwiftClass.factory(12, 42, arena); otherObj.voidMethod(); - MySwiftStruct swiftValue = MySwiftStruct.init_cap_len(2222, 1111, arena); + MySwiftStruct swiftValue = MySwiftStruct.init(2222, 1111, arena); CallTraces.trace("swiftValue.capacity = " + swiftValue.getCapacity()); - swiftValue.withCapLen__((cap, len) -> { + swiftValue.withCapLen((cap, len) -> { CallTraces.trace("withCapLenCallback: cap=" + cap + ", len=" + len); }); } @@ -86,8 +86,8 @@ static void examples() { var origDat = Data.init_bytes_count(origBytes, origBytes.byteSize(), arena); CallTraces.trace("origDat.count = " + origDat.getCount()); - var retDat = MySwiftLibrary.globalReceiveReturnData_data(origDat, arena); - retDat.withUnsafeBytes__((retBytes) -> { + var retDat = MySwiftLibrary.globalReceiveReturnData(origDat, arena); + retDat.withUnsafeBytes((retBytes) -> { var str = retBytes.getString(0); CallTraces.trace("retStr=" + str); }); @@ -96,10 +96,14 @@ static void examples() { try (var arena = AllocatingSwiftArena.ofConfined()) { var bytes = arena.allocateFrom("hello"); var dat = Data.init_bytes_count(bytes, bytes.byteSize(), arena); - MySwiftLibrary.globalReceiveSomeDataProtocol_data(dat); - MySwiftLibrary.globalReceiveOptional_o1_o2(OptionalLong.of(12), Optional.of(dat)); + MySwiftLibrary.globalReceiveSomeDataProtocol(dat); + MySwiftLibrary.globalReceiveOptional(OptionalLong.of(12), Optional.of(dat)); } + // Test overload conflict detection: only conflicting methods get suffixes + MySwiftLibrary.globalMethodOverloadingInt_a(100); + MySwiftLibrary.globalMethodOverloadingInt_b(200); + CallTraces.trace("Overload conflict detection test passed!"); System.out.println("DONE."); } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift index b3fa1b30b..136f3bc68 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift @@ -40,7 +40,7 @@ extension FFMSwift2JavaGenerator { */ public static \(typeName) fromByteArray(byte[] bytes, AllocatingSwiftArena arena) { Objects.requireNonNull(bytes, "bytes cannot be null"); - return \(typeName).init(bytes, arena); + return \(typeName).init__(bytes, arena); } """ ) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 61f9bad24..50f4d1227 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -27,7 +27,8 @@ extension FFMSwift2JavaGenerator { do { let translation = JavaTranslation( config: self.config, - knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable) + knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable), + dupeNames: self.currentDupeNames ) translated = try translation.translate(decl) } catch { @@ -39,6 +40,37 @@ extension FFMSwift2JavaGenerator { return translated } + /// Encapsulates detection and storage of Swift method base names that would produce + /// duplicate Java method signatures due to parameter-label overloading. + /// + /// Swift allows overloads via parameter labels (e.g., `takeValue(a:)` and `takeValue(b:)`), + /// but Java only distinguishes methods by their parameter types. This type identifies + /// which base names have actual Java naming conflicts so suffixes are applied selectively. + struct DuplicateNames { + private var duplicates: Set = [] + + /// Detects which base names among `methods` would clash in Java. + init(for methods: [ImportedFunc]) { + var seen: Set = [] + for method in methods { + let baseName = + switch method.apiKind { + case .getter, .subscriptGetter: method.javaGetterName + case .setter, .subscriptSetter: method.javaSetterName + case .function, .synthesizedFunction, .initializer, .enumCase: method.name + } + if !seen.insert(baseName).inserted { + duplicates.insert(baseName) + } + } + } + + /// Returns whether the given base name has a Java naming conflict and needs a parameter label suffix. + func needsSuffix(for baseName: String) -> Bool { + duplicates.contains(baseName) + } + } + /// Represent a Swift API parameter translated to Java. struct TranslatedParameter { /// Java parameter(s) mapped to the Swift parameter. @@ -145,10 +177,12 @@ extension FFMSwift2JavaGenerator { struct JavaTranslation { let config: Configuration var knownTypes: SwiftKnownTypes + var dupeNames: DuplicateNames - init(config: Configuration, knownTypes: SwiftKnownTypes) { + init(config: Configuration, knownTypes: SwiftKnownTypes, dupeNames: DuplicateNames = DuplicateNames(for: [])) { self.config = config self.knownTypes = knownTypes + self.dupeNames = dupeNames } func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { @@ -156,30 +190,7 @@ extension FFMSwift2JavaGenerator { let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. - let baseName = switch decl.apiKind { - case .getter, .subscriptGetter: decl.javaGetterName - case .setter, .subscriptSetter: decl.javaSetterName - case .function, .initializer, .enumCase: decl.name - } - - // Add parameter labels to make method names unique (for overloading support) - let suffix: String - switch decl.apiKind { - case .getter, .subscriptGetter, .setter, .subscriptSetter: - suffix = "" - default: - suffix = decl.functionSignature.parameters - .map { "_" + ($0.argumentLabel ?? "_") } - .joined() - } - - let javaName = baseName + suffix - let javaName = - switch decl.apiKind { - case .getter, .subscriptGetter: decl.javaGetterName - case .setter, .subscriptSetter: decl.javaSetterName - case .function, .synthesizedFunction, .initializer, .enumCase: decl.name - } + let javaName = makeJavaMethodName(decl) // Signature. let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName) @@ -211,6 +222,34 @@ extension FFMSwift2JavaGenerator { ) } + /// Returns the Java method name for the given Swift declaration, applying parameter + /// label suffixes only when a naming conflict with another method would occur. + private func makeJavaMethodName(_ decl: ImportedFunc) -> String { + let baseName = switch decl.apiKind { + case .getter, .subscriptGetter: decl.javaGetterName + case .setter, .subscriptSetter: decl.javaSetterName + case .function, .synthesizedFunction, .initializer, .enumCase: decl.name + } + return baseName + makeMethodNameWithParamsSuffix(decl, baseName: baseName) + } + + /// Returns the parameter label suffix for a method name if needed, or an empty string. + /// + /// Suffixes are only applied when the method's base name has a Java naming conflict + /// (i.e., another Swift overload would produce the same Java method name). + /// Getters and setters never receive suffixes. + private func makeMethodNameWithParamsSuffix(_ decl: ImportedFunc, baseName: String) -> String { + switch decl.apiKind { + case .getter, .subscriptGetter, .setter, .subscriptSetter: + return "" + default: + guard dupeNames.needsSuffix(for: baseName) else { return "" } + return decl.functionSignature.parameters + .map { "_" + ($0.argumentLabel ?? "_") } + .joined() + } + } + /// Translate Swift closure type to Java functional interface. func translateFunctionType( name: String, diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 9dbc38321..0f1ea6234 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -122,6 +122,7 @@ extension FFMSwift2JavaGenerator { self.lookupContext.symbolTable.printImportedModules(&printer) + self.currentDupeNames = DuplicateNames(for: self.analysis.importedGlobalFuncs + self.analysis.importedGlobalVariables) for thunk in stt.renderGlobalThunks() { printer.print(thunk) printer.println() @@ -140,6 +141,8 @@ extension FFMSwift2JavaGenerator { package func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws { let stt = SwiftThunkTranslator(self) + self.currentDupeNames = DuplicateNames(for: ty.initializers + ty.variables + ty.methods) + printer.print( """ // Generated by swift-java diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index ffc317fee..d5b20ed32 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -38,6 +38,9 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { /// Cached Java translation result. 'nil' indicates failed translation. var translatedDecls: [ImportedFunc: TranslatedFunctionDecl?] = [:] + /// The set of method base names that have Java naming conflicts for the current type/module being generated. + var currentDupeNames: DuplicateNames = DuplicateNames(for: []) + /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet, /// and write an empty file for those. /// @@ -169,6 +172,7 @@ extension FFMSwift2JavaGenerator { printPackage(&printer) printImports(&printer) + self.currentDupeNames = DuplicateNames(for: self.analysis.importedGlobalFuncs + self.analysis.importedGlobalVariables) printModuleClass(&printer) { printer in for decl in analysis.importedGlobalVariables { @@ -188,6 +192,7 @@ extension FFMSwift2JavaGenerator { printPackage(&printer) printImports(&printer) // TODO: we could have some imports be driven from types used in the generated decl + self.currentDupeNames = DuplicateNames(for: decl.initializers + decl.variables + decl.methods) printNominal(&printer, decl) { printer in // We use a static field to abuse the initialization order such that by the time we get type metadata, // we already have loaded the library where it will be obtained from.