Skip to content

Java2Swift: Account for covariant overrides so we don't have duplicates #121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 28, 2024
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
50 changes: 44 additions & 6 deletions Sources/Java2SwiftLib/JavaClassTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ struct JavaClassTranslator {
var constructors: [Constructor<JavaObject>] = []

/// The (instance) methods of the Java class.
var methods: [Method] = []
var methods: MethodCollector = MethodCollector()

/// The static methods of the Java class.
var staticMethods: [Method] = []
var staticMethods: MethodCollector = MethodCollector()

/// The native instance methods of the Java class, which are also reflected
/// in a `*NativeMethods` protocol so they can be implemented in Swift.
Expand Down Expand Up @@ -215,8 +215,8 @@ extension JavaClassTranslator {
/// Add a method to the appropriate list for later translation.
private mutating func addMethod(_ method: Method, isNative: Bool) {
switch (method.isStatic, isNative) {
case (false, false): methods.append(method)
case (true, false): staticMethods.append(method)
case (false, false): methods.add(method)
case (true, false): staticMethods.add(method)
case (false, true): nativeMethods.append(method)
case (true, true): nativeStaticMethods.append(method)
}
Expand Down Expand Up @@ -266,7 +266,7 @@ extension JavaClassTranslator {
}

// Render all of the instance methods in Swift.
let instanceMethods = methods.compactMap { method in
let instanceMethods = methods.methods.compactMap { method in
do {
return try renderMethod(method, implementedInSwift: false)
} catch {
Expand Down Expand Up @@ -355,7 +355,7 @@ extension JavaClassTranslator {
}

// Render static methods.
let methods = staticMethods.compactMap { method in
let methods = staticMethods.methods.compactMap { method in
// Translate each static method.
do {
return try renderMethod(
Expand Down Expand Up @@ -553,3 +553,41 @@ extension JavaClassTranslator {
}
}
}

/// Helper struct that collects methods, removing any that have been overridden
/// by a covariant method.
struct MethodCollector {
var methods: [Method] = []

/// Mapping from method names to the indices of each method within the
/// list of methods.
var methodsByName: [String: [Int]] = [:]

/// Add this method to the collector.
mutating func add(_ method: Method) {
// Compare against existing methods with this same name.
for existingMethodIndex in methodsByName[method.getName()] ?? [] {
let existingMethod = methods[existingMethodIndex]
switch MethodVariance(method, existingMethod) {
case .equivalent, .unrelated:
// Nothing to do.
continue

case .contravariantResult:
// This method is worse than what we have; there is nothing to do.
return

case .covariantResult:
// This method is better than the one we have; replace the one we
// have with it.
methods[existingMethodIndex] = method
return
}
}

// If we get here, there is no related method in the list. Add this
// new method.
methodsByName[method.getName(), default: []].append(methods.count)
methods.append(method)
}
}
102 changes: 102 additions & 0 deletions Sources/Java2SwiftLib/MethodVariance.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
import JavaKit
import JavaKitReflection

/// Captures the relationship between two methods by comparing their parameter
/// and result types.
enum MethodVariance {
/// The methods are equivalent.
case equivalent

/// The methods are unrelated, e.g., some parameter types are different or
/// the return types cannot be compared.
case unrelated

/// The second method is covariant with the first, meaning that its result
/// type is a subclass of the result type of the first method.
case covariantResult

/// The second method is contravariant with the first, meaning that its result
/// type is a superclass of the result type of the first method.
///
/// This is the same as getting a covariant result when flipping the order
/// of the methods.
case contravariantResult

init(_ first: Method, _ second: Method) {
// If there are obvious differences, note that these are unrelated.
if first.getName() != second.getName() ||
first.isStatic != second.isStatic ||
first.getParameterCount() != second.getParameterCount() {
self = .unrelated
return
}

// Check the parameter types.
for (firstParamType, secondParamType) in zip(first.getParameterTypes(), second.getParameterTypes()) {
guard let firstParamType, let secondParamType else { continue }

// If the parameter types don't match, these methods are unrelated.
guard firstParamType.equals(secondParamType.as(JavaObject.self)) else {
self = .unrelated
return
}
}

// Check the result type.
let firstResultType = first.getReturnType()!
let secondResultType = second.getReturnType()!

// If the result types are equivalent, the methods are equivalent.
if firstResultType.equals(secondResultType.as(JavaObject.self)) {
self = .equivalent
return
}

// If first result type is a subclass of the second result type, it's
// covariant.
if firstResultType.isSubclass(of: secondResultType.as(JavaClass<JavaObject>.self)!) {
self = .covariantResult
return
}

// If second result type is a subclass of the first result type, it's
// contravariant.
if secondResultType.isSubclass(of: firstResultType.as(JavaClass<JavaObject>.self)!) {
self = .contravariantResult
return
}

// Treat the methods as unrelated, because we cannot compare their result
// types.
self = .unrelated
}
}

extension JavaClass {
/// Whether this Java class is a subclass of the other Java class.
func isSubclass(of other: JavaClass<JavaObject>) -> Bool {
var current = self.as(JavaClass<JavaObject>.self)
while let currentClass = current {
if currentClass.equals(other.as(JavaObject.self)) {
return true
}

current = currentClass.getSuperclass()
}

return false
}
}
Loading