diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift new file mode 100644 index 00000000..fca6236f --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 { + 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/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/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."); } } 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/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/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..16efddea 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -34,538 +34,526 @@ 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 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 + } - /// 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] + + /// 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 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) + /// Translated Java API representing a Swift API. /// - /// 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 -} + /// 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 + /// Functional interfaces required for the Java method. + let functionTypes: [TranslatedFunctionType] -/// 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 + /// Function signature. + let translatedSignature: TranslatedFunctionSignature - /// Functional interfaces required for the Java method. - let functionTypes: [TranslatedFunctionType] - - /// Function signature. - let translatedSignature: TranslatedFunctionSignature - - /// Cdecl lowerd signature. - let loweredSignature: LoweredFunctionSignature -} - -/// Function signature for a Java API. -struct TranslatedFunctionSignature { - var selfParameter: TranslatedParameter? - var parameters: [TranslatedParameter] - var result: TranslatedResult -} - -/// 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(name: parameterName, type: cType.javaType) + ], + conversion: .placeholder + ) + } + + switch type { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + switch knownType { + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: .javaForeignMemorySegment) ], - 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( + name: parameterName, type: javaType + ) + ], + 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( + name: parameterName, type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType")) + ], + 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(name: parameterName, type: .javaForeignMemorySegment), + ], + conversion: .commaSeparated([ + .placeholder, + .method(.placeholder, methodName: "byteSize", arguments: [], withArena: false) + ]) + ) - default: + case .string: + return TranslatedParameter( + javaParameters: [ + JavaParameter( + name: parameterName, type: .javaLangString + ) + ], + 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( + name: parameterName, type: try translate(swiftType: swiftType) + ) + ], + 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( + name: parameterName, type: JavaType.class(package: nil, name: "\(methodName).\(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(name: "pointer", type: .javaForeignMemorySegment), + JavaParameter(name: "count", type: .long), ], - 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(name: "", type: javaType) + ], + 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) + // The input exploded into components. + case explodedName(component: String) - // A fixed value - case constant(String) + // A fixed value + case constant(String) - // 'value.$memorySegment()' - indirect case swiftValueSelfSegment(JavaConversionStep) + // 'value.$memorySegment()' + indirect case swiftValueSelfSegment(JavaConversionStep) - // 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 specified function using the placeholder as arguments. + // If `withArena` is true, `arena$` argument is added. + indirect case call(JavaConversionStep, function: String, withArena: Bool) - // Apply a method on the placeholder. - // If `withArena` is true, `arena$` argument is added. - indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) + // 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 'new \(Type)(\(placeholder), swiftArena$)'. - indirect case constructSwiftValue(JavaConversionStep, JavaType) + // Call 'new \(Type)(\(placeholder), swiftArena$)'. + indirect case constructSwiftValue(JavaConversionStep, JavaType) - // Construct the type using the placeholder as arguments. - indirect case construct(JavaConversionStep, JavaType) + // Construct the type using the placeholder as arguments. + indirect case construct(JavaConversionStep, JavaType) - // Casting the placeholder to the certain type. - indirect case cast(JavaConversionStep, JavaType) + // Casting the placeholder to the certain type. + indirect case cast(JavaConversionStep, JavaType) - // Convert the results of the inner steps to a comma separated list. - indirect case commaSeparated([JavaConversionStep]) + // 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) + // Refer an exploded argument suffixed with `_\(name)`. + indirect case readMemorySegment(JavaConversionStep, as: JavaType) - var isPlaceholder: Bool { - return if case .placeholder = self { true } else { false } + var isPlaceholder: Bool { + return if case .placeholder = self { true } else { false } + } } } + +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 + } + + /// 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 0a5952f3..76d53d88 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 { @@ -74,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 new file mode 100644 index 00000000..ac775638 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -0,0 +1,197 @@ +//===----------------------------------------------------------------------===// +// +// 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 + ) { + logger.info("[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 + 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; + + private \(decl.swiftNominal.name)(long selfPointer) { + this.selfPointer = selfPointer; + } + """ + ) + + printer.println() + + for initializer in decl.initializers { + printInitializerBindings(&printer, initializer, type: decl) + } + + 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) { + 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(\.name) + printer.print( + """ + long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); + return new \(type.qualifiedName)(selfPointer); + """ + ) + } + + 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( + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ + """ + ) + } + + /// Renders a Java function signature + /// + /// `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) \(translatedDecl.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..a5df702c --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -0,0 +1,123 @@ +//===----------------------------------------------------------------------===// +// +// 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(swiftModuleName: self.swiftModuleName) + let translated = translation.translate(decl) + + translatedDecls[decl] = translated + return translated + } + + struct JavaTranslation { + let swiftModuleName: String + + func translate(_ decl: ImportedFunc) -> TranslatedFunctionDecl { + let translatedFunctionSignature = translate(functionSignature: decl.functionSignature) + // Types with no parent will be outputted inside a "module" class. + let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName + + return TranslatedFunctionDecl( + name: decl.name, + parentName: parentName, + translatedFunctionSignature: 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) + } + + 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 + + /// The name of the Java parent scope this function is declared in + let parentName: String + + /// Function signature + let translatedFunctionSignature: TranslatedFunctionSignature + } + + struct TranslatedFunctionSignature { + let parameters: [JavaParameter] + let resultType: JavaType + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift new file mode 100644 index 00000000..c61dd929 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -0,0 +1,268 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +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 initializer in type.initializers { + printInitializerThunk(&printer, initializer) + printer.println() + } + + for method in type.methods { + printSwiftFunctionThunk(&printer, method) + printer.println() + } + } + + private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + let translatedDecl = translatedDecl(for: decl) + let typeName = translatedDecl.parentName + + 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) + """ + ) + } + } + + 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 + + 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))" + + 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 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 || !decl.hasParent, + 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 + ) { + 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( + """ + // Generated by swift-java + + import JavaKit + + """ + ) + } + + /// 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/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 0f16e697..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,262 +74,3 @@ 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 { - 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/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)" + } +} 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 new file mode 100644 index 00000000..c39d5815 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -0,0 +1,173 @@ +//===----------------------------------------------------------------------===// +// +// 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 { + 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 + } + } + """ + + @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 { + 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) { + this.selfPointer = selfPointer; + } + """, + ]) + } + + @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 Java_com_example_swift_MyClass_method__(environment: UnsafeMutablePointer!, thisClass: jclass) { + MyClass.method() + } + """ + ] + ) + } + + @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(); + """ + ] + ) + } + + @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) + } + """ + ] + ) + } +} 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)