diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index f8aecec7..59477f3f 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -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)") @@ -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 { @@ -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 """ @@ -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 @@ -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)" } diff --git a/Sources/Java2SwiftLib/JavaTranslator.swift b/Sources/Java2SwiftLib/JavaTranslator.swift index 89e417e6..9f5fa292 100644 --- a/Sources/Java2SwiftLib/JavaTranslator.swift +++ b/Sources/Java2SwiftLib/JavaTranslator.swift @@ -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.self), typeVariable.getBounds().count == 1, @@ -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)]" } @@ -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. @@ -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 } diff --git a/Sources/Java2SwiftLib/OptionalKind.swift b/Sources/Java2SwiftLib/OptionalKind.swift new file mode 100644 index 00000000..57951fa4 --- /dev/null +++ b/Sources/Java2SwiftLib/OptionalKind.swift @@ -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 +} diff --git a/Sources/JavaKitMacros/JavaFieldMacro.swift b/Sources/JavaKitMacros/JavaFieldMacro.swift index cb85d352..02280570 100644 --- a/Sources/JavaKitMacros/JavaFieldMacro.swift +++ b/Sources/JavaKitMacros/JavaFieldMacro.swift @@ -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 [] diff --git a/Sources/JavaKitMacros/JavaMethodMacro.swift b/Sources/JavaKitMacros/JavaMethodMacro.swift index f525c985..0a4be1f0 100644 --- a/Sources/JavaKitMacros/JavaMethodMacro.swift +++ b/Sources/JavaKitMacros/JavaMethodMacro.swift @@ -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: ", ") @@ -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 + } +} diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index e1224746..d1e19551 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -74,7 +74,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaStaticMethod - public func forName(_ arg0: JavaString) throws -> MyJavaClass? where ObjectType == MyJavaClass + public func forName(_ arg0: JavaString) throws -> MyJavaClass! where ObjectType == MyJavaClass """, ] ) @@ -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 @@ -105,7 +105,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaStaticField(isFinal: true) - public var APRIL: Month? + public var APRIL: Month! """ ]) } @@ -121,7 +121,7 @@ class Java2SwiftTests: XCTestCase { expectedChunks: [ """ @JavaMethod - public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList? + public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! """ ] ) @@ -138,7 +138,7 @@ class Java2SwiftTests: XCTestCase { expectedChunks: [ """ @JavaMethod - public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList? + public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! """ ] ) @@ -161,7 +161,7 @@ class Java2SwiftTests: XCTestCase { "import JavaKit", """ @JavaMethod - public func redirectInput() -> ProcessBuilder.Redirect? + public func redirectInput() -> ProcessBuilder.Redirect! """, """ extension ProcessBuilder { @@ -169,7 +169,7 @@ class Java2SwiftTests: XCTestCase { public struct Redirect { """, """ - public func redirectError() -> ProcessBuilder.Redirect? + public func redirectError() -> ProcessBuilder.Redirect! """, """ extension ProcessBuilder.Redirect { @@ -178,7 +178,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaMethod - public func type() -> ProcessBuilder.Redirect.`Type`? + public func type() -> ProcessBuilder.Redirect.`Type`! """, ] ) @@ -201,7 +201,7 @@ class Java2SwiftTests: XCTestCase { "import JavaKit", """ @JavaMethod - public func redirectInput() -> ProcessBuilder.PBRedirect? + public func redirectInput() -> ProcessBuilder.PBRedirect! """, """ extension ProcessBuilder { @@ -209,7 +209,7 @@ class Java2SwiftTests: XCTestCase { public struct PBRedirect { """, """ - public func redirectError() -> ProcessBuilder.PBRedirect? + public func redirectError() -> ProcessBuilder.PBRedirect! """, """ extension ProcessBuilder.PBRedirect { @@ -218,7 +218,7 @@ class Java2SwiftTests: XCTestCase { """, """ @JavaMethod - public func type() -> ProcessBuilder.PBRedirect.JavaType? + public func type() -> ProcessBuilder.PBRedirect.JavaType! """ ] ) diff --git a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift b/Tests/JavaKitMacroTests/JavaClassMacroTests.swift index e033bc0f..42ce532f 100644 --- a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift +++ b/Tests/JavaKitMacroTests/JavaClassMacroTests.swift @@ -42,6 +42,9 @@ class JavaKitMacroTests: XCTestCase { @JavaField public var myField: Int64 + @JavaField + public var objectField: JavaObject! + @JavaField(isFinal: true) public var myFinalField: Int64 } @@ -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]