Skip to content

Commit 34a18e3

Browse files
authored
Merge pull request #124 from DougGregor/javakit-implicitly-unwrapped-optionals
Java2Swift: Use implicitly-unwrapped optionals for field types and method results
2 parents 6d50dad + e71cfb1 commit 34a18e3

File tree

7 files changed

+93
-28
lines changed

7 files changed

+93
-28
lines changed

Sources/Java2SwiftLib/JavaClassTranslator.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ struct JavaClassTranslator {
131131
}
132132

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

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

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

550-
let typeName = try translator.getSwiftTypeNameAsString(javaParameter.getParameterizedType()!, outerOptional: true)
550+
let typeName = try translator.getSwiftTypeNameAsString(javaParameter.getParameterizedType()!, outerOptional: .optional)
551551
let paramName = javaParameter.getName()
552552
return "_ \(raw: paramName): \(raw: typeName)"
553553
}

Sources/Java2SwiftLib/JavaTranslator.swift

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ extension JavaTranslator {
104104
// MARK: Type translation
105105
extension JavaTranslator {
106106
/// Turn a Java type into a string.
107-
func getSwiftTypeNameAsString(_ javaType: Type, outerOptional: Bool) throws -> String {
107+
func getSwiftTypeNameAsString(_ javaType: Type, outerOptional: OptionalKind) throws -> String {
108108
// Replace type variables with their bounds.
109109
if let typeVariable = javaType.as(TypeVariable<GenericDeclaration>.self),
110110
typeVariable.getBounds().count == 1,
@@ -124,7 +124,7 @@ extension JavaTranslator {
124124

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

@@ -135,21 +135,21 @@ extension JavaTranslator {
135135
{
136136
var rawSwiftType = try getSwiftTypeNameAsString(rawJavaType, outerOptional: outerOptional)
137137

138-
let makeOptional: Bool
139-
if rawSwiftType.last == "?" {
140-
makeOptional = true
138+
let optionalSuffix: String
139+
if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" {
140+
optionalSuffix = "\(lastChar)"
141141
rawSwiftType.removeLast()
142142
} else {
143-
makeOptional = false
143+
optionalSuffix = ""
144144
}
145145

146146
let typeArguments = try parameterizedType.getActualTypeArguments().compactMap { typeArg in
147147
try typeArg.map { typeArg in
148-
try getSwiftTypeNameAsString(typeArg, outerOptional: false)
148+
try getSwiftTypeNameAsString(typeArg, outerOptional: .nonoptional)
149149
}
150150
}
151151

152-
return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(makeOptional ? "?" : "")"
152+
return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)"
153153
}
154154

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

160160
let (swiftName, isOptional) = try getSwiftTypeName(javaClass)
161161
var resultString = swiftName
162-
if isOptional && outerOptional {
163-
resultString += "?"
162+
if isOptional {
163+
switch outerOptional {
164+
case .implicitlyUnwrappedOptional:
165+
resultString += "!"
166+
case .optional:
167+
resultString += "?"
168+
case .nonoptional:
169+
break
170+
}
164171
}
165172
return resultString
166173
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
/// Describes the kind of optional type to use.
16+
enum OptionalKind {
17+
/// The value is nonoptional.
18+
case nonoptional
19+
20+
/// The value is optional.
21+
case optional
22+
23+
/// The value uses an implicitly-unwrapped optional.
24+
case implicitlyUnwrappedOptional
25+
}

Sources/JavaKitMacros/JavaFieldMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ extension JavaFieldMacro: AccessorMacro {
2727
guard let varDecl = declaration.as(VariableDeclSyntax.self),
2828
let binding = varDecl.bindings.first,
2929
let fieldNameAsWritten = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.trimmed.text,
30-
let fieldType = binding.typeAnnotation?.type.trimmed,
30+
let fieldType = binding.typeAnnotation?.type.typeReference,
3131
binding.accessorBlock == nil
3232
else {
3333
return []

Sources/JavaKitMacros/JavaMethodMacro.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ extension JavaMethodMacro: BodyMacro {
5555
let params = funcDecl.signature.parameterClause.parameters
5656
let resultType: String =
5757
funcDecl.signature.returnClause.map { result in
58-
", resultType: \(result.type.trimmedDescription).self"
58+
", resultType: \(result.type.typeReferenceString).self"
5959
} ?? ""
6060
let paramNames = params.map { param in param.parameterName?.text ?? "" }.joined(separator: ", ")
6161

@@ -126,3 +126,25 @@ extension FunctionParameterListSyntax {
126126
return firstIndex { $0.parameterName?.text == name }
127127
}
128128
}
129+
130+
extension TypeSyntaxProtocol {
131+
/// Produce a reference to the given type syntax node with any adjustments
132+
/// needed to pretty-print it back into source.
133+
var typeReference: TypeSyntax {
134+
if let iuoType = self.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) {
135+
return TypeSyntax(
136+
OptionalTypeSyntax(
137+
wrappedType: iuoType.wrappedType.trimmed
138+
)
139+
)
140+
}
141+
142+
return TypeSyntax(trimmed)
143+
}
144+
145+
/// Produce a reference to the given type syntax node with any adjustments
146+
/// needed to pretty-print it back into source, as a string.
147+
var typeReferenceString: String {
148+
typeReference.description
149+
}
150+
}

Tests/Java2SwiftTests/Java2SwiftTests.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class Java2SwiftTests: XCTestCase {
7474
""",
7575
"""
7676
@JavaStaticMethod
77-
public func forName<T: AnyJavaObject>(_ arg0: JavaString) throws -> MyJavaClass<JavaObject>? where ObjectType == MyJavaClass<T>
77+
public func forName<T: AnyJavaObject>(_ arg0: JavaString) throws -> MyJavaClass<JavaObject>! where ObjectType == MyJavaClass<T>
7878
""",
7979
]
8080
)
@@ -88,7 +88,7 @@ class Java2SwiftTests: XCTestCase {
8888
"import JavaKit",
8989
"enum MonthCases: Equatable",
9090
"case APRIL",
91-
"public var enumValue: MonthCases?",
91+
"public var enumValue: MonthCases!",
9292
"""
9393
} else if self.equals(classObj.APRIL?.as(JavaObject.self)) {
9494
return MonthCases.APRIL
@@ -105,7 +105,7 @@ class Java2SwiftTests: XCTestCase {
105105
""",
106106
"""
107107
@JavaStaticField(isFinal: true)
108-
public var APRIL: Month?
108+
public var APRIL: Month!
109109
"""
110110
])
111111
}
@@ -121,7 +121,7 @@ class Java2SwiftTests: XCTestCase {
121121
expectedChunks: [
122122
"""
123123
@JavaMethod
124-
public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList<JavaObject>?
124+
public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList<JavaObject>!
125125
"""
126126
]
127127
)
@@ -138,7 +138,7 @@ class Java2SwiftTests: XCTestCase {
138138
expectedChunks: [
139139
"""
140140
@JavaMethod
141-
public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList<JavaObject>?
141+
public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList<JavaObject>!
142142
"""
143143
]
144144
)
@@ -161,15 +161,15 @@ class Java2SwiftTests: XCTestCase {
161161
"import JavaKit",
162162
"""
163163
@JavaMethod
164-
public func redirectInput() -> ProcessBuilder.Redirect?
164+
public func redirectInput() -> ProcessBuilder.Redirect!
165165
""",
166166
"""
167167
extension ProcessBuilder {
168168
@JavaClass("java.lang.ProcessBuilder$Redirect")
169169
public struct Redirect {
170170
""",
171171
"""
172-
public func redirectError() -> ProcessBuilder.Redirect?
172+
public func redirectError() -> ProcessBuilder.Redirect!
173173
""",
174174
"""
175175
extension ProcessBuilder.Redirect {
@@ -178,7 +178,7 @@ class Java2SwiftTests: XCTestCase {
178178
""",
179179
"""
180180
@JavaMethod
181-
public func type() -> ProcessBuilder.Redirect.`Type`?
181+
public func type() -> ProcessBuilder.Redirect.`Type`!
182182
""",
183183
]
184184
)
@@ -201,15 +201,15 @@ class Java2SwiftTests: XCTestCase {
201201
"import JavaKit",
202202
"""
203203
@JavaMethod
204-
public func redirectInput() -> ProcessBuilder.PBRedirect?
204+
public func redirectInput() -> ProcessBuilder.PBRedirect!
205205
""",
206206
"""
207207
extension ProcessBuilder {
208208
@JavaClass("java.lang.ProcessBuilder$Redirect")
209209
public struct PBRedirect {
210210
""",
211211
"""
212-
public func redirectError() -> ProcessBuilder.PBRedirect?
212+
public func redirectError() -> ProcessBuilder.PBRedirect!
213213
""",
214214
"""
215215
extension ProcessBuilder.PBRedirect {
@@ -218,7 +218,7 @@ class Java2SwiftTests: XCTestCase {
218218
""",
219219
"""
220220
@JavaMethod
221-
public func type() -> ProcessBuilder.PBRedirect.JavaType?
221+
public func type() -> ProcessBuilder.PBRedirect.JavaType!
222222
"""
223223
]
224224
)

Tests/JavaKitMacroTests/JavaClassMacroTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ class JavaKitMacroTests: XCTestCase {
4242
@JavaField
4343
public var myField: Int64
4444
45+
@JavaField
46+
public var objectField: JavaObject!
47+
4548
@JavaField(isFinal: true)
4649
public var myFinalField: Int64
4750
}
@@ -76,6 +79,14 @@ class JavaKitMacroTests: XCTestCase {
7679
self[javaFieldName: "myField", fieldType: Int64.self] = newValue
7780
}
7881
}
82+
public var objectField: JavaObject! {
83+
get {
84+
self[javaFieldName: "objectField", fieldType: JavaObject?.self]
85+
}
86+
nonmutating set {
87+
self[javaFieldName: "objectField", fieldType: JavaObject?.self] = newValue
88+
}
89+
}
7990
public var myFinalField: Int64 {
8091
get {
8192
self[javaFieldName: "myFinalField", fieldType: Int64.self]

0 commit comments

Comments
 (0)