diff --git a/Samples/JavaProbablyPrime/Package.swift b/Samples/JavaProbablyPrime/Package.swift index 4cc887f8..0f9d86d3 100644 --- a/Samples/JavaProbablyPrime/Package.swift +++ b/Samples/JavaProbablyPrime/Package.swift @@ -7,7 +7,7 @@ import PackageDescription let package = Package( name: "JavaProbablyPrime", platforms: [ - .macOS(.v10_15), + .macOS(.v15), ], products: [ diff --git a/Sources/JavaKit/BridgedValues/JavaValue+SwiftJavaFailedImportType.swift b/Sources/JavaKit/BridgedValues/JavaValue+SwiftJavaFailedImportType.swift new file mode 100644 index 00000000..bed92121 --- /dev/null +++ b/Sources/JavaKit/BridgedValues/JavaValue+SwiftJavaFailedImportType.swift @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +/// A type used to represent a Java type that failed to import during swift-java importing. +/// This may be because the type was not known to swift-java, or because the Java type is not +/// representable in Swift. +/// +/// See comments on the imported declaration containing this type for further details. +public struct SwiftJavaFailedImportType: JavaValue { + public typealias JNIType = jobject? + + public static var jvalueKeyPath: WritableKeyPath { \.l } + + public static var javaType: JavaType { + .class(package: "java.lang", name: "Object") + } + + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!") + } + + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { + fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!") + } + + public static func jniMethodCall(in environment: JNIEnvironment) -> JNIMethodCall { + fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!") + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!") + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!") + } + + public static func jniStaticMethodCall(in environment: JNIEnvironment) -> JNIStaticMethodCall { + fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!") + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!") + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!") + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!") + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!") + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!") + } + + public static var jniPlaceholderValue: jstring? { + nil + } +} diff --git a/Sources/SwiftJavaLib/JavaClassTranslator.swift b/Sources/SwiftJavaLib/JavaClassTranslator.swift index bfd1657f..73c8ad9f 100644 --- a/Sources/SwiftJavaLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaLib/JavaClassTranslator.swift @@ -312,7 +312,7 @@ extension JavaClassTranslator { // Render all of the instance methods in Swift. let instanceMethods = methods.methods.compactMap { method in do { - return try renderMethod(method, implementedInSwift: false) + return try renderMethod(method, implementedInSwift: false, renderFailureAsBestEffortUnavailableDecl: true) } catch { translator.logUntranslated("Unable to translate '\(javaClass.getName())' method '\(method.getName())': \(error)") return nil @@ -461,7 +461,8 @@ extension JavaClassTranslator { do { return try renderMethod( method, - implementedInSwift: true + implementedInSwift: true, + ) } catch { translator.logUntranslated("Unable to translate '\(javaClass.getName())' method '\(method.getName())': \(error)") @@ -517,14 +518,20 @@ extension JavaClassTranslator { package func renderConstructor( _ javaConstructor: Constructor ) throws -> DeclSyntax { - let parameters = try translateParameters(javaConstructor.getParameters()) + ["environment: JNIEnvironment? = nil"] + var errors = TranslationErrorCollector(bestEffortRecover: false) + + let parameters = try translateParameters(javaConstructor.getParameters(), translationErrors: &errors) + ["environment: JNIEnvironment? = nil"] let parametersStr = parameters.map { $0.description }.joined(separator: ", ") let throwsStr = javaConstructor.throwsCheckedException ? "throws" : "" let accessModifier = javaConstructor.isPublic ? "public " : "" let convenienceModifier = translateAsClass ? "convenience " : "" let nonoverrideAttribute = translateAsClass ? "@_nonoverride " : "" return """ + /** + \(raw: errors.doccCommentsText) + */ @JavaMethod + \(raw: errors.unavailableDueToImportErrorsText) \(raw: nonoverrideAttribute)\(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr) """ } @@ -534,10 +541,13 @@ extension JavaClassTranslator { _ javaMethod: Method, implementedInSwift: Bool, genericParameterClause: String = "", - whereClause: String = "" + whereClause: String = "", + renderFailureAsBestEffortUnavailableDecl: Bool = false ) throws -> DeclSyntax { + var errors = TranslationErrorCollector(bestEffortRecover: renderFailureAsBestEffortUnavailableDecl) + // Map the parameters. - let parameters = try translateParameters(javaMethod.getParameters()) + let parameters = try translateParameters(javaMethod.getParameters(), translationErrors: &errors) let parametersStr = parameters.map { $0.description }.joined(separator: ", ") @@ -589,6 +599,8 @@ extension JavaClassTranslator { return """ + \(raw: errors.doccCommentsText) + \(raw: errors.unavailableDueToImportErrorsText) \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) \(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)Optional\(raw: genericParameterClause)(\(raw: parameters.map(\.clause.description).joined(separator: ", ")))\(raw: throwsStr) -> \(raw: resultOptional)\(raw: whereClause) { @@ -597,6 +609,8 @@ extension JavaClassTranslator { """ } else { return """ + \(raw: errors.doccCommentsText) + \(raw: errors.unavailableDueToImportErrorsText) \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) """ } @@ -700,21 +714,63 @@ extension JavaClassTranslator { } // Translate a Java parameter list into Swift parameters. - private func translateParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { - return try parameters.compactMap { javaParameter in + private func translateParameters(_ parameters: [Parameter?], translationErrors: inout TranslationErrorCollector) throws -> [FunctionParameterSyntax] { + return try parameters.compactMap { (javaParameter) -> (FunctionParameterSyntax?) in guard let javaParameter else { return nil } - let typeName = try translator.getSwiftTypeNameAsString( - javaParameter.getParameterizedType()!, - preferValueTypes: true, - outerOptional: .optional - ) + let typeName: String + do { + typeName = try translator.getSwiftTypeNameAsString( + javaParameter.getParameterizedType()!, + preferValueTypes: true, + outerOptional: .optional + ) + } catch { + translationErrors.record("Failed to convert parameter '\(javaParameter.getName())' type '\(javaParameter.getParameterizedType()!) to Swift'") + typeName = "SwiftJavaFailedImportType" // best-effort placeholder + } let paramName = javaParameter.getName() return "_ \(raw: paramName): \(raw: typeName)" } } } +package struct TranslationErrorCollector { + package let bestEffortRecover: Bool + private var errors: [String] = [] + + package var doccCommentsText: String { + guard errors.count > 0 else { + return "" + } + + return """ + /// + /// ### Swift-Java import errors + /// + /// * \(errors.joined(separator: "\n /// * ")) + """ + } + + package var unavailableDueToImportErrorsText: String { + guard errors.count > 0 else { + return "" + } + + return """ + @available(*, unavailable, message: "swift-java was unable to import this method. See doc comments for import error details.") + """ + } + + mutating func record(_ errorMessage: String) { + errors.append(errorMessage) + } + + package init(bestEffortRecover: Bool) { + self.bestEffortRecover = bestEffortRecover + } +} + /// Helper struct that collects methods, removing any that have been overridden /// by a covariant method. struct MethodCollector {