Skip to content
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
4 changes: 2 additions & 2 deletions Sources/SwiftJava/Optional+JavaOptional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

extension Optional where Wrapped: AnyJavaObject {
public func toJavaOptional() -> JavaOptional<Wrapped> {
try! JavaClass<JavaOptional<Wrapped>>().ofNullable(self?.as(JavaObject.self)).as(JavaOptional<Wrapped>.self)!
try! JavaClass<JavaOptional<Wrapped>>().ofNullable(self)
}

public init(javaOptional: JavaOptional<Wrapped>?) {
Expand All @@ -29,7 +29,7 @@ extension Optional where Wrapped: AnyJavaObject {
extension Optional where Wrapped == String {
public func toJavaOptional() -> JavaOptional<JavaString> {
if let self {
return try! JavaClass<JavaOptional<JavaString>>().of(JavaString(self).as(JavaObject.self)).as(JavaOptional<JavaString>.self)!
return try! JavaClass<JavaOptional<JavaString>>().of(JavaString(self))
} else {
return try! JavaClass<JavaOptional<JavaString>>().empty().as(JavaOptional<JavaString>.self)!
}
Expand Down
92 changes: 78 additions & 14 deletions Sources/SwiftJava/generated/JavaOptional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,115 @@ import SwiftJavaJNICore

@JavaClass("java.util.Optional")
open class JavaOptional<T: AnyJavaObject>: JavaObject {
@JavaMethod(typeErasedResult: "T")
/// Java method `get`.
///
/// ### Java method signature
/// ```java
/// public T java.util.Optional.get()
/// ```
@JavaMethod(typeErasedResult: "T!")
open func get() -> T!

/// Java method `equals`.
///
/// ### Java method signature
/// ```java
/// public boolean java.util.Optional.equals(java.lang.Object)
/// ```
@JavaMethod
open override func equals(_ arg0: JavaObject?) -> Bool

/// Java method `toString`.
///
/// ### Java method signature
/// ```java
/// public java.lang.String java.util.Optional.toString()
/// ```
@JavaMethod
open override func toString() -> String

/// Java method `hashCode`.
///
/// ### Java method signature
/// ```java
/// public int java.util.Optional.hashCode()
/// ```
@JavaMethod
open override func hashCode() -> Int32

/// Java method `isEmpty`.
///
/// ### Java method signature
/// ```java
/// public boolean java.util.Optional.isEmpty()
/// ```
@JavaMethod
open func isEmpty() -> Bool

@JavaMethod
open func isPresent() -> Bool
/// Java method `orElse`.
///
/// ### Java method signature
/// ```java
/// public T java.util.Optional.orElse(T)
/// ```
@JavaMethod(typeErasedResult: "T!")
open func orElse(_ arg0: T?) -> T!

/// Java method `isPresent`.
///
/// ### Java method signature
/// ```java
/// public boolean java.util.Optional.isPresent()
/// ```
@JavaMethod
open func orElse(_ arg0: JavaObject?) -> JavaObject!
open func isPresent() -> Bool

@JavaMethod
open func orElseThrow() -> JavaObject!
/// Java method `orElseThrow`.
///
/// ### Java method signature
/// ```java
/// public T java.util.Optional.orElseThrow()
/// ```
@JavaMethod(typeErasedResult: "T!")
open func orElseThrow() -> T!
}
extension JavaClass {
/// Java method `of`.
///
/// ### Java method signature
/// ```java
/// public static <T> java.util.Optional<T> java.util.Optional.of(T)
/// ```
@JavaStaticMethod
public func of<T: AnyJavaObject>(_ arg0: JavaObject?) -> JavaOptional<JavaObject>! where ObjectType == JavaOptional<T>
public func of<T: AnyJavaObject>(_ arg0: T?) -> JavaOptional<T>! where ObjectType == JavaOptional<T>

public func ofOptional<T: AnyJavaObject>(_ arg0: JavaObject?) -> JavaObject? where ObjectType == JavaOptional<T> {
public func ofOptional<T: AnyJavaObject>(_ arg0: T?) -> T? where ObjectType == JavaOptional<T> {
Optional(javaOptional: of(arg0))
}

/// Java method `empty`.
///
/// ### Java method signature
/// ```java
/// public static <T> java.util.Optional<T> java.util.Optional.empty()
/// ```
@JavaStaticMethod
public func empty<T: AnyJavaObject>() -> JavaOptional<JavaObject>! where ObjectType == JavaOptional<T>
public func empty<T: AnyJavaObject>() -> JavaOptional<T>! where ObjectType == JavaOptional<T>

public func emptyOptional<T: AnyJavaObject>() -> JavaObject? where ObjectType == JavaOptional<T> {
public func emptyOptional<T: AnyJavaObject>() -> T? where ObjectType == JavaOptional<T> {
Optional(javaOptional: empty())
}

/// Java method `ofNullable`.
///
/// ### Java method signature
/// ```java
/// public static <T> java.util.Optional<T> java.util.Optional.ofNullable(T)
/// ```
@JavaStaticMethod
public func ofNullable<T: AnyJavaObject>(_ arg0: JavaObject?) -> JavaOptional<JavaObject>!
where ObjectType == JavaOptional<T>
public func ofNullable<T: AnyJavaObject>(_ arg0: T?) -> JavaOptional<T>! where ObjectType == JavaOptional<T>

public func ofNullableOptional<T: AnyJavaObject>(_ arg0: JavaObject?) -> JavaObject?
where ObjectType == JavaOptional<T> {
public func ofNullableOptional<T: AnyJavaObject>(_ arg0: T?) -> T? where ObjectType == JavaOptional<T> {
Optional(javaOptional: ofNullable(arg0))
}
}
22 changes: 17 additions & 5 deletions Sources/SwiftJava/generated/List.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,23 @@ public struct List<E: AnyJavaObject> {
@JavaMethod
public func isEmpty() -> Bool

@JavaMethod
public func add(_ arg0: JavaObject?) -> Bool

@JavaMethod
public func add(_ arg0: Int32, _ arg1: JavaObject?)
/// Java method `add`.
///
/// ### Java method signature
/// ```java
/// public abstract void java.util.List.add(int,E)
/// ```
@JavaMethod
public func add(_ arg0: Int32, _ arg1: E?)

/// Java method `add`.
///
/// ### Java method signature
/// ```java
/// public abstract boolean java.util.List.add(E)
/// ```
@JavaMethod
public func add(_ arg0: E?) -> Bool

@JavaMethod
public func subList(_ arg0: Int32, _ arg1: Int32) -> List<JavaObject>!
Expand Down
95 changes: 87 additions & 8 deletions Sources/SwiftJavaMacros/JavaMethodMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ extension JavaMethodMacro: BodyMacro {
fatalError("not a function: \(declaration)")
}

var resultStatements: [CodeBlockItemSyntax] = []

let funcName =
if case .argumentList(let arguments) = node.arguments,
let argument = arguments.first,
Expand All @@ -65,7 +67,28 @@ extension JavaMethodMacro: BodyMacro {

let isStatic = node.attributeName.trimmedDescription == "JavaStaticMethod"
let params = funcDecl.signature.parameterClause.parameters
let paramNames = params.map { param in param.parameterName?.text ?? "" }.joined(separator: ", ")

var paramNames: [String] = []
for param in params {
guard let name = param.parameterName else {
throw MacroErrors.parameterMustHaveName(method: funcName, paramSyntax: param.trimmedDescription)
}
if isJNIGenericParameter(param.type, funcDecl: funcDecl, in: context) {
let erasedName: TokenSyntax = "\(name)$erased"
if param.type.optionalUnwrappedType() != nil {
resultStatements.append(
"let \(erasedName) = \(name).map { JavaObject(javaHolder: $0.javaHolder) }"
)
} else {
resultStatements.append(
"let \(erasedName) = JavaObject(javaHolder: \(name).javaHolder)"
)
}
paramNames.append(erasedName.text)
} else {
paramNames.append(name.text)
}
}

let genericResultType: String? =
if case let .argumentList(arguments) = node.arguments,
Expand Down Expand Up @@ -106,7 +129,7 @@ extension JavaMethodMacro: BodyMacro {
if paramNames.isEmpty {
parametersAsArgs = ""
} else {
parametersAsArgs = ", arguments: \(paramNames)"
parametersAsArgs = ", arguments: \(paramNames.joined(separator: ", "))"
}

let canRethrowError = funcDecl.signature.effectSpecifiers?.throwsClause != nil
Expand Down Expand Up @@ -137,23 +160,60 @@ extension JavaMethodMacro: BodyMacro {
"""

if let genericResultType {
return [
resultStatements.append(
"""
/* convert erased return value to \(raw: genericResultType) */
let result$ = \(resultSyntax)
"""
)
resultStatements.append(
"""
if let result$ {
return \(raw: genericResultType)(javaThis: result$.javaThis, environment: try! JavaVirtualMachine.shared().environment())
} else {
return nil
}
"""
]
)
} else {
// no return type conversions
resultStatements.append("return \(resultSyntax)")
}

// no return type conversions
return [
"return \(resultSyntax)"
]
return resultStatements
}

/// Determines whether an argument is generic in heuristic way.
/// Since Optional does not appear in JNI signatures, it is removed before checking.
/// FIXME: It might be preferable to explicitly specify the type from JavaClass, similar to `typeErasedResult`.
private static func isJNIGenericParameter(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's document that this is a heuristic please, since it may get things wrong. It's good to mark this as source of potential mixups

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upon further review, I noticed that typeErasedResult references JavaClass information, which I believe is more preferable. I've added a comment regarding this point.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, thank you

_ type: TypeSyntax,
funcDecl: FunctionDeclSyntax,
in context: some MacroExpansionContext
) -> Bool {
let baseType = type.optionalUnwrappedType() ?? type
guard let identifier = baseType.as(IdentifierTypeSyntax.self) else {
return false
}
let typeName = identifier.name.text

if let genericParams = funcDecl.genericParameterClause?.parameters {
if genericParams.contains(where: { $0.name.text == typeName }) {
return true
}
}

for contextNode in context.lexicalContext {
if let decl = contextNode.asProtocol(WithGenericParametersSyntax.self) {
if decl.genericParameterClause?.parameters.contains(where: {
$0.name.text == typeName
}) == true {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to == true

Suggested change
}) == true {
}) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

genericParameterClause? produces optional bool here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah i see

return true
}
}
}

return false
}

/// Bridge an initializer into a call to Java.
Expand Down Expand Up @@ -238,4 +298,23 @@ extension TypeSyntaxProtocol {
var typeReferenceString: String {
typeReference.description
}

func optionalUnwrappedType() -> TypeSyntax? {
if let optionalType = self.as(OptionalTypeSyntax.self) {
return optionalType.wrappedType
}

if let implicitlyUnwrappedType = self.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) {
return implicitlyUnwrappedType.wrappedType
}

if let identifierType = self.as(IdentifierTypeSyntax.self),
identifierType.name.text == "Optional",
let genericArgumentClause = identifierType.genericArgumentClause
{
return genericArgumentClause.arguments.first?.argument.as(TypeSyntax.self)
}

return nil
}
}
1 change: 1 addition & 0 deletions Sources/SwiftJavaMacros/MacroErrors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ enum MacroErrors: Error {
case methodNotOnFunction
case missingEnvironment
case macroOutOfContext(String)
case parameterMustHaveName(method: String, paramSyntax: String)
}
Loading
Loading