Skip to content
Open
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
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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)")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
"""
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<String> = []

/// Detects which base names among `methods` would clash in Java.
init(for methods: [ImportedFunc]) {
var seen: Set<String> = []
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.
Expand Down Expand Up @@ -145,23 +177,20 @@ 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 {
let lowering = CdeclLowering(knownTypes: knownTypes)
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)
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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 {
Expand All @@ -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.
Expand Down