From 908bfeceee830cba206a7a3afaeff5b2a93c5dba Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 30 Jun 2025 12:57:54 +0200 Subject: [PATCH 1/9] add jni sample app --- .../com/example/swift/HelloJava2Swift.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java new file mode 100644 index 00000000..3c7edf45 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +// Import swift-extract generated sources + +// Import javakit/swiftkit support libraries + +import org.swift.swiftkit.SwiftKit; + +public class HelloJava2Swift { + + public static void main(String[] args) { + System.out.print("Property: java.library.path = " + SwiftKit.getJavaLibraryPath()); + + examples(); + } + + static void examples() { + MySwiftLibrary.helloWorld(); + + MySwiftLibrary.globalTakeInt(1337); + MySwiftLibrary.globalTakeIntInt(1337, 42); + + long cnt = MySwiftLibrary.globalWriteString("String from Java"); + SwiftKit.trace("count = " + cnt); + + long i = MySwiftLibrary.globalMakeInt(); + SwiftKit.trace("globalMakeInt() = " + i); + + System.out.println("DONE."); + } +} From d28959a0b2063d31a2d59d138dae620b1f68dfc6 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 30 Jun 2025 13:35:02 +0200 Subject: [PATCH 2/9] import classes with static methods --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 19 ++ .../com/example/swift/HelloJava2Swift.java | 2 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 137 +++++++++++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 172 ++++++++++++++ .../JNI/JNISwift2JavaGenerator.swift | 213 ------------------ .../JNI/JNIClassTests.swift | 79 +++++++ 6 files changed, 409 insertions(+), 213 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIClassTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift new file mode 100644 index 00000000..a853779f --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -0,0 +1,19 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +public class MySwiftClass { + public static func method() { + p("Hello from static method in a class!") + } +} diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index 3c7edf45..a19f6603 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -40,6 +40,8 @@ static void examples() { long i = MySwiftLibrary.globalMakeInt(); SwiftKit.trace("globalMakeInt() = " + i); + MySwiftClass.method(); + System.out.println("DONE."); } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift new file mode 100644 index 00000000..6cfb94e8 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -0,0 +1,137 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +extension JNISwift2JavaGenerator { + func writeExportedJavaSources() throws { + var printer = CodePrinter() + try writeExportedJavaSources(&printer) + } + + package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { + for (_, ty) in analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { + let filename = "\(ty.swiftNominal.name).java" + logger.info("Printing contents: \(filename)") + printImportedNominal(&printer, ty) + + if let outputFile = try printer.writeContents( + outputDirectory: javaOutputDirectory, + javaPackagePath: javaPackagePath, + filename: filename + ) { + print("[swift-java] Generated: \(ty.swiftNominal.name.bold).java (at \(outputFile))") + } + } + + let filename = "\(self.swiftModuleName).java" + logger.trace("Printing module class: \(filename)") + printModule(&printer) + + if let outputFile = try printer.writeContents( + outputDirectory: javaOutputDirectory, + javaPackagePath: javaPackagePath, + filename: filename + ) { + logger.info("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))") + } + } + + private func printModule(_ printer: inout CodePrinter) { + printHeader(&printer) + printPackage(&printer) + + printModuleClass(&printer) { printer in + printer.print( + """ + static final String LIB_NAME = "\(swiftModuleName)"; + + static { + System.loadLibrary(LIB_NAME); + } + """ + ) + + for decl in analysis.importedGlobalFuncs { + self.logger.trace("Print global function: \(decl)") + printFunctionBinding(&printer, decl) + printer.println() + } + } + } + + private func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printHeader(&printer) + printPackage(&printer) + + printNominal(&printer, decl) { printer in + for method in decl.methods { + printFunctionBinding(&printer, method) + } + } + } + + private func printHeader(_ printer: inout CodePrinter) { + printer.print( + """ + // Generated by jextract-swift + // Swift module: \(swiftModuleName) + + """ + ) + } + + private func printPackage(_ printer: inout CodePrinter) { + printer.print( + """ + package \(javaPackage); + + """ + ) + } + + private func printNominal( + _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void + ) { + printer.printBraceBlock("public final class \(decl.swiftNominal.name)") { printer in + body(&printer) + } + } + + private func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) { + printer.printBraceBlock("public final class \(swiftModuleName)") { printer in + body(&printer) + } + } + + private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + let returnType = decl.functionSignature.result.type.javaType + let params = decl.functionSignature.parameters.enumerated().map { idx, param in + "\(param.type.javaType) \(param.parameterName ?? "arg\(idx))")" + } + let throwsClause = decl.isThrowing ? " throws Exception" : "" + + printer.print( + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ + """ + ) + printer.print( + "public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);" + ) + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift new file mode 100644 index 00000000..9a77a20c --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -0,0 +1,172 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +extension JNISwift2JavaGenerator { + func writeSwiftThunkSources() throws { + var printer = CodePrinter() + try writeSwiftThunkSources(&printer) + } + + package func writeSwiftExpectedEmptySources() throws { + for expectedFileName in self.expectedOutputSwiftFiles { + logger.trace("Write empty file: \(expectedFileName) ...") + + var printer = CodePrinter() + printer.print("// Empty file generated on purpose") + _ = try printer.writeContents( + outputDirectory: self.swiftOutputDirectory, + javaPackagePath: nil, + filename: expectedFileName) + } + } + + package func writeSwiftThunkSources(_ printer: inout CodePrinter) throws { + let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" + let moduleFilename = "\(moduleFilenameBase).swift" + + do { + logger.trace("Printing swift module class: \(moduleFilename)") + + try printGlobalSwiftThunkSources(&printer) + + if let outputFile = try printer.writeContents( + outputDirectory: self.swiftOutputDirectory, + javaPackagePath: nil, + filename: moduleFilename + ) { + print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + self.expectedOutputSwiftFiles.remove(moduleFilename) + } + + for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { + let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" + let filename = "\(fileNameBase).swift" + logger.info("Printing contents: \(filename)") + + do { + try printNominalTypeThunks(&printer, ty) + + if let outputFile = try printer.writeContents( + outputDirectory: self.swiftOutputDirectory, + javaPackagePath: nil, + filename: filename) { + print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile))") + self.expectedOutputSwiftFiles.remove(filename) + } + } catch { + logger.warning("Failed to write to Swift thunks: \(filename)") + } + } + } catch { + logger.warning("Failed to write to Swift thunks: \(moduleFilename)") + } + } + + private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { + printHeader(&printer) + + for decl in analysis.importedGlobalFuncs { + printSwiftFunctionThunk(&printer, decl) + printer.println() + } + } + + private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { + printHeader(&printer) + + for decl in type.methods { + printSwiftFunctionThunk(&printer, decl, sorroundingType: type) + printer.println() + } + } + + private func printSwiftFunctionThunk( + _ printer: inout CodePrinter, + _ decl: ImportedFunc, + sorroundingType: ImportedNominalType? = nil + ) { + let parentName = sorroundingType?.swiftNominal.qualifiedName ?? swiftModuleName + + let cName = + "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") + "_\(parentName)_" + + decl.name + let thunkName = thunkNameRegistry.functionThunkName(decl: decl) + let translatedParameters = decl.functionSignature.parameters.enumerated().map { idx, param in + (param.parameterName ?? "arg\(idx)", param.type.javaType) + } + + let thunkParameters = + [ + "environment: UnsafeMutablePointer!", + "thisClass: jclass", + ] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)" } + let swiftReturnType = decl.functionSignature.result.type + let thunkReturnType = + !swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : "" + + printer.printBraceBlock( + """ + @_cdecl("\(cName)") + func \(thunkName)(\(thunkParameters.joined(separator: ", ")))\(thunkReturnType) + """ + ) { printer in + let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { + originalParam, translatedParam in + let label = originalParam.argumentLabel.map { "\($0): " } ?? "" + return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)" + } + let tryClause: String = decl.isThrowing ? "try " : "" + let functionDowncall = + "\(tryClause)\(parentName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" + + let innerBody = + if swiftReturnType.isVoid { + functionDowncall + } else { + """ + let result = \(functionDowncall) + return result.getJNIValue(in: environment) + """ + } + + if decl.isThrowing { + let dummyReturn = + !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" + printer.print( + """ + do { + \(innerBody) + } catch { + environment.throwAsException(error) + \(dummyReturn) + } + """ + ) + } else { + printer.print(innerBody) + } + } + } + + private func printHeader(_ printer: inout CodePrinter) { + printer.print( + """ + // Generated by swift-java + + import JavaKit + + """ + ) + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 0f16e697..3122c4d3 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -72,219 +72,6 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { } } -extension JNISwift2JavaGenerator { - func writeExportedJavaSources() throws { - var printer = CodePrinter() - try writeExportedJavaSources(&printer) - } - - package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { - let filename = "\(self.swiftModuleName).java" - logger.trace("Printing module class: \(filename)") - printModule(&printer) - - if let outputFile = try printer.writeContents( - outputDirectory: javaOutputDirectory, - javaPackagePath: javaPackagePath, - filename: filename - ) { - logger.info("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))") - } - } - - func writeSwiftThunkSources() throws { - var printer = CodePrinter() - try writeSwiftThunkSources(&printer) - } - - package func writeSwiftExpectedEmptySources() throws { - for expectedFileName in self.expectedOutputSwiftFiles { - logger.trace("Write empty file: \(expectedFileName) ...") - - var printer = CodePrinter() - printer.print("// Empty file generated on purpose") - _ = try printer.writeContents( - outputDirectory: self.swiftOutputDirectory, - javaPackagePath: nil, - filename: expectedFileName) - } - } - - package func writeSwiftThunkSources(_ printer: inout CodePrinter) throws { - let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" - let moduleFilename = "\(moduleFilenameBase).swift" - - do { - logger.trace("Printing swift module class: \(moduleFilename)") - - try printGlobalSwiftThunkSources(&printer) - - if let outputFile = try printer.writeContents( - outputDirectory: self.swiftOutputDirectory, - javaPackagePath: nil, - filename: moduleFilename - ) { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") - self.expectedOutputSwiftFiles.remove(moduleFilename) - } - } catch { - logger.warning("Failed to write to Swift thunks: \(moduleFilename)") - } - } -} - -extension JNISwift2JavaGenerator { - private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { - printer.print( - """ - // Generated by swift-java - - import JavaKit - - """) - - for decl in analysis.importedGlobalFuncs { - printSwiftFunctionThunk(&printer, decl) - printer.println() - } - } - - private func printSwiftFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - // TODO: Replace swiftModuleName with class name if non-global - let cName = - "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") + "_\(swiftModuleName)_" - + decl.name - let thunkName = thunkNameRegistry.functionThunkName(decl: decl) - let translatedParameters = decl.functionSignature.parameters.enumerated().map { idx, param in - (param.parameterName ?? "arg\(idx)", param.type.javaType) - } - - let thunkParameters = - [ - "environment: UnsafeMutablePointer!", - "thisClass: jclass", - ] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)" } - let swiftReturnType = decl.functionSignature.result.type - let thunkReturnType = - !swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : "" - - printer.printBraceBlock( - """ - @_cdecl("\(cName)") - func \(thunkName)(\(thunkParameters.joined(separator: ", ")))\(thunkReturnType) - """ - ) { printer in - let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { - originalParam, translatedParam in - let label = originalParam.argumentLabel.map { "\($0): " } ?? "" - return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)" - } - let tryClause: String = decl.isThrowing ? "try " : "" - let functionDowncall = - "\(tryClause)\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" - - let innerBody = - if swiftReturnType.isVoid { - functionDowncall - } else { - """ - let result = \(functionDowncall) - return result.getJNIValue(in: environment) - """ - } - - if decl.isThrowing { - let dummyReturn = - !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" - printer.print( - """ - do { - \(innerBody) - } catch { - environment.throwAsException(error) - \(dummyReturn) - } - """ - ) - } else { - printer.print(innerBody) - } - } - } -} - -extension JNISwift2JavaGenerator { - private func printModule(_ printer: inout CodePrinter) { - printHeader(&printer) - printPackage(&printer) - - printModuleClass(&printer) { printer in - printer.print( - """ - static final String LIB_NAME = "\(swiftModuleName)"; - - static { - System.loadLibrary(LIB_NAME); - } - """ - ) - - for decl in analysis.importedGlobalFuncs { - self.logger.trace("Print global function: \(decl)") - printFunctionBinding(&printer, decl) - printer.println() - } - } - } - - private func printHeader(_ printer: inout CodePrinter) { - printer.print( - """ - // Generated by jextract-swift - // Swift module: \(swiftModuleName) - - """ - ) - } - - private func printPackage(_ printer: inout CodePrinter) { - printer.print( - """ - package \(javaPackage); - - """ - ) - } - - private func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) { - printer.printBraceBlock("public final class \(swiftModuleName)") { printer in - body(&printer) - } - } - - private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let returnType = decl.functionSignature.result.type.javaType - let params = decl.functionSignature.parameters.enumerated().map { idx, param in - "\(param.type.javaType) \(param.parameterName ?? "arg\(idx))")" - } - let throwsClause = decl.isThrowing ? " throws Exception" : "" - - printer.print( - """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * \(decl.signatureString) - * } - */ - """ - ) - printer.print( - "public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);" - ) - } -} - extension SwiftType { var javaType: JavaType { switch self { diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift new file mode 100644 index 00000000..597ae5bb --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 JExtractSwiftLib +import Testing + +@Suite +struct JNIClassTests { + let source = """ + public class MyClass { + public static func method() { + + } + } + """ + + @Test + func generatesJavaClass() throws { + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule + + package com.example.swift; + + public final class MyClass { + """ + ]) + } + + @Test + func staticMethod_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public static func method() + * } + */ + public static native void method(); + """ + ] + ) + } + + @Test + func staticMethod_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass_method") + func swiftjava_SwiftModule_MyClass_method(environment: UnsafeMutablePointer!, thisClass: jclass) { + MyClass.method() + } + """ + ] + ) + } +} From e1f0a8c5d90a07b5f1078f7d5bdaca2e9de92860 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 1 Jul 2025 11:39:59 +0200 Subject: [PATCH 3/9] generate java bindings for initializers --- Sources/JExtractSwiftLib/ImportedDecls.swift | 4 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 57 +++++++- ...ISwift2JavaGenerator+JavaTranslation.swift | 128 ++++++++++++++++++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 8 +- .../JNI/JNISwift2JavaGenerator.swift | 49 +------ .../JNI/JNIClassTests.swift | 63 ++++++++- 6 files changed, 250 insertions(+), 59 deletions(-) create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 0a5952f3..32b3c8bf 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -40,6 +40,10 @@ package class ImportedNominalType: ImportedDecl { var swiftType: SwiftType { return .nominal(.init(nominalTypeDecl: swiftNominal)) } + + var qualifiedName: String { + self.swiftNominal.qualifiedName + } } public final class ImportedFunc: ImportedDecl, CustomStringConvertible { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 6cfb94e8..5d9713ab 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -74,6 +74,20 @@ extension JNISwift2JavaGenerator { printPackage(&printer) printNominal(&printer, decl) { printer in + printer.print( + """ + private long selfPointer; + + private MyClass(long selfPointer) { + this.selfPointer = selfPointer; + } + """ + ) + + for initializer in decl.initializers { + printInitializerBindings(&printer, initializer, type: decl) + } + for method in decl.methods { printFunctionBinding(&printer, method) } @@ -114,12 +128,31 @@ extension JNISwift2JavaGenerator { } private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let returnType = decl.functionSignature.result.type.javaType - let params = decl.functionSignature.parameters.enumerated().map { idx, param in - "\(param.type.javaType) \(param.parameterName ?? "arg\(idx))")" + printDeclDocumentation(&printer, decl) + printer.print( + "public static native \(renderFunctionSignature(decl));" + ) + } + + private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) { + let translatedDecl = translatedDecl(for: decl) + + printDeclDocumentation(&printer, decl) + printer.printBraceBlock("public static \(renderFunctionSignature(decl))") { printer in + let initArguments = translatedDecl.translatedFunctionSignature.parameters.map(\.asArgument) + printer.print( + """ + long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); + return new \(type.qualifiedName)(selfPointer); + """ + ) } - let throwsClause = decl.isThrowing ? " throws Exception" : "" + let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + printer.print("private static native long allocatingInit(\(parameters.joined(separator: ", ")));") + } + + private func printDeclDocumentation(_ printer: inout CodePrinter, _ decl: ImportedFunc) { printer.print( """ /** @@ -130,8 +163,18 @@ extension JNISwift2JavaGenerator { */ """ ) - printer.print( - "public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);" - ) + } + + /// Renders a function signature such + /// + /// `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 resultType = translatedDecl.translatedFunctionSignature.resultType + let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + let throwsClause = decl.isThrowing ? " throws Exception" : "" + + return "\(resultType) \(decl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift new file mode 100644 index 00000000..beac5e3b --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -0,0 +1,128 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 + +extension JNISwift2JavaGenerator { + func translatedDecl( + for decl: ImportedFunc + ) -> TranslatedFunctionDecl { + if let cached = translatedDecls[decl] { + return cached + } + + let translation = JavaTranslation() + let translated = translation.translate(decl) + + translatedDecls[decl] = translated + return translated + } + + struct JavaTranslation { + func translate(_ decl: ImportedFunc) -> TranslatedFunctionDecl { + let translatedFunctionSignature = translate(functionSignature: decl.functionSignature) + + return TranslatedFunctionDecl( + name: decl.name, + translatedFunctionSignature: translatedFunctionSignature + ) + } + + func translate(functionSignature: SwiftFunctionSignature) -> TranslatedFunctionSignature { + let parameters = functionSignature.parameters.enumerated().map { idx, param in + let parameterName = param.parameterName ?? "arg\(idx))" + return translate(swiftParam: param, parameterName: parameterName) + } + + return TranslatedFunctionSignature( + parameters: parameters, + resultType: translate(swiftType: functionSignature.result.type) + ) + } + + func translate(swiftParam: SwiftParameter, parameterName: String) -> JavaParameter { + return JavaParameter( + name: parameterName, + type: translate(swiftType: swiftParam.type) + ) + } + + func translate(swiftType: SwiftType) -> JavaType { + switch swiftType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { + guard let javaType = translate(standardLibraryType: knownType) else { + fatalError("unsupported known type: \(knownType)") + } + return javaType + } + + return .class(package: nil, name: nominalType.nominalTypeDecl.name) + + case .tuple([]): + return .void + + case .metatype, .optional, .tuple, .function: + fatalError("unsupported type: \(self)") + } + } + + func translate(standardLibraryType: SwiftStandardLibraryTypeKind) -> JavaType? { + switch standardLibraryType { + case .bool: .boolean + case .int8: .byte + case .uint16: .char + case .int16: .short + case .int32: .int + case .int64: .long + case .float: .float + case .double: .double + case .void: .void + case .string: .javaLangString + case .int, .uint, .uint8, .uint32, .uint64, + .unsafeRawPointer, .unsafeMutableRawPointer, + .unsafePointer, .unsafeMutablePointer, + .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer: + nil + } + } + } + + struct TranslatedFunctionDecl { + /// Java function name + let name: String + + /// Function signature + let translatedFunctionSignature: TranslatedFunctionSignature + } + + struct JavaParameter { + let name: String + let type: JavaType + + var asParameter: String { + "\(type) \(name)" + } + + var asArgument: String { + name + } + } + + struct TranslatedFunctionSignature { + let parameters: [JavaParameter] + let resultType: JavaType + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 9a77a20c..385962e1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -96,14 +96,16 @@ extension JNISwift2JavaGenerator { _ decl: ImportedFunc, sorroundingType: ImportedNominalType? = nil ) { + let translatedDecl = translatedDecl(for: decl) let parentName = sorroundingType?.swiftNominal.qualifiedName ?? swiftModuleName let cName = "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") + "_\(parentName)_" + decl.name let thunkName = thunkNameRegistry.functionThunkName(decl: decl) - let translatedParameters = decl.functionSignature.parameters.enumerated().map { idx, param in - (param.parameterName ?? "arg\(idx)", param.type.javaType) + // TODO: Add a similair construct as `LoweredFunctionSignature` to `TranslatedFunctionSignature` + let translatedParameters = translatedDecl.translatedFunctionSignature.parameters.map { param in + (param.name, param.type) } let thunkParameters = @@ -113,7 +115,7 @@ extension JNISwift2JavaGenerator { ] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)" } let swiftReturnType = decl.functionSignature.result.type let thunkReturnType = - !swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : "" + !swiftReturnType.isVoid ? " -> \(translatedDecl.translatedFunctionSignature.resultType.jniTypeName)" : "" printer.printBraceBlock( """ diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 3122c4d3..b242cf66 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -28,6 +28,9 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { var thunkNameRegistry = ThunkNameRegistry() + /// Cached Java translation result. 'nil' indicates failed translation. + var translatedDecls: [ImportedFunc: TranslatedFunctionDecl] = [:] + /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet, /// and write an empty file for those. var expectedOutputSwiftFiles: Set @@ -71,49 +74,3 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { } } } - -extension SwiftType { - var javaType: JavaType { - switch self { - case .nominal(let nominalType): - if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { - guard let javaType = knownType.javaType else { - fatalError("unsupported known type: \(knownType)") - } - return javaType - } - - fatalError("unsupported nominal type: \(nominalType)") - - case .tuple([]): - return .void - - case .metatype, .optional, .tuple, .function: - fatalError("unsupported type: \(self)") - } - } -} - -extension SwiftStandardLibraryTypeKind { - var javaType: JavaType? { - switch self { - case .bool: .boolean - case .int: .long // TODO: Handle 32-bit or 64-bit - case .int8: .byte - case .uint16: .char - case .int16: .short - case .int32: .int - case .int64: .long - case .float: .float - case .double: .double - case .void: .void - case .string: .javaLangString - case .uint, .uint8, .uint32, .uint64, - .unsafeRawPointer, .unsafeMutableRawPointer, - .unsafePointer, .unsafeMutablePointer, - .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer: - nil - } - } -} diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 597ae5bb..b7eab6eb 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -19,8 +19,19 @@ import Testing struct JNIClassTests { let source = """ public class MyClass { - public static func method() { + let x: Int64 + let y: Int64 + public static func method() {} + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public init() { + self.x = 0 + self.y = 0 } } """ @@ -34,8 +45,13 @@ struct JNIClassTests { package com.example.swift; - public final class MyClass { - """ + public final class MyClass { + private long selfPointer; + + private MyClass(long selfPointer) { + this.selfPointer = selfPointer; + } + """, ]) } @@ -76,4 +92,45 @@ struct JNIClassTests { ] ) } + + @Test + func initializer_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init(x: Int64, y: Int64) + * } + */ + public static MyClass init(long x, long y) { + long selfPointer = MyClass.allocatingInit(x, y); + return new MyClass(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init() + * } + */ + public static MyClass init() { + long selfPointer = MyClass.allocatingInit(); + return new MyClass(selfPointer); + } + """, + """ + private static native long allocatingInit(long x, long y); + """, + """ + private static native long allocatingInit(); + """ + ] + ) + } } From 57fa106e8dfab6f99a84055ee209febdce02bee6 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 2 Jul 2025 21:47:22 +0200 Subject: [PATCH 4/9] generate swift thunks for initializers --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 18 + .../com/example/swift/HelloJava2Swift.java | 3 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 890 +++++++++--------- Sources/JExtractSwiftLib/ImportedDecls.swift | 14 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 6 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 7 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 164 +++- Sources/JavaTypes/JavaType+JNI.swift | 27 + Sources/JavaTypes/JavaType+SwiftNames.swift | 7 + .../JNI/JNIClassTests.swift | 28 + 11 files changed, 683 insertions(+), 483 deletions(-) diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index a853779f..fca6236f 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -13,7 +13,25 @@ //===----------------------------------------------------------------------===// public class MySwiftClass { + let x: Int64 + let y: Int64 + public static func method() { p("Hello from static method in a class!") } + + public init(x: Int64, y: Int64) { + self.x = x + self.y = y + p("\(self)") + } + + public init() { + self.x = 10 + self.y = 5 + } + + deinit { + p("deinit called!") + } } diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index a19f6603..b140bbd8 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -42,6 +42,9 @@ static void examples() { MySwiftClass.method(); + MySwiftClass myClass = MySwiftClass.init(10, 5); + MySwiftClass myClass2 = MySwiftClass.init(); + System.out.println("DONE."); } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index a6cc6b26..364fd270 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -439,7 +439,7 @@ extension FFMSwift2JavaGenerator { } } -extension JavaConversionStep { +extension FFMSwift2JavaGenerator.JavaConversionStep { /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index af1ddd09..5c22233e 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -34,538 +34,540 @@ extension FFMSwift2JavaGenerator { translatedDecls[decl] = translated return translated } -} - -/// Represent a parameter in Java code. -struct JavaParameter { - /// The type. - var type: JavaType - - /// The name. - var name: String -} -/// Represent a Swift API parameter translated to Java. -struct TranslatedParameter { - /// Java parameter(s) mapped to the Swift parameter. - /// - /// Array because one Swift parameter can be mapped to multiple parameters. - var javaParameters: [JavaParameter] + /// Represent a parameter in Java code. + struct JavaParameter { + /// The type. + var type: JavaType - /// Describes how to convert the Java parameter to the lowered arguments for - /// the foreign function. - var conversion: JavaConversionStep -} - -/// Represent a Swift API result translated to Java. -struct TranslatedResult { - /// Java type that represents the Swift result type. - var javaResultType: JavaType - - /// Required indirect return receivers for receiving the result. - /// - /// 'JavaParameter.name' is the suffix for the receiver variable names. For example - /// - /// var _result_pointer = MemorySegment.allocate(...) - /// var _result_count = MemroySegment.allocate(...) - /// downCall(_result_pointer, _result_count) - /// return constructResult(_result_pointer, _result_count) - /// - /// This case, there're two out parameter, named '_pointer' and '_count'. - var outParameters: [JavaParameter] + /// The name. + var name: String + } - /// Describes how to construct the Java result from the foreign function return - /// value and/or the out parameters. - var conversion: JavaConversionStep -} + /// Represent a Swift API parameter translated to Java. + struct TranslatedParameter { + /// Java parameter(s) mapped to the Swift parameter. + /// + /// Array because one Swift parameter can be mapped to multiple parameters. + var javaParameters: [JavaParameter] + /// Describes how to convert the Java parameter to the lowered arguments for + /// the foreign function. + var conversion: JavaConversionStep + } -/// Translated Java API representing a Swift API. -/// -/// Since this holds the lowered signature, and the original `SwiftFunctionSignature` -/// in it, this contains all the API information (except the name) to generate the -/// cdecl thunk, Java binding, and the Java wrapper function. -struct TranslatedFunctionDecl { - /// Java function name. - let name: String + /// Represent a Swift API result translated to Java. + struct TranslatedResult { + /// Java type that represents the Swift result type. + var javaResultType: JavaType + + /// Required indirect return receivers for receiving the result. + /// + /// 'JavaParameter.name' is the suffix for the receiver variable names. For example + /// + /// var _result_pointer = MemorySegment.allocate(...) + /// var _result_count = MemroySegment.allocate(...) + /// downCall(_result_pointer, _result_count) + /// return constructResult(_result_pointer, _result_count) + /// + /// This case, there're two out parameter, named '_pointer' and '_count'. + var outParameters: [JavaParameter] + + /// Describes how to construct the Java result from the foreign function return + /// value and/or the out parameters. + var conversion: JavaConversionStep + } - /// Functional interfaces required for the Java method. - let functionTypes: [TranslatedFunctionType] - /// Function signature. - let translatedSignature: TranslatedFunctionSignature + /// Translated Java API representing a Swift API. + /// + /// Since this holds the lowered signature, and the original `SwiftFunctionSignature` + /// in it, this contains all the API information (except the name) to generate the + /// cdecl thunk, Java binding, and the Java wrapper function. + struct TranslatedFunctionDecl { + /// Java function name. + let name: String - /// Cdecl lowerd signature. - let loweredSignature: LoweredFunctionSignature -} + /// Functional interfaces required for the Java method. + let functionTypes: [TranslatedFunctionType] -/// Function signature for a Java API. -struct TranslatedFunctionSignature { - var selfParameter: TranslatedParameter? - var parameters: [TranslatedParameter] - var result: TranslatedResult -} + /// Function signature. + let translatedSignature: TranslatedFunctionSignature -/// Represent a Swift closure type in the user facing Java API. -/// -/// Closures are translated to named functional interfaces in Java. -struct TranslatedFunctionType { - var name: String - var parameters: [TranslatedParameter] - var result: TranslatedResult - var swiftType: SwiftFunctionType - var cdeclType: SwiftFunctionType - - /// Whether or not this functional interface with C ABI compatible. - var isCompatibleWithC: Bool { - result.conversion.isPlaceholder && parameters.allSatisfy(\.conversion.isPlaceholder) + /// Cdecl lowerd signature. + let loweredSignature: LoweredFunctionSignature } -} -extension TranslatedFunctionSignature { - /// Whether or not if the down-calling requires temporary "Arena" which is - /// only used during the down-calling. - var requiresTemporaryArena: Bool { - if self.parameters.contains(where: { $0.conversion.requiresTemporaryArena }) { - return true - } - if self.selfParameter?.conversion.requiresTemporaryArena ?? false { - return true - } - if self.result.conversion.requiresTemporaryArena { - return true - } - return false + /// Function signature for a Java API. + struct TranslatedFunctionSignature { + var selfParameter: TranslatedParameter? + var parameters: [TranslatedParameter] + var result: TranslatedResult } - /// Whether if the down-calling requires "SwiftArena" or not, which should be - /// passed-in by the API caller. This is needed if the API returns a `SwiftValue` - var requiresSwiftArena: Bool { - return self.result.conversion.requiresSwiftArena + /// Represent a Swift closure type in the user facing Java API. + /// + /// Closures are translated to named functional interfaces in Java. + struct TranslatedFunctionType { + var name: String + var parameters: [TranslatedParameter] + var result: TranslatedResult + var swiftType: SwiftFunctionType + var cdeclType: SwiftFunctionType + + /// Whether or not this functional interface with C ABI compatible. + var isCompatibleWithC: Bool { + result.conversion.isPlaceholder && parameters.allSatisfy(\.conversion.isPlaceholder) + } } -} -struct JavaTranslation { - var swiftStdlibTypes: SwiftStandardLibraryTypeDecls + struct JavaTranslation { + var swiftStdlibTypes: SwiftStandardLibraryTypeDecls - func translate( - _ decl: ImportedFunc - ) throws -> TranslatedFunctionDecl { - let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes) - let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) + func translate( + _ decl: ImportedFunc + ) throws -> TranslatedFunctionDecl { + let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes) + let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) - // Name. - let javaName = switch decl.apiKind { - case .getter: "get\(decl.name.toCamelCase)" - case .setter: "set\(decl.name.toCamelCase)" - case .function, .initializer: decl.name - } + // Name. + let javaName = switch decl.apiKind { + case .getter: "get\(decl.name.toCamelCase)" + case .setter: "set\(decl.name.toCamelCase)" + case .function, .initializer: decl.name + } - // Signature. - let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName) - - // Closures. - var funcTypes: [TranslatedFunctionType] = [] - for (idx, param) in decl.functionSignature.parameters.enumerated() { - switch param.type { - case .function(let funcTy): - let paramName = param.parameterName ?? "_\(idx)" - guard case .function( let cdeclTy) = loweredSignature.parameters[idx].cdeclParameters[0].type else { - preconditionFailure("closure parameter wasn't lowered to a function type; \(funcTy)") + // Signature. + let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName) + + // Closures. + var funcTypes: [TranslatedFunctionType] = [] + for (idx, param) in decl.functionSignature.parameters.enumerated() { + switch param.type { + case .function(let funcTy): + let paramName = param.parameterName ?? "_\(idx)" + guard case .function( let cdeclTy) = loweredSignature.parameters[idx].cdeclParameters[0].type else { + preconditionFailure("closure parameter wasn't lowered to a function type; \(funcTy)") + } + let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy, cdeclType: cdeclTy) + funcTypes.append(translatedClosure) + case .tuple: + // TODO: Implement + break + default: + break } - let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy, cdeclType: cdeclTy) - funcTypes.append(translatedClosure) - case .tuple: - // TODO: Implement - break - default: - break } - } - - return TranslatedFunctionDecl( - name: javaName, - functionTypes: funcTypes, - translatedSignature: translatedSignature, - loweredSignature: loweredSignature - ) - } - /// Translate Swift closure type to Java functional interface. - func translateFunctionType( - name: String, - swiftType: SwiftFunctionType, - cdeclType: SwiftFunctionType - ) throws -> TranslatedFunctionType { - var translatedParams: [TranslatedParameter] = [] - - for (i, param) in swiftType.parameters.enumerated() { - let paramName = param.parameterName ?? "_\(i)" - translatedParams.append( - try translateClosureParameter(param.type, convention: param.convention, parameterName: paramName) + return TranslatedFunctionDecl( + name: javaName, + functionTypes: funcTypes, + translatedSignature: translatedSignature, + loweredSignature: loweredSignature ) } - guard let resultCType = try? CType(cdeclType: swiftType.resultType) else { - throw JavaTranslationError.unhandledType(.function(swiftType)) - } + /// Translate Swift closure type to Java functional interface. + func translateFunctionType( + name: String, + swiftType: SwiftFunctionType, + cdeclType: SwiftFunctionType + ) throws -> TranslatedFunctionType { + var translatedParams: [TranslatedParameter] = [] + + for (i, param) in swiftType.parameters.enumerated() { + let paramName = param.parameterName ?? "_\(i)" + translatedParams.append( + try translateClosureParameter(param.type, convention: param.convention, parameterName: paramName) + ) + } - let transltedResult = TranslatedResult( - javaResultType: resultCType.javaType, - outParameters: [], - conversion: .placeholder - ) - - return TranslatedFunctionType( - name: name, - parameters: translatedParams, - result: transltedResult, - swiftType: swiftType, - cdeclType: cdeclType - ) - } + guard let resultCType = try? CType(cdeclType: swiftType.resultType) else { + throw JavaTranslationError.unhandledType(.function(swiftType)) + } - func translateClosureParameter( - _ type: SwiftType, - convention: SwiftParameterConvention, - parameterName: String - ) throws -> TranslatedParameter { - if let cType = try? CType(cdeclType: type) { - return TranslatedParameter( - javaParameters: [ - JavaParameter(type: cType.javaType, name: parameterName) - ], + let transltedResult = TranslatedResult( + javaResultType: resultCType.javaType, + outParameters: [], conversion: .placeholder ) + + return TranslatedFunctionType( + name: name, + parameters: translatedParams, + result: transltedResult, + swiftType: swiftType, + cdeclType: cdeclType + ) } - switch type { - case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { - switch knownType { - case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: - return TranslatedParameter( - javaParameters: [ - JavaParameter(type: .javaForeignMemorySegment, name: parameterName) - ], - conversion: .method( - .explodedName(component: "pointer"), - methodName: "reinterpret", - arguments: [ - .explodedName(component: "count") + func translateClosureParameter( + _ type: SwiftType, + convention: SwiftParameterConvention, + parameterName: String + ) throws -> TranslatedParameter { + if let cType = try? CType(cdeclType: type) { + return TranslatedParameter( + javaParameters: [ + JavaParameter(type: cType.javaType, name: parameterName) + ], + conversion: .placeholder + ) + } + + switch type { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + switch knownType { + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedParameter( + javaParameters: [ + JavaParameter(type: .javaForeignMemorySegment, name: parameterName) ], - withArena: false + conversion: .method( + .explodedName(component: "pointer"), + methodName: "reinterpret", + arguments: [ + .explodedName(component: "count") + ], + withArena: false + ) ) - ) - default: - break + default: + break + } } + default: + break } - default: - break + throw JavaTranslationError.unhandledType(type) } - throw JavaTranslationError.unhandledType(type) - } - /// Translate a Swift API signature to the user-facing Java API signature. - /// - /// Note that the result signature is for the high-level Java API, not the - /// low-level FFM down-calling interface. - func translate( - loweredFunctionSignature: LoweredFunctionSignature, - methodName: String - ) throws -> TranslatedFunctionSignature { - let swiftSignature = loweredFunctionSignature.original - - // 'self' - let selfParameter: TranslatedParameter? - if case .instance(let swiftSelf) = swiftSignature.selfParameter { - selfParameter = try self.translate( - swiftParam: swiftSelf, - loweredParam: loweredFunctionSignature.selfParameter!, - methodName: methodName, - parameterName: swiftSelf.parameterName ?? "self" - ) - } else { - selfParameter = nil - } - - // Regular parameters. - let parameters: [TranslatedParameter] = try swiftSignature.parameters.enumerated() - .map { (idx, swiftParam) in - let loweredParam = loweredFunctionSignature.parameters[idx] - let parameterName = swiftParam.parameterName ?? "_\(idx)" - return try self.translate( - swiftParam: swiftParam, - loweredParam: loweredParam, + /// Translate a Swift API signature to the user-facing Java API signature. + /// + /// Note that the result signature is for the high-level Java API, not the + /// low-level FFM down-calling interface. + func translate( + loweredFunctionSignature: LoweredFunctionSignature, + methodName: String + ) throws -> TranslatedFunctionSignature { + let swiftSignature = loweredFunctionSignature.original + + // 'self' + let selfParameter: TranslatedParameter? + if case .instance(let swiftSelf) = swiftSignature.selfParameter { + selfParameter = try self.translate( + swiftParam: swiftSelf, + loweredParam: loweredFunctionSignature.selfParameter!, methodName: methodName, - parameterName: parameterName + parameterName: swiftSelf.parameterName ?? "self" ) + } else { + selfParameter = nil } - // Result. - let result = try self.translate( - swiftResult: swiftSignature.result, - loweredResult: loweredFunctionSignature.result - ) - - return TranslatedFunctionSignature( - selfParameter: selfParameter, - parameters: parameters, - result: result - ) - } - - /// Translate a Swift API parameter to the user-facing Java API parameter. - func translate( - swiftParam: SwiftParameter, - loweredParam: LoweredParameter, - methodName: String, - parameterName: String - ) throws -> TranslatedParameter { - let swiftType = swiftParam.type - - // If there is a 1:1 mapping between this Swift type and a C type, that can - // be expressed as a Java primitive type. - if let cType = try? CType(cdeclType: swiftType) { - let javaType = cType.javaType - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: javaType, - name: parameterName + // Regular parameters. + let parameters: [TranslatedParameter] = try swiftSignature.parameters.enumerated() + .map { (idx, swiftParam) in + let loweredParam = loweredFunctionSignature.parameters[idx] + let parameterName = swiftParam.parameterName ?? "_\(idx)" + return try self.translate( + swiftParam: swiftParam, + loweredParam: loweredParam, + methodName: methodName, + parameterName: parameterName ) - ], - conversion: .placeholder + } + + // Result. + let result = try self.translate( + swiftResult: swiftSignature.result, + loweredResult: loweredFunctionSignature.result ) - } - switch swiftType { - case .metatype: - // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType"), - name: parameterName) - ], - conversion: .swiftValueSelfSegment(.placeholder) + return TranslatedFunctionSignature( + selfParameter: selfParameter, + parameters: parameters, + result: result ) + } - case .nominal(let swiftNominalType): - if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { - if swiftParam.convention == .inout { - // FIXME: Support non-trivial 'inout' for builtin types. - throw JavaTranslationError.inoutNotSupported(swiftType) - } - switch knownType { - case .unsafePointer, .unsafeMutablePointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) - case .unsafeBufferPointer, .unsafeMutableBufferPointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) + /// Translate a Swift API parameter to the user-facing Java API parameter. + func translate( + swiftParam: SwiftParameter, + loweredParam: LoweredParameter, + methodName: String, + parameterName: String + ) throws -> TranslatedParameter { + let swiftType = swiftParam.type + + // If there is a 1:1 mapping between this Swift type and a C type, that can + // be expressed as a Java primitive type. + if let cType = try? CType(cdeclType: swiftType) { + let javaType = cType.javaType + return TranslatedParameter( + javaParameters: [ + JavaParameter( + type: javaType, + name: parameterName + ) + ], + conversion: .placeholder + ) + } - case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: - return TranslatedParameter( - javaParameters: [ - JavaParameter(type: .javaForeignMemorySegment, name: parameterName), - ], - conversion: .commaSeparated([ - .placeholder, - .method(.placeholder, methodName: "byteSize", arguments: [], withArena: false) - ]) - ) + switch swiftType { + case .metatype: + // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' + return TranslatedParameter( + javaParameters: [ + JavaParameter( + type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType"), + name: parameterName) + ], + conversion: .swiftValueSelfSegment(.placeholder) + ) - case .string: - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: .javaLangString, - name: parameterName - ) - ], - conversion: .call(.placeholder, function: "SwiftKit.toCString", withArena: true) - ) + case .nominal(let swiftNominalType): + if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { + if swiftParam.convention == .inout { + // FIXME: Support non-trivial 'inout' for builtin types. + throw JavaTranslationError.inoutNotSupported(swiftType) + } + switch knownType { + case .unsafePointer, .unsafeMutablePointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedParameter( + javaParameters: [ + JavaParameter(type: .javaForeignMemorySegment, name: parameterName), + ], + conversion: .commaSeparated([ + .placeholder, + .method(.placeholder, methodName: "byteSize", arguments: [], withArena: false) + ]) + ) - default: + case .string: + return TranslatedParameter( + javaParameters: [ + JavaParameter( + type: .javaLangString, + name: parameterName + ) + ], + conversion: .call(.placeholder, function: "SwiftKit.toCString", withArena: true) + ) + + default: + throw JavaTranslationError.unhandledType(swiftType) + } + } + + // Generic types are not supported yet. + guard swiftNominalType.genericArguments == nil else { throw JavaTranslationError.unhandledType(swiftType) } - } - // Generic types are not supported yet. - guard swiftNominalType.genericArguments == nil else { - throw JavaTranslationError.unhandledType(swiftType) - } + return TranslatedParameter( + javaParameters: [ + JavaParameter( + type: try translate(swiftType: swiftType), + name: parameterName + ) + ], + conversion: .swiftValueSelfSegment(.placeholder) + ) - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: try translate(swiftType: swiftType), - name: parameterName - ) - ], - conversion: .swiftValueSelfSegment(.placeholder) - ) + case .tuple: + // TODO: Implement. + throw JavaTranslationError.unhandledType(swiftType) - case .tuple: - // TODO: Implement. - throw JavaTranslationError.unhandledType(swiftType) - - case .function: - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)"), - name: parameterName) - ], - conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) - ) + case .function: + return TranslatedParameter( + javaParameters: [ + JavaParameter( + type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)"), + name: parameterName) + ], + conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) + ) - case .optional: - throw JavaTranslationError.unhandledType(swiftType) + case .optional: + throw JavaTranslationError.unhandledType(swiftType) + } } - } - /// Translate a Swift API result to the user-facing Java API result. - func translate( - swiftResult: SwiftResult, - loweredResult: LoweredResult - ) throws -> TranslatedResult { - let swiftType = swiftResult.type - - // If there is a 1:1 mapping between this Swift type and a C type, that can - // be expressed as a Java primitive type. - if let cType = try? CType(cdeclType: swiftType) { - let javaType = cType.javaType - return TranslatedResult( - javaResultType: javaType, - outParameters: [], - conversion: .placeholder - ) - } + /// Translate a Swift API result to the user-facing Java API result. + func translate( + swiftResult: SwiftResult, + loweredResult: LoweredResult + ) throws -> TranslatedResult { + let swiftType = swiftResult.type + + // If there is a 1:1 mapping between this Swift type and a C type, that can + // be expressed as a Java primitive type. + if let cType = try? CType(cdeclType: swiftType) { + let javaType = cType.javaType + return TranslatedResult( + javaResultType: javaType, + outParameters: [], + conversion: .placeholder + ) + } - switch swiftType { - case .metatype(_): - // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' - let javaType = JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType") - return TranslatedResult( - javaResultType: javaType, - outParameters: [], - conversion: .construct(.placeholder, javaType) - ) + switch swiftType { + case .metatype(_): + // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' + let javaType = JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType") + return TranslatedResult( + javaResultType: javaType, + outParameters: [], + conversion: .construct(.placeholder, javaType) + ) - case .nominal(let swiftNominalType): - if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { - switch knownType { - case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: - return TranslatedResult( - javaResultType: .javaForeignMemorySegment, - outParameters: [ - JavaParameter(type: .javaForeignMemorySegment, name: "pointer"), - JavaParameter(type: .long, name: "count"), - ], - conversion: .method( - .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), - methodName: "reinterpret", - arguments: [ - .readMemorySegment(.explodedName(component: "count"), as: .long), + case .nominal(let swiftNominalType): + if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { + switch knownType { + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedResult( + javaResultType: .javaForeignMemorySegment, + outParameters: [ + JavaParameter(type: .javaForeignMemorySegment, name: "pointer"), + JavaParameter(type: .long, name: "count"), ], - withArena: false + conversion: .method( + .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), + methodName: "reinterpret", + arguments: [ + .readMemorySegment(.explodedName(component: "count"), as: .long), + ], + withArena: false + ) ) - ) - case .unsafePointer, .unsafeMutablePointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) - case .unsafeBufferPointer, .unsafeMutableBufferPointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) - case .string: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) - default: + case .unsafePointer, .unsafeMutablePointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + case .string: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + default: + throw JavaTranslationError.unhandledType(swiftType) + } + } + + // Generic types are not supported yet. + guard swiftNominalType.genericArguments == nil else { throw JavaTranslationError.unhandledType(swiftType) } - } - // Generic types are not supported yet. - guard swiftNominalType.genericArguments == nil else { - throw JavaTranslationError.unhandledType(swiftType) - } + let javaType: JavaType = .class(package: nil, name: swiftNominalType.nominalTypeDecl.name) + return TranslatedResult( + javaResultType: javaType, + outParameters: [ + JavaParameter(type: javaType, name: "") + ], + conversion: .constructSwiftValue(.placeholder, javaType) + ) - let javaType: JavaType = .class(package: nil, name: swiftNominalType.nominalTypeDecl.name) - return TranslatedResult( - javaResultType: javaType, - outParameters: [ - JavaParameter(type: javaType, name: "") - ], - conversion: .constructSwiftValue(.placeholder, javaType) - ) + case .tuple: + // TODO: Implement. + throw JavaTranslationError.unhandledType(swiftType) - case .tuple: - // TODO: Implement. - throw JavaTranslationError.unhandledType(swiftType) + case .optional, .function: + throw JavaTranslationError.unhandledType(swiftType) + } - case .optional, .function: - throw JavaTranslationError.unhandledType(swiftType) } - } - - func translate( - swiftType: SwiftType - ) throws -> JavaType { - guard let nominalName = swiftType.asNominalTypeDeclaration?.name else { - throw JavaTranslationError.unhandledType(swiftType) + func translate( + swiftType: SwiftType + ) throws -> JavaType { + guard let nominalName = swiftType.asNominalTypeDeclaration?.name else { + throw JavaTranslationError.unhandledType(swiftType) + } + return .class(package: nil, name: nominalName) } - return .class(package: nil, name: nominalName) } -} -/// Describes how to convert values between Java types and FFM types. -enum JavaConversionStep { - // The input - case placeholder + /// Describes how to convert values between Java types and FFM types. + enum JavaConversionStep { + // The input + case placeholder + + // The input exploded into components. + case explodedName(component: String) + + // A fixed value + case constant(String) - // The input exploded into components. - case explodedName(component: String) + // 'value.$memorySegment()' + indirect case swiftValueSelfSegment(JavaConversionStep) - // A fixed value - case constant(String) + // call specified function using the placeholder as arguments. + // If `withArena` is true, `arena$` argument is added. + indirect case call(JavaConversionStep, function: String, withArena: Bool) - // 'value.$memorySegment()' - indirect case swiftValueSelfSegment(JavaConversionStep) + // Apply a method on the placeholder. + // If `withArena` is true, `arena$` argument is added. + indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) - // call specified function using the placeholder as arguments. - // If `withArena` is true, `arena$` argument is added. - indirect case call(JavaConversionStep, function: String, withArena: Bool) + // Call 'new \(Type)(\(placeholder), swiftArena$)'. + indirect case constructSwiftValue(JavaConversionStep, JavaType) - // Apply a method on the placeholder. - // If `withArena` is true, `arena$` argument is added. - indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) + // Construct the type using the placeholder as arguments. + indirect case construct(JavaConversionStep, JavaType) - // Call 'new \(Type)(\(placeholder), swiftArena$)'. - indirect case constructSwiftValue(JavaConversionStep, JavaType) + // Casting the placeholder to the certain type. + indirect case cast(JavaConversionStep, JavaType) - // Construct the type using the placeholder as arguments. - indirect case construct(JavaConversionStep, JavaType) + // Convert the results of the inner steps to a comma separated list. + indirect case commaSeparated([JavaConversionStep]) - // Casting the placeholder to the certain type. - indirect case cast(JavaConversionStep, JavaType) + // Refer an exploded argument suffixed with `_\(name)`. + indirect case readMemorySegment(JavaConversionStep, as: JavaType) + + var isPlaceholder: Bool { + return if case .placeholder = self { true } else { false } + } + } +} - // Convert the results of the inner steps to a comma separated list. - indirect case commaSeparated([JavaConversionStep]) - // Refer an exploded argument suffixed with `_\(name)`. - indirect case readMemorySegment(JavaConversionStep, as: JavaType) +extension FFMSwift2JavaGenerator.TranslatedFunctionSignature { + /// Whether or not if the down-calling requires temporary "Arena" which is + /// only used during the down-calling. + var requiresTemporaryArena: Bool { + if self.parameters.contains(where: { $0.conversion.requiresTemporaryArena }) { + return true + } + if self.selfParameter?.conversion.requiresTemporaryArena ?? false { + return true + } + if self.result.conversion.requiresTemporaryArena { + return true + } + return false + } - var isPlaceholder: Bool { - return if case .placeholder = self { true } else { false } + /// Whether if the down-calling requires "SwiftArena" or not, which should be + /// passed-in by the API caller. This is needed if the API returns a `SwiftValue` + var requiresSwiftArena: Bool { + return self.result.conversion.requiresSwiftArena } } + extension CType { /// Map lowered C type to Java type for FFM binding. var javaType: JavaType { diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 32b3c8bf..76d53d88 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -78,6 +78,20 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } } + var isStatic: Bool { + if case .staticMethod = functionSignature.selfParameter { + return true + } + return false + } + + var isInitializer: Bool { + if case .initializer = functionSignature.selfParameter { + return true + } + 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+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 5d9713ab..cf1c27b1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -78,7 +78,7 @@ extension JNISwift2JavaGenerator { """ private long selfPointer; - private MyClass(long selfPointer) { + private \(decl.swiftNominal.name)(long selfPointer) { this.selfPointer = selfPointer; } """ @@ -165,7 +165,7 @@ extension JNISwift2JavaGenerator { ) } - /// Renders a function signature such + /// Renders a Java function signature /// /// `func method(x: Int, y: Int) -> Int` becomes /// `long method(long x, long y)` @@ -175,6 +175,6 @@ extension JNISwift2JavaGenerator { let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) let throwsClause = decl.isThrowing ? " throws Exception" : "" - return "\(resultType) \(decl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" + return "\(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index beac5e3b..bd1fbd72 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -32,14 +32,16 @@ extension JNISwift2JavaGenerator { struct JavaTranslation { func translate(_ decl: ImportedFunc) -> TranslatedFunctionDecl { let translatedFunctionSignature = translate(functionSignature: decl.functionSignature) + let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName return TranslatedFunctionDecl( name: decl.name, + parentName: parentName, translatedFunctionSignature: translatedFunctionSignature ) } - func translate(functionSignature: SwiftFunctionSignature) -> TranslatedFunctionSignature { + func translate(functionSignature: SwiftFunctionSignature, isInitializer: Bool = false) -> TranslatedFunctionSignature { let parameters = functionSignature.parameters.enumerated().map { idx, param in let parameterName = param.parameterName ?? "arg\(idx))" return translate(swiftParam: param, parameterName: parameterName) @@ -104,6 +106,9 @@ extension JNISwift2JavaGenerator { /// Java function name let name: String + /// The name of the parent scope this function is declared in (or nil if global) + let parentName: String? + /// Function signature let translatedFunctionSignature: TranslatedFunctionSignature } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 385962e1..542fc58a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import JavaTypes + extension JNISwift2JavaGenerator { func writeSwiftThunkSources() throws { var printer = CodePrinter() @@ -85,52 +87,61 @@ extension JNISwift2JavaGenerator { private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { printHeader(&printer) - for decl in type.methods { - printSwiftFunctionThunk(&printer, decl, sorroundingType: type) + for initializer in type.initializers { + printInitializerThunk(&printer, initializer) + printer.println() + } + + for method in type.methods { + printSwiftFunctionThunk(&printer, method) printer.println() } } - private func printSwiftFunctionThunk( - _ printer: inout CodePrinter, - _ decl: ImportedFunc, - sorroundingType: ImportedNominalType? = nil - ) { + private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { let translatedDecl = translatedDecl(for: decl) - let parentName = sorroundingType?.swiftNominal.qualifiedName ?? swiftModuleName + // Initializers must have a parent + let typeName = translatedDecl.parentName! - let cName = - "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") + "_\(parentName)_" - + decl.name - let thunkName = thunkNameRegistry.functionThunkName(decl: decl) - // TODO: Add a similair construct as `LoweredFunctionSignature` to `TranslatedFunctionSignature` - let translatedParameters = translatedDecl.translatedFunctionSignature.parameters.map { param in - (param.name, param.type) + printCDecl( + &printer, + javaMethodName: "allocatingInit", + parentName: translatedDecl.parentName, + parameters: translatedDecl.translatedFunctionSignature.parameters, + isStatic: true, + resultType: .long + ) { printer in + let downcallArguments = renderDowncallArguments( + swiftFunctionSignature: decl.functionSignature, + translatedFunctionSignature: translatedDecl.translatedFunctionSignature + ) + // TODO: Throwing initializers + printer.print( + """ + let selfPointer = UnsafeMutablePointer<\(typeName)>.allocate(capacity: 1) + selfPointer.initialize(to: \(typeName)(\(downcallArguments))) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + """ + ) } + } - let thunkParameters = - [ - "environment: UnsafeMutablePointer!", - "thisClass: jclass", - ] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)" } + private func printSwiftFunctionThunk( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + let translatedDecl = self.translatedDecl(for: decl) + let parentName = translatedDecl.parentName ?? swiftModuleName let swiftReturnType = decl.functionSignature.result.type - let thunkReturnType = - !swiftReturnType.isVoid ? " -> \(translatedDecl.translatedFunctionSignature.resultType.jniTypeName)" : "" - printer.printBraceBlock( - """ - @_cdecl("\(cName)") - func \(thunkName)(\(thunkParameters.joined(separator: ", ")))\(thunkReturnType) - """ - ) { printer in - let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { - originalParam, translatedParam in - let label = originalParam.argumentLabel.map { "\($0): " } ?? "" - return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)" - } + printCDecl(&printer, decl) { printer in + let downcallParameters = renderDowncallArguments( + swiftFunctionSignature: decl.functionSignature, + translatedFunctionSignature: translatedDecl.translatedFunctionSignature + ) let tryClause: String = decl.isThrowing ? "try " : "" let functionDowncall = - "\(tryClause)\(parentName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" + "\(tryClause)\(parentName).\(decl.name)(\(downcallParameters))" let innerBody = if swiftReturnType.isVoid { @@ -161,6 +172,76 @@ extension JNISwift2JavaGenerator { } } + private func printCDecl( + _ printer: inout CodePrinter, + _ decl: ImportedFunc, + _ body: (inout CodePrinter) -> Void + ) { + let translatedDecl = translatedDecl(for: decl) + let parentName = translatedDecl.parentName + + printCDecl( + &printer, + javaMethodName: translatedDecl.name, + parentName: parentName, + parameters: translatedDecl.translatedFunctionSignature.parameters, + isStatic: decl.isStatic || decl.isInitializer, + resultType: translatedDecl.translatedFunctionSignature.resultType, + body + ) + } + + private func printCDecl( + _ printer: inout CodePrinter, + javaMethodName: String, + parentName: String?, + parameters: [JavaParameter], + isStatic: Bool, + resultType: JavaType, + _ body: (inout CodePrinter) -> Void + ) { + let parentName = parentName ?? swiftModuleName + var jniSignature = parameters.reduce(into: "") { signature, parameter in + signature += parameter.type.jniTypeSignature + } + + // Escape signature characters + jniSignature = jniSignature + .replacingOccurrences(of: "_", with: "_1") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: ";", with: "_2") + .replacingOccurrences(of: "[", with: "_3") + + let cName = + "Java_" + + self.javaPackage.replacingOccurrences(of: ".", with: "_") + + "_\(parentName)_" + + javaMethodName + + "__" + + jniSignature + let translatedParameters = parameters.map { + "\($0.name): \($0.type.jniTypeName)" + } + let thisParameter = isStatic ? "thisClass: jclass" : "thisObject: jobject" + + let thunkParameters = + [ + "environment: UnsafeMutablePointer!", + thisParameter + ] + translatedParameters + let thunkReturnType = !resultType.isVoid ? " -> \(resultType.jniTypeName)" : "" + + // TODO: Think about function overloads + printer.printBraceBlock( + """ + @_cdecl("\(cName)") + func \(cName)(\(thunkParameters.joined(separator: ", ")))\(thunkReturnType) + """ + ) { printer in + body(&printer) + } + } + private func printHeader(_ printer: inout CodePrinter) { printer.print( """ @@ -171,4 +252,19 @@ extension JNISwift2JavaGenerator { """ ) } + + /// Renders the arguments for making a downcall + private func renderDowncallArguments( + swiftFunctionSignature: SwiftFunctionSignature, + translatedFunctionSignature: TranslatedFunctionSignature + ) -> String { + zip( + swiftFunctionSignature.parameters, + translatedFunctionSignature.parameters + ).map { originalParam, translatedParam in + let label = originalParam.argumentLabel.map { "\($0): " } ?? "" + return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.name), in: environment!)" + } + .joined(separator: ", ") + } } diff --git a/Sources/JavaTypes/JavaType+JNI.swift b/Sources/JavaTypes/JavaType+JNI.swift index 08361bac..2bb1239b 100644 --- a/Sources/JavaTypes/JavaType+JNI.swift +++ b/Sources/JavaTypes/JavaType+JNI.swift @@ -12,6 +12,12 @@ // //===----------------------------------------------------------------------===// +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + extension JavaType { /// Map this Java type to the appropriate JNI type name. package var jniTypeName: String { @@ -57,4 +63,25 @@ extension JavaType { case .void: fatalError("There is no field name for 'void'") } } + + package var jniTypeSignature: String { + switch self { + case .boolean: "Z" + case .byte: "B" + case .char: "C" + case .short: "S" + case .int: "I" + case .long: "J" + case .float: "F" + case .double: "D" + case .class(let package, let name): + if let package { + "L\(package.replacingOccurrences(of: ".", with: "/"))/\(name);" + } else { + "L\(name);" + } + case .array(let javaType): "[\(javaType.jniTypeSignature)" + case .void: fatalError("There is no type signature for 'void'") + } + } } diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index d01398b8..492ff459 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -45,6 +45,13 @@ extension JavaType { } } + public var isVoid: Bool { + if case .void = self { + return true + } + return false + } + public var isString: Bool { switch self { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index b7eab6eb..1809c80f 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -133,4 +133,32 @@ struct JNIClassTests { ] ) } + + @Test + func initializer_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass_allocatingInit__") + func Java_com_example_swift_MyClass_allocatingInit__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) + selfPointer.initialize(to: MyClass()) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass_allocatingInit__JJ") + func Java_com_example_swift_MyClass_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { + let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) + selfPointer.initialize(to: MyClass(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + } + """ + ] + ) + } } From 42ea1e311535f5c777401b1afda6c9ed201485ae Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 2 Jul 2025 21:57:42 +0200 Subject: [PATCH 5/9] delete duplicate main file --- .../com/example/swift/HelloJava2Swift.java | 50 ------------------- .../com/example/swift/HelloJava2SwiftJNI.java | 5 ++ 2 files changed, 5 insertions(+), 50 deletions(-) delete mode 100644 Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java deleted file mode 100644 index b140bbd8..00000000 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ /dev/null @@ -1,50 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -package com.example.swift; - -// Import swift-extract generated sources - -// Import javakit/swiftkit support libraries - -import org.swift.swiftkit.SwiftKit; - -public class HelloJava2Swift { - - public static void main(String[] args) { - System.out.print("Property: java.library.path = " + SwiftKit.getJavaLibraryPath()); - - examples(); - } - - static void examples() { - MySwiftLibrary.helloWorld(); - - MySwiftLibrary.globalTakeInt(1337); - MySwiftLibrary.globalTakeIntInt(1337, 42); - - long cnt = MySwiftLibrary.globalWriteString("String from Java"); - SwiftKit.trace("count = " + cnt); - - long i = MySwiftLibrary.globalMakeInt(); - SwiftKit.trace("globalMakeInt() = " + i); - - MySwiftClass.method(); - - MySwiftClass myClass = MySwiftClass.init(10, 5); - MySwiftClass myClass2 = MySwiftClass.init(); - - System.out.println("DONE."); - } -} diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 36bad93c..7d0d77e1 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -40,6 +40,11 @@ static void examples() { long i = MySwiftLibrary.globalMakeInt(); SwiftKit.trace("globalMakeInt() = " + i); + MySwiftClass.method(); + + MySwiftClass myClass = MySwiftClass.init(10, 5); + MySwiftClass myClass2 = MySwiftClass.init(); + System.out.println("DONE."); } } From 2377ddd4c56e4fa2cba78bc8b91e813615c393aa Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 3 Jul 2025 10:08:40 +0200 Subject: [PATCH 6/9] pull request feedback --- Samples/JExtractJNISampleApp/build.gradle | 5 ++ Samples/JExtractJNISampleApp/ci-validate.sh | 3 +- .../com/example/swift/MySwiftClassTest.java | 37 ++++++++++++ .../com/example/swift/MySwiftLibraryTest.java | 57 +++++++++++++++++++ ...MSwift2JavaGenerator+JavaTranslation.swift | 36 ++++-------- ...t2JavaGenerator+JavaBindingsPrinting.swift | 4 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 13 ----- Sources/JExtractSwiftLib/JavaParameter.swift | 25 ++++++++ 8 files changed, 139 insertions(+), 41 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java create mode 100644 Sources/JExtractSwiftLib/JavaParameter.swift diff --git a/Samples/JExtractJNISampleApp/build.gradle b/Samples/JExtractJNISampleApp/build.gradle index d0e32857..03794ff7 100644 --- a/Samples/JExtractJNISampleApp/build.gradle +++ b/Samples/JExtractJNISampleApp/build.gradle @@ -156,6 +156,11 @@ dependencies { tasks.named('test', Test) { useJUnitPlatform() + + testLogging { + events "failed" + exceptionFormat "full" + } } application { diff --git a/Samples/JExtractJNISampleApp/ci-validate.sh b/Samples/JExtractJNISampleApp/ci-validate.sh index 07b42627..c7a68d22 100755 --- a/Samples/JExtractJNISampleApp/ci-validate.sh +++ b/Samples/JExtractJNISampleApp/ci-validate.sh @@ -3,4 +3,5 @@ set -x set -e -./gradlew run \ No newline at end of file +./gradlew run +./gradlew test \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java new file mode 100644 index 00000000..994240cb --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import com.example.swift.MySwiftLibrary; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MySwiftClassTest { + @Test + void init_noParameters() { + MySwiftClass c = MySwiftClass.init(); + assertNotNull(c); + } + + @Test + void init_withParameters() { + MySwiftClass c = MySwiftClass.init(1337, 42); + assertNotNull(c); + } + +} \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java new file mode 100644 index 00000000..f1cefd19 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import com.example.swift.MySwiftLibrary; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +public class MySwiftLibraryTest { + @Test + void call_helloWorld() { + MySwiftLibrary.helloWorld(); + } + + @Test + void call_globalTakeInt() { + MySwiftLibrary.globalTakeInt(12); + } + + @Test + void call_globalMakeInt() { + long i = MySwiftLibrary.globalMakeInt(); + assertEquals(42, i); + } + + @Test + void call_globalTakeIntInt() { + MySwiftLibrary.globalTakeIntInt(1337, 42); + } + + @Test + void call_writeString_jextract() { + var string = "Hello Swift!"; + long reply = MySwiftLibrary.globalWriteString(string); + + assertEquals(string.length(), reply); + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 5c22233e..16efddea 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -35,15 +35,6 @@ extension FFMSwift2JavaGenerator { return translated } - /// Represent a parameter in Java code. - struct JavaParameter { - /// The type. - var type: JavaType - - /// The name. - var name: String - } - /// Represent a Swift API parameter translated to Java. struct TranslatedParameter { /// Java parameter(s) mapped to the Swift parameter. @@ -209,7 +200,7 @@ extension FFMSwift2JavaGenerator { if let cType = try? CType(cdeclType: type) { return TranslatedParameter( javaParameters: [ - JavaParameter(type: cType.javaType, name: parameterName) + JavaParameter(name: parameterName, type: cType.javaType) ], conversion: .placeholder ) @@ -222,7 +213,7 @@ extension FFMSwift2JavaGenerator { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedParameter( javaParameters: [ - JavaParameter(type: .javaForeignMemorySegment, name: parameterName) + JavaParameter(name: parameterName, type: .javaForeignMemorySegment) ], conversion: .method( .explodedName(component: "pointer"), @@ -309,8 +300,7 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - type: javaType, - name: parameterName + name: parameterName, type: javaType ) ], conversion: .placeholder @@ -323,8 +313,7 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType"), - name: parameterName) + name: parameterName, type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType")) ], conversion: .swiftValueSelfSegment(.placeholder) ) @@ -346,7 +335,7 @@ extension FFMSwift2JavaGenerator { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedParameter( javaParameters: [ - JavaParameter(type: .javaForeignMemorySegment, name: parameterName), + JavaParameter(name: parameterName, type: .javaForeignMemorySegment), ], conversion: .commaSeparated([ .placeholder, @@ -358,8 +347,7 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - type: .javaLangString, - name: parameterName + name: parameterName, type: .javaLangString ) ], conversion: .call(.placeholder, function: "SwiftKit.toCString", withArena: true) @@ -378,8 +366,7 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - type: try translate(swiftType: swiftType), - name: parameterName + name: parameterName, type: try translate(swiftType: swiftType) ) ], conversion: .swiftValueSelfSegment(.placeholder) @@ -393,8 +380,7 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)"), - name: parameterName) + name: parameterName, type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)")) ], conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) ) @@ -439,8 +425,8 @@ extension FFMSwift2JavaGenerator { return TranslatedResult( javaResultType: .javaForeignMemorySegment, outParameters: [ - JavaParameter(type: .javaForeignMemorySegment, name: "pointer"), - JavaParameter(type: .long, name: "count"), + JavaParameter(name: "pointer", type: .javaForeignMemorySegment), + JavaParameter(name: "count", type: .long), ], conversion: .method( .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), @@ -475,7 +461,7 @@ extension FFMSwift2JavaGenerator { return TranslatedResult( javaResultType: javaType, outParameters: [ - JavaParameter(type: javaType, name: "") + JavaParameter(name: "", type: javaType) ], conversion: .constructSwiftValue(.placeholder, javaType) ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index cf1c27b1..4c58e41a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -29,7 +29,7 @@ extension JNISwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: filename ) { - print("[swift-java] Generated: \(ty.swiftNominal.name.bold).java (at \(outputFile))") + logger.info("[swift-java] Generated: \(ty.swiftNominal.name.bold).java (at \(outputFile))") } } @@ -139,7 +139,7 @@ extension JNISwift2JavaGenerator { printDeclDocumentation(&printer, decl) printer.printBraceBlock("public static \(renderFunctionSignature(decl))") { printer in - let initArguments = translatedDecl.translatedFunctionSignature.parameters.map(\.asArgument) + let initArguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) printer.print( """ long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index bd1fbd72..0e1f435e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -113,19 +113,6 @@ extension JNISwift2JavaGenerator { let translatedFunctionSignature: TranslatedFunctionSignature } - struct JavaParameter { - let name: String - let type: JavaType - - var asParameter: String { - "\(type) \(name)" - } - - var asArgument: String { - name - } - } - struct TranslatedFunctionSignature { let parameters: [JavaParameter] let resultType: JavaType diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift new file mode 100644 index 00000000..72896419 --- /dev/null +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 + +/// Represent a parameter in Java code. +struct JavaParameter { + let name: String + let type: JavaType + + var asParameter: String { + "\(type) \(name)" + } +} From a3aadd0eaa510cfde475074b36ecd812db37253f Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 3 Jul 2025 10:17:14 +0200 Subject: [PATCH 7/9] load lib in classes --- ...ift2JavaGenerator+JavaBindingsPrinting.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 4c58e41a..ac775638 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -74,6 +74,21 @@ extension JNISwift2JavaGenerator { printPackage(&printer) printNominal(&printer, decl) { printer in + printer.print( + """ + static final String LIB_NAME = "\(swiftModuleName)"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + """ + ) + + printer.println() + printer.print( """ private long selfPointer; @@ -84,6 +99,8 @@ extension JNISwift2JavaGenerator { """ ) + printer.println() + for initializer in decl.initializers { printInitializerBindings(&printer, initializer, type: decl) } From 42cbfd55f2521a372631f0f999ec78e64e869939 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 3 Jul 2025 10:35:21 +0200 Subject: [PATCH 8/9] fix swift tests --- ...ISwift2JavaGenerator+JavaTranslation.swift | 11 ++++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 8 ++--- .../JNI/JNIClassTests.swift | 15 ++++++++-- .../JNI/JNIModuleTests.swift | 30 +++++++++---------- 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 0e1f435e..a5df702c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -22,7 +22,7 @@ extension JNISwift2JavaGenerator { return cached } - let translation = JavaTranslation() + let translation = JavaTranslation(swiftModuleName: self.swiftModuleName) let translated = translation.translate(decl) translatedDecls[decl] = translated @@ -30,9 +30,12 @@ extension JNISwift2JavaGenerator { } struct JavaTranslation { + let swiftModuleName: String + func translate(_ decl: ImportedFunc) -> TranslatedFunctionDecl { let translatedFunctionSignature = translate(functionSignature: decl.functionSignature) - let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName + // Types with no parent will be outputted inside a "module" class. + let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName return TranslatedFunctionDecl( name: decl.name, @@ -106,8 +109,8 @@ extension JNISwift2JavaGenerator { /// Java function name let name: String - /// The name of the parent scope this function is declared in (or nil if global) - let parentName: String? + /// The name of the Java parent scope this function is declared in + let parentName: String /// Function signature let translatedFunctionSignature: TranslatedFunctionSignature diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 542fc58a..c61dd929 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -100,8 +100,7 @@ extension JNISwift2JavaGenerator { private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { let translatedDecl = translatedDecl(for: decl) - // Initializers must have a parent - let typeName = translatedDecl.parentName! + let typeName = translatedDecl.parentName printCDecl( &printer, @@ -185,7 +184,7 @@ extension JNISwift2JavaGenerator { javaMethodName: translatedDecl.name, parentName: parentName, parameters: translatedDecl.translatedFunctionSignature.parameters, - isStatic: decl.isStatic || decl.isInitializer, + isStatic: decl.isStatic || decl.isInitializer || !decl.hasParent, resultType: translatedDecl.translatedFunctionSignature.resultType, body ) @@ -194,13 +193,12 @@ extension JNISwift2JavaGenerator { private func printCDecl( _ printer: inout CodePrinter, javaMethodName: String, - parentName: String?, + parentName: String, parameters: [JavaParameter], isStatic: Bool, resultType: JavaType, _ body: (inout CodePrinter) -> Void ) { - let parentName = parentName ?? swiftModuleName var jniSignature = parameters.reduce(into: "") { signature, parameter in signature += parameter.type.jniTypeSignature } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 1809c80f..c39d5815 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -45,7 +45,16 @@ struct JNIClassTests { package com.example.swift; - public final class MyClass { + public final class MyClass { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + private long selfPointer; private MyClass(long selfPointer) { @@ -84,8 +93,8 @@ struct JNIClassTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_MyClass_method") - func swiftjava_SwiftModule_MyClass_method(environment: UnsafeMutablePointer!, thisClass: jclass) { + @_cdecl("Java_com_example_swift_MyClass_method__") + func Java_com_example_swift_MyClass_method__(environment: UnsafeMutablePointer!, thisClass: jclass) { MyClass.method() } """ diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index e483a4dd..d6a030a8 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -19,7 +19,7 @@ import Testing struct JNIModuleTests { let globalMethodWithPrimitives = """ public func helloWorld() - public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int) -> UInt16 + public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 public func otherPrimitives(b: Bool, f: Float, d: Double) """ @@ -73,7 +73,7 @@ struct JNIModuleTests { /** * Downcall to Swift: * {@snippet lang=swift : - * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int) -> UInt16 + * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 * } */ public static native char takeIntegers(byte i1, short i2, int i3, long i4); @@ -100,21 +100,21 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_helloWorld") - func swiftjava_SwiftModule_helloWorld(environment: UnsafeMutablePointer!, thisClass: jclass) { + @_cdecl("Java_com_example_swift_SwiftModule_helloWorld__") + func Java_com_example_swift_SwiftModule_helloWorld__(environment: UnsafeMutablePointer!, thisClass: jclass) { SwiftModule.helloWorld() } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_takeIntegers") - func swiftjava_SwiftModule_takeIntegers_i1_i2_i3_i4(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { - let result = SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int(fromJNI: i4, in: environment!)) + @_cdecl("Java_com_example_swift_SwiftModule_takeIntegers__BSIJ") + func Java_com_example_swift_SwiftModule_takeIntegers__BSIJ(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { + let result = SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int64(fromJNI: i4, in: environment!)) return result.getJNIValue(in: environment) } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_otherPrimitives") - func swiftjava_SwiftModule_otherPrimitives_b_f_d(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) { + @_cdecl("Java_com_example_swift_SwiftModule_otherPrimitives__ZFD") + func Java_com_example_swift_SwiftModule_otherPrimitives__ZFD(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) { SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment!), f: Float(fromJNI: f, in: environment!), d: Double(fromJNI: d, in: environment!)) } """ @@ -151,8 +151,8 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_copy") - func swiftjava_SwiftModule_copy__(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { + @_cdecl("Java_com_example_swift_SwiftModule_copy__Ljava_lang_String_2") + func Java_com_example_swift_SwiftModule_copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { let result = SwiftModule.copy(String(fromJNI: string, in: environment!)) return result.getJNIValue(in: environment) } @@ -199,8 +199,8 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_methodA") - func swiftjava_SwiftModule_methodA(environment: UnsafeMutablePointer!, thisClass: jclass) { + @_cdecl("Java_com_example_swift_SwiftModule_methodA__") + func Java_com_example_swift_SwiftModule_methodA__(environment: UnsafeMutablePointer!, thisClass: jclass) { do { try SwiftModule.methodA() } catch { @@ -209,8 +209,8 @@ struct JNIModuleTests { } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_methodB") - func swiftjava_SwiftModule_methodB(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + @_cdecl("Java_com_example_swift_SwiftModule_methodB__") + func Java_com_example_swift_SwiftModule_methodB__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { do { let result = try SwiftModule.methodB() return result.getJNIValue(in: environment) From 9abac424216b90d8455b602327d526dd2c2f2a6c Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 3 Jul 2025 11:03:49 +0200 Subject: [PATCH 9/9] move jni type signature to jextract instead of JavaTypes --- .../Convenience/JavaType+Extensions.swift | 38 +++++++++++++++++++ Sources/JavaTypes/JavaType+JNI.swift | 27 ------------- 2 files changed, 38 insertions(+), 27 deletions(-) create mode 100644 Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift new file mode 100644 index 00000000..9da3ae5b --- /dev/null +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 + +extension JavaType { + var jniTypeSignature: String { + switch self { + case .boolean: "Z" + case .byte: "B" + case .char: "C" + case .short: "S" + case .int: "I" + case .long: "J" + case .float: "F" + case .double: "D" + case .class(let package, let name): + if let package { + "L\(package.replacingOccurrences(of: ".", with: "/"))/\(name);" + } else { + "L\(name);" + } + case .array(let javaType): "[\(javaType.jniTypeSignature)" + case .void: fatalError("There is no type signature for 'void'") + } + } +} diff --git a/Sources/JavaTypes/JavaType+JNI.swift b/Sources/JavaTypes/JavaType+JNI.swift index 2bb1239b..08361bac 100644 --- a/Sources/JavaTypes/JavaType+JNI.swift +++ b/Sources/JavaTypes/JavaType+JNI.swift @@ -12,12 +12,6 @@ // //===----------------------------------------------------------------------===// -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif - extension JavaType { /// Map this Java type to the appropriate JNI type name. package var jniTypeName: String { @@ -63,25 +57,4 @@ extension JavaType { case .void: fatalError("There is no field name for 'void'") } } - - package var jniTypeSignature: String { - switch self { - case .boolean: "Z" - case .byte: "B" - case .char: "C" - case .short: "S" - case .int: "I" - case .long: "J" - case .float: "F" - case .double: "D" - case .class(let package, let name): - if let package { - "L\(package.replacingOccurrences(of: ".", with: "/"))/\(name);" - } else { - "L\(name);" - } - case .array(let javaType): "[\(javaType.jniTypeSignature)" - case .void: fatalError("There is no type signature for 'void'") - } - } }