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 a13125478..6b835a05a 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -83,7 +83,7 @@ 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); @@ -95,11 +95,15 @@ static void examples() { try (var arena = AllocatingSwiftArena.ofConfined()) { var bytes = arena.allocateFrom("hello"); - var dat = Data.init(bytes, bytes.byteSize(), arena); + var dat = Data.init_bytes_count(bytes, bytes.byteSize(), arena); 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 a066fa40d..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,12 +190,7 @@ extension FFMSwift2JavaGenerator { let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. - 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) @@ -193,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.