Skip to content

Commit a986fd0

Browse files
committed
properly handle Optional::ofNullable, de-dupe generic params; use SortedSet
1 parent ad11f85 commit a986fd0

File tree

8 files changed

+135
-58
lines changed

8 files changed

+135
-58
lines changed

Package.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ let package = Package(
209209
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
210210
.package(url: "https://github.com/apple/swift-system", from: "1.4.0"),
211211
.package(url: "https://github.com/apple/swift-log", from: "1.2.0"),
212+
.package(url: "https://github.com/apple/swift-collections", .upToNextMinor(from: "1.3.0")), // primarily for ordered collections
212213

213214
// // FIXME: swift-subprocess stopped supporting 6.0 when it moved into a package;
214215
// // we'll need to drop 6.0 as well, but currently blocked on doing so by swiftpm plugin pending design questions
@@ -400,6 +401,7 @@ let package = Package(
400401
name: "SwiftJavaToolLib",
401402
dependencies: [
402403
.product(name: "Logging", package: "swift-log"),
404+
.product(name: "OrderedCollections", package: "swift-collections"),
403405
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
404406
.product(name: "SwiftSyntax", package: "swift-syntax"),
405407
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),

Sources/SwiftJava/Macros.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,13 @@ public macro JavaMethod(
150150
/// The macro must be used within a specific JavaClass instance.
151151
///
152152
/// ```swift
153-
/// @JavaMethod
153+
/// @JavaStaticMethod
154154
/// func sayHelloBack(_ i: Int32) -> Double
155155
/// ```
156156
@attached(body)
157-
public macro JavaStaticMethod() = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro")
157+
public macro JavaStaticMethod(
158+
genericResult: String? = nil
159+
) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro")
158160

159161
/// Macro that marks extensions to specify that all of the @JavaMethod
160162
/// methods are implementations of Java methods spelled as `native`.

Sources/SwiftJava/generated/JavaLong.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,6 @@ extension JavaClass<JavaLong> {
140140
@JavaStaticMethod
141141
public func decode(_ arg0: String) throws -> JavaLong!
142142

143-
@JavaStaticMethod
144-
public func highestOneBit(_ arg0: Int64) -> Int64
145-
146143
@JavaStaticMethod
147144
public func sum(_ arg0: Int64, _ arg1: Int64) -> Int64
148145

@@ -158,6 +155,9 @@ extension JavaClass<JavaLong> {
158155
@JavaStaticMethod
159156
public func toBinaryString(_ arg0: Int64) -> String
160157

158+
@JavaStaticMethod
159+
public func highestOneBit(_ arg0: Int64) -> Int64
160+
161161
@JavaStaticMethod
162162
public func lowestOneBit(_ arg0: Int64) -> Int64
163163

Sources/SwiftJava/generated/JavaOptionalDouble.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ open class JavaOptionalDouble: JavaObject {
1616
open func isEmpty() -> Bool
1717

1818
@JavaMethod
19-
open func isPresent() -> Bool
19+
open func orElse(_ arg0: Double) -> Double
2020

2121
@JavaMethod
22-
open func orElse(_ arg0: Double) -> Double
22+
open func isPresent() -> Bool
2323

2424
@JavaMethod
2525
open func orElseThrow() -> Double

Sources/SwiftJavaToolLib/JavaClassTranslator.swift

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import SwiftJava
1616
import JavaLangReflect
1717
import SwiftSyntax
18+
import OrderedCollections
1819
import SwiftJavaConfigurationShared
1920
import Logging
2021

@@ -606,25 +607,35 @@ extension JavaClassTranslator {
606607
return false
607608
}
608609

609-
/// Translates the given Java method into a Swift declaration.
610-
package func renderMethod(
611-
_ javaMethod: Method,
612-
implementedInSwift: Bool,
613-
genericParameters: [String] = [],
614-
whereClause: String = ""
615-
) throws -> DeclSyntax {
616-
// Map the generic params on the method.
617-
var allGenericParameters = genericParameters
610+
// TODO: make it more precise with the "origin" of the generic parameter (outer class etc)
611+
func collectMethodGenericParameters(
612+
genericParameters: [String],
613+
method javaMethod: Method
614+
) -> OrderedSet<String> {
615+
var allGenericParameters = OrderedSet(genericParameters)
618616
let typeParameters = javaMethod.getTypeParameters()
619617
if typeParameters.contains(where: {$0 != nil }) {
620-
allGenericParameters += typeParameters.compactMap { typeParam in
618+
allGenericParameters.appending(contentsOf: typeParameters.compactMap { typeParam in
621619
guard let typeParam else { return nil }
622620
guard genericParameterIsUsedInSignature(typeParam, in: javaMethod) else {
623621
return nil
624622
}
625623
return "\(typeParam.getTypeName()): AnyJavaObject"
626-
}
624+
})
627625
}
626+
627+
return allGenericParameters
628+
}
629+
630+
/// Translates the given Java method into a Swift declaration.
631+
package func renderMethod(
632+
_ javaMethod: Method,
633+
implementedInSwift: Bool,
634+
genericParameters: [String] = [],
635+
whereClause: String = ""
636+
) throws -> DeclSyntax {
637+
// Map the generic params on the method.
638+
let allGenericParameters = collectMethodGenericParameters(genericParameters: genericParameters, method: javaMethod)
628639
let genericParameterClauseStr =
629640
if allGenericParameters.isEmpty {
630641
""
@@ -657,7 +668,7 @@ extension JavaClassTranslator {
657668
let throwsStr = javaMethod.throwsCheckedException ? "throws" : ""
658669
let swiftMethodName = javaMethod.getName().escapedSwiftName
659670

660-
// Compute the '@...JavaMethod(...)' details
671+
// Compute the parameters for '@...JavaMethod(...)'
661672
let methodAttribute: AttributeSyntax
662673
if implementedInSwift {
663674
methodAttribute = ""
@@ -674,6 +685,7 @@ extension JavaClassTranslator {
674685
parameters.append("genericResult: \"\(resultType)\"")
675686
}
676687
// TODO: generic parameters?
688+
677689
if !parameters.isEmpty {
678690
methodAttributeStr += "("
679691
methodAttributeStr.append(parameters.joined(separator: ", "))
@@ -690,6 +702,7 @@ extension JavaClassTranslator {
690702
? "override "
691703
: ""
692704

705+
// FIXME: refactor this so we don't have to duplicate the method signatures
693706
if resultType.optionalWrappedType() != nil || parameters.contains(where: { $0.type.trimmedDescription.optionalWrappedType() != nil }) {
694707
let parameters = parameters.map { param -> (clause: FunctionParameterSyntax, passedArg: String) in
695708
let name = param.secondName!.trimmedDescription
@@ -711,15 +724,17 @@ extension JavaClassTranslator {
711724
}
712725

713726

714-
return """
727+
return
728+
"""
715729
\(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause)
716730
717731
\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)Optional\(raw: genericParameterClauseStr)(\(raw: parameters.map(\.clause.description).joined(separator: ", ")))\(raw: throwsStr) -> \(raw: resultOptional)\(raw: whereClause) {
718732
\(body)
719733
}
720734
"""
721735
} else {
722-
return """
736+
return
737+
"""
723738
\(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause)
724739
"""
725740
}
@@ -900,6 +915,7 @@ struct MethodCollector {
900915
}
901916

902917
// MARK: Utility functions
918+
903919
extension JavaClassTranslator {
904920
/// Determine whether this method is an override of another Java
905921
/// method.
@@ -935,7 +951,7 @@ extension JavaClassTranslator {
935951
return true
936952
}
937953
} catch {
938-
// FIXME: logging
954+
log.warning("Failed to determine if method '\(method)' is an override, error: \(error)")
939955
}
940956
}
941957

Sources/SwiftJavaToolLib/JavaTranslator.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,11 @@ extension JavaTranslator {
138138

139139
// Special handle the case when the return type is the generic type of the method: `<T> T foo()`
140140

141-
// if isGenericJavaType(genericReturnType) {
142-
// print("[swift] generic method! \(method.getDeclaringClass().getName()).\(method.getName())")
143-
// getGenericJavaTypeOriginInfo(genericReturnType, from: method)
144-
// }
145-
146-
return try getSwiftTypeNameAsString(method: method, genericReturnType!, preferValueTypes: preferValueTypes, outerOptional: outerOptional)
141+
return try getSwiftTypeNameAsString(
142+
method: method,
143+
genericReturnType!,
144+
preferValueTypes: preferValueTypes,
145+
outerOptional: outerOptional)
147146
}
148147

149148
/// Turn a Java type into a string.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
15+
@_spi(Testing) import SwiftJava
16+
import SwiftJavaToolLib
17+
import JavaUtilJar
18+
import SwiftJavaShared
19+
import JavaNet
20+
import SwiftJavaConfigurationShared
21+
import _Subprocess
22+
import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43
23+
24+
final class BasicWrapJavaTests: XCTestCase {
25+
26+
func testWrapJavaFromCompiledJavaSource() async throws {
27+
let classpathURL = try await compileJava(
28+
"""
29+
package com.example;
30+
31+
class ExampleSimpleClass {}
32+
""")
33+
34+
try assertWrapJavaOutput(
35+
javaClassNames: [
36+
"com.example.ExampleSimpleClass"
37+
],
38+
classpath: [classpathURL],
39+
expectedChunks: [
40+
"""
41+
import CSwiftJavaJNI
42+
import SwiftJava
43+
""",
44+
"""
45+
@JavaClass("com.example.ExampleSimpleClass")
46+
open class ExampleSimpleClass: JavaObject {
47+
"""
48+
]
49+
)
50+
}
51+
52+
}

Tests/SwiftJavaToolLibTests/WrapJavaTests.swift renamed to Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,35 +21,8 @@ import SwiftJavaConfigurationShared
2121
import _Subprocess
2222
import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43
2323

24-
final class WrapJavaTests: XCTestCase {
24+
final class GenericsWrapJavaTests: XCTestCase {
2525

26-
func testWrapJavaFromCompiledJavaSource() async throws {
27-
let classpathURL = try await compileJava(
28-
"""
29-
package com.example;
30-
31-
class ExampleSimpleClass {}
32-
""")
33-
34-
try assertWrapJavaOutput(
35-
javaClassNames: [
36-
"com.example.ExampleSimpleClass"
37-
],
38-
classpath: [classpathURL],
39-
expectedChunks: [
40-
"""
41-
import CSwiftJavaJNI
42-
import SwiftJava
43-
""",
44-
"""
45-
@JavaClass("com.example.ExampleSimpleClass")
46-
open class ExampleSimpleClass: JavaObject {
47-
"""
48-
]
49-
)
50-
}
51-
52-
// @Test
5326
func testWrapJavaGenericMethod_singleGeneric() async throws {
5427
let classpathURL = try await compileJava(
5528
"""
@@ -271,7 +244,7 @@ final class WrapJavaTests: XCTestCase {
271244
)
272245
}
273246

274-
func testWrapJavaGenericMethodTypeErasure_returnType() async throws {
247+
func test_wrapJava_genericMethodTypeErasure_returnType() async throws {
275248
let classpathURL = try await compileJava(
276249
"""
277250
package com.example;
@@ -301,4 +274,37 @@ final class WrapJavaTests: XCTestCase {
301274
]
302275
)
303276
}
304-
}
277+
278+
func test_wrapJava_genericMethodTypeErasure_ofNullableOptional() async throws {
279+
let classpathURL = try await compileJava(
280+
"""
281+
package com.example;
282+
283+
final class Optional<T> {
284+
public static <T> Optional<T> ofNullable(T value) { return null; }
285+
}
286+
""")
287+
288+
try assertWrapJavaOutput(
289+
javaClassNames: [
290+
"com.example.Optional"
291+
],
292+
classpath: [classpathURL],
293+
expectedChunks: [
294+
"""
295+
@JavaClass("com.example.Optional")
296+
open class Optional<T: AnyJavaObject>: JavaObject {
297+
298+
}
299+
""",
300+
"""
301+
extension JavaClass {
302+
@JavaStaticMethod(genericResult: "Optional<T>!")
303+
public func ofNullable<T: AnyJavaObject>(_ arg0: T?) -> Optional<T>! where ObjectType == Optional<T>
304+
}
305+
"""
306+
]
307+
)
308+
}
309+
310+
}

0 commit comments

Comments
 (0)