Skip to content

Java2Swift: Use implicitly-unwrapped optionals for field types and method results #124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
10 changes: 5 additions & 5 deletions Sources/Java2SwiftLib/JavaClassTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ struct JavaClassTranslator {
}

do {
let typeName = try translator.getSwiftTypeNameAsString(javaType, outerOptional: false)
let typeName = try translator.getSwiftTypeNameAsString(javaType, outerOptional: .nonoptional)
return "\(typeName)"
} catch {
translator.logUntranslated("Unable to translate '\(fullName)' interface '\(javaType.getTypeName())': \(error)")
Expand Down Expand Up @@ -459,7 +459,7 @@ extension JavaClassTranslator {

// Map the result type.
let resultTypeStr: String
let resultType = try translator.getSwiftTypeNameAsString(javaMethod.getGenericReturnType()!, outerOptional: true)
let resultType = try translator.getSwiftTypeNameAsString(javaMethod.getGenericReturnType()!, outerOptional: .implicitlyUnwrappedOptional)
if resultType != "Void" {
resultTypeStr = " -> \(resultType)"
} else {
Expand All @@ -480,7 +480,7 @@ extension JavaClassTranslator {
/// Render a single Java field into the corresponding Swift property, or
/// throw an error if that is not possible for any reason.
package func renderField(_ javaField: Field) throws -> DeclSyntax {
let typeName = try translator.getSwiftTypeNameAsString(javaField.getGenericType()!, outerOptional: true)
let typeName = try translator.getSwiftTypeNameAsString(javaField.getGenericType()!, outerOptional: .implicitlyUnwrappedOptional)
let fieldAttribute: AttributeSyntax = javaField.isStatic ? "@JavaStaticField" : "@JavaField";
let swiftFieldName = javaField.getName().escapedSwiftName
return """
Expand All @@ -501,7 +501,7 @@ extension JavaClassTranslator {
"""

let mappingSyntax: DeclSyntax = """
public var enumValue: \(raw: name)? {
public var enumValue: \(raw: name)! {
let classObj = self.javaClass
\(raw: enumConstants.map {
// The equals method takes a java object, so we need to cast it here
Expand Down Expand Up @@ -547,7 +547,7 @@ extension JavaClassTranslator {
return try parameters.compactMap { javaParameter in
guard let javaParameter else { return nil }

let typeName = try translator.getSwiftTypeNameAsString(javaParameter.getParameterizedType()!, outerOptional: true)
let typeName = try translator.getSwiftTypeNameAsString(javaParameter.getParameterizedType()!, outerOptional: .optional)
let paramName = javaParameter.getName()
return "_ \(raw: paramName): \(raw: typeName)"
}
Expand Down
27 changes: 17 additions & 10 deletions Sources/Java2SwiftLib/JavaTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ extension JavaTranslator {
// MARK: Type translation
extension JavaTranslator {
/// Turn a Java type into a string.
func getSwiftTypeNameAsString(_ javaType: Type, outerOptional: Bool) throws -> String {
func getSwiftTypeNameAsString(_ javaType: Type, outerOptional: OptionalKind) throws -> String {
// Replace type variables with their bounds.
if let typeVariable = javaType.as(TypeVariable<GenericDeclaration>.self),
typeVariable.getBounds().count == 1,
Expand All @@ -124,7 +124,7 @@ extension JavaTranslator {

// Handle array types by recursing into the component type.
if let arrayType = javaType.as(GenericArrayType.self) {
let elementType = try getSwiftTypeNameAsString(arrayType.getGenericComponentType()!, outerOptional: true)
let elementType = try getSwiftTypeNameAsString(arrayType.getGenericComponentType()!, outerOptional: .optional)
return "[\(elementType)]"
}

Expand All @@ -135,21 +135,21 @@ extension JavaTranslator {
{
var rawSwiftType = try getSwiftTypeNameAsString(rawJavaType, outerOptional: outerOptional)

let makeOptional: Bool
if rawSwiftType.last == "?" {
makeOptional = true
let optionalSuffix: String
if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" {
optionalSuffix = "\(lastChar)"
rawSwiftType.removeLast()
} else {
makeOptional = false
optionalSuffix = ""
}

let typeArguments = try parameterizedType.getActualTypeArguments().compactMap { typeArg in
try typeArg.map { typeArg in
try getSwiftTypeNameAsString(typeArg, outerOptional: false)
try getSwiftTypeNameAsString(typeArg, outerOptional: .nonoptional)
}
}

return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(makeOptional ? "?" : "")"
return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)"
}

// Handle direct references to Java classes.
Expand All @@ -159,8 +159,15 @@ extension JavaTranslator {

let (swiftName, isOptional) = try getSwiftTypeName(javaClass)
var resultString = swiftName
if isOptional && outerOptional {
resultString += "?"
if isOptional {
switch outerOptional {
case .implicitlyUnwrappedOptional:
resultString += "!"
case .optional:
resultString += "?"
case .nonoptional:
break
}
}
return resultString
}
Expand Down
25 changes: 25 additions & 0 deletions Sources/Java2SwiftLib/OptionalKind.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// Describes the kind of optional type to use.
enum OptionalKind {
/// The value is nonoptional.
case nonoptional

/// The value is optional.
case optional

/// The value uses an implicitly-unwrapped optional.
case implicitlyUnwrappedOptional
}
2 changes: 1 addition & 1 deletion Sources/JavaKitMacros/JavaFieldMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ extension JavaFieldMacro: AccessorMacro {
guard let varDecl = declaration.as(VariableDeclSyntax.self),
let binding = varDecl.bindings.first,
let fieldNameAsWritten = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.trimmed.text,
let fieldType = binding.typeAnnotation?.type.trimmed,
let fieldType = binding.typeAnnotation?.type.typeReference,
binding.accessorBlock == nil
else {
return []
Expand Down
24 changes: 23 additions & 1 deletion Sources/JavaKitMacros/JavaMethodMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ extension JavaMethodMacro: BodyMacro {
let params = funcDecl.signature.parameterClause.parameters
let resultType: String =
funcDecl.signature.returnClause.map { result in
", resultType: \(result.type.trimmedDescription).self"
", resultType: \(result.type.typeReferenceString).self"
} ?? ""
let paramNames = params.map { param in param.parameterName?.text ?? "" }.joined(separator: ", ")

Expand Down Expand Up @@ -126,3 +126,25 @@ extension FunctionParameterListSyntax {
return firstIndex { $0.parameterName?.text == name }
}
}

extension TypeSyntaxProtocol {
/// Produce a reference to the given type syntax node with any adjustments
/// needed to pretty-print it back into source.
var typeReference: TypeSyntax {
if let iuoType = self.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) {
return TypeSyntax(
OptionalTypeSyntax(
wrappedType: iuoType.wrappedType.trimmed
)
)
}

return TypeSyntax(trimmed)
}

/// Produce a reference to the given type syntax node with any adjustments
/// needed to pretty-print it back into source, as a string.
var typeReferenceString: String {
typeReference.description
}
}
22 changes: 11 additions & 11 deletions Tests/Java2SwiftTests/Java2SwiftTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class Java2SwiftTests: XCTestCase {
""",
"""
@JavaStaticMethod
public func forName<T: AnyJavaObject>(_ arg0: JavaString) throws -> MyJavaClass<JavaObject>? where ObjectType == MyJavaClass<T>
public func forName<T: AnyJavaObject>(_ arg0: JavaString) throws -> MyJavaClass<JavaObject>! where ObjectType == MyJavaClass<T>
""",
]
)
Expand All @@ -88,7 +88,7 @@ class Java2SwiftTests: XCTestCase {
"import JavaKit",
"enum MonthCases: Equatable",
"case APRIL",
"public var enumValue: MonthCases?",
"public var enumValue: MonthCases!",
"""
} else if self.equals(classObj.APRIL?.as(JavaObject.self)) {
return MonthCases.APRIL
Expand All @@ -105,7 +105,7 @@ class Java2SwiftTests: XCTestCase {
""",
"""
@JavaStaticField(isFinal: true)
public var APRIL: Month?
public var APRIL: Month!
"""
])
}
Expand All @@ -121,7 +121,7 @@ class Java2SwiftTests: XCTestCase {
expectedChunks: [
"""
@JavaMethod
public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList<JavaObject>?
public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList<JavaObject>!
"""
]
)
Expand All @@ -138,7 +138,7 @@ class Java2SwiftTests: XCTestCase {
expectedChunks: [
"""
@JavaMethod
public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList<JavaObject>?
public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList<JavaObject>!
"""
]
)
Expand All @@ -161,15 +161,15 @@ class Java2SwiftTests: XCTestCase {
"import JavaKit",
"""
@JavaMethod
public func redirectInput() -> ProcessBuilder.Redirect?
public func redirectInput() -> ProcessBuilder.Redirect!
""",
"""
extension ProcessBuilder {
@JavaClass("java.lang.ProcessBuilder$Redirect")
public struct Redirect {
""",
"""
public func redirectError() -> ProcessBuilder.Redirect?
public func redirectError() -> ProcessBuilder.Redirect!
""",
"""
extension ProcessBuilder.Redirect {
Expand All @@ -178,7 +178,7 @@ class Java2SwiftTests: XCTestCase {
""",
"""
@JavaMethod
public func type() -> ProcessBuilder.Redirect.`Type`?
public func type() -> ProcessBuilder.Redirect.`Type`!
""",
]
)
Expand All @@ -201,15 +201,15 @@ class Java2SwiftTests: XCTestCase {
"import JavaKit",
"""
@JavaMethod
public func redirectInput() -> ProcessBuilder.PBRedirect?
public func redirectInput() -> ProcessBuilder.PBRedirect!
""",
"""
extension ProcessBuilder {
@JavaClass("java.lang.ProcessBuilder$Redirect")
public struct PBRedirect {
""",
"""
public func redirectError() -> ProcessBuilder.PBRedirect?
public func redirectError() -> ProcessBuilder.PBRedirect!
""",
"""
extension ProcessBuilder.PBRedirect {
Expand All @@ -218,7 +218,7 @@ class Java2SwiftTests: XCTestCase {
""",
"""
@JavaMethod
public func type() -> ProcessBuilder.PBRedirect.JavaType?
public func type() -> ProcessBuilder.PBRedirect.JavaType!
"""
]
)
Expand Down
11 changes: 11 additions & 0 deletions Tests/JavaKitMacroTests/JavaClassMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ class JavaKitMacroTests: XCTestCase {
@JavaField
public var myField: Int64

@JavaField
public var objectField: JavaObject!

@JavaField(isFinal: true)
public var myFinalField: Int64
}
Expand Down Expand Up @@ -76,6 +79,14 @@ class JavaKitMacroTests: XCTestCase {
self[javaFieldName: "myField", fieldType: Int64.self] = newValue
}
}
public var objectField: JavaObject! {
get {
self[javaFieldName: "objectField", fieldType: JavaObject?.self]
}
nonmutating set {
self[javaFieldName: "objectField", fieldType: JavaObject?.self] = newValue
}
}
public var myFinalField: Int64 {
get {
self[javaFieldName: "myFinalField", fieldType: Int64.self]
Expand Down