diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift index 006d75ab..70f5afdc 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift @@ -57,3 +57,15 @@ public func stringArray(array: [String]) -> [String] { public func objectArray(array: [MySwiftClass]) -> [MySwiftClass] { array } + +public func nestedByteArray(array: [[UInt8]]) -> [[UInt8]] { + array +} + +public func nestedLongArray(array: [[Int64]]) -> [[Int64]] { + array +} + +public func nestedStringArray(array: [[String]]) -> [[String]] { + array +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java index bf19b370..6efe1a0b 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java @@ -102,4 +102,76 @@ void objectArray() { assertEquals(3, MySwiftLibrary.objectArray(input, arena).length); } } -} \ No newline at end of file + + @Test + void nestedByteArray() { + byte[][] input = new byte[][] { + { 1, 2, 3 }, + { 4, 5 }, + { 6 } + }; + byte[][] result = MySwiftLibrary.nestedByteArray(input); + assertEquals(input.length, result.length); + assertArrayEquals(input[0], result[0]); + assertArrayEquals(input[1], result[1]); + assertArrayEquals(input[2], result[2]); + } + + @Test + void nestedByteArray_empty() { + byte[][] input = new byte[][] {}; + byte[][] result = MySwiftLibrary.nestedByteArray(input); + assertEquals(0, result.length); + } + + @Test + void nestedByteArray_emptyInner() { + byte[][] input = new byte[][] { {}, { 1 }, {} }; + byte[][] result = MySwiftLibrary.nestedByteArray(input); + assertEquals(3, result.length); + assertArrayEquals(new byte[] {}, result[0]); + assertArrayEquals(new byte[] { 1 }, result[1]); + assertArrayEquals(new byte[] {}, result[2]); + } + + @Test + void nestedLongArray() { + long[][] input = new long[][] { + { 100, 200, 300 }, + { 400, 500 } + }; + long[][] result = MySwiftLibrary.nestedLongArray(input); + assertEquals(input.length, result.length); + assertArrayEquals(input[0], result[0]); + assertArrayEquals(input[1], result[1]); + } + + @Test + void nestedStringArray() { + String[][] input = new String[][] { + { "hello", "world" }, + { "foo", "bar", "baz" } + }; + String[][] result = MySwiftLibrary.nestedStringArray(input); + assertEquals(input.length, result.length); + assertArrayEquals(input[0], result[0]); + assertArrayEquals(input[1], result[1]); + } + + @Test + void nestedStringArray_empty() { + String[][] input = new String[][] {}; + String[][] result = MySwiftLibrary.nestedStringArray(input); + assertEquals(0, result.length); + } + + @Test + void nestedStringArray_emptyInner() { + String[][] input = new String[][] { {}, { "a" }, {} }; + String[][] result = MySwiftLibrary.nestedStringArray(input); + assertEquals(3, result.length); + assertArrayEquals(new String[] {}, result[0]); + assertArrayEquals(new String[] { "a" }, result[1]); + assertArrayEquals(new String[] {}, result[2]); + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java index 63b9e2b0..c5aca8eb 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java @@ -44,7 +44,7 @@ void fishBoxHasExpectedMethods() throws Exception { @Test void fishBoxDoesNotHaveGenericTypeParameter() { - // FishBox is a concrete specialization — no generic type parameters + // FishBox is a concrete specialization - no generic type parameters assertEquals(0, FishBox.class.getTypeParameters().length, "FishBox should have no generic type parameters"); } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftDictionaryMapTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftDictionaryMapTest.java index e7b4aa38..635d46f2 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftDictionaryMapTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftDictionaryMapTest.java @@ -63,7 +63,7 @@ void insertIntoStringToLongDictionary() { assertEquals(2L, modified.get("world")); assertEquals(42L, modified.get("swift")); - // The original dictionary is unchanged (Swift value semantics — it's a copy) + // The original dictionary is unchanged (Swift value semantics - it's a copy) assertEquals(2, original.size()); assertNull(original.get("swift")); } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftSetTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftSetTest.java index 13490ef6..84733093 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftSetTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/SwiftSetTest.java @@ -61,7 +61,7 @@ void insertIntoStringSet() { assertTrue(modified.contains("world")); assertTrue(modified.contains("swift")); - // The original set is unchanged (Swift value semantics — it's a copy) + // The original set is unchanged (Swift value semantics - it's a copy) assertEquals(2, original.size()); assertFalse(original.contains("swift")); } diff --git a/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift b/Sources/JExtractSwiftLib/Common/JavaTypeAnnotations.swift similarity index 57% rename from Sources/JExtractSwiftLib/Common/TypeAnnotations.swift rename to Sources/JExtractSwiftLib/Common/JavaTypeAnnotations.swift index 0297d96a..7f540e52 100644 --- a/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift +++ b/Sources/JExtractSwiftLib/Common/JavaTypeAnnotations.swift @@ -17,7 +17,7 @@ import SwiftJavaJNICore /// Determine if the given type needs any extra annotations that should be included /// in Java sources when the corresponding Java type is rendered. -func getTypeAnnotations(swiftType: SwiftType, config: Configuration) -> [JavaAnnotation] { +func getJavaTypeAnnotations(swiftType: SwiftType, config: Configuration) -> [JavaAnnotation] { if swiftType.isUnsignedInteger { return [JavaAnnotation.unsigned] } @@ -25,6 +25,19 @@ func getTypeAnnotations(swiftType: SwiftType, config: Configuration) -> [JavaAnn switch swiftType.asNominalType?.asKnownType { case .array(let element) where element.isUnsignedInteger: return [JavaAnnotation.unsigned] + case .array(let element): // check recursively for [[UInt8]] etc + return getJavaTypeAnnotations(swiftType: element, config: config) + + case .set(let element) where element.isUnsignedInteger: + return [JavaAnnotation.unsigned] + case .set(let element): + return getJavaTypeAnnotations(swiftType: element, config: config) + + case .dictionary(let key, let value) where key.isUnsignedInteger || value.isUnsignedInteger: + return [JavaAnnotation.unsigned] + case .dictionary(let key, let value): + return getJavaTypeAnnotations(swiftType: key, config: config) + getJavaTypeAnnotations(swiftType: value, config: config) + default: return [] } diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index 68059eed..68ff5a6a 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -121,6 +121,8 @@ extension JavaType { switch self { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: true + case .array(let element): + element.implementsJavaValue default: false } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index c750fdc0..e3a61dc7 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -358,7 +358,7 @@ extension FFMSwift2JavaGenerator { genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { // If the result type should cause any annotations on the method, include them here. - let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + let parameterAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config) // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. @@ -694,7 +694,7 @@ extension FFMSwift2JavaGenerator { ) throws -> TranslatedResult { let swiftType = swiftResult.type // If the result type should cause any annotations on the method, include them here. - let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + let resultAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config) // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 69455ba6..f5a2fe32 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -463,7 +463,7 @@ extension JNISwift2JavaGenerator { ) throws -> TranslatedParameter { // If the result type should cause any annotations on the method, include them here. - let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + let parameterAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config) switch swiftType { case .nominal(let nominalType): @@ -804,7 +804,7 @@ extension JNISwift2JavaGenerator { genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement], ) throws -> TranslatedParameter { - let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + let parameterAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config) switch swiftType { case .nominal(let nominalType): @@ -896,7 +896,7 @@ extension JNISwift2JavaGenerator { let swiftType = swiftResult.type // If the result type should cause any annotations on the method, include them here. - let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + let resultAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config) switch swiftType { case .nominal(let nominalType): @@ -1251,7 +1251,7 @@ extension JNISwift2JavaGenerator { ) throws -> TranslatedResult { let discriminatorName = "\(resultName)$_discriminator$" - let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + let parameterAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: swiftType, config: config) switch swiftType { case .nominal(let nominalType): @@ -1365,9 +1365,30 @@ extension JNISwift2JavaGenerator { genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement], ) throws -> TranslatedParameter { - let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config) + let parameterAnnotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: elementType, config: config) switch elementType { + case .nominal(let nominalType) where nominalType.nominalTypeDecl.knownTypeKind == .array: + guard let fullKnownType = nominalType.asKnownType else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + guard case .array(let innerElement) = fullKnownType else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + let innerParam = try translateArrayParameter( + elementType: innerElement, + parameterName: parameterName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + let innerJavaType = innerParam.parameter.type.javaType + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: .array(innerJavaType), annotations: parameterAnnotations), + conversion: .requireNonNull(.placeholder, message: "\(parameterName) must not be null") + ) + case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { @@ -1417,9 +1438,30 @@ extension JNISwift2JavaGenerator { genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement], ) throws -> TranslatedResult { - let annotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config) + let annotations: [JavaAnnotation] = getJavaTypeAnnotations(swiftType: elementType, config: config) switch elementType { + case .nominal(let nominalType) where nominalType.nominalTypeDecl.knownTypeKind == .array: + guard let fullKnownType = nominalType.asKnownType else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + guard case .array(let innerElement) = fullKnownType else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + let innerResult = try translateArrayResult( + elementType: innerElement, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + return TranslatedResult( + javaType: .array(innerResult.javaType), + annotations: annotations, + outParameters: [], + conversion: .placeholder + ) + case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 777e1e30..2351ea47 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -846,6 +846,22 @@ extension JNISwift2JavaGenerator { resultName: String ) throws -> NativeResult { switch elementType { + case .nominal(let nominalType) where nominalType.nominalTypeDecl.knownTypeKind == .array: + guard let fullKnownType = nominalType.asKnownType else { + throw JavaTranslationError.unsupportedSwiftType(known: .array(elementType)) + } + + guard case .array(let innerElement) = fullKnownType else { + throw JavaTranslationError.unsupportedSwiftType(known: .array(elementType)) + } + + let innerResult = try translateArrayResult(elementType: innerElement, resultName: resultName) + return NativeResult( + javaType: .array(innerResult.javaType), + conversion: .getJNIValue(.placeholder), + outParameters: [] + ) + case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), @@ -897,6 +913,28 @@ extension JNISwift2JavaGenerator { parameterName: String ) throws -> NativeParameter { switch elementType { + case .nominal(let nominalType) where nominalType.nominalTypeDecl.knownTypeKind == .array: + guard let fullKnownType = nominalType.asKnownType else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + guard case .array(let innerElement) = fullKnownType else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + let innerParam = try translateArrayParameter(elementType: innerElement, parameterName: parameterName) + guard case .concrete(let innerJavaType) = innerParam.parameters.first?.type else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: .array(innerJavaType)) + ], + conversion: .initFromJNI(.placeholder, swiftType: knownTypes.arraySugar(elementType)), + indirectConversion: nil, + conversionCheck: nil + ) + case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), diff --git a/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift b/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift index 0689ca9d..3be27da7 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift @@ -193,4 +193,122 @@ struct JNIArrayTest { ] ) } + + // ==== ----------------------------------------------------------------------- + // MARK: Nested arrays (array of arrays) + + @Test("Import: ([[UInt8]]) -> byte[][] (Java)") + func uint8NestedArray_java() throws { + try assertOutput( + input: "public func f(data: [[UInt8]]) -> [[UInt8]] {}", + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @Unsigned + public static byte[][] f(@Unsigned byte[][] data) { + return SwiftModule.$f(Objects.requireNonNull(data, "data must not be null")); + } + """, + """ + private static native byte[][] $f(byte[][] data); + """, + ] + ) + } + + @Test("Import: ([[UInt8]]) -> byte[][] (Swift)") + func uint8NestedArray_swift() throws { + try assertOutput( + input: "public func f(data: [[UInt8]]) -> [[UInt8]] {}", + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3_3B") + public func Java_com_example_swift_SwiftModule__00024f___3_3B(environment: UnsafeMutablePointer!, thisClass: jclass, data: jobjectArray?) -> jobjectArray? { + return SwiftModule.f(data: [[UInt8]](fromJNI: data, in: environment)).getJNILocalRefValue(in: environment) + } + """ + ] + ) + } + + @Test("Import: ([[Int64]]) -> long[][] (Java)") + func int64NestedArray_java() throws { + try assertOutput( + input: "public func f(data: [[Int64]]) -> [[Int64]] {}", + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static long[][] f(long[][] data) { + return SwiftModule.$f(Objects.requireNonNull(data, "data must not be null")); + } + """, + """ + private static native long[][] $f(long[][] data); + """, + ] + ) + } + + @Test("Import: ([[Int64]]) -> long[][] (Swift)") + func int64NestedArray_swift() throws { + try assertOutput( + input: "public func f(data: [[Int64]]) -> [[Int64]] {}", + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3_3J") + public func Java_com_example_swift_SwiftModule__00024f___3_3J(environment: UnsafeMutablePointer!, thisClass: jclass, data: jobjectArray?) -> jobjectArray? { + return SwiftModule.f(data: [[Int64]](fromJNI: data, in: environment)).getJNILocalRefValue(in: environment) + } + """ + ] + ) + } + + @Test("Import: ([[String]]) -> String[][] (Java)") + func stringNestedArray_java() throws { + try assertOutput( + input: "public func f(data: [[String]]) -> [[String]] {}", + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static java.lang.String[][] f(java.lang.String[][] data) { + return SwiftModule.$f(Objects.requireNonNull(data, "data must not be null")); + } + """, + """ + private static native java.lang.String[][] $f(java.lang.String[][] data); + """, + ] + ) + } + + @Test("Import: ([[String]]) -> String[][] (Swift)") + func stringNestedArray_swift() throws { + try assertOutput( + input: "public func f(data: [[String]]) -> [[String]] {}", + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3_3Ljava_lang_String_2") + public func Java_com_example_swift_SwiftModule__00024f___3_3Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, data: jobjectArray?) -> jobjectArray? { + return SwiftModule.f(data: [[String]](fromJNI: data, in: environment)).getJNILocalRefValue(in: environment) + } + """ + ] + ) + } } diff --git a/Tests/JExtractSwiftTests/JavaTypeAnnotationsTests.swift b/Tests/JExtractSwiftTests/JavaTypeAnnotationsTests.swift new file mode 100644 index 00000000..7a5165aa --- /dev/null +++ b/Tests/JExtractSwiftTests/JavaTypeAnnotationsTests.swift @@ -0,0 +1,135 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJavaConfigurationShared +import SwiftSyntax +import Testing + +@testable import JExtractSwiftLib + +@Suite +struct JavaTypeAnnotationsTests { + + let knownTypes: SwiftKnownTypes + let config: Configuration + + init() { + let symbolTable = SwiftSymbolTable.setup( + moduleName: "TestModule", + [SwiftJavaInputFile(syntax: "" as SourceFileSyntax, path: "Fake.swift")], + config: nil, + log: Logger(label: "test", logLevel: .critical) + ) + self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) + self.config = Configuration() + } + + // ==== ----------------------------------------------------------------------- + // MARK: Primitives + + @Test("UInt8 is @Unsigned") + func uint8_unsigned() { + let annotations = getJavaTypeAnnotations(swiftType: knownTypes.uint8, config: config) + #expect(annotations == [.unsigned]) + } + + @Test("Int64 is not @Unsigned") + func int64_not_unsigned() { + let annotations = getJavaTypeAnnotations(swiftType: knownTypes.int64, config: config) + #expect(annotations.isEmpty) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Arrays + + @Test("[UInt8] is @Unsigned") + func array_uint8_unsigned() { + let type = knownTypes.arraySugar(knownTypes.uint8) + let annotations = getJavaTypeAnnotations(swiftType: type, config: config) + #expect(annotations == [.unsigned]) + } + + @Test("[Int64] is not @Unsigned") + func array_int64_not_unsigned() { + let type = knownTypes.arraySugar(knownTypes.int64) + let annotations = getJavaTypeAnnotations(swiftType: type, config: config) + #expect(annotations.isEmpty) + } + + @Test("[[UInt8]] is @Unsigned") + func nested_array_uint8_unsigned() { + let inner = knownTypes.arraySugar(knownTypes.uint8) + let type = knownTypes.arraySugar(inner) + let annotations = getJavaTypeAnnotations(swiftType: type, config: config) + #expect(annotations == [.unsigned]) + } + + @Test("[[Int64]] is not @Unsigned") + func nested_array_int64_not_unsigned() { + let inner = knownTypes.arraySugar(knownTypes.int64) + let type = knownTypes.arraySugar(inner) + let annotations = getJavaTypeAnnotations(swiftType: type, config: config) + #expect(annotations.isEmpty) + } + + @Test("[[[UInt8]]] is @Unsigned (deeply nested)") + func deeply_nested_array_uint8_unsigned() { + let inner1 = knownTypes.arraySugar(knownTypes.uint8) + let inner2 = knownTypes.arraySugar(inner1) + let type = knownTypes.arraySugar(inner2) + let annotations = getJavaTypeAnnotations(swiftType: type, config: config) + #expect(annotations == [.unsigned]) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Sets + + @Test("Set is @Unsigned") + func set_uint8_unsigned() { + let type = knownTypes.set(knownTypes.uint8) + let annotations = getJavaTypeAnnotations(swiftType: type, config: config) + #expect(annotations == [.unsigned]) + } + + @Test("Set is not @Unsigned") + func set_int64_not_unsigned() { + let type = knownTypes.set(knownTypes.int64) + let annotations = getJavaTypeAnnotations(swiftType: type, config: config) + #expect(annotations.isEmpty) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Dictionaries + + @Test("[UInt8: Int64] is @Unsigned (unsigned key)") + func dictionary_uint8_key_unsigned() { + let type = knownTypes.dictionarySugar(knownTypes.uint8, knownTypes.int64) + let annotations = getJavaTypeAnnotations(swiftType: type, config: config) + #expect(annotations == [.unsigned]) + } + + @Test("[Int64: UInt8] is @Unsigned (unsigned value)") + func dictionary_uint8_value_unsigned() { + let type = knownTypes.dictionarySugar(knownTypes.int64, knownTypes.uint8) + let annotations = getJavaTypeAnnotations(swiftType: type, config: config) + #expect(annotations == [.unsigned]) + } + + @Test("[Int64: Int32] is not @Unsigned") + func dictionary_signed_not_unsigned() { + let type = knownTypes.dictionarySugar(knownTypes.int64, knownTypes.int32) + let annotations = getJavaTypeAnnotations(swiftType: type, config: config) + #expect(annotations.isEmpty) + } +}