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
Expand Up @@ -101,6 +101,17 @@ public func globalReceiveOptional(o1: Int?, o2: (some DataProtocol)?) -> Int {
}
}

// ==== -----------------------------------------------------------------------
// MARK: Overloaded functions

public func globalOverloaded(a: Int) {
p("globalOverloaded(a: \(a))")
}

public func globalOverloaded(b: Int) {
p("globalOverloaded(b: \(b))")
}

// ==== Internal helpers

func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ static void examples() {
}


// Overloaded functions with label-based disambiguation
MySwiftLibrary.globalOverloadedA(100);
MySwiftLibrary.globalOverloadedB(200);

System.out.println("DONE.");
}

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),
javaIdentifiers: self.currentJavaIdentifiers
)
translated = try translation.translate(decl)
} catch {
Expand Down Expand Up @@ -142,26 +143,26 @@ extension FFMSwift2JavaGenerator {
}
}

// ==== -------------------------------------------------------------------
// MARK: Java translation

struct JavaTranslation {
let config: Configuration
var knownTypes: SwiftKnownTypes
var javaIdentifiers: JavaIdentifierFactory

init(config: Configuration, knownTypes: SwiftKnownTypes) {
init(config: Configuration, knownTypes: SwiftKnownTypes, javaIdentifiers: JavaIdentifierFactory) {
self.config = config
self.knownTypes = knownTypes
self.javaIdentifiers = javaIdentifiers
}

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, .initializer, .enumCase: decl.name
}
let javaName = javaIdentifiers.makeJavaMethodName(decl)

// Signature.
let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ extension FFMSwift2JavaGenerator {

self.lookupContext.symbolTable.printImportedModules(&printer)

self.currentJavaIdentifiers = JavaIdentifierFactory(
self.analysis.importedGlobalFuncs + self.analysis.importedGlobalVariables
)

for thunk in stt.renderGlobalThunks() {
printer.print(thunk)
printer.println()
Expand Down Expand Up @@ -152,6 +156,10 @@ extension FFMSwift2JavaGenerator {

self.lookupContext.symbolTable.printImportedModules(&printer)

self.currentJavaIdentifiers = JavaIdentifierFactory(
ty.initializers + ty.variables + ty.methods
)

for thunk in stt.renderThunks(forType: ty) {
printer.print("\(thunk)")
printer.print("")
Expand Down
11 changes: 11 additions & 0 deletions Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
/// Cached Java translation result. 'nil' indicates failed translation.
var translatedDecls: [ImportedFunc: TranslatedFunctionDecl?] = [:]

/// Duplicate identifier tracking for the current batch of methods being generated.
var currentJavaIdentifiers: JavaIdentifierFactory = JavaIdentifierFactory()

/// 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 @@ -170,6 +173,10 @@ extension FFMSwift2JavaGenerator {
printPackage(&printer)
printImports(&printer)

self.currentJavaIdentifiers = JavaIdentifierFactory(
self.analysis.importedGlobalFuncs + self.analysis.importedGlobalVariables
)

printModuleClass(&printer) { printer in

for decl in analysis.importedGlobalVariables {
Expand All @@ -189,6 +196,10 @@ extension FFMSwift2JavaGenerator {
printPackage(&printer)
printImports(&printer) // TODO: we could have some imports be driven from types used in the generated decl

self.currentJavaIdentifiers = JavaIdentifierFactory(
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
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ extension JNISwift2JavaGenerator {
printPackage(&printer)
printImports(&printer)

self.currentJavaIdentifiers = JavaIdentifierFactory(
self.analysis.importedGlobalFuncs + self.analysis.importedGlobalVariables
)

printModuleClass(&printer) { printer in
printer.print(
"""
Expand Down Expand Up @@ -124,6 +128,10 @@ extension JNISwift2JavaGenerator {
printPackage(&printer)
printImports(&printer)

self.currentJavaIdentifiers = JavaIdentifierFactory(
decl.initializers + decl.variables + decl.methods
)

switch decl.swiftNominal.kind {
case .actor, .class, .enum, .struct:
printConcreteType(&printer, decl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ extension JNISwift2JavaGenerator {
javaClassLookupTable: self.javaClassLookupTable,
knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable),
protocolWrappers: self.interfaceProtocolWrappers,
logger: self.logger
logger: self.logger,
javaIdentifiers: self.currentJavaIdentifiers
)
}

Expand Down Expand Up @@ -64,7 +65,8 @@ extension JNISwift2JavaGenerator {
javaClassLookupTable: self.javaClassLookupTable,
knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable),
protocolWrappers: self.interfaceProtocolWrappers,
logger: self.logger
logger: self.logger,
javaIdentifiers: self.currentJavaIdentifiers
)
translated = try translation.translate(enumCase: decl)
} catch {
Expand All @@ -84,6 +86,7 @@ extension JNISwift2JavaGenerator {
var knownTypes: SwiftKnownTypes
let protocolWrappers: [ImportedNominalType: JavaInterfaceSwiftWrapper]
let logger: Logger
var javaIdentifiers: JavaIdentifierFactory

func translate(enumCase: ImportedEnumCase) throws -> TranslatedEnumCase {
let nativeTranslation = NativeJavaTranslation(
Expand Down Expand Up @@ -226,12 +229,7 @@ extension JNISwift2JavaGenerator {
let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName

// Name.
let javaName =
switch decl.apiKind {
case .getter, .subscriptGetter: decl.javaGetterName
case .setter, .subscriptSetter: decl.javaSetterName
case .function, .initializer, .enumCase: decl.name
}
let javaName = javaIdentifiers.makeJavaMethodName(decl)

// Swift -> Java
var translatedFunctionSignature = try translate(
Expand Down
3 changes: 3 additions & 0 deletions Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator {
var translatedEnumCases: [ImportedEnumCase: TranslatedEnumCase] = [:]
var interfaceProtocolWrappers: [ImportedNominalType: JavaInterfaceSwiftWrapper] = [:]

/// Duplicate identifier tracking for the current batch of methods being generated.
var currentJavaIdentifiers: JavaIdentifierFactory = JavaIdentifierFactory()

/// 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
89 changes: 89 additions & 0 deletions Sources/JExtractSwiftLib/JavaIdentifierFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

/// Detects Java method name conflicts caused by Swift overloads that differ
/// only in parameter labels. When a conflict is detected, the affected methods
/// get a camelCase suffix derived from their parameter labels (e.g. `takeValueA`,
/// `takeValueB`) so that Java can distinguish them.
package struct JavaIdentifierFactory {
private var duplicates: Set<String> = []

package init() {}

package init(_ methods: [ImportedFunc]) {
self.init()
record(methods)
}

/// Analyze the given methods and record any base names that have conflicts.
private mutating func record(_ methods: [ImportedFunc]) {
// Group methods by their Java base name.
var methodsByBaseName: [String: [ImportedFunc]] = [:]
for method in methods {
let baseName: String =
switch method.apiKind {
case .getter, .subscriptGetter: method.javaGetterName
case .setter, .subscriptSetter: method.javaSetterName
case .function, .initializer, .enumCase: method.name
}
methodsByBaseName[baseName, default: []].append(method)
}

// For each group with 2+ methods, check if any two share the same
// Swift parameter types (which means identical Java parameter types).
for (baseName, group) in methodsByBaseName where group.count > 1 {
var seenSignatures: Set<String> = []
for method in group {
let key = method.functionSignature.parameters
.map { $0.type.description }
.joined(separator: ",")
if !seenSignatures.insert(key).inserted {
duplicates.insert(baseName)
break
}
}
}
}

package func needsSuffix(for baseName: String) -> Bool {
duplicates.contains(baseName)
}

/// Compute the disambiguated Java method name for a declaration.
package func makeJavaMethodName(_ decl: ImportedFunc) -> String {
let baseName: String =
switch decl.apiKind {
case .getter, .subscriptGetter: decl.javaGetterName
case .setter, .subscriptSetter: decl.javaSetterName
case .function, .initializer, .enumCase: decl.name
}
return baseName + paramsSuffix(decl, baseName: baseName)
}

private func paramsSuffix(_ decl: ImportedFunc, baseName: String) -> String {
switch decl.apiKind {
case .getter, .subscriptGetter, .setter, .subscriptSetter:
return ""
default:
guard needsSuffix(for: baseName) else { return "" }
let labels = decl.functionSignature.parameters
.compactMap { $0.argumentLabel }
// A parameterless function that still conflicts (e.g. with a property
// getter) gets a bare "_" so it compiles as a distinct Java method.
guard !labels.isEmpty else { return "_" }
// Join labels in camelCase: takeValue(a:) → takeValueA
return labels.map { $0.prefix(1).uppercased() + $0.dropFirst() }.joined()
}
}
}
Loading
Loading