Skip to content

Commit bce9733

Browse files
committed
Java2Swift: Account for covariant overrides so we don't have duplicates
When we see methods with the same name and parameter types but different result types, check whether the result types are related and only keep the more specialized method.
1 parent 633c03b commit bce9733

File tree

2 files changed

+146
-6
lines changed

2 files changed

+146
-6
lines changed

Sources/Java2SwiftLib/JavaClassTranslator.swift

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ struct JavaClassTranslator {
5757
var constructors: [Constructor<JavaObject>] = []
5858

5959
/// The (instance) methods of the Java class.
60-
var methods: [Method] = []
60+
var methods: MethodCollector = MethodCollector()
6161

6262
/// The static methods of the Java class.
63-
var staticMethods: [Method] = []
63+
var staticMethods: MethodCollector = MethodCollector()
6464

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

268268
// Render all of the instance methods in Swift.
269-
let instanceMethods = methods.compactMap { method in
269+
let instanceMethods = methods.methods.compactMap { method in
270270
do {
271271
return try renderMethod(method, implementedInSwift: false)
272272
} catch {
@@ -355,7 +355,7 @@ extension JavaClassTranslator {
355355
}
356356

357357
// Render static methods.
358-
let methods = staticMethods.compactMap { method in
358+
let methods = staticMethods.methods.compactMap { method in
359359
// Translate each static method.
360360
do {
361361
return try renderMethod(
@@ -553,3 +553,41 @@ extension JavaClassTranslator {
553553
}
554554
}
555555
}
556+
557+
/// Helper struct that collects methods, removing any that have been overridden
558+
/// by a covariant method.
559+
struct MethodCollector {
560+
var methods: [Method] = []
561+
562+
/// Mapping from method names to the indices of each method within the
563+
/// list of methods.
564+
var methodsByName: [String: [Int]] = [:]
565+
566+
/// Add this method to the collector.
567+
mutating func add(_ method: Method) {
568+
// Compare against existing methods with this same name.
569+
for existingMethodIndex in methodsByName[method.getName()] ?? [] {
570+
let existingMethod = methods[existingMethodIndex]
571+
switch MethodVariance(method, existingMethod) {
572+
case .equivalent, .unrelated:
573+
// Nothing to do.
574+
continue
575+
576+
case .contravariantResult:
577+
// This method is worse than what we have; there is nothing to do.
578+
return
579+
580+
case .covariantResult:
581+
// This method is better than the one we have; replace the one we
582+
// have with it.
583+
methods[existingMethodIndex] = method
584+
return
585+
}
586+
}
587+
588+
// If we get here, there is no related method in the list. Add this
589+
// new method.
590+
methodsByName[method.getName(), default: []].append(methods.count)
591+
methods.append(method)
592+
}
593+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import JavaKit
15+
import JavaKitReflection
16+
17+
/// Captures the relationship between two methods by comparing their parameter
18+
/// and result types.
19+
enum MethodVariance {
20+
/// The methods are equivalent.
21+
case equivalent
22+
23+
/// The methods are unrelated, e.g., some parameter types are different or
24+
/// the return types cannot be compared.
25+
case unrelated
26+
27+
/// The second method is covariant with the first, meaning that its result
28+
/// type is a subclass of the result type of the first method.
29+
case covariantResult
30+
31+
/// The second method is contravariant with the first, meaning that its result
32+
/// type is a superclass of the result type of the first method.
33+
///
34+
/// This is the same as getting a covariant result when flipping the order
35+
/// of the methods.
36+
case contravariantResult
37+
38+
init(_ first: Method, _ second: Method) {
39+
// If there are obvious differences, note that these are unrelated.
40+
if first.getName() != second.getName() ||
41+
first.isStatic != second.isStatic ||
42+
first.getParameterCount() != second.getParameterCount() {
43+
self = .unrelated
44+
return
45+
}
46+
47+
// Check the parameter types.
48+
for (firstParamType, secondParamType) in zip(first.getParameterTypes(), second.getParameterTypes()) {
49+
guard let firstParamType, let secondParamType else { continue }
50+
51+
// If the parameter types don't match, these methods are unrelated.
52+
guard firstParamType.equals(secondParamType.as(JavaObject.self)) else {
53+
self = .unrelated
54+
return
55+
}
56+
}
57+
58+
// Check the result type.
59+
let firstResultType = first.getReturnType()!
60+
let secondResultType = second.getReturnType()!
61+
62+
// If the result types are equivalent, the methods are equivalent.
63+
if firstResultType.equals(secondResultType.as(JavaObject.self)) {
64+
self = .equivalent
65+
return
66+
}
67+
68+
// If first result type is a subclass of the second result type, it's
69+
// covariant.
70+
if firstResultType.isSubclass(of: secondResultType.as(JavaClass<JavaObject>.self)!) {
71+
self = .covariantResult
72+
return
73+
}
74+
75+
// If second result type is a subclass of the first result type, it's
76+
// contravariant.
77+
if secondResultType.isSubclass(of: firstResultType.as(JavaClass<JavaObject>.self)!) {
78+
self = .contravariantResult
79+
return
80+
}
81+
82+
// Treat the methods as unrelated, because we cannot compare their result
83+
// types.
84+
self = .unrelated
85+
}
86+
}
87+
88+
extension JavaClass {
89+
/// Whether this Java class is a subclass of the other Java class.
90+
func isSubclass(of other: JavaClass<JavaObject>) -> Bool {
91+
var current = self.as(JavaClass<JavaObject>.self)
92+
while let currentClass = current {
93+
if currentClass.equals(other.as(JavaObject.self)) {
94+
return true
95+
}
96+
97+
current = currentClass.getSuperclass()
98+
}
99+
100+
return false
101+
}
102+
}

0 commit comments

Comments
 (0)