diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift index 4e0041fb..d4597327 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift @@ -30,6 +30,14 @@ public func makeStringID(_ value: String) -> MyID { MyID(value) } +public func takeIntValue(from value: MyID) -> Int { + value.rawValue +} + +public func takeStringValue(from value: MyID) -> String { + value.rawValue +} + public struct MyEntity { public var id: MyID public var name: String diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java index fa0d76da..bedf0eca 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java @@ -21,20 +21,22 @@ public class GenericTypeTest { @Test - void returnsGenericType() { + void genericTypeValueRoundtrip() { try (var arena = SwiftArena.ofConfined()) { - MyID stringId = MySwiftLibrary.makeStringID("Java", arena); + MyID stringId = MySwiftLibrary.makeStringID("Java", arena); assertEquals("Java", stringId.getDescription()); + assertEquals("Java", MySwiftLibrary.takeStringValue(stringId)); - MyID intId = MySwiftLibrary.makeIntID(42, arena); + MyID intId = MySwiftLibrary.makeIntID(42, arena); assertEquals("42", intId.getDescription()); + assertEquals(42, MySwiftLibrary.takeIntValue(intId)); } } @Test void genericTypeProperty() { try (var arena = SwiftArena.ofConfined()) { - MyID intId = MySwiftLibrary.makeIntID(42, arena); + MyID intId = MySwiftLibrary.makeIntID(42, arena); MyEntity entity = MyEntity.init(intId, "name", arena); assertEquals("42", entity.getId(arena).getDescription()); } @@ -43,7 +45,7 @@ void genericTypeProperty() { @Test void genericEnum() { try (var arena = SwiftArena.ofConfined()) { - GenericEnum value = MySwiftLibrary.makeIntGenericEnum(arena); + GenericEnum value = MySwiftLibrary.makeIntGenericEnum(arena); switch (value.getCase()) { case GenericEnum.Foo _ -> assertTrue(value.getAsFoo().isPresent()); case GenericEnum.Bar _ -> assertTrue(value.getAsBar().isPresent()); diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 759aa741..6c99d3de 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -234,6 +234,12 @@ extension JNISwift2JavaGenerator { ) } printer.println() + let genericClause = + if decl.swiftNominal.isGeneric { + "<\(decl.swiftNominal.genericParameters.map(\.name).joined(separator: ", "))>" + } else { + "" + } printer.print( """ /** @@ -245,12 +251,12 @@ extension JNISwift2JavaGenerator { *
  • This operation does not copy, or retain, the pointed at pointer, so its lifetime must be ensured manually to be valid when wrapping.
  • * */ - public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(\(swiftPointerArg), SwiftArena swiftArena) { - return new \(decl.swiftNominal.name)(\(swiftPointerParams.joined(separator: ", ")), swiftArena); + public static\(genericClause) \(decl.swiftNominal.name)\(genericClause) wrapMemoryAddressUnsafe(\(swiftPointerArg), SwiftArena swiftArena) { + return new \(decl.swiftNominal.name)\(genericClause)(\(swiftPointerParams.joined(separator: ", ")), swiftArena); } - public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(\(swiftPointerArg)) { - return new \(decl.swiftNominal.name)(\(swiftPointerParams.joined(separator: ", ")), SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA); + public static\(genericClause) \(decl.swiftNominal.name)\(genericClause) wrapMemoryAddressUnsafe(\(swiftPointerArg)) { + return new \(decl.swiftNominal.name)\(genericClause)(\(swiftPointerParams.joined(separator: ", ")), SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA); } """ ) @@ -384,8 +390,14 @@ extension JNISwift2JavaGenerator { .filter { $0.kind == .protocol } .map(\.name) let implementsClause = implements.joined(separator: ", ") + let genericClause = + if decl.swiftNominal.isGeneric { + "<\(decl.swiftNominal.genericParameters.map(\.name).joined(separator: ", "))>" + } else { + "" + } printer.printBraceBlock( - "\(modifiers.joined(separator: " ")) class \(decl.swiftNominal.name) implements \(implementsClause)" + "\(modifiers.joined(separator: " ")) class \(decl.swiftNominal.name)\(genericClause) implements \(implementsClause)" ) { printer in body(&printer) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 966230a5..2563c1a5 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -173,7 +173,7 @@ extension JNISwift2JavaGenerator { ), parameters: [], resultType: TranslatedResult( - javaType: .class(package: nil, name: "Optional<\(caseName)>"), + javaType: .class(package: nil, name: "Optional", typeParameters: [.class(package: nil, name: caseName)]), outParameters: conversions.flatMap(\.translated.outParameters), conversion: enumCase.parameters.isEmpty ? constructRecordConversion @@ -355,7 +355,11 @@ extension JNISwift2JavaGenerator { exceptions.append(.integerOverflow) } - let resultType = try translate(swiftResult: functionSignature.result) + let resultType = try translate( + swiftResult: functionSignature.result, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements + ) return TranslatedFunctionSignature( selfParameter: selfParameter, @@ -462,7 +466,9 @@ extension JNISwift2JavaGenerator { } return try translateOptionalParameter( wrappedType: genericArgs[0], - parameterName: parameterName + parameterName: parameterName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .array: @@ -471,7 +477,9 @@ extension JNISwift2JavaGenerator { } return try translateArrayParameter( elementType: elementType, - parameterName: parameterName + parameterName: parameterName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .dictionary: @@ -481,7 +489,9 @@ extension JNISwift2JavaGenerator { return try translateDictionaryParameter( keyType: genericArgs[0], valueType: genericArgs[1], - parameterName: parameterName + parameterName: parameterName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .set: @@ -490,7 +500,9 @@ extension JNISwift2JavaGenerator { } return try translateSetParameter( elementType: genericArgs[0], - parameterName: parameterName + parameterName: parameterName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .foundationDate, .essentialsDate: @@ -528,11 +540,23 @@ extension JNISwift2JavaGenerator { ) } + let javaType = JavaType.class( + package: nil, + name: nominalTypeName, + typeParameters: try nominalType.genericArguments?.map { swiftType in + try translateGenericTypeParameter( + swiftType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + } ?? [] + ) + // We assume this is a JExtract class. return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .concrete(.class(package: nil, name: nominalTypeName)), + type: .concrete(javaType), annotations: parameterAnnotations ), conversion: .valueMemoryAddress(.placeholder) @@ -557,7 +581,9 @@ extension JNISwift2JavaGenerator { case .optional(let wrapped): return try translateOptionalParameter( wrappedType: wrapped, - parameterName: parameterName + parameterName: parameterName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .opaque(let proto), .existential(let proto): @@ -568,7 +594,9 @@ extension JNISwift2JavaGenerator { return try translateProtocolParameter( protocolType: proto, parameterName: parameterName, - javaGenericName: "_T\(parameterPosition)" + javaGenericName: "_T\(parameterPosition)", + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .genericParameter(let generic): @@ -579,7 +607,9 @@ extension JNISwift2JavaGenerator { return try translateProtocolParameter( protocolType: concreteTy, parameterName: parameterName, - javaGenericName: generic.name + javaGenericName: generic.name, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) } @@ -588,20 +618,26 @@ extension JNISwift2JavaGenerator { case .array(let elementType): return try translateArrayParameter( elementType: elementType, - parameterName: parameterName + parameterName: parameterName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .dictionary(let keyType, let valueType): return try translateDictionaryParameter( keyType: keyType, valueType: valueType, - parameterName: parameterName + parameterName: parameterName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .set(let elementType): return try translateSetParameter( elementType: elementType, - parameterName: parameterName + parameterName: parameterName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .metatype: @@ -740,21 +776,27 @@ extension JNISwift2JavaGenerator { func translateProtocolParameter( protocolType: SwiftType, parameterName: String, - javaGenericName: String + javaGenericName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { switch protocolType { case .nominal: return try translateProtocolParameter( protocolTypes: [protocolType], parameterName: parameterName, - javaGenericName: javaGenericName + javaGenericName: javaGenericName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .composite(let types): return try translateProtocolParameter( protocolTypes: types, parameterName: parameterName, - javaGenericName: javaGenericName + javaGenericName: javaGenericName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) default: @@ -765,17 +807,16 @@ extension JNISwift2JavaGenerator { private func translateProtocolParameter( protocolTypes: [SwiftType], parameterName: String, - javaGenericName: String + javaGenericName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { let javaProtocolTypes = try protocolTypes.map { - switch $0 { - case .nominal(let nominalType): - let nominalTypeName = nominalType.nominalTypeDecl.name - return JavaType.class(package: nil, name: nominalTypeName) - - default: - throw JavaTranslationError.unsupportedSwiftType($0) - } + try translateGenericTypeParameter( + $0, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) } // We just pass down the jobject @@ -791,13 +832,15 @@ extension JNISwift2JavaGenerator { func translateOptionalParameter( wrappedType swiftType: SwiftType, - parameterName: String + parameterName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) switch swiftType { case .nominal(let nominalType): - let nominalTypeName = nominalType.nominalTypeDecl.name + let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName if let knownType = nominalType.nominalTypeDecl.knownTypeKind { switch knownType { @@ -853,14 +896,19 @@ extension JNISwift2JavaGenerator { } // Assume JExtract imported class + let javaType = try translateGenericTypeParameter( + swiftType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: nil, name: "Optional<\(nominalTypeName)>"), + type: .class(package: nil, name: "Optional", typeParameters: [javaType]), annotations: parameterAnnotations ), conversion: .method( - .method(.placeholder, function: "map", arguments: [.constant("\(nominalType)::$memoryAddress")]), + .method(.placeholder, function: "map", arguments: [.constant("\(javaType)::$memoryAddress")]), function: "orElse", arguments: [.constant("0L")] ) @@ -870,7 +918,12 @@ extension JNISwift2JavaGenerator { } } - func translate(swiftResult: SwiftResult, resultName: String = "result") throws -> TranslatedResult { + func translate( + swiftResult: SwiftResult, + resultName: String = "result", + genericParameters: [SwiftGenericParameterDeclaration] = [], + genericRequirements: [SwiftGenericRequirement] = [] + ) throws -> TranslatedResult { let swiftType = swiftResult.type // If the result type should cause any annotations on the method, include them here. @@ -884,14 +937,21 @@ extension JNISwift2JavaGenerator { guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { throw JavaTranslationError.unsupportedSwiftType(swiftType) } - return try translateOptionalResult(wrappedType: genericArgs[0], resultName: resultName) + return try translateOptionalResult( + wrappedType: genericArgs[0], + resultName: resultName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) case .array: guard let elementType = nominalType.genericArguments?.first else { throw JavaTranslationError.unsupportedSwiftType(swiftType) } return try translateArrayResult( - elementType: elementType + elementType: elementType, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .dictionary: @@ -900,7 +960,9 @@ extension JNISwift2JavaGenerator { } return try translateDictionaryResult( keyType: genericArgs[0], - valueType: genericArgs[1] + valueType: genericArgs[1], + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .set: @@ -908,7 +970,9 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.setRequiresElementType(swiftType) } return try translateSetResult( - elementType: genericArgs[0] + elementType: genericArgs[0], + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .foundationDate, .essentialsDate: @@ -948,9 +1012,19 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftType) } - // We assume this is a JExtract class. - let javaType = JavaType.class(package: nil, name: nominalType.nominalTypeDecl.qualifiedName) + let javaType = JavaType.class( + package: nil, + name: nominalType.nominalTypeDecl.qualifiedName, + typeParameters: try nominalType.genericArguments?.map { swiftType in + try translateGenericTypeParameter( + swiftType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + } ?? [] + ) + // We assume this is a JExtract class. if nominalType.nominalTypeDecl.isGeneric { return TranslatedResult( javaType: javaType, @@ -983,35 +1057,208 @@ extension JNISwift2JavaGenerator { return TranslatedResult(javaType: .void, outParameters: [], conversion: .placeholder) case .optional(let wrapped): - return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) + return try translateOptionalResult( + wrappedType: wrapped, + resultName: resultName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) case .array(let elementType): return try translateArrayResult( - elementType: elementType + elementType: elementType, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .dictionary(let keyType, let valueType): return try translateDictionaryResult( keyType: keyType, - valueType: valueType + valueType: valueType, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .set(let elementType): return try translateSetResult( - elementType: elementType + elementType: elementType, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) case .tuple(let elements) where !elements.isEmpty: - return try translateTupleResult(elements: elements, resultName: resultName) + return try translateTupleResult( + elements: elements, + resultName: resultName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) case .metatype, .tuple, .function, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } + private func translateGenericTypeParameter( + _ swiftType: SwiftType, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] + ) throws -> JavaType { + switch swiftType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + let wrappedType = try translateGenericTypeParameter( + genericArgs[0], + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + return .class(package: "java.util", name: "Optional", typeParameters: [wrappedType]) + + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + let elementJavaType = try translateGenericTypeParameter( + elementType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + return .array(elementJavaType) + + case .dictionary: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 2 else { + throw JavaTranslationError.dictionaryRequiresKeyAndValueTypes(swiftType) + } + let keyJavaType = try translateGenericTypeParameter( + genericArgs[0], + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + let valueJavaType = try translateGenericTypeParameter( + genericArgs[1], + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + return .swiftDictionaryMap(keyJavaType, valueJavaType) + + case .set: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.setRequiresElementType(swiftType) + } + let elementJavaType = try translateGenericTypeParameter( + genericArgs[0], + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + return .swiftSet(elementJavaType) + + case .foundationDate, .essentialsDate: + return .class(package: nil, name: "Date") + + case .foundationData, .essentialsData: + return .class(package: nil, name: "Data") + + case .foundationUUID, .essentialsUUID: + return .javaUtilUUID + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return javaType.boxedType + } + } + + if nominalType.isSwiftJavaWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromSwiftJavaName(in: self.javaClassLookupTable) else { + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) + } + return javaType + } + + // We assume this is a JExtract class. + let typeParameters = + try nominalType.genericArguments?.map { swiftType in + try translateGenericTypeParameter( + swiftType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + } ?? [] + + return .class( + package: nil, + name: nominalTypeName, + typeParameters: typeParameters + ) + + case .genericParameter(let generic): + if let concreteTy = swiftType.typeIn( + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) { + return try translateGenericTypeParameter( + concreteTy, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + } + return .class(package: nil, name: generic.name) + + case .optional(let wrapped): + let wrappedType = try translateGenericTypeParameter( + wrapped, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + return .class(package: "java.util", name: "Optional", typeParameters: [wrappedType]) + + case .array(let elementType): + let elementJavaType = try translateGenericTypeParameter( + elementType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + return .array(elementJavaType) + + case .dictionary(let keyType, let valueType): + let keyJavaType = try translateGenericTypeParameter( + keyType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + let valueJavaType = try translateGenericTypeParameter( + valueType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + return .swiftDictionaryMap(keyJavaType, valueJavaType) + + case .set(let elementType): + let elementJavaType = try translateGenericTypeParameter( + elementType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + return .swiftSet(elementJavaType) + + case .metatype, .tuple, .function, .existential, .opaque, .composite: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + func translateTupleResult( elements: [SwiftTupleElement], - resultName: String = "result" + resultName: String = "result", + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedResult { let arity = elements.count var outParameters: [OutParameter] = [] @@ -1023,7 +1270,11 @@ extension JNISwift2JavaGenerator { let outParamName = "\(resultName)_\(idx)$" // Determine the Java type for this element - let (javaType, elementConversion) = try translateTupleElementResult(type: element.type) + let (javaType, elementConversion) = try translateTupleElementResult( + type: element.type, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) let arrayType: JavaType = .array(javaType) outParameters.append( @@ -1051,7 +1302,11 @@ extension JNISwift2JavaGenerator { } /// Translate a single element type for tuple results on the Java side. - private func translateTupleElementResult(type: SwiftType) throws -> (JavaType, JavaNativeConversionStep) { + private func translateTupleElementResult( + type: SwiftType, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] + ) throws -> (JavaType, JavaNativeConversionStep) { switch type { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { @@ -1062,13 +1317,16 @@ extension JNISwift2JavaGenerator { return (javaType, .placeholder) } - let nominalTypeName = nominalType.nominalTypeDecl.name guard !nominalType.isSwiftJavaWrapper else { throw JavaTranslationError.unsupportedSwiftType(type) } + let javaType = try translateGenericTypeParameter( + type, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) // JExtract class: wrap memory address - let javaType: JavaType = .class(package: nil, name: nominalTypeName) return (.long, .constructSwiftValue(.placeholder, javaType)) default: @@ -1078,7 +1336,9 @@ extension JNISwift2JavaGenerator { func translateOptionalResult( wrappedType swiftType: SwiftType, - resultName: String = "result" + resultName: String = "result", + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedResult { let discriminatorName = "\(resultName)$_discriminator$" @@ -1086,8 +1346,6 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): - let nominalTypeName = nominalType.nominalTypeDecl.name - if let knownType = nominalType.nominalTypeDecl.knownTypeKind { switch knownType { case .foundationDate, .essentialsDate: @@ -1149,7 +1407,12 @@ extension JNISwift2JavaGenerator { } // We assume this is a JExtract class. - let returnType = JavaType.class(package: nil, name: "Optional<\(nominalTypeName)>") + let javaType = try translateGenericTypeParameter( + swiftType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + let returnType = JavaType.class(package: nil, name: "Optional", typeParameters: [javaType]) return TranslatedResult( javaType: returnType, annotations: parameterAnnotations, @@ -1160,7 +1423,7 @@ extension JNISwift2JavaGenerator { discriminatorName: .combinedName(component: "discriminator$"), optionalClass: "Optional", javaType: .long, - toValue: .wrapMemoryAddressUnsafe(.placeholder, .class(package: nil, name: nominalTypeName)), + toValue: .wrapMemoryAddressUnsafe(.placeholder, javaType), resultName: resultName ) ) @@ -1172,14 +1435,14 @@ extension JNISwift2JavaGenerator { func translateArrayParameter( elementType: SwiftType, - parameterName: String + parameterName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config) switch elementType { case .nominal(let nominalType): - let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName - if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(elementType) @@ -1195,18 +1458,23 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(elementType) } + let javaType = try translateGenericTypeParameter( + elementType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) // Assume JExtract imported class return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .array(.class(package: nil, name: nominalTypeName)), + type: .array(javaType), annotations: parameterAnnotations ), conversion: .method( .method( .arraysStream(.requireNonNull(.placeholder, message: "\(parameterName) must not be null")), function: "mapToLong", - arguments: [.constant("\(nominalTypeName)::$memoryAddress")] + arguments: [.constant("\(javaType)::$memoryAddress")] ), function: "toArray", arguments: [] @@ -1219,14 +1487,14 @@ extension JNISwift2JavaGenerator { } func translateArrayResult( - elementType: SwiftType + elementType: SwiftType, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedResult { let annotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config) switch elementType { case .nominal(let nominalType): - let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName - if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(elementType) @@ -1244,10 +1512,14 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(elementType) } - let objectType = JavaType.class(package: nil, name: nominalTypeName) + let javaType = try translateGenericTypeParameter( + elementType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) // We assume this is a JExtract class. return TranslatedResult( - javaType: .array(objectType), + javaType: .array(javaType), annotations: annotations, outParameters: [], conversion: .method( @@ -1257,12 +1529,12 @@ extension JNISwift2JavaGenerator { arguments: [ .lambda( args: ["pointer"], - body: .wrapMemoryAddressUnsafe(.constant("pointer"), objectType) + body: .wrapMemoryAddressUnsafe(.constant("pointer"), javaType) ) ] ), function: "toArray", - arguments: [.constant("\(objectType)[]::new")] + arguments: [.constant("\(javaType)[]::new")] ) ) @@ -1271,29 +1543,35 @@ extension JNISwift2JavaGenerator { } } - func javaTypeForDictionaryComponent(_ swiftType: SwiftType) throws -> JavaType { - switch swiftType { - case .nominal(let nominalType): - if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { - throw JavaTranslationError.unsupportedSwiftType(swiftType) - } - return javaType - } - throw JavaTranslationError.unsupportedSwiftType(swiftType) - - default: - throw JavaTranslationError.unsupportedSwiftType(swiftType) - } + func javaTypeForDictionaryComponent( + _ swiftType: SwiftType, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] + ) throws -> JavaType { + try translateGenericTypeParameter( + swiftType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) } func translateDictionaryParameter( keyType: SwiftType, valueType: SwiftType, - parameterName: String + parameterName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { - let keyJavaType = try javaTypeForDictionaryComponent(keyType) - let valueJavaType = try javaTypeForDictionaryComponent(valueType) + let keyJavaType = try javaTypeForDictionaryComponent( + keyType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + let valueJavaType = try javaTypeForDictionaryComponent( + valueType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) let dictType = JavaType.swiftDictionaryMap(keyJavaType, valueJavaType) return TranslatedParameter( @@ -1308,10 +1586,20 @@ extension JNISwift2JavaGenerator { func translateDictionaryResult( keyType: SwiftType, - valueType: SwiftType + valueType: SwiftType, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedResult { - let keyJavaType = try javaTypeForDictionaryComponent(keyType) - let valueJavaType = try javaTypeForDictionaryComponent(valueType) + let keyJavaType = try javaTypeForDictionaryComponent( + keyType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + let valueJavaType = try javaTypeForDictionaryComponent( + valueType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) let dictType = JavaType.swiftDictionaryMap(keyJavaType, valueJavaType) return TranslatedResult( @@ -1323,9 +1611,15 @@ extension JNISwift2JavaGenerator { func translateSetParameter( elementType: SwiftType, - parameterName: String + parameterName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { - let elementJavaType = try javaTypeForDictionaryComponent(elementType) + let elementJavaType = try javaTypeForDictionaryComponent( + elementType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) let setType = JavaType.swiftSet(elementJavaType) return TranslatedParameter( @@ -1339,9 +1633,15 @@ extension JNISwift2JavaGenerator { } func translateSetResult( - elementType: SwiftType + elementType: SwiftType, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedResult { - let elementJavaType = try javaTypeForDictionaryComponent(elementType) + let elementJavaType = try javaTypeForDictionaryComponent( + elementType, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) let setType = JavaType.swiftSet(elementJavaType) return TranslatedResult( diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift index 7a5d6988..68f8cc7f 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift @@ -44,4 +44,8 @@ extension JavaType { .class(package: "org.swift.swiftkit.core.collections", name: "SwiftSet", typeParameters: [E.boxedType]) } + /// A container for receiving Swift generic instances. + static var _OutSwiftGenericInstance: JavaType { + .class(package: "org.swift.swiftkit.core", name: "_OutSwiftGenericInstance") + } } diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 95e97f4b..f1799af5 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -374,7 +374,6 @@ or set the `asyncFuncMode` configuration value in `swift-java.config` > Note: Generic types are currently only supported in JNI mode. Support for generic types is still work-in-progress and limited. -The generated Java classes do not have generic signatures. Any members containing type parameters (such as T) are not exported. ```swift @@ -404,11 +403,11 @@ public func makeIntID() -> MyID { will be exported as ```java -public final class MyID implements JNISwiftInstance { +public final class MyID implements JNISwiftInstance { public String getDescription(); } public final class MySwiftLibrary { - public static MyID makeIntID(); + public static MyID makeIntID(); } ``` diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index d0cddec3..cf5beded 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -32,6 +32,10 @@ struct JNIGenericTypeTests { public func makeStringID(_ value: String) -> MyID { return MyID(value) } + + public func takeIntID(_ value: MyID) -> Int { + return value.rawValue + } """# @Test @@ -43,18 +47,18 @@ struct JNIGenericTypeTests { detectChunkByInitialLines: 2, expectedChunks: [ """ - public final class MyID implements JNISwiftInstance { + public final class MyID implements JNISwiftInstance { """, """ private MyID(long selfPointer, long selfTypePointer, SwiftArena swiftArena) { """, """ - public static MyID wrapMemoryAddressUnsafe(long selfPointer, long selfTypePointer, SwiftArena swiftArena) { - return new MyID(selfPointer, selfTypePointer, swiftArena); + public static MyID wrapMemoryAddressUnsafe(long selfPointer, long selfTypePointer, SwiftArena swiftArena) { + return new MyID(selfPointer, selfTypePointer, swiftArena); } - public static MyID wrapMemoryAddressUnsafe(long selfPointer, long selfTypePointer) { - return new MyID(selfPointer, selfTypePointer, SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA); + public static MyID wrapMemoryAddressUnsafe(long selfPointer, long selfTypePointer) { + return new MyID(selfPointer, selfTypePointer, SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA); } """, """ @@ -150,7 +154,7 @@ struct JNIGenericTypeTests { detectChunkByInitialLines: 2, expectedChunks: [ """ - public static MyID makeStringID(java.lang.String value, SwiftArena swiftArena) { + public static MyID makeStringID(java.lang.String value, SwiftArena swiftArena) { org.swift.swiftkit.core._OutSwiftGenericInstance instance = new org.swift.swiftkit.core._OutSwiftGenericInstance(); SwiftModule.$makeStringID(value, instance); return MyID.wrapMemoryAddressUnsafe(instance.selfPointer, instance.selfTypePointer, swiftArena); @@ -159,6 +163,14 @@ struct JNIGenericTypeTests { """ private static native void $makeStringID(java.lang.String value, org.swift.swiftkit.core._OutSwiftGenericInstance out); """, + """ + public static long takeIntID(MyID value) { + return SwiftModule.$takeIntID(value.$memoryAddress()); + } + """, + """ + private static native long $takeIntID(long value); + """, ] ) } @@ -183,7 +195,19 @@ struct JNIGenericTypeTests { environment.interface.SetLongField(environment, out, _JNIMethodIDCache._OutSwiftGenericInstance.selfTypePointer, metadataPointerBits$.getJNIValue(in: environment)) return } + """, """ + @_cdecl("Java_com_example_swift_SwiftModule__00024takeIntID__J") + public func Java_com_example_swift_SwiftModule__00024takeIntID__J(environment: UnsafeMutablePointer!, thisClass: jclass, value: jlong) -> jlong { + assert(value != 0, "value memory address was null") + let valueBits$ = Int(Int64(fromJNI: value, in: environment)) + let value$ = UnsafeMutablePointer>(bitPattern: valueBits$) + guard let value$ else { + fatalError("value memory address was null in call to \\(#function)!") + } + return Int64(SwiftModule.takeIntID(value$.pointee)).getJNILocalRefValue(in: environment) + } + """, ] ) }