Skip to content

Commit f5cbcdf

Browse files
authored
Merge pull request swiftlang#121 from DougGregor/covariant-overrides
Java2Swift: Account for covariant overrides so we don't have duplicates
2 parents 633c03b + 3ea7c33 commit f5cbcdf

File tree

5 files changed

+3367
-9
lines changed

5 files changed

+3367
-9
lines changed

Sources/Java2SwiftLib/JavaClassTranslator.swift

+44-6
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+
}
+102
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)