diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift index 985771bef..a874c5954 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift @@ -28,6 +28,11 @@ public protocol CallbackProtocol { func withObject(_ input: MySwiftClass) -> MySwiftClass func withOptionalInt64(_ input: Int64?) -> Int64? func withOptionalObject(_ input: MySwiftClass?) -> Optional + func withInt64Array(_ input: [Int64]) -> [Int64] + func withStringArray(_ input: [String]) -> [String] + func withObjectArray(_ input: [MySwiftClass]) -> [MySwiftClass] + func successfulThrowingFunction() throws + func throwingFunction() throws } public struct CallbackOutput { @@ -43,6 +48,21 @@ public struct CallbackOutput { public let object: MySwiftClass public let optionalInt64: Int64? public let optionalObject: MySwiftClass? + public let int64Array: [Int64] + public let stringArray: [String] + public let objectArray: [MySwiftClass] +} + +public func callProtocolVoid(_ callbacks: some CallbackProtocol) { + callbacks.withVoid(); +} + +public func callProtocolWithFailedThrowingFunction(_ callbacks: some CallbackProtocol) throws { + try callbacks.throwingFunction(); +} + +public func callProtocolWithSuccessfulThrowingFunction(_ callbacks: some CallbackProtocol) throws { + try callbacks.successfulThrowingFunction(); } public func outputCallbacks( @@ -58,7 +78,10 @@ public func outputCallbacks( string: String, object: MySwiftClass, optionalInt64: Int64?, - optionalObject: MySwiftClass? + optionalObject: MySwiftClass?, + int64Array: [Int64], + stringArray: [String], + objectArray: [MySwiftClass] ) -> CallbackOutput { return CallbackOutput( bool: callbacks.withBool(bool), @@ -72,6 +95,9 @@ public func outputCallbacks( string: callbacks.withString(string), object: callbacks.withObject(object), optionalInt64: callbacks.withOptionalInt64(optionalInt64), - optionalObject: callbacks.withOptionalObject(optionalObject) + optionalObject: callbacks.withOptionalObject(optionalObject), + int64Array: callbacks.withInt64Array(int64Array), + stringArray: callbacks.withStringArray(stringArray), + objectArray: callbacks.withObjectArray(objectArray) ) } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java index b4ebe8532..6d70d0194 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java @@ -87,6 +87,50 @@ public OptionalLong withOptionalInt64(OptionalLong input) { public Optional withOptionalObject(Optional input, SwiftArena swiftArena$) { return input; } + + @Override + public long[] withInt64Array(long[] input) { + return input; + } + + @Override + public String[] withStringArray(String[] input) { + return input; + } + + @Override + public MySwiftClass[] withObjectArray(MySwiftClass[] input, SwiftArena swiftArena$) { + return input; + } + + @Override + public void throwingFunction() throws Exception { + throw new Exception("Failed in Java"); + } + + @Override + public void successfulThrowingFunction() throws Exception { + + } + } + + @Test + void voidTest() { + JavaCallbacks callbacks = new JavaCallbacks(); + MySwiftLibrary.callProtocolVoid(callbacks); + } + + @Test + void throwingFunction_thatDoesNotThrow() { + JavaCallbacks callbacks = new JavaCallbacks(); + assertDoesNotThrow(() -> MySwiftLibrary.callProtocolWithSuccessfulThrowingFunction(callbacks)); + } + + @Test + void throwingFunction_thatThrows() { + JavaCallbacks callbacks = new JavaCallbacks(); + Exception exception = assertThrows(Exception.class, () -> MySwiftLibrary.callProtocolWithFailedThrowingFunction(callbacks)); + assertEquals("Failed in Java", exception.getMessage()); } @Test @@ -95,7 +139,28 @@ void primitiveCallbacks() { JavaCallbacks callbacks = new JavaCallbacks(); var object = MySwiftClass.init(5, 3, arena); var optionalObject = Optional.of(MySwiftClass.init(10, 10, arena)); - var output = MySwiftLibrary.outputCallbacks(callbacks, true, (byte) 1, (char) 16, (short) 16, (int) 32, 64L, 1.34f, 1.34, "Hello from Java!", object, OptionalLong.empty(), optionalObject, arena); + var int64Array = new long[]{1, 2, 3}; + var stringArray = new String[]{"Hey", "there"}; + var objectArray = new MySwiftClass[]{MySwiftClass.init(1, 1, arena), MySwiftClass.init(2, 2, arena)}; + var output = MySwiftLibrary.outputCallbacks( + callbacks, + true, + (byte) 1, + (char) 16, + (short) 16, + (int) 32, + 64L, + 1.34f, + 1.34, + "Hello from Java!", + object, + OptionalLong.empty(), + optionalObject, + int64Array, + stringArray, + objectArray, + arena + ); assertEquals(1, output.getInt8()); assertEquals(16, output.getUint16()); @@ -112,6 +177,14 @@ void primitiveCallbacks() { var optionalObjectOutput = output.getOptionalObject(arena); assertTrue(optionalObjectOutput.isPresent()); assertEquals(10, optionalObjectOutput.get().getX()); + + assertArrayEquals(new long[]{1, 2,3}, output.getInt64Array()); + assertArrayEquals(new String[]{"Hey", "there"}, output.getStringArray()); + + var objectArrayOutput = output.getObjectArray(arena); + assertEquals(2, objectArrayOutput.length); + assertEquals(1, objectArrayOutput[0].getX()); + assertEquals(2, objectArrayOutput[1].getX()); } } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift index f1e7c6851..abf67f3e3 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift @@ -133,6 +133,10 @@ extension JNISwift2JavaGenerator { private func translateParameter(parameterName: String, type: SwiftType) throws -> UpcallConversionStep { + if type.isDirectlyTranslatedToWrapJava { + return .placeholder + } + switch type { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { @@ -146,12 +150,17 @@ extension JNISwift2JavaGenerator { wrappedType: genericArgs[0] ) - default: - guard knownType.isDirectlyTranslatedToWrapJava else { + case .array: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { throw JavaTranslationError.unsupportedSwiftType(type) } + return try translateArrayParameter( + name: parameterName, + elementType: genericArgs[0] + ) - return .placeholder + default: + throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -171,11 +180,32 @@ extension JNISwift2JavaGenerator { wrappedType: wrappedType ) - case .genericParameter, .function, .metatype, .tuple, .existential, .opaque, .composite, .array: + case .array(let elementType): + return try translateArrayParameter(name: parameterName, elementType: elementType) + + case .genericParameter, .function, .metatype, .tuple, .existential, .opaque, .composite: throw JavaTranslationError.unsupportedSwiftType(type) } } + private func translateArrayParameter(name: String, elementType: SwiftType) throws -> UpcallConversionStep { + switch elementType { + case .nominal(let nominalType): + // We assume this is a JExtracted type + return .map( + .placeholder, + body: .toJavaWrapper( + .placeholder, + name: "arrayElement", + nominalType: nominalType + ) + ) + + case .array, .composite, .existential, .function, .genericParameter, .metatype, .opaque, .optional, .tuple: + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + } + private func translateOptionalParameter(name: String, wrappedType: SwiftType) throws -> UpcallConversionStep { let wrappedConversion = try translateParameter(parameterName: name, type: wrappedType) return .toJavaOptional(.map(.placeholder, body: wrappedConversion)) @@ -190,6 +220,10 @@ extension JNISwift2JavaGenerator { methodName: String, allowNilForObjects: Bool = false ) throws -> UpcallConversionStep { + if type.isDirectlyTranslatedToWrapJava { + return .placeholder + } + switch type { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { @@ -203,11 +237,14 @@ extension JNISwift2JavaGenerator { methodName: methodName ) - default: - guard knownType.isDirectlyTranslatedToWrapJava else { + case .array: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { throw JavaTranslationError.unsupportedSwiftType(type) } - return .placeholder + return try self.translateArrayResult(elementType: genericArgs[0]) + + default: + throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -228,11 +265,32 @@ extension JNISwift2JavaGenerator { case .optional(let wrappedType): return try self.translateOptionalResult(wrappedType: wrappedType, methodName: methodName) - case .genericParameter, .function, .metatype, .tuple, .existential, .opaque, .composite, .array: + case .array(let elementType): + return try self.translateArrayResult(elementType: elementType) + + case .genericParameter, .function, .metatype, .tuple, .existential, .opaque, .composite: throw JavaTranslationError.unsupportedSwiftType(type) } } + private func translateArrayResult(elementType: SwiftType) throws -> UpcallConversionStep { + switch elementType { + case .nominal(let nominalType): + // We assume this is a JExtracted type + return .map( + .placeholder, + body: .toSwiftClass( + .unwrapOptional(.placeholder, message: "Element of array was nil"), + name: "arrayElement", + nominalType: nominalType + ) + ) + + case .array, .composite, .existential, .function, .genericParameter, .metatype, .opaque, .optional, .tuple: + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + } + private func translateOptionalResult(wrappedType: SwiftType, methodName: String) throws -> UpcallConversionStep { // The `fromJavaOptional` will handle the nullability let wrappedConversion = try translateResult( @@ -340,3 +398,31 @@ extension JNISwift2JavaGenerator { } } } + +extension SwiftType { + /// Indicates whether this type is translated by `wrap-java` + /// into the same type as `jextract`. + /// + /// This means we do not have to perform any mapping when passing + /// this type between jextract and wrap-java + var isDirectlyTranslatedToWrapJava: Bool { + switch self { + case .nominal(let swiftNominalType): + guard let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind else { + return false + } + switch knownType { + case .bool, .int, .uint, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64, .float, .double, .string, .void: + return true + default: + return false + } + + case .array(let elementType): + return elementType.isDirectlyTranslatedToWrapJava + + case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque, .composite: + return false + } + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 13955430a..687767b36 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -153,7 +153,8 @@ extension JNISwift2JavaGenerator { conversion.render(&printer, param.parameterName!) } - let javaUpcall = "\(wrapper.javaInterfaceVariableName).\(function.swiftFunctionName)(\(upcallArguments.joined(separator: ", ")))" + let tryClause = function.originalFunctionSignature.isThrowing ? "try " : "" + let javaUpcall = "\(tryClause)\(wrapper.javaInterfaceVariableName).\(function.swiftFunctionName)(\(upcallArguments.joined(separator: ", ")))" let resultType = function.originalFunctionSignature.result.type let result = function.resultConversion.render(&printer, javaUpcall)