diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift index 3c08cb56..b5842301 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift @@ -91,3 +91,11 @@ public func multipleOptionals( ) -> Int64? { 1 } + +public func optionalTuple() -> (Int64, String)? { + (42, "hello") +} + +public func optionalTuple2() -> (Int64?, Alignment?)? { + (42, .horizontal) +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift index b3038f97..d92ad184 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift @@ -45,3 +45,7 @@ public func makeBigTuple() -> ( public func namedByteArrayTuple() -> (name: [UInt8], another: [UInt8]) { (name: [1, 2, 3], another: [4, 5]) } + +public func genericTypeTuple() -> (MyID, Alignment) { + (MyID(1.23), .horizontal) +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java index 771cffc3..6db74849 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java @@ -143,4 +143,30 @@ void optionalThrows() { assertEquals("swiftError", exception.getMessage()); } } -} \ No newline at end of file + + @Test + void optionalTuple() { + var result = MySwiftLibrary.optionalTuple(); + assertDoesNotThrow(() -> { + var resultUnwrapped = result.orElseThrow(); + assertEquals(42, resultUnwrapped.$0); + assertEquals("hello", resultUnwrapped.$1); + }); + } + + @Test + void optionalTuple2() { + try (var arena = SwiftArena.ofConfined()) { + var result = MySwiftLibrary.optionalTuple2(arena); + assertDoesNotThrow(() -> { + var resultUnwrapped = result.orElseThrow(); + assertDoesNotThrow(() -> { + assertEquals(42, resultUnwrapped.$0.orElseThrow()); + }); + assertDoesNotThrow(() -> { + assertEquals(Alignment.Discriminator.HORIZONTAL, resultUnwrapped.$1.orElseThrow().getDiscriminator()); + }); + }); + } + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java index 1a110eb2..cc89e124 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java @@ -15,6 +15,7 @@ package com.example.swift; import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; import org.swift.swiftkit.core.tuple.Tuple2; import org.swift.swiftkit.core.tuple.Tuple3; import org.swift.swiftkit.core.tuple.Tuple16; @@ -95,4 +96,13 @@ void namedByteArrayTuple() { assertArrayEquals(new byte[] { 1, 2, 3 }, (byte[]) result.$0); assertArrayEquals(new byte[] { 4, 5 }, (byte[]) result.$1); } + + @Test + void genericTypeTuple() { + try (var arena = SwiftArena.ofConfined()) { + var result = MySwiftLibrary.genericTypeTuple(arena); + assertEquals("1.23", result.$0.getDescription()); + assertEquals(Alignment.Discriminator.HORIZONTAL, result.$1.getDiscriminator()); + } + } } diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index 53844559..b2d5394a 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -88,6 +88,15 @@ extension JavaType { } } + var swiftJniPlaceholderExpr: String { + switch self { + case .boolean, .byte, .char, .short, .int, .long: "0" + case .float, .double: "0.0" + case .array, .class: "nil" + case .void: "()" + } + } + var jniCallMethodAName: String { switch self { case .boolean: "CallBooleanMethodA" diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index dbf357f3..3e4b06c1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -1049,6 +1049,13 @@ extension JNISwift2JavaGenerator { if let knownType = nominalType.asKnownType { switch knownType { case .optional(let wrapped): + if let wrappedKnownKind = wrapped.asNominalTypeDeclaration?.knownTypeKind, + let javaType = JNIJavaTypeTranslator.translate(knownType: wrappedKnownKind, config: self.config), + let optionalType = javaType.optionalType + { + return .class(package: nil, name: optionalType) + } + let wrappedType = try translateGenericTypeParameter( wrapped, genericParameters: genericParameters, @@ -1141,7 +1148,17 @@ extension JNISwift2JavaGenerator { } return .class(package: nil, name: generic.name) - case .metatype, .tuple, .function, .existential, .opaque, .composite: + case .tuple(let elements): + let elementJavaTypes = try elements.map { element in + try translateGenericTypeParameter( + element.type, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + } + return .tuple(elementTypes: elementJavaTypes) + + case .metatype, .function, .existential, .opaque, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -1259,22 +1276,11 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - switch knownType { - case .foundationDate, .essentialsDate: - // Handled as wrapped struct - break - - case .foundationData, .essentialsData: - // Handled as wrapped struct - break - - default: - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { - throw JavaTranslationError.unsupportedSwiftType(swiftType) - } - + if let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue + { guard let returnType = javaType.optionalType, let optionalClass = javaType.optionalWrapperType else { - throw JavaTranslationError.unsupportedSwiftType(swiftType) + break } // Check if we can fit the value and a discriminator byte in a primitive. @@ -1320,41 +1326,44 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftType) } - // We assume this is a JExtract class. - let javaType = try translateGenericTypeParameter( - swiftType, - genericParameters: genericParameters, - genericRequirements: genericRequirements, - ) - - let wrappedValueResult = try translateResult( - swiftType: swiftType, - methodName: methodName, - resultName: resultName + "Wrapped$", - genericParameters: genericParameters, - genericRequirements: genericRequirements, - ) - - let returnType = JavaType.optional(javaType) - return TranslatedResult( - javaType: returnType, - nativeJavaType: wrappedValueResult.nativeJavaType, - annotations: parameterAnnotations, - outParameters: [ - OutParameter(name: discriminatorName, type: .array(.byte), allocation: .newArray(.byte, size: 1)) - ] + wrappedValueResult.outParameters, - conversion: .toOptionalFromIndirectReturn( - discriminatorName: .constant(discriminatorName), - optionalClass: "Optional", - nativeResultJavaType: wrappedValueResult.nativeJavaType, - toValue: wrappedValueResult.conversion, - resultName: resultName - ) - ) + case .tuple: + break default: throw JavaTranslationError.unsupportedSwiftType(swiftType) } + + // Common indirect conversion + let javaType = try translateGenericTypeParameter( + swiftType, + genericParameters: genericParameters, + genericRequirements: genericRequirements, + ) + + let wrappedValueResult = try translateResult( + swiftType: swiftType, + methodName: methodName, + resultName: resultName + "Wrapped$", + genericParameters: genericParameters, + genericRequirements: genericRequirements, + ) + + let returnType = JavaType.optional(javaType) + return TranslatedResult( + javaType: returnType, + nativeJavaType: wrappedValueResult.nativeJavaType, + annotations: parameterAnnotations, + outParameters: [ + OutParameter(name: discriminatorName, type: .array(.byte), allocation: .newArray(.byte, size: 1)) + ] + wrappedValueResult.outParameters, + conversion: .toOptionalFromIndirectReturn( + discriminatorName: .constant(discriminatorName), + optionalClass: "Optional", + nativeResultJavaType: wrappedValueResult.nativeJavaType, + toValue: wrappedValueResult.conversion, + resultName: resultName + ) + ) } func translateArrayParameter( diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 1391499e..0b210bbf 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -531,23 +531,9 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - switch knownType { - case .foundationDate, .essentialsDate: - // Handled as wrapped struct - break - - case .foundationData, .essentialsData: - // Handled as wrapped struct - break - - default: - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), - javaType.implementsJavaValue - else { - self.logger.debug("Known type \(knownType) is not supported for optional results, skipping.") - throw JavaTranslationError.unsupportedSwiftType(swiftType) - } - + if let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue + { // Check if we can fit the value and a discriminator byte in a primitive. // so the return JNI value will be (value, discriminator) if let nextIntergralTypeWithSpaceForByte = javaType.nextIntergralTypeWithSpaceForByte { @@ -571,12 +557,9 @@ extension JNISwift2JavaGenerator { javaType: javaType, conversion: .optionalRaisingIndirectReturn( .getJNIValue(.placeholder), + resultName: "\(resultName)$", returnType: javaType, - discriminatorParameterName: discriminatorName, - placeholderValue: .member( - .constant("\(swiftType)"), - member: "jniPlaceholderValue" - ) + discriminatorParameterName: discriminatorName ), outParameters: [ JavaParameter(name: discriminatorName, type: .array(.byte)) @@ -591,29 +574,31 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftType) } - let wrappedValueResult = try translateResult( - swiftType: swiftType, - methodName: methodName, - resultName: resultName + "Wrapped" - ) - - // Assume JExtract imported class - return NativeResult( - javaType: wrappedValueResult.javaType, - conversion: .optionalRaisingIndirectReturn( - wrappedValueResult.conversion, - returnType: wrappedValueResult.javaType, - discriminatorParameterName: discriminatorName, - placeholderValue: .constant("0") - ), - outParameters: [ - JavaParameter(name: discriminatorName, type: .array(.byte)) - ] + wrappedValueResult.outParameters - ) + case .tuple: + break default: throw JavaTranslationError.unsupportedSwiftType(swiftType) } + + // Common indirect conversion + let wrappedValueResult = try translateResult( + swiftType: swiftType, + methodName: methodName, + resultName: resultName + "Wrapped" + ) + return NativeResult( + javaType: wrappedValueResult.javaType, + conversion: .optionalRaisingIndirectReturn( + wrappedValueResult.conversion, + resultName: "\(resultName)$", + returnType: wrappedValueResult.javaType, + discriminatorParameterName: discriminatorName + ), + outParameters: [ + JavaParameter(name: discriminatorName, type: .array(.byte)) + ] + wrappedValueResult.outParameters + ) } func translateClosureResult( @@ -1162,9 +1147,9 @@ extension JNISwift2JavaGenerator { indirect case optionalRaisingIndirectReturn( NativeSwiftConversionStep, + resultName: String, returnType: JavaType, - discriminatorParameterName: String, - placeholderValue: NativeSwiftConversionStep + discriminatorParameterName: String ) indirect case genericValueIndirectReturn( @@ -1530,17 +1515,17 @@ extension JNISwift2JavaGenerator { case .optionalRaisingIndirectReturn( let inner, + let resultName, let returnType, - let discriminatorParameterName, - let placeholderValue + let discriminatorParameterName ): if !returnType.isVoid { - printer.print("let result$: \(returnType.jniTypeName)") + printer.print("let \(resultName): \(returnType.jniTypeName)") } printer.printBraceBlock("if let innerResult$ = \(placeholder)") { printer in let inner = inner.render(&printer, "innerResult$") if !returnType.isVoid { - printer.print("result$ = \(inner)") + printer.print("\(resultName) = \(inner)") } printer.print( """ @@ -1550,9 +1535,8 @@ extension JNISwift2JavaGenerator { ) } printer.printBraceBlock("else") { printer in - let placeholderValue = placeholderValue.render(&printer, placeholder) if !returnType.isVoid { - printer.print("result$ = \(placeholderValue)") + printer.print("\(resultName) = \(returnType.swiftJniPlaceholderExpr)") } printer.print( """ @@ -1562,7 +1546,7 @@ extension JNISwift2JavaGenerator { ) } if !returnType.isVoid { - return "result$" + return resultName } else { return "" } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index cf626240..3ad676be 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -751,14 +751,7 @@ extension JNISwift2JavaGenerator { } private func dummyReturn(for nativeSignature: NativeFunctionSignature) -> String { - if nativeSignature.result.javaType.isVoid { - "return" - } else if nativeSignature.result.javaType.isString { - "return String.jniPlaceholderValue" - } else { - // We assume it is something that implements JavaValue - "return \(nativeSignature.result.javaType.swiftTypeName(resolver: { _ in "" })).jniPlaceholderValue" - } + "return \(nativeSignature.result.javaType.swiftJniPlaceholderExpr)" } private func printCDecl( diff --git a/Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift b/Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift index 0ee49313..f9245349 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift @@ -70,7 +70,7 @@ struct JNIIntConversionChecksTests { #if _pointerBitWidth(_32) guard normalInt$indirect >= Int32.min && normalInt$indirect <= Int32.max else { environment.throwJavaException(javaException: .integerOverflow) - return Int64.jniPlaceholderValue + return 0 """, """ #endif @@ -96,7 +96,7 @@ struct JNIIntConversionChecksTests { #if _pointerBitWidth(_32) guard unsignedInt$indirect >= UInt32.min && unsignedInt$indirect <= UInt32.max else { environment.throwJavaException(javaException: .integerOverflow) - return Int64.jniPlaceholderValue + return 0 """, """ #endif @@ -201,7 +201,7 @@ struct JNIIntConversionChecksTests { #if _pointerBitWidth(_32) guard arg$indirect >= Int32.min && arg$indirect <= Int32.max else { environment.throwJavaException(javaException: .integerOverflow) - return Int64.jniPlaceholderValue + return 0 """, """ #endif @@ -230,7 +230,7 @@ struct JNIIntConversionChecksTests { #if _pointerBitWidth(_32) guard arg$indirect >= UInt32.min && arg$indirect <= UInt32.max else { environment.throwJavaException(javaException: .integerOverflow) - return Int64.jniPlaceholderValue + return 0 """, """ assert(selfPointer != 0, "selfPointer memory address was null") @@ -258,7 +258,7 @@ struct JNIIntConversionChecksTests { #if _pointerBitWidth(_32) guard arg0$indirect >= UInt32.min && arg0$indirect <= UInt32.max else { environment.throwJavaException(javaException: .integerOverflow) - return Int64.jniPlaceholderValue + return 0 """, """ #endif diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 1fb187ff..b0c3bfbb 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -249,7 +249,7 @@ struct JNIModuleTests { try SwiftModule.methodA() } catch { environment.throwAsException(error) - return + return () } } """, @@ -260,7 +260,7 @@ struct JNIModuleTests { return try SwiftModule.methodB().getJNILocalRefValue(in: environment) } catch { environment.throwAsException(error) - return Int64.jniPlaceholderValue + return 0 } } """, @@ -271,7 +271,7 @@ struct JNIModuleTests { return try SwiftModule.methodC().getJNILocalRefValue(in: environment) } catch { environment.throwAsException(error) - return String.jniPlaceholderValue + return nil } } """, diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index 628dcfdc..5847f34d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -129,7 +129,7 @@ struct JNIOptionalTests { environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) } else { - result$ = String.jniPlaceholderValue + result$ = nil var flag$ = Int8(0) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) } @@ -250,4 +250,33 @@ struct JNIOptionalTests { ] ) } + + @Test + func optionalTuple() throws { + let input = """ + public struct Foo {} + public func optionalTuple() -> (Int64?, Foo)? { + (42, Foo()) + } + """ + + try assertOutput( + input: input, + .jni, + .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + byte[] result$_discriminator$ = new byte[1]; + byte[] resultWrapped$_0$$_discriminator$ = new byte[1]; + long[] resultWrapped$_0$ = new long[1]; + long[] resultWrapped$_1$ = new long[1]; + SwiftModule.$optionalTuple(result$_discriminator$, resultWrapped$_0$$_discriminator$, resultWrapped$_0$, resultWrapped$_1$); + """, + """ + private static native void $optionalTuple(byte[] result_discriminator$, byte[] resultWrapped_0$_discriminator$, long[] resultWrapped_0$, long[] resultWrapped_1$); + """, + ] + ) + } } diff --git a/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift b/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift index 67efc431..ef90053f 100644 --- a/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift @@ -147,4 +147,31 @@ struct JNITupleTests { ] ) } + + @Test + func genericTuple() throws { + let input = """ + public struct Box {} + public func genericTuple() -> (Box, Box) { + fatalError() + } + """ + + try assertOutput( + input: input, + .jni, + .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + org.swift.swiftkit.core._OutSwiftGenericInstance result_0$ = new org.swift.swiftkit.core._OutSwiftGenericInstance(); + org.swift.swiftkit.core._OutSwiftGenericInstance result_1$ = new org.swift.swiftkit.core._OutSwiftGenericInstance(); + SwiftModule.$genericTuple(result_0$, result_1$); + """, + """ + private static native void $genericTuple(org.swift.swiftkit.core._OutSwiftGenericInstance result_0$Out, org.swift.swiftkit.core._OutSwiftGenericInstance result_1$Out); + """, + ] + ) + } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 6f13c463..11eb08bf 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -242,7 +242,7 @@ struct JNIVariablesTests { return try selfPointer$.pointee.computedThrowing.getJNILocalRefValue(in: environment) } catch { environment.throwAsException(error) - return Int64.jniPlaceholderValue + return 0 } } """