Skip to content

Commit 37deee3

Browse files
committed
Improve Optional handling, and generic get() -> T
1 parent c907ef8 commit 37deee3

File tree

9 files changed

+142
-23
lines changed

9 files changed

+142
-23
lines changed

Sources/SwiftJava/AnyJavaObject.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ public protocol AnyJavaObjectWithCustomClassLoader: AnyJavaObject {
5959
extension AnyJavaObject {
6060
/// Retrieve the underlying Java object.
6161
public var javaThis: jobject {
62-
javaHolder.object!
62+
javaHolder.object! // FIXME: this is a bad idea, can be null
63+
}
64+
65+
public var javaThisOptional: jobject? {
66+
javaHolder.object
6367
}
6468

6569
/// Retrieve the environment in which this Java object resides.

Sources/SwiftJava/JavaObject+Inheritance.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ extension AnyJavaObject {
2222
private func isInstanceOf<OtherClass: AnyJavaObject>(
2323
_ otherClass: OtherClass.Type
2424
) -> jclass? {
25-
try? otherClass.withJNIClass(in: javaEnvironment) { otherJavaClass in
26-
if javaEnvironment.interface.IsInstanceOf(
27-
javaEnvironment,
28-
javaThis,
29-
otherJavaClass
30-
) == 0 {
31-
return nil
32-
}
25+
guard let this: jobject = javaThisOptional else {
26+
return nil
27+
}
28+
29+
return try? otherClass.withJNIClass(in: javaEnvironment) { otherJavaClass in
30+
if javaEnvironment.interface.IsInstanceOf(javaEnvironment, this, otherJavaClass) == 0 {
31+
return nil
32+
}
3333

34-
return otherJavaClass
34+
return otherJavaClass
3535
}
3636
}
3737

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
import CSwiftJavaJNI
16+
import JavaTypes
17+
18+
extension JavaString: CustomStringConvertible, CustomDebugStringConvertible {
19+
public var description: String {
20+
return toString()
21+
}
22+
public var debugDescription: String {
23+
return "\"" + toString() + "\""
24+
}
25+
}
26+
27+
extension Optional where Wrapped == JavaString {
28+
public var description: String {
29+
switch self {
30+
case .some(let value): "Optional(\(value.toString())"
31+
case .none: "nil"
32+
}
33+
}
34+
}

Sources/SwiftJava/Macros.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = fal
124124
///
125125
/// corresponds to the Java constructor `HelloSwift(String name)`.
126126
@attached(body)
127-
public macro JavaMethod() = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro")
127+
public macro JavaMethod(
128+
genericResult: String? = nil
129+
) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro")
128130

129131
/// Attached macro that turns a Swift method on JavaClass into one that wraps
130132
/// a Java static method on the underlying Java class object.

Sources/SwiftJava/Optional+JavaOptional.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,17 @@ public extension Optional where Wrapped == Int64 {
7979
}
8080
}
8181
}
82+
83+
extension JavaOptional {
84+
public func empty(environment: JNIEnvironment? = nil) -> JavaOptional<T>! {
85+
guard let env = try? environment ?? JavaVirtualMachine.shared().environment() else {
86+
return nil
87+
}
88+
89+
guard let opt = try? JavaClass<JavaOptional<T>>(environment: env).empty() else {
90+
return nil
91+
}
92+
93+
return opt.as(JavaOptional<T>.self)
94+
}
95+
}

Sources/SwiftJava/String+Extensions.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import Foundation
16-
// import SwiftJavaToolLib
17-
// import SwiftJava
18-
// import JavaUtilJar
19-
// import SwiftJavaConfigurationShared
2016

2117
extension String {
2218
/// For a String that's of the form java.util.Vector, return the "Vector"

Sources/SwiftJava/generated/JavaOptional.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import CSwiftJavaJNI
33

44
@JavaClass("java.util.Optional")
55
open class JavaOptional<T: AnyJavaObject>: JavaObject {
6-
@JavaMethod
7-
open func get() -> JavaObject! // FIXME: Currently we do generate -> T https://github.com/swiftlang/swift-java/issues/439
6+
@JavaMethod(genericResult: "T")
7+
open func get() -> T!
88

99
@JavaMethod
1010
open override func equals(_ arg0: JavaObject?) -> Bool

Sources/SwiftJavaMacros/JavaMethodMacro.swift

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,41 @@ extension JavaMethodMacro: BodyMacro {
4747
}
4848

4949
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
50-
fatalError("not a function")
50+
fatalError("not a function: \(declaration)")
5151
}
5252

5353
let isStatic = node.attributeName.trimmedDescription == "JavaStaticMethod"
5454
let funcName = funcDecl.name.text
5555
let params = funcDecl.signature.parameterClause.parameters
56-
let resultType: String =
57-
funcDecl.signature.returnClause.map { result in
58-
", resultType: \(result.type.typeReferenceString).self"
59-
} ?? ""
6056
let paramNames = params.map { param in param.parameterName?.text ?? "" }.joined(separator: ", ")
6157

58+
let genericResultType: String? =
59+
if case let .argumentList(arguments) = node.arguments,
60+
let firstElement = arguments.first,
61+
let stringLiteral = firstElement.expression
62+
.as(StringLiteralExprSyntax.self),
63+
stringLiteral.segments.count == 1,
64+
case let .stringSegment(wrapperName)? = stringLiteral.segments.first {
65+
"\(wrapperName)"
66+
} else {
67+
nil
68+
}
69+
70+
// Determine the result type
71+
let resultType: String =
72+
if let returnClause = funcDecl.signature.returnClause {
73+
if let genericResultType {
74+
// we need to type-erase the signature, because on JVM level generics are erased and we'd otherwise
75+
// form a signature with the "concrete" type, which would not match the real byte-code level signature
76+
// of the method we're trying to call -- which would result in a MethodNotFound exception.
77+
", resultType: /*type-erased:\(genericResultType)*/JavaObject?.self"
78+
} else {
79+
", resultType: \(returnClause.type.typeReferenceString).self"
80+
}
81+
} else {
82+
""
83+
}
84+
6285
let parametersAsArgs: String
6386
if paramNames.isEmpty {
6487
parametersAsArgs = ""
@@ -70,8 +93,25 @@ extension JavaMethodMacro: BodyMacro {
7093
funcDecl.signature.effectSpecifiers?.throwsClause != nil
7194
? "try" : "try!"
7295

96+
let resultSyntax: CodeBlockItemSyntax =
97+
"\(raw: tryKeyword) dynamicJava\(raw: isStatic ? "Static" : "")MethodCall(methodName: \(literal: funcName)\(raw: parametersAsArgs)\(raw: resultType))"
98+
99+
if let genericResultType {
100+
return [
101+
"""
102+
/* convert erased return value to \(raw: genericResultType) */
103+
if let result$ = \(resultSyntax) {
104+
return \(raw: genericResultType)(javaThis: result$.javaThis, environment: try! JavaVirtualMachine.shared().environment())
105+
} else {
106+
return nil
107+
}
108+
"""
109+
]
110+
}
111+
112+
// no return type conversions
73113
return [
74-
"return \(raw: tryKeyword) dynamicJava\(raw: isStatic ? "Static" : "")MethodCall(methodName: \(literal: funcName)\(raw: parametersAsArgs)\(raw: resultType))"
114+
"return \(resultSyntax)"
75115
]
76116
}
77117

Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,5 +296,34 @@ class JavaKitMacroTests: XCTestCase {
296296
macros: Self.javaKitMacros
297297
)
298298
}
299+
300+
func testJavaOptionalGenericGet() throws {
301+
assertMacroExpansion("""
302+
@JavaClass("java.lang.Optional")
303+
open class JavaOptional<T: AnyJavaObject>: JavaObject {
304+
@JavaMethod(genericResult: "T")
305+
open func get() -> T!
306+
}
307+
""",
308+
expandedSource: """
309+
310+
open class JavaOptional<T: AnyJavaObject>: JavaObject {
311+
open func get() -> T! {
312+
return try! dynamicJavaMethodCall(methodName: "get", resultType: /*type-erased:T*/ JavaObject?.self)
313+
}
314+
315+
/// The full Java class name for this Swift type.
316+
open override class var fullJavaClassName: String {
317+
"java.lang.Optional"
318+
}
319+
320+
public required init(javaHolder: JavaObjectHolder) {
321+
super.init(javaHolder: javaHolder)
322+
}
323+
}
324+
""",
325+
macros: Self.javaKitMacros
326+
)
327+
}
299328
}
300329

0 commit comments

Comments
 (0)