Skip to content

Commit 748a3c7

Browse files
committed
wrap-java: print failed-to-import methods and include errors in docc
This way users of a library still get a decl and can figure out when they need a method "why" it wasn't imported. Rather than just having no clue why a method is "missing". resolves #228
1 parent 23055cd commit 748a3c7

File tree

3 files changed

+147
-13
lines changed

3 files changed

+147
-13
lines changed

Samples/JavaProbablyPrime/Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import PackageDescription
77
let package = Package(
88
name: "JavaProbablyPrime",
99
platforms: [
10-
.macOS(.v10_15),
10+
.macOS(.v15),
1111
],
1212

1313
products: [
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
import JavaTypes
16+
17+
/// A type used to represent a Java type that failed to import during swift-java importing.
18+
/// This may be because the type was not known to swift-java, or because the Java type is not
19+
/// representable in Swift.
20+
///
21+
/// See comments on the imported declaration containing this type for further details.
22+
public struct SwiftJavaFailedImportType: JavaValue {
23+
public typealias JNIType = jobject?
24+
25+
public static var jvalueKeyPath: WritableKeyPath<jvalue, JNIType> { \.l }
26+
27+
public static var javaType: JavaType {
28+
.class(package: "java.lang", name: "Object")
29+
}
30+
31+
public init(fromJNI value: JNIType, in environment: JNIEnvironment) {
32+
fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!")
33+
}
34+
35+
public func getJNIValue(in environment: JNIEnvironment) -> JNIType {
36+
fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!")
37+
}
38+
39+
public static func jniMethodCall(in environment: JNIEnvironment) -> JNIMethodCall<JNIType> {
40+
fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!")
41+
}
42+
43+
public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet<JNIType> {
44+
fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!")
45+
}
46+
47+
public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet<JNIType> {
48+
fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!")
49+
}
50+
51+
public static func jniStaticMethodCall(in environment: JNIEnvironment) -> JNIStaticMethodCall<JNIType> {
52+
fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!")
53+
}
54+
55+
public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet<JNIType> {
56+
fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!")
57+
}
58+
59+
public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet<JNIType> {
60+
fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!")
61+
}
62+
63+
public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray {
64+
fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!")
65+
}
66+
67+
public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion<JNIType> {
68+
fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!")
69+
}
70+
71+
public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion<JNIType> {
72+
fatalError("\(Self.self) is a placeholder type that means a type failed to import, and cannot be used at runtime!")
73+
}
74+
75+
public static var jniPlaceholderValue: jstring? {
76+
nil
77+
}
78+
}

Sources/SwiftJavaLib/JavaClassTranslator.swift

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ extension JavaClassTranslator {
312312
// Render all of the instance methods in Swift.
313313
let instanceMethods = methods.methods.compactMap { method in
314314
do {
315-
return try renderMethod(method, implementedInSwift: false)
315+
return try renderMethod(method, implementedInSwift: false, renderFailureAsBestEffortUnavailableDecl: true)
316316
} catch {
317317
translator.logUntranslated("Unable to translate '\(javaClass.getName())' method '\(method.getName())': \(error)")
318318
return nil
@@ -461,7 +461,8 @@ extension JavaClassTranslator {
461461
do {
462462
return try renderMethod(
463463
method,
464-
implementedInSwift: true
464+
implementedInSwift: true,
465+
465466
)
466467
} catch {
467468
translator.logUntranslated("Unable to translate '\(javaClass.getName())' method '\(method.getName())': \(error)")
@@ -517,14 +518,20 @@ extension JavaClassTranslator {
517518
package func renderConstructor(
518519
_ javaConstructor: Constructor<some AnyJavaObject>
519520
) throws -> DeclSyntax {
520-
let parameters = try translateParameters(javaConstructor.getParameters()) + ["environment: JNIEnvironment? = nil"]
521+
var errors = TranslationErrorCollector(bestEffortRecover: false)
522+
523+
let parameters = try translateParameters(javaConstructor.getParameters(), translationErrors: &errors) + ["environment: JNIEnvironment? = nil"]
521524
let parametersStr = parameters.map { $0.description }.joined(separator: ", ")
522525
let throwsStr = javaConstructor.throwsCheckedException ? "throws" : ""
523526
let accessModifier = javaConstructor.isPublic ? "public " : ""
524527
let convenienceModifier = translateAsClass ? "convenience " : ""
525528
let nonoverrideAttribute = translateAsClass ? "@_nonoverride " : ""
526529
return """
530+
/**
531+
\(raw: errors.doccCommentsText)
532+
*/
527533
@JavaMethod
534+
\(raw: errors.unavailableDueToImportErrorsText)
528535
\(raw: nonoverrideAttribute)\(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr)
529536
"""
530537
}
@@ -534,10 +541,13 @@ extension JavaClassTranslator {
534541
_ javaMethod: Method,
535542
implementedInSwift: Bool,
536543
genericParameterClause: String = "",
537-
whereClause: String = ""
544+
whereClause: String = "",
545+
renderFailureAsBestEffortUnavailableDecl: Bool = false
538546
) throws -> DeclSyntax {
547+
var errors = TranslationErrorCollector(bestEffortRecover: renderFailureAsBestEffortUnavailableDecl)
548+
539549
// Map the parameters.
540-
let parameters = try translateParameters(javaMethod.getParameters())
550+
let parameters = try translateParameters(javaMethod.getParameters(), translationErrors: &errors)
541551

542552
let parametersStr = parameters.map { $0.description }.joined(separator: ", ")
543553

@@ -589,6 +599,8 @@ extension JavaClassTranslator {
589599

590600

591601
return """
602+
\(raw: errors.doccCommentsText)
603+
\(raw: errors.unavailableDueToImportErrorsText)
592604
\(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause)
593605
594606
\(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 {
597609
"""
598610
} else {
599611
return """
612+
\(raw: errors.doccCommentsText)
613+
\(raw: errors.unavailableDueToImportErrorsText)
600614
\(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause)
601615
"""
602616
}
@@ -700,21 +714,63 @@ extension JavaClassTranslator {
700714
}
701715

702716
// Translate a Java parameter list into Swift parameters.
703-
private func translateParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] {
704-
return try parameters.compactMap { javaParameter in
717+
private func translateParameters(_ parameters: [Parameter?], translationErrors: inout TranslationErrorCollector) throws -> [FunctionParameterSyntax] {
718+
return try parameters.compactMap { (javaParameter) -> (FunctionParameterSyntax?) in
705719
guard let javaParameter else { return nil }
706720

707-
let typeName = try translator.getSwiftTypeNameAsString(
708-
javaParameter.getParameterizedType()!,
709-
preferValueTypes: true,
710-
outerOptional: .optional
711-
)
721+
let typeName: String
722+
do {
723+
typeName = try translator.getSwiftTypeNameAsString(
724+
javaParameter.getParameterizedType()!,
725+
preferValueTypes: true,
726+
outerOptional: .optional
727+
)
728+
} catch {
729+
translationErrors.record("Failed to convert parameter '\(javaParameter.getName())' type '\(javaParameter.getParameterizedType()!) to Swift'")
730+
typeName = "SwiftJavaFailedImportType" // best-effort placeholder
731+
}
712732
let paramName = javaParameter.getName()
713733
return "_ \(raw: paramName): \(raw: typeName)"
714734
}
715735
}
716736
}
717737

738+
package struct TranslationErrorCollector {
739+
package let bestEffortRecover: Bool
740+
private var errors: [String] = []
741+
742+
package var doccCommentsText: String {
743+
guard errors.count > 0 else {
744+
return ""
745+
}
746+
747+
return """
748+
///
749+
/// ### Swift-Java import errors
750+
///
751+
/// * \(errors.joined(separator: "\n /// * "))
752+
"""
753+
}
754+
755+
package var unavailableDueToImportErrorsText: String {
756+
guard errors.count > 0 else {
757+
return ""
758+
}
759+
760+
return """
761+
@available(*, unavailable, message: "swift-java was unable to import this method. See doc comments for import error details.")
762+
"""
763+
}
764+
765+
mutating func record(_ errorMessage: String) {
766+
errors.append(errorMessage)
767+
}
768+
769+
package init(bestEffortRecover: Bool) {
770+
self.bestEffortRecover = bestEffortRecover
771+
}
772+
}
773+
718774
/// Helper struct that collects methods, removing any that have been overridden
719775
/// by a covariant method.
720776
struct MethodCollector {

0 commit comments

Comments
 (0)