diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift new file mode 100644 index 00000000..4e0041fb --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public struct MyID { + public var rawValue: T + public init(_ rawValue: T) { + self.rawValue = rawValue + } + public var description: String { + "\(rawValue)" + } +} + +public func makeIntID(_ value: Int) -> MyID { + MyID(value) +} + +public func makeStringID(_ value: String) -> MyID { + MyID(value) +} + +public struct MyEntity { + public var id: MyID + public var name: String + public init(id: MyID, name: String) { + self.id = id + self.name = name + } +} + +public enum GenericEnum { + case foo + case bar +} + +public func makeIntGenericEnum() -> GenericEnum { + if Bool.random() { return .foo } else { return .bar } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java new file mode 100644 index 00000000..fa0d76da --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import static org.junit.jupiter.api.Assertions.*; + +public class GenericTypeTest { + @Test + void returnsGenericType() { + try (var arena = SwiftArena.ofConfined()) { + MyID stringId = MySwiftLibrary.makeStringID("Java", arena); + assertEquals("Java", stringId.getDescription()); + + MyID intId = MySwiftLibrary.makeIntID(42, arena); + assertEquals("42", intId.getDescription()); + } + } + + @Test + void genericTypeProperty() { + try (var arena = SwiftArena.ofConfined()) { + MyID intId = MySwiftLibrary.makeIntID(42, arena); + MyEntity entity = MyEntity.init(intId, "name", arena); + assertEquals("42", entity.getId(arena).getDescription()); + } + } + + @Test + void genericEnum() { + try (var arena = SwiftArena.ofConfined()) { + 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 87a5d358..ca2b1e6a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -199,14 +199,33 @@ extension JNISwift2JavaGenerator { * @param selfPointer a pointer to the memory containing the value * @param swiftArena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. */ - private \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) { - SwiftObjects.requireNonZero(selfPointer, "selfPointer"); - this.selfPointer = selfPointer; + """ + ) + var swiftPointerParams = ["selfPointer"] + if decl.swiftNominal.isGeneric { + swiftPointerParams.append("selfTypePointer") + } + let swiftPointerArg = swiftPointerParams.map { "long \($0)" }.joined(separator: ", ") + printer.printBraceBlock("private \(decl.swiftNominal.name)(\(swiftPointerArg), SwiftArena swiftArena)") { printer in + for param in swiftPointerParams { + printer.print( + """ + SwiftObjects.requireNonZero(\(param), "\(param)"); + this.\(param) = \(param); + """ + ) + } + printer.print( + """ // Only register once we have fully initialized the object since this will need the object pointer. swiftArena.register(this); - } - + """ + ) + } + printer.println() + printer.print( + """ /** * Assume that the passed {@code long} represents a memory address of a {@link \(decl.swiftNominal.name)}. *

@@ -216,12 +235,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(long selfPointer, SwiftArena swiftArena) { - return new \(decl.swiftNominal.name)(selfPointer, swiftArena); + public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(\(swiftPointerArg), SwiftArena swiftArena) { + return new \(decl.swiftNominal.name)(\(swiftPointerParams.joined(separator: ", ")), swiftArena); } - public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(long selfPointer) { - return new \(decl.swiftNominal.name)(selfPointer, SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA); + public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(\(swiftPointerArg)) { + return new \(decl.swiftNominal.name)(\(swiftPointerParams.joined(separator: ", ")), SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA); } """ ) @@ -245,6 +264,11 @@ extension JNISwift2JavaGenerator { """ ) + if decl.swiftNominal.isGeneric { + printer.print("/** Pointer to the metatype of Self */") + printer.print("private final long selfTypePointer;") + } + printer.println() if decl.swiftNominal.kind == .enum { @@ -367,10 +391,17 @@ extension JNISwift2JavaGenerator { } // TODO: Consider whether all of these "utility" functions can be printed using our existing printing logic. - printer.printBraceBlock("public Discriminator getDiscriminator()") { printer in - printer.print("return Discriminator.values()[$getDiscriminator(this.$memoryAddress())];") + if decl.swiftNominal.isGeneric { + printer.printBraceBlock("public Discriminator getDiscriminator()") { printer in + printer.print("return Discriminator.values()[$getDiscriminator(this.$memoryAddress(), this.$typeMetadataAddress())];") + } + printer.print("private static native int $getDiscriminator(long self, long selfType);") + } else { + printer.printBraceBlock("public Discriminator getDiscriminator()") { printer in + printer.print("return Discriminator.values()[$getDiscriminator(this.$memoryAddress())];") + } + printer.print("private static native int $getDiscriminator(long self);") } - printer.print("private static native int $getDiscriminator(long self);") } private func printEnumCaseInterface(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { @@ -399,6 +430,11 @@ extension JNISwift2JavaGenerator { } private func printEnumStaticInitializers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + if !decl.cases.isEmpty && decl.swiftNominal.isGeneric { + self.logger.debug("Skipping generic static initializers in '\(decl.swiftNominal.name)'") + return + } + for enumCase in decl.cases { printFunctionDowncallMethods(&printer, enumCase.caseFunction) } @@ -605,6 +641,9 @@ extension JNISwift2JavaGenerator { if let selfParameter = nativeSignature.selfParameter?.parameters { parameters += selfParameter } + if let selfTypeParameter = nativeSignature.selfTypeParameter?.parameters { + parameters += selfTypeParameter + } parameters += nativeSignature.result.outParameters let renderedParameters = parameters.map { javaParameter in @@ -633,6 +672,12 @@ extension JNISwift2JavaGenerator { arguments.append(lowered) } + // 'Self' metatype. + if let selfTypeParameter = translatedFunctionSignature.selfTypeParameter { + let lowered = selfTypeParameter.conversion.render(&printer, "this") + arguments.append(lowered) + } + // Indirect return receivers for outParameter in translatedFunctionSignature.resultType.outParameters { printer.print( @@ -657,51 +702,86 @@ extension JNISwift2JavaGenerator { } private func printTypeMetadataAddressFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - printer.print("private static native long $typeMetadataAddressDowncall();") - - let funcName = "$typeMetadataAddress" - printer.print("@Override") - printer.printBraceBlock("public long $typeMetadataAddress()") { printer in - printer.print( - """ - long self$ = this.$memoryAddress(); - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)", - "this", this, - "self", self$); - } - return \(type.swiftNominal.name).$typeMetadataAddressDowncall(); - """ - ) + if type.swiftNominal.isGeneric { + printer.print("@Override") + printer.printBraceBlock("public long $typeMetadataAddress()") { printer in + printer.print("return this.selfTypePointer;") + } + } else { + printer.print("private static native long $typeMetadataAddressDowncall();") + + let funcName = "$typeMetadataAddress" + printer.print("@Override") + printer.printBraceBlock("public long $typeMetadataAddress()") { printer in + printer.print( + """ + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)", + "this", this, + "self", self$); + } + """ + ) + printer.print("return \(type.swiftNominal.name).$typeMetadataAddressDowncall();") + } } } /// Prints the destroy function for a `JNISwiftInstance` private func printDestroyFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - printer.print("private static native void $destroy(long selfPointer);") + let isGeneric = type.swiftNominal.isGeneric + if isGeneric { + printer.print("private static native void $destroy(long selfPointer, long selfType);") + } else { + printer.print("private static native void $destroy(long selfPointer);") + } let funcName = "$createDestroyFunction" printer.print("@Override") printer.printBraceBlock("public Runnable \(funcName)()") { printer in - printer.print( - """ - long self$ = this.$memoryAddress(); - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)", - "this", this, - "self", self$); - } - return new Runnable() { - @Override - public void run() { - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("\(type.swiftNominal.name).$destroy", "self", self$); + printer.print("long self$ = this.$memoryAddress();") + if isGeneric { + printer.print( + """ + long selfType$ = this.$typeMetadataAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)", + "this", this, + "self", self$, + "selfType", selfType$); + } + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("\(type.swiftNominal.name).$destroy", "self", self$, "selfType", selfType$); + } + \(type.swiftNominal.name).$destroy(self$, selfType$); } - \(type.swiftNominal.name).$destroy(self$); + }; + """ + ) + } else { + printer.print( + """ + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)", + "this", this, + "self", self$); } - }; - """ - ) + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("\(type.swiftNominal.name).$destroy", "self", self$); + } + \(type.swiftNominal.name).$destroy(self$); + } + }; + """ + ) + } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 26999878..ae359520 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -138,6 +138,8 @@ extension JNISwift2JavaGenerator { exceptions.append(.integerOverflow) } + let isGenericParent = enumCase.caseFunction.parentType?.asNominalTypeDeclaration?.isGeneric == true + let getAsCaseFunction = TranslatedFunctionDecl( name: getAsCaseName, isStatic: false, @@ -159,6 +161,12 @@ extension JNISwift2JavaGenerator { ] ) ), + selfTypeParameter: !isGenericParent + ? nil + : .init( + parameter: JavaParameter(name: "selfType", type: .long), + conversion: .typeMetadataAddress(.placeholder) + ), parameters: [], resultType: TranslatedResult( javaType: .class(package: nil, name: "Optional<\(caseName)>"), @@ -176,6 +184,14 @@ extension JNISwift2JavaGenerator { indirectConversion: nil, conversionCheck: nil ), + selfTypeParameter: !isGenericParent + ? nil + : .init( + parameters: [JavaParameter(name: "selfType", type: .long)], + conversion: .extractMetatypeValue(.placeholder), + indirectConversion: nil, + conversionCheck: nil + ), parameters: [], result: NativeResult( javaType: nativeParametersType, @@ -326,6 +342,14 @@ extension JNISwift2JavaGenerator { genericRequirements: functionSignature.genericRequirements ) + let selfTypeParameter = try self.translateSelfTypeParameter( + functionSignature.selfParameter, + methodName: methodName, + parentName: parentName, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements + ) + var exceptions: [JavaExceptionType] = [] if functionSignature.parameters.contains(where: \.type.isArchDependingInteger) { @@ -336,6 +360,7 @@ extension JNISwift2JavaGenerator { return TranslatedFunctionSignature( selfParameter: selfParameter, + selfTypeParameter: selfTypeParameter, parameters: parameters, resultType: resultType, exceptions: exceptions @@ -386,6 +411,39 @@ extension JNISwift2JavaGenerator { } } + func translateSelfTypeParameter( + _ selfParameter: SwiftSelfParameter?, + methodName: String, + parentName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] + ) throws -> TranslatedParameter? { + guard let selfParameter else { + return nil + } + + let isGeneric = + switch selfParameter { + case .instance(let selfParameter): + selfParameter.type.asNominalTypeDeclaration?.isGeneric == true + case .initializer(let swiftType), .staticMethod(let swiftType): + swiftType.asNominalTypeDeclaration?.isGeneric == true + } + if isGeneric { + return try self.translateParameter( + swiftType: .metatype(selfParameter.selfType), + parameterName: "selfType", + methodName: methodName, + parentName: parentName, + genericParameters: genericParameters, + genericRequirements: genericRequirements, + parameterPosition: nil + ) + } else { + return nil + } + } + func translateParameter( swiftType: SwiftType, parameterName: String, @@ -521,7 +579,13 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) - case .metatype, .tuple, .composite: + case .metatype: + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: .long), + conversion: .typeMetadataAddress(.placeholder) + ) + + case .tuple, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -789,12 +853,34 @@ extension JNISwift2JavaGenerator { // We assume this is a JExtract class. let javaType = JavaType.class(package: nil, name: nominalType.nominalTypeDecl.name) - return TranslatedResult( - javaType: javaType, - annotations: resultAnnotations, - outParameters: [], - conversion: .wrapMemoryAddressUnsafe(.placeholder, javaType) - ) + + if nominalType.nominalTypeDecl.isGeneric { + return TranslatedResult( + javaType: javaType, + annotations: resultAnnotations, + outParameters: [.init(name: "instance", type: .OutSwiftGenericInstance, allocation: .new)], + conversion: .aggregate( + variable: nil, + [ + .print(.placeholder), + .wrapMemoryAddressUnsafe( + .commaSeparated([ + .member(.constant("instance"), field: "selfPointer"), + .member(.constant("instance"), field: "selfTypePointer"), + ]), + javaType + ), + ] + ) + ) + } else { + return TranslatedResult( + javaType: javaType, + annotations: resultAnnotations, + outParameters: [], + conversion: .wrapMemoryAddressUnsafe(.placeholder, javaType) + ) + } case .tuple([]): return TranslatedResult(javaType: .void, outParameters: [], conversion: .placeholder) @@ -1076,6 +1162,7 @@ extension JNISwift2JavaGenerator { struct TranslatedFunctionSignature { var selfParameter: TranslatedParameter? + var selfTypeParameter: TranslatedParameter? var parameters: [TranslatedParameter] var resultType: TranslatedResult var exceptions: [JavaExceptionType] @@ -1177,6 +1264,8 @@ extension JNISwift2JavaGenerator { indirect case method(JavaNativeConversionStep, function: String, arguments: [JavaNativeConversionStep] = []) + indirect case member(JavaNativeConversionStep, field: String) + case isOptionalPresent indirect case combinedValueToOptional( @@ -1294,6 +1383,10 @@ extension JNISwift2JavaGenerator { let argsStr = args.joined(separator: ", ") return "\(inner).\(methodName)(\(argsStr))" + case .member(let inner, let fieldName): + let inner = inner.render(&printer, placeholder) + return "\(inner).\(fieldName)" + case .combinedValueToOptional( let combined, let combinedType, @@ -1413,6 +1506,9 @@ extension JNISwift2JavaGenerator { case .method(let inner, _, let args): return inner.requiresSwiftArena || args.contains(where: \.requiresSwiftArena) + case .member(let inner, _): + return inner.requiresSwiftArena + case .combinedValueToOptional(let inner, _, _, _, _, _): return inner.requiresSwiftArena diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 18f0b34c..077d0800 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -62,10 +62,27 @@ extension JNISwift2JavaGenerator { nil } + let selfTypeParameter: NativeParameter? = + if let selfType = functionSignature.selfParameter?.selfType, + selfType.asNominalTypeDeclaration?.isGeneric == true + { + try translateParameter( + type: .metatype(selfType), + parameterName: "selfType", + methodName: methodName, + parentName: parentName, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements + ) + } else { + nil + } + let result = try translate(swiftResult: functionSignature.result) return NativeFunctionSignature( selfParameter: nativeSelf, + selfTypeParameter: selfTypeParameter, parameters: parameters, result: result ) @@ -290,7 +307,17 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) - case .metatype, .tuple, .composite: + case .metatype: + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: .long) + ], + conversion: .extractMetatypeValue(.placeholder), + indirectConversion: nil, + conversionCheck: nil + ) + + case .tuple, .composite: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -667,11 +694,23 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } - return NativeResult( - javaType: .long, - conversion: .getJNIValue(.allocateSwiftValue(.placeholder, name: resultName, swiftType: swiftResult.type)), - outParameters: [] - ) + if nominalType.nominalTypeDecl.isGeneric { + return NativeResult( + javaType: .void, + conversion: .genericValueIndirectReturn( + .getJNIValue(.allocateSwiftValue(.placeholder, name: resultName, swiftType: swiftResult.type)), + swiftFunctionResultType: swiftResult.type, + outArgumentName: "out" + ), + outParameters: [.init(name: "out", type: .OutSwiftGenericInstance)] + ) + } else { + return NativeResult( + javaType: .long, + conversion: .getJNIValue(.allocateSwiftValue(.placeholder, name: resultName, swiftType: swiftResult.type)), + outParameters: [] + ) + } case .tuple([]): return NativeResult( @@ -804,6 +843,7 @@ extension JNISwift2JavaGenerator { struct NativeFunctionSignature { let selfParameter: NativeParameter? + var selfTypeParameter: NativeParameter? var parameters: [NativeParameter] var result: NativeResult } @@ -872,6 +912,8 @@ extension JNISwift2JavaGenerator { convertLongFromJNI: Bool = true ) + indirect case extractMetatypeValue(NativeSwiftConversionStep) + /// Allocate memory for a Swift value and outputs the pointer indirect case allocateSwiftValue(NativeSwiftConversionStep, name: String, swiftType: SwiftType) @@ -909,6 +951,12 @@ extension JNISwift2JavaGenerator { placeholderValue: NativeSwiftConversionStep ) + indirect case genericValueIndirectReturn( + NativeSwiftConversionStep, + swiftFunctionResultType: SwiftType, + outArgumentName: String + ) + indirect case method( NativeSwiftConversionStep, function: String, @@ -1073,6 +1121,19 @@ extension JNISwift2JavaGenerator { } return pointerName + case .extractMetatypeValue(let inner): + let inner = inner.render(&printer, placeholder) + let pointerName = "\(inner)$" + printer.print( + """ + let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment)) + guard let \(pointerName) = UnsafeRawPointer(bitPattern: \(inner)Bits$) else { + fatalError("\(inner) metadata address was null") + } + """ + ) + return "unsafeBitCast(\(pointerName), to: Any.Type.self)" + case .allocateSwiftValue(let inner, let name, let swiftType): let inner = inner.render(&printer, placeholder) let pointerName = "\(name)$" @@ -1256,6 +1317,18 @@ extension JNISwift2JavaGenerator { return "result$" + case .genericValueIndirectReturn(let inner, let swiftFunctionResultType, let outArgumentName): + let inner = inner.render(&printer, placeholder) + printer.print( + """ + environment.interface.SetLongField(environment, \(outArgumentName), _JNIMethodIDCache.OutSwiftGenericInstance.selfPointer, \(inner)) + let metadataPointer = unsafeBitCast(\(swiftFunctionResultType).self, to: UnsafeRawPointer.self) + let metadataPointerBits$ = Int64(Int(bitPattern: metadataPointer)) + environment.interface.SetLongField(environment, \(outArgumentName), _JNIMethodIDCache.OutSwiftGenericInstance.selfTypePointer, metadataPointerBits$.getJNIValue(in: environment)) + """ + ) + return "" + case .method(let inner, let methodName, let arguments): let inner = inner.render(&printer, placeholder) let args = arguments.map { name, value in diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 347d2024..6e750e28 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes +import SwiftSyntax #if canImport(FoundationEssentials) import FoundationEssentials @@ -20,6 +21,7 @@ import FoundationEssentials import Foundation #endif + extension JNISwift2JavaGenerator { func writeSwiftThunkSources() throws { var printer = CodePrinter() @@ -246,6 +248,11 @@ extension JNISwift2JavaGenerator { } private func printConcreteTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + if type.swiftNominal.isGeneric { + printOpenerProtocol(&printer, type) + printer.println() + } + for initializer in type.initializers { printSwiftFunctionThunk(&printer, initializer) printer.println() @@ -255,9 +262,11 @@ extension JNISwift2JavaGenerator { printEnumDiscriminator(&printer, type) printer.println() - for enumCase in type.cases { - printEnumCase(&printer, enumCase) - printer.println() + if !type.swiftNominal.isGeneric { + for enumCase in type.cases { + printEnumCase(&printer, enumCase) + printer.println() + } } } @@ -287,21 +296,48 @@ extension JNISwift2JavaGenerator { private func printEnumDiscriminator(_ printer: inout CodePrinter, _ type: ImportedNominalType) { let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) + var parameters = [selfPointerParam] + if type.swiftNominal.isGeneric { + parameters.append(JavaParameter(name: "selfType", type: .long)) + } + printCDecl( &printer, javaMethodName: "$getDiscriminator", parentName: type.swiftNominal.name, - parameters: [selfPointerParam], + parameters: parameters, resultType: .int ) { printer in - let selfPointer = self.printSelfJLongToUnsafeMutablePointer( - &printer, - swiftParentName: type.swiftNominal.name, - selfPointerParam - ) - printer.printBraceBlock("switch (\(selfPointer).pointee)") { printer in - for (idx, enumCase) in type.cases.enumerated() { - printer.print("case .\(enumCase.name): return \(idx)") + if type.swiftNominal.isGeneric { + let knownTypes = SwiftKnownTypes(symbolTable: lookupContext.symbolTable) + let discriminatorFunctionSignature = SwiftFunctionSignature( + selfParameter: .instance(SwiftParameter(convention: .byValue, parameterName: "selfPointer", type: type.swiftType)), + parameters: [], + result: .init(convention: .direct, type: knownTypes.int), + effectSpecifiers: [], + genericParameters: [], + genericRequirements: [] + ) + printFunctionOpenerCall( + &printer, + .init( + module: swiftModuleName, + swiftDecl: DeclSyntax("func getDiscriminator() -> Int"), + name: "getDiscriminator", + apiKind: .function, + functionSignature: discriminatorFunctionSignature + ) + ) + } else { + let selfPointer = self.printSelfJLongToUnsafeMutablePointer( + &printer, + swiftParentName: type.swiftNominal.name, + selfPointerParam + ) + printer.printBraceBlock("switch (\(selfPointer).pointee)") { printer in + for (idx, enumCase) in type.cases.enumerated() { + printer.print("case .\(enumCase.name): return \(idx)") + } } } } @@ -401,7 +437,11 @@ extension JNISwift2JavaGenerator { &printer, translatedDecl ) { printer in - self.printFunctionDowncall(&printer, decl) + if let parent = decl.parentType?.asNominalType, parent.nominalTypeDecl.isGeneric { + self.printFunctionOpenerCall(&printer, decl) + } else { + self.printFunctionDowncall(&printer, decl) + } } } @@ -676,7 +716,9 @@ extension JNISwift2JavaGenerator { if let selfParameter = nativeSignature.selfParameter { parameters += selfParameter.parameters } - + if let selfTypeParameter = nativeSignature.selfTypeParameter { + parameters += selfTypeParameter.parameters + } parameters += nativeSignature.result.outParameters printCDecl( @@ -748,6 +790,10 @@ extension JNISwift2JavaGenerator { } private func printTypeMetadataAddressThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + if type.swiftNominal.isGeneric { + return + } + printCDecl( &printer, javaMethodName: "$typeMetadataAddressDowncall", @@ -767,25 +813,49 @@ extension JNISwift2JavaGenerator { /// Prints the implementation of the destroy function. private func printDestroyFunctionThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) + var parameters = [selfPointerParam] + if type.swiftNominal.isGeneric { + parameters.append(JavaParameter(name: "selfType", type: .long)) + } + printCDecl( &printer, javaMethodName: "$destroy", parentName: type.swiftNominal.qualifiedName, - parameters: [ - selfPointerParam - ], + parameters: parameters, resultType: .void ) { printer in - let parentName = type.qualifiedName - let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) - // Deinitialize the pointer allocated (which will call the VWT destroy method) - // then deallocate the memory. - printer.print( - """ - \(selfVar).deinitialize(count: 1) - \(selfVar).deallocate() - """ - ) + if type.swiftNominal.isGeneric { + let destroyFunctionSignature = SwiftFunctionSignature( + selfParameter: .instance(SwiftParameter(convention: .byValue, parameterName: "selfPointer", type: type.swiftType)), + parameters: [], + result: .void, + effectSpecifiers: [], + genericParameters: [], + genericRequirements: [] + ) + printFunctionOpenerCall( + &printer, + .init( + module: swiftModuleName, + swiftDecl: DeclSyntax("func destroy()"), + name: "destroy", + apiKind: .function, + functionSignature: destroyFunctionSignature + ) + ) + } else { + let parentName = type.qualifiedName + let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) + // Deinitialize the pointer allocated (which will call the VWT destroy method) + // then deallocate the memory. + printer.print( + """ + \(selfVar).deinitialize(count: 1) + \(selfVar).deallocate() + """ + ) + } } } @@ -850,6 +920,137 @@ extension JNISwift2JavaGenerator { } } + private func printFunctionOpenerCall(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + guard let translatedDecl = self.translatedDecl(for: decl) else { + fatalError("Cannot print function opener for a function that can't be translated: \(decl)") + } + guard let parentNominalType = decl.parentType?.asNominalType else { + fatalError("Only functions with nominal type parents can have openers") + } + let nativeSignature = translatedDecl.nativeFunctionSignature + + let selfType = nativeSignature.selfTypeParameter!.conversion.render(&printer, "selfType") + let openerName = openerProtocolName(for: parentNominalType.nominalTypeDecl) + printer.print("let openerType = \(selfType) as! (any \(openerName).Type)") + + var parameters = nativeSignature.parameters.flatMap(\.parameters) + if let selfParameter = nativeSignature.selfParameter { + parameters += selfParameter.parameters + } + parameters += nativeSignature.result.outParameters + + let openerArguments = + [ + "environment: environment", + "thisClass: thisClass", + ] + + parameters.map { javaParameter in + "\(javaParameter.name): \(javaParameter.name)" + } + let call = "openerType.\(decl.openerMethodName)(\(openerArguments.joined(separator: ", ")))" + + if !decl.functionSignature.result.type.isVoid { + printer.print("return \(call)") + } else { + printer.print(call) + } + } + + private func openerProtocolName(for type: SwiftNominalTypeDeclaration) -> String { + "_\(swiftModuleName)_\(type.name)_opener" + } + + private func printOpenerProtocol(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + let protocolName = openerProtocolName(for: type.swiftNominal) + + func printFunctionDecl(_ printer: inout CodePrinter, decl: ImportedFunc, skipMethodBody: Bool) { + guard let translatedDecl = self.translatedDecl(for: decl) else { return } + let nativeSignature = translatedDecl.nativeFunctionSignature + + var parameters = nativeSignature.parameters.flatMap(\.parameters) + if let selfParameter = nativeSignature.selfParameter { + parameters += selfParameter.parameters + } + parameters += nativeSignature.result.outParameters + + let resultType = nativeSignature.result.javaType + + let translatedParameters = parameters.map { + "\($0.name): \($0.type.jniTypeName)" + } + + let thunkParameters = + [ + "environment: UnsafeMutablePointer!", + "thisClass: jclass", + ] + translatedParameters + let thunkReturnType = resultType != .void ? " -> \(resultType.jniTypeName)" : "" + + let signature = #"static func \#(decl.openerMethodName)(\#(thunkParameters.joined(separator: ", ")))\#(thunkReturnType)"# + if !skipMethodBody { + printer.printBraceBlock(signature) { printer in + printFunctionDowncall(&printer, decl) + } + } else { + printer.print(signature) + } + } + + printer.printBraceBlock("protocol \(protocolName)") { printer in + for variable in type.variables { + printFunctionDecl(&printer, decl: variable, skipMethodBody: true) + } + + for method in type.methods { + printFunctionDecl(&printer, decl: method, skipMethodBody: true) + } + + if type.swiftNominal.kind == .enum { + printer.print("static func _getDiscriminator(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jint") + } + + printer.print("static func _destroy(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong)") + } + printer.println() + printer.printBraceBlock("extension \(type.swiftNominal.name): \(protocolName)") { printer in + for variable in type.variables { + if variable.isStatic { continue } + printFunctionDecl(&printer, decl: variable, skipMethodBody: false) + } + + for method in type.methods { + if method.isStatic { continue } + printFunctionDecl(&printer, decl: method, skipMethodBody: false) + } + + if type.swiftNominal.kind == .enum { + printer.printBraceBlock("static func _getDiscriminator(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jint") { printer in + let selfPointer = self.printSelfJLongToUnsafeMutablePointer( + &printer, + swiftParentName: "Self", + JavaParameter(name: "selfPointer", type: .long) + ) + printer.printBraceBlock("switch (\(selfPointer).pointee)") { printer in + for (idx, enumCase) in type.cases.enumerated() { + printer.print("case .\(enumCase.name): return \(idx)") + } + } + } + } + + printer.printBraceBlock("static func _destroy(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong)") { printer in + printer.print(#"assert(selfPointer != 0, "self memory address was null")"#) + printer.print("let selfBits$ = Int(Int64(fromJNI: selfPointer, in: environment))") + printer.print("let self$ = UnsafeMutablePointer(bitPattern: selfBits$)") + printer.printBraceBlock("guard let self$ else") { printer in + printer.print("fatalError(\"self memory address was null in call to \\(#function)!\")") + } + printer.print("self$.deinitialize(count: 1)") + printer.print("self$.deallocate()") + } + } + } + /// Print the necessary conversion logic to go from a `jlong` to a `UnsafeMutablePointer` /// /// - Returns: name of the created "self" variable @@ -915,3 +1116,15 @@ extension SwiftNominalTypeDeclaration { return "Java\(self.name)" } } + +extension ImportedFunc { + fileprivate var openerMethodName: String { + let prefix = + switch apiKind { + case .getter: "_get_" + case .setter: "_set_" + default: "_" + } + return "\(prefix)\(name)" + } +} diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 9d3a60dd..ebfb25c6 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -162,6 +162,11 @@ final class Swift2JavaVisitor { functionSignature: signature ) + if typeContext?.swiftNominal.isGeneric == true && imported.isStatic { + log.debug("Skip importing static function in generic type: '\(node.qualifiedNameForDebug)'") + return + } + log.debug("Record imported method \(node.qualifiedNameForDebug)") if let typeContext { typeContext.methods.append(imported) @@ -268,6 +273,11 @@ final class Swift2JavaVisitor { return } + if typeContext.swiftNominal.isGeneric { + log.debug("Skip Importing generic type initializer \(node.kind) '\(node.qualifiedNameForDebug)'") + return + } + self.log.debug("Import initializer: \(node.kind) '\(node.qualifiedNameForDebug)'") let signature: SwiftFunctionSignature @@ -365,6 +375,11 @@ final class Swift2JavaVisitor { functionSignature: signature ) + if typeContext?.swiftNominal.isGeneric == true && imported.isStatic { + log.debug("Skip importing static accessor in generic type: '\(node.qualifiedNameForDebug)'") + return + } + log.debug( "Record imported variable accessor \(kind == .getter || kind == .subscriptGetter ? "getter" : "setter"):\(node.qualifiedNameForDebug)" ) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 2dab3b96..f4be77f8 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -67,6 +67,15 @@ enum SwiftSelfParameter: Equatable { /// 'self' is the type for a call to an initializer. We only need the type /// to form the call. case initializer(SwiftType) + + var selfType: SwiftType { + switch self { + case .instance(let swiftParameter): + return swiftParameter.type + case .staticMethod(let swiftType), .initializer(let swiftType): + return swiftType + } + } } extension SwiftFunctionSignature { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 4c6b8b0c..78ae5289 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -73,7 +73,8 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { /// MyCollection.Iterator. let parent: SwiftNominalTypeDeclaration? - // TODO: Generic parameters. + /// The generic parameters of this nominal type. + let genericParameters: [SwiftGenericParameterDeclaration] /// Identify this nominal declaration as one of the known standard library /// types, like 'Swift.Int[. @@ -91,6 +92,10 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { ) { self.parent = parent self.syntax = node + self.genericParameters = + node.asProtocol(WithGenericParametersSyntax.self)?.genericParameterClause?.parameters.map { + SwiftGenericParameterDeclaration(sourceFilePath: sourceFilePath, moduleName: moduleName, node: $0) + } ?? [] // Determine the kind from the syntax node. switch Syntax(node).as(SyntaxEnum.self) { @@ -158,6 +163,10 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { return false } } + + var isGeneric: Bool { + !genericParameters.isEmpty + } } package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { diff --git a/Sources/JavaTypes/JavaType.swift b/Sources/JavaTypes/JavaType.swift index 803952bf..8f7a3eac 100644 --- a/Sources/JavaTypes/JavaType.swift +++ b/Sources/JavaTypes/JavaType.swift @@ -80,3 +80,9 @@ extension JavaType { } } } + +extension JavaType { + public 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 0789700e..ab8f2217 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -60,6 +60,9 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Async functions `func async` and properties: `var { get async {} }` | ❌ | ✅ | | Arrays: `[UInt8]`, `[MyType]`, `Array` etc | ❌ | ✅ | | Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | +| Generic type: `struct S` | ❌ | ✅ | +| Functions or properties using generic type param: `struct S { func f(_: T) {} }` | ❌ | ❌ | +| Static functions or properties in generic type | ❌ | ❌ | | Generic parameters in functions: `func f(x: T)` | ❌ | ✅ | | Generic return values in functions: `func f() -> T` | ❌ | ❌ | | Tuples: `(Int, String)`, `(A, B, C)` | ❌ | ❌ | @@ -354,3 +357,47 @@ This is a mode for legacy platforms, where `CompletableFuture` is not available, In this mode `async` functions in Swift are extracted as Java methods returning a `java.util.concurrent.Future`. To enable this mode pass the `--async-func-mode future` command line option, or set the `asyncFuncMode` configuration value in `swift-java.config` + +### Generic types + +> Note: Generic types are currently only supported in JNI mode. + +Support for generic types is limited. +The generated Java classes do not have generic signatures. +Any members containing type parameters (such as T) are not exported. + +```swift +public struct MyID { + // Not exported: Contains the type parameter 'T' + public var rawValue: T + + // Not exported: The initializer depends on 'T' + public init(rawValue: T) { + self.rawValue = rawValue + } + + // Exported: Does not depend on 'T' + public var description: String { "\(rawValue)" } + + // Not exported: Although it doesn't use 'T' directly, + // it is a member of a generic context (MyID.foo). + public static func foo() -> String { "" } +} + +// Exported: A specialized function with a concrete type (MyID) +public func makeIntID() -> MyID { + ... +} +``` + +will be exported as + +```java +public final class MyID implements JNISwiftInstance { + public String getDescription(); +} + +public final class MySwiftLibrary { + public static MyID makeIntID(); +} +``` diff --git a/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift index 7be79a9f..402d953d 100644 --- a/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift +++ b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift @@ -120,4 +120,33 @@ extension _JNIMethodIDCache { cache.methods[typeMetadataAddressMethod]! } } + + public enum OutSwiftGenericInstance { + private static let selfPointerField = Field( + name: "selfPointer", + signature: "J" + ) + + private static let selfTypePointerField = Field( + name: "selfTypePointer", + signature: "J" + ) + + private static let cache = _JNIMethodIDCache( + className: "org/swift/swiftkit/core/OutSwiftGenericInstance", + fields: [selfPointerField, selfTypePointerField] + ) + + public static var `class`: jclass { + cache.javaClass + } + + public static var selfPointer: jfieldID { + cache.fields[selfPointerField]! + } + + public static var selfTypePointer: jfieldID { + cache.fields[selfTypePointerField]! + } + } } diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index e5ae1a6f..fcb1e3c5 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -32,8 +32,21 @@ public final class _JNIMethodIDCache: Sendable { } } + public struct Field: Hashable { + public let name: String + public let signature: String + public let isStatic: Bool + + public init(name: String, signature: String, isStatic: Bool = false) { + self.name = name + self.signature = signature + self.isStatic = isStatic + } + } + nonisolated(unsafe) let _class: jclass? nonisolated(unsafe) let methods: [Method: jmethodID] + nonisolated(unsafe) let fields: [Field: jfieldID] public var javaClass: jclass { self._class! @@ -44,7 +57,7 @@ public final class _JNIMethodIDCache: Sendable { /// This is to make sure that the underlying reference remains valid nonisolated(unsafe) private let javaObjectHolder: JavaObjectHolder? - public init(className: String, methods: [Method]) { + public init(className: String, methods: [Method] = [], fields: [Field] = []) { let environment = try! JavaVirtualMachine.shared().environment() let clazz: jobject @@ -89,12 +102,33 @@ public final class _JNIMethodIDCache: Sendable { } } } + self.fields = fields.reduce(into: [:]) { (result, field) in + if field.isStatic { + if let fieldID = environment.interface.GetStaticFieldID(environment, clazz, field.name, field.signature) { + result[field] = fieldID + } else { + fatalError( + "Static field \(field.signature) with signature \(field.signature) not found in class \(className)" + ) + } + } else { + if let fieldID = environment.interface.GetFieldID(environment, clazz, field.name, field.signature) { + result[field] = fieldID + } else { + fatalError("field \(field.signature) with signature \(field.signature) not found in class \(className)") + } + } + } } public subscript(_ method: Method) -> jmethodID? { methods[method] } + public subscript(_ field: Field) -> jfieldID? { + fields[field] + } + public func cleanup(environment: UnsafeMutablePointer!) { environment.interface.DeleteGlobalRef(environment, self._class) } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java new file mode 100644 index 00000000..018b5108 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +/** + * A container for receiving Swift generic instances. + *

    + * This class acts as an "indirect return" receiver (out-parameter) for + * native calls that return Swift generic types. It pairs + * the object instance with its corresponding type metadata. + *

    + */ +public final class OutSwiftGenericInstance { + public long selfPointer; + public long selfTypePointer; +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift new file mode 100644 index 00000000..8bc4a0e0 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -0,0 +1,191 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIGenericTypeTests { + let genericFile = + #""" + public struct MyID { + public var rawValue: T + public init(_ rawValue: T) { + self.rawValue = rawValue + } + public var description: String { + "\(rawValue)" + } + } + + public func makeStringID(_ value: String) -> MyID { + return MyID(value) + } + """# + + @Test + func generateJavaClass() throws { + try assertOutput( + input: genericFile, + .jni, + .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + 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) { + return new MyID(selfPointer, selfTypePointer, SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA); + } + """, + """ + private final long selfTypePointer; + """, + """ + public java.lang.String getDescription() { + return MyID.$getDescription(this.$memoryAddress(), this.$typeMetadataAddress()); + } + private static native java.lang.String $getDescription(long self, long selfType); + """, + """ + @Override + public long $typeMetadataAddress() { + return this.selfTypePointer; + } + """, + """ + private static native void $destroy(long selfPointer, long selfType); + @Override + public Runnable $createDestroyFunction() { + long self$ = this.$memoryAddress(); + long selfType$ = this.$typeMetadataAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyID.$createDestroyFunction", + "this", this, + "self", self$, + "selfType", selfType$); + } + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyID.$destroy", "self", self$, "selfType", selfType$); + } + MyID.$destroy(self$, selfType$); + } + }; + } + """, + ] + ) + } + + @Test + func generateSwiftThunk() throws { + try assertOutput( + input: genericFile, + .jni, + .swift, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + protocol _SwiftModule_MyID_opener { + static func _get_description(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jstring? + ... + } + """, + #""" + extension MyID: _SwiftModule_MyID_opener { + static func _get_description(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jstring? { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { + fatalError("self memory address was null in call to \(#function)!") + } + return self$.pointee.description.getJNIValue(in: environment) + } + ... + } + """#, + """ + @_cdecl("Java_com_example_swift_MyID__00024getDescription__JJ") + public func Java_com_example_swift_MyID__00024getDescription__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong, selfType: jlong) -> jstring? { + let selfTypeBits$ = Int(Int64(fromJNI: selfType, in: environment)) + guard let selfType$ = UnsafeRawPointer(bitPattern: selfTypeBits$) else { + fatalError("selfType metadata address was null") + } + let openerType = unsafeBitCast(selfType$, to: Any.Type.self) as! (any _SwiftModule_MyID_opener.Type) + return openerType._get_description(environment: environment, thisClass: thisClass, self: self) + } + """, + ] + ) + } + + @Test + func returnsGenericValueFuncJava() throws { + try assertOutput( + input: genericFile, + .jni, + .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + 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$); + } + """, + """ + private static native void $makeStringID(java.lang.String value, org.swift.swiftkit.core.OutSwiftGenericInstance out); + """, + ] + ) + } + + @Test + func returnsGenericValueFuncSwift() throws { + try assertOutput( + input: genericFile, + .jni, + .swift, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024makeStringID__Ljava_lang_String_2Lorg_swift_swiftkit_core_OutSwiftGenericInstance_2") + public func Java_com_example_swift_SwiftModule__00024makeStringID__Ljava_lang_String_2Lorg_swift_swiftkit_core_OutSwiftGenericInstance_2(environment: UnsafeMutablePointer!, thisClass: jclass, value: jstring?, out: jobject?) { + let result$ = UnsafeMutablePointer>.allocate(capacity: 1) + result$.initialize(to: SwiftModule.makeStringID(String(fromJNI: value, in: environment))) + let resultBits$ = Int64(Int(bitPattern: result$)) + environment.interface.SetLongField(environment, out, _JNIMethodIDCache.OutSwiftGenericInstance.selfPointer, resultBits$.getJNIValue(in: environment)) + let metadataPointer = unsafeBitCast(MyID.self, to: UnsafeRawPointer.self) + let metadataPointerBits$ = Int64(Int(bitPattern: metadataPointer)) + environment.interface.SetLongField(environment, out, _JNIMethodIDCache.OutSwiftGenericInstance.selfTypePointer, metadataPointerBits$.getJNIValue(in: environment)) + return + } + """ + ] + ) + } +}