From 3cab67b84cf38d79c73dcd6c2deb656849caa26b Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 12 Jul 2025 12:49:02 +0200 Subject: [PATCH 1/5] add support for variables --- Sources/JExtractSwiftLib/ImportedDecls.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 4b8a478a..715776c0 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -91,7 +91,7 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } return false } - + /// If this function/method is member of a class/struct/protocol, /// this will contain that declaration's imported name. /// From 1d771bd9a8964a4d1d542c8ec02180cd888d7bb6 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 12 Jul 2025 13:14:51 +0200 Subject: [PATCH 2/5] prefix boolean variables with `is` to match Java conventions --- .../FFM/FFMSwift2JavaGenerator+JavaTranslation.swift | 1 + Sources/JExtractSwiftLib/ImportedDecls.swift | 2 +- .../JNI/JNISwift2JavaGenerator+JavaTranslation.swift | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index e548db5c..595cba7f 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -126,6 +126,7 @@ extension FFMSwift2JavaGenerator { let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. + let returnsBoolean = decl.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool let javaName = switch decl.apiKind { case .getter: decl.javaGetterName case .setter: decl.javaSetterName diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 715776c0..4b8a478a 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -91,7 +91,7 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } return false } - + /// If this function/method is member of a class/struct/protocol, /// this will contain that declaration's imported name. /// diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 179991df..ed8dba37 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -38,6 +38,7 @@ extension JNISwift2JavaGenerator { let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName // Name. + let returnsBoolean = translatedFunctionSignature.resultType == .boolean let javaName = switch decl.apiKind { case .getter: decl.javaGetterName case .setter: decl.javaSetterName From 5211bee5fb222e704ffb16d56e209b8a664317bd Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 12 Jul 2025 14:06:22 +0200 Subject: [PATCH 3/5] add variable tests --- .../FFM/FFMSwift2JavaGenerator+JavaTranslation.swift | 1 - .../JNI/JNISwift2JavaGenerator+JavaTranslation.swift | 1 - 2 files changed, 2 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 595cba7f..e548db5c 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -126,7 +126,6 @@ extension FFMSwift2JavaGenerator { let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. - let returnsBoolean = decl.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool let javaName = switch decl.apiKind { case .getter: decl.javaGetterName case .setter: decl.javaSetterName diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index ed8dba37..179991df 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -38,7 +38,6 @@ extension JNISwift2JavaGenerator { let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName // Name. - let returnsBoolean = translatedFunctionSignature.resultType == .boolean let javaName = switch decl.apiKind { case .getter: decl.javaGetterName case .setter: decl.javaSetterName From 3fdb12737e27bfe9573a10c3d31fcfc3b8cf4907 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 13 Jul 2025 08:38:40 +0200 Subject: [PATCH 4/5] throw errors instead of crashing on unsupported type --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 1 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 14 +++++-- ...ISwift2JavaGenerator+JavaTranslation.swift | 38 ++++++++++++------- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 17 +++++++-- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 386a7227..fd0ce488 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -16,6 +16,7 @@ public class MySwiftClass { let x: Int64 let y: Int64 + public let byte: UInt8 = 0 public let constant: Int64 = 100 public var mutable: Int64 = 0 public var product: Int64 { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index ec0fa9c4..1e73e00d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -176,6 +176,11 @@ extension JNISwift2JavaGenerator { } private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + guard let translatedDecl = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } + if decl.isStatic || decl.isInitializer || !decl.hasParent { printStaticFunctionBinding(&printer, decl) } else { @@ -196,7 +201,7 @@ extension JNISwift2JavaGenerator { /// and passes it down to another native function along with the arguments /// to call the Swift implementation. private func printMemberMethodBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = translatedDecl(for: decl) + let translatedDecl = translatedDecl(for: decl)! printDeclDocumentation(&printer, decl) printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in @@ -220,7 +225,10 @@ extension JNISwift2JavaGenerator { } private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) { - let translatedDecl = translatedDecl(for: decl) + guard let translatedDecl = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } printDeclDocumentation(&printer, decl) printer.printBraceBlock("public static \(renderFunctionSignature(decl))") { printer in @@ -275,7 +283,7 @@ extension JNISwift2JavaGenerator { /// `func method(x: Int, y: Int) -> Int` becomes /// `long method(long x, long y)` private func renderFunctionSignature(_ decl: ImportedFunc) -> String { - let translatedDecl = translatedDecl(for: decl) + let translatedDecl = translatedDecl(for: decl)! let resultType = translatedDecl.translatedFunctionSignature.resultType var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 179991df..8197d25b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -17,13 +17,19 @@ import JavaTypes extension JNISwift2JavaGenerator { func translatedDecl( for decl: ImportedFunc - ) -> TranslatedFunctionDecl { + ) -> TranslatedFunctionDecl? { if let cached = translatedDecls[decl] { return cached } - let translation = JavaTranslation(swiftModuleName: self.swiftModuleName) - let translated = translation.translate(decl) + let translated: TranslatedFunctionDecl? + do { + let translation = JavaTranslation(swiftModuleName: swiftModuleName) + translated = try translation.translate(decl) + } catch { + self.logger.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") + translated = nil + } translatedDecls[decl] = translated return translated @@ -32,8 +38,8 @@ extension JNISwift2JavaGenerator { struct JavaTranslation { let swiftModuleName: String - func translate(_ decl: ImportedFunc) -> TranslatedFunctionDecl { - let translatedFunctionSignature = translate(functionSignature: decl.functionSignature) + func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { + let translatedFunctionSignature = try translate(functionSignature: decl.functionSignature) // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName @@ -51,31 +57,31 @@ extension JNISwift2JavaGenerator { ) } - func translate(functionSignature: SwiftFunctionSignature, isInitializer: Bool = false) -> TranslatedFunctionSignature { - let parameters = functionSignature.parameters.enumerated().map { idx, param in + func translate(functionSignature: SwiftFunctionSignature, isInitializer: Bool = false) throws -> TranslatedFunctionSignature { + let parameters = try functionSignature.parameters.enumerated().map { idx, param in let parameterName = param.parameterName ?? "arg\(idx))" - return translate(swiftParam: param, parameterName: parameterName) + return try translate(swiftParam: param, parameterName: parameterName) } - return TranslatedFunctionSignature( + return try TranslatedFunctionSignature( parameters: parameters, resultType: translate(swiftType: functionSignature.result.type) ) } - func translate(swiftParam: SwiftParameter, parameterName: String) -> JavaParameter { - return JavaParameter( + func translate(swiftParam: SwiftParameter, parameterName: String) throws -> JavaParameter { + return try JavaParameter( name: parameterName, type: translate(swiftType: swiftParam.type) ) } - func translate(swiftType: SwiftType) -> JavaType { + func translate(swiftType: SwiftType) throws -> JavaType { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = translate(knownType: knownType) else { - fatalError("unsupported known type: \(knownType)") + throw JavaTranslationError.unsupportedSwiftType(swiftType) } return javaType } @@ -86,7 +92,7 @@ extension JNISwift2JavaGenerator { return .void case .metatype, .optional, .tuple, .function, .existential, .opaque: - fatalError("unsupported type: \(self)") + throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -127,4 +133,8 @@ extension JNISwift2JavaGenerator { let parameters: [JavaParameter] let resultType: JavaType } + + enum JavaTranslationError: Error { + case unsupportedSwiftType(SwiftType) + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index c32e0e27..031863b2 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -116,7 +116,11 @@ extension JNISwift2JavaGenerator { } private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = translatedDecl(for: decl) + guard let translatedDecl = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } + let typeName = translatedDecl.parentName printCDecl( @@ -146,6 +150,11 @@ extension JNISwift2JavaGenerator { _ printer: inout CodePrinter, _ decl: ImportedFunc ) { + guard let _ = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } + // Free functions does not have a parent if decl.isStatic || !decl.hasParent { self.printSwiftStaticFunctionThunk(&printer, decl) @@ -155,7 +164,7 @@ extension JNISwift2JavaGenerator { } private func printSwiftStaticFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl) + let translatedDecl = self.translatedDecl(for: decl)! printCDecl( &printer, @@ -172,7 +181,7 @@ extension JNISwift2JavaGenerator { } private func printSwiftMemberFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl) + let translatedDecl = self.translatedDecl(for: decl)! let swiftParentName = decl.parentType!.asNominalTypeDeclaration!.qualifiedName printCDecl( @@ -199,7 +208,7 @@ extension JNISwift2JavaGenerator { _ decl: ImportedFunc, calleeName: String ) { - let translatedDecl = self.translatedDecl(for: decl) + let translatedDecl = self.translatedDecl(for: decl)! let swiftReturnType = decl.functionSignature.result.type let tryClause: String = decl.isThrowing ? "try " : "" From 87a0e4fea7f7d4a01ef22c55363e841070b7f267 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 13 Jul 2025 19:21:18 +0200 Subject: [PATCH 5/5] PR feedback --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 1 + .../JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift | 8 +++++--- .../JNI/JNISwift2JavaGenerator+JavaTranslation.swift | 2 +- .../JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 8 +++++--- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 97f5149e..30661045 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -14,6 +14,7 @@ public class MySwiftClass { + public let byte: UInt8 = 0 public var len: Int public var cap: Int diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 1e73e00d..80b47760 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -176,7 +176,7 @@ extension JNISwift2JavaGenerator { } private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - guard let translatedDecl = translatedDecl(for: decl) else { + guard let _ = translatedDecl(for: decl) else { // Failed to translate. Skip. return } @@ -201,7 +201,7 @@ extension JNISwift2JavaGenerator { /// and passes it down to another native function along with the arguments /// to call the Swift implementation. private func printMemberMethodBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = translatedDecl(for: decl)! + let translatedDecl = translatedDecl(for: decl)! // We will only call this method if we can translate the decl. printDeclDocumentation(&printer, decl) printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in @@ -283,7 +283,9 @@ extension JNISwift2JavaGenerator { /// `func method(x: Int, y: Int) -> Int` becomes /// `long method(long x, long y)` private func renderFunctionSignature(_ decl: ImportedFunc) -> String { - let translatedDecl = translatedDecl(for: decl)! + guard let translatedDecl = translatedDecl(for: decl) else { + fatalError("Unable to render function signature for a function that cannot be translated: \(decl)") + } let resultType = translatedDecl.translatedFunctionSignature.resultType var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 8197d25b..9dc433d4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -27,7 +27,7 @@ extension JNISwift2JavaGenerator { let translation = JavaTranslation(swiftModuleName: swiftModuleName) translated = try translation.translate(decl) } catch { - self.logger.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") + self.logger.debug("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") translated = nil } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 031863b2..30db69d1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -164,7 +164,7 @@ extension JNISwift2JavaGenerator { } private func printSwiftStaticFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl)! + let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if we can translate the decl. printCDecl( &printer, @@ -181,7 +181,7 @@ extension JNISwift2JavaGenerator { } private func printSwiftMemberFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl)! + let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if can translate the decl. let swiftParentName = decl.parentType!.asNominalTypeDeclaration!.qualifiedName printCDecl( @@ -208,7 +208,9 @@ extension JNISwift2JavaGenerator { _ decl: ImportedFunc, calleeName: String ) { - let translatedDecl = self.translatedDecl(for: decl)! + guard let translatedDecl = self.translatedDecl(for: decl) else { + fatalError("Cannot print function downcall for a function that can't be translated: \(decl)") + } let swiftReturnType = decl.functionSignature.result.type let tryClause: String = decl.isThrowing ? "try " : ""