From 5caa74fc7b3eb58798e82f1b425e234153b5b9c9 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 13 Feb 2026 15:29:57 +0900 Subject: [PATCH 01/23] Add test code for generic type --- .../Sources/MySwiftLibrary/GenericType.swift | 21 ++++++ .../JNI/JNIGenericTypeTests.swift | 69 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift new file mode 100644 index 00000000..c3134ba4 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +public struct MyID { + public var rawValue: T + public init(_ rawValue: T) { + self.rawValue = rawValue + } +} + diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift new file mode 100644 index 00000000..b0acd96a --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// +// 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 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)" + } + } + """# + + @Test + func generateJavaClass() throws { + try assertOutput( + input: genericFile, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public final class MyID implements JNISwiftInstance { + """, + """ + private final long t0MetaPointer; + """, + """ + public java.lang.String getDescription() { + return MyID.$getDescription(this.$memoryAddress(), this.t0MetaPointer); + } + private static native java.lang.String $getDescription(long self, long t0MetaPointer); + """, + """ + private static native long $typeMetadataAddressDowncall(long t0MetaPointer); + @Override + public long $typeMetadataAddress() { + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyID.$typeMetadataAddress", + "this", this, + "self", self$); + } + return MyID.$typeMetadataAddressDowncall(t0MetaPointer); + } + """ + ] + ) + } +} From d46019f03ef013cc9fe0059369c4c42bea15157f Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 16 Feb 2026 12:47:57 +0900 Subject: [PATCH 02/23] Implement type parameter translations --- Sources/JExtractSwiftLib/ImportedDecls.swift | 3 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 77 +++++++++++++++---- ...ISwift2JavaGenerator+JavaTranslation.swift | 27 ++++++- ...wift2JavaGenerator+NativeTranslation.swift | 38 +++++++++ .../SwiftNominalTypeDeclaration.swift | 6 +- .../JNI/JNIGenericTypeTests.swift | 9 ++- 6 files changed, 140 insertions(+), 20 deletions(-) diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 7bb2f041..b67b4e43 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -43,6 +43,9 @@ package final class ImportedNominalType: ImportedDecl { package var variables: [ImportedFunc] = [] package var cases: [ImportedEnumCase] = [] var inheritedTypes: [SwiftType] + package var genericParameters: [SwiftGenericParameterDeclaration] { + self.swiftNominal.genericParameters + } package var parent: SwiftNominalTypeDeclaration? init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index ffc5fffd..8dbc4e5b 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"] + for (index, _) in decl.genericParameters.enumerated() { + swiftPointerParams.append("t\(index)MetaPointer") + } + 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 { """ ) + for (index, param) in decl.genericParameters.enumerated() { + printer.print("/** Pointer to the metatype of type parameter '\(param.name)' */") + printer.print("private final long t\(index)MetaPointer;") + } + printer.println() if decl.swiftNominal.kind == .enum { @@ -295,25 +319,34 @@ extension JNISwift2JavaGenerator { } private func printToStringMethods(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + var arguments = ["selfPointer"] + var parameters = ["this.$memoryAddress()"] + for (index, _) in decl.genericParameters.enumerated() { + arguments.append("t\(index)MetaPointer") + parameters.append("this.t\(index)MetaPointer") + } + let argument = arguments.map { "long \($0)" }.joined(separator: ", ") + let parameter = parameters.joined(separator: ", ") + printer.printBraceBlock("public String toString()") { printer in printer.print( """ - return $toString(this.$memoryAddress()); + return $toString(\(parameter)); """ ) } - printer.print("private static native java.lang.String $toString(long selfPointer);") + printer.print("private static native java.lang.String $toString(\(argument));") printer.println() printer.printBraceBlock("public String toDebugString()") { printer in printer.print( """ - return $toDebugString(this.$memoryAddress()); + return $toDebugString(\(parameter)); """ ) } - printer.print("private static native java.lang.String $toDebugString(long selfPointer);") + printer.print("private static native java.lang.String $toDebugString(\(argument));") } private func printHeader(_ printer: inout CodePrinter) { @@ -630,6 +663,7 @@ extension JNISwift2JavaGenerator { if let selfParameter = nativeSignature.selfParameter?.parameters { parameters += selfParameter } + parameters += nativeSignature.selfTypeParameters.flatMap(\.parameters) parameters += nativeSignature.result.outParameters let renderedParameters = parameters.map { javaParameter in @@ -658,6 +692,12 @@ extension JNISwift2JavaGenerator { arguments.append(lowered) } + // Generic metadata pointers in Self + for typeParameter in translatedFunctionSignature.selfTypeParameters { + let lowered = typeParameter.conversion.render(&printer, "this") + arguments.append(lowered) + } + // Indirect return receivers for outParameter in translatedFunctionSignature.resultType.outParameters { printer.print( @@ -682,7 +722,14 @@ extension JNISwift2JavaGenerator { } private func printTypeMetadataAddressFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - printer.print("private static native long $typeMetadataAddressDowncall();") + var downcallParams = [String]() + if !type.genericParameters.isEmpty { + for (index, _) in type.genericParameters.enumerated() { + downcallParams.append("t\(index)MetaPointer") + } + } + let downcallArg = downcallParams.map { "long \($0)" }.joined(separator: ", ") + printer.print("private static native long $typeMetadataAddressDowncall(\(downcallArg));") let funcName = "$typeMetadataAddress" printer.print("@Override") @@ -695,9 +742,9 @@ extension JNISwift2JavaGenerator { "this", this, "self", self$); } - return \(type.swiftNominal.name).$typeMetadataAddressDowncall(); """ ) + printer.print("return \(type.swiftNominal.name).$typeMetadataAddressDowncall(\(downcallParams.joined(separator: ", ")));") } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 436956d2..c1561329 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -155,6 +155,7 @@ extension JNISwift2JavaGenerator { ] ) ), + selfTypeParameters: [], // TODO: iceman parameters: [], resultType: TranslatedResult( javaType: .class(package: nil, name: "Optional<\(caseName)>"), @@ -172,6 +173,7 @@ extension JNISwift2JavaGenerator { indirectConversion: nil, conversionCheck: nil ), + selfTypeParameters: [], // TODO: iceman parameters: [], result: NativeResult( javaType: nativeParametersType, @@ -321,6 +323,22 @@ extension JNISwift2JavaGenerator { genericRequirements: functionSignature.genericRequirements ) + func translateTypeParameters(_ decl: SwiftNominalTypeDeclaration) -> [TranslatedParameter] { + decl.genericParameters.enumerated().map { index, _ in + TranslatedParameter( + parameter: JavaParameter(name: "t\(index)MetaPointer", type: .long), + conversion: .member("t\(index)MetaPointer") + ) + } + } + let typeParameters: [TranslatedParameter] = switch functionSignature.selfParameter { + case .instance(let selfParameter): + selfParameter.type.asNominalTypeDeclaration.map(translateTypeParameters) ?? [] + case .initializer(let swiftType), .staticMethod(let swiftType): + swiftType.asNominalTypeDeclaration.map(translateTypeParameters) ?? [] + case nil: [] + } + var exceptions: [JavaExceptionType] = [] if functionSignature.parameters.contains(where: \.type.isArchDependingInteger) { @@ -331,6 +349,7 @@ extension JNISwift2JavaGenerator { return TranslatedFunctionSignature( selfParameter: selfParameter, + selfTypeParameters: typeParameters, parameters: parameters, resultType: resultType, exceptions: exceptions @@ -1049,6 +1068,7 @@ extension JNISwift2JavaGenerator { struct TranslatedFunctionSignature { var selfParameter: TranslatedParameter? + var selfTypeParameters: [TranslatedParameter] var parameters: [TranslatedParameter] var resultType: TranslatedResult var exceptions: [JavaExceptionType] @@ -1148,6 +1168,8 @@ extension JNISwift2JavaGenerator { indirect case call(JavaNativeConversionStep, function: String) + case member(String) + indirect case method(JavaNativeConversionStep, function: String, arguments: [JavaNativeConversionStep] = []) case isOptionalPresent @@ -1258,6 +1280,9 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(function)(\(inner))" + case .member(let member): + return "\(placeholder).\(member)" + case .isOptionalPresent: return "(byte) (\(placeholder).isPresent() ? 1 : 0)" @@ -1365,7 +1390,7 @@ extension JNISwift2JavaGenerator { /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder, .constant, .isOptionalPresent, .combinedName: + case .placeholder, .constant, .isOptionalPresent, .combinedName, .member: return false case .constructSwiftValue, .wrapMemoryAddressUnsafe: diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 5bbeff4e..ca82cae5 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -61,10 +61,31 @@ extension JNISwift2JavaGenerator { nil } + func translateTypeParameters(_ decl: SwiftNominalTypeDeclaration) -> [NativeParameter] { + decl.genericParameters.enumerated().map { index, _ in + NativeParameter( + parameters: [ + JavaParameter(name: "t\(index)MetaPointer", type: .long) + ], + conversion: .genericMetadataPointer(.placeholder), + indirectConversion: nil, + conversionCheck: nil + ) + } + } + let typeParameters: [NativeParameter] = switch functionSignature.selfParameter { + case .instance(let selfParameter): + selfParameter.type.asNominalTypeDeclaration.map(translateTypeParameters) ?? [] + case .initializer(let swiftType), .staticMethod(let swiftType): + swiftType.asNominalTypeDeclaration.map(translateTypeParameters) ?? [] + case nil: [] + } + let result = try translate(swiftResult: functionSignature.result) return NativeFunctionSignature( selfParameter: nativeSelf, + selfTypeParameters: typeParameters, parameters: parameters, result: result ) @@ -768,6 +789,7 @@ extension JNISwift2JavaGenerator { struct NativeFunctionSignature { let selfParameter: NativeParameter? + var selfTypeParameters: [NativeParameter] var parameters: [NativeParameter] var result: NativeResult } @@ -836,6 +858,8 @@ extension JNISwift2JavaGenerator { convertLongFromJNI: Bool = true ) + indirect case genericMetadataPointer(NativeSwiftConversionStep) + /// Allocate memory for a Swift value and outputs the pointer indirect case allocateSwiftValue(NativeSwiftConversionStep, name: String, swiftType: SwiftType) @@ -1034,6 +1058,20 @@ extension JNISwift2JavaGenerator { } return pointerName + case .genericMetadataPointer(let inner): + let inner = inner.render(&printer, placeholder) + let bitsName = "\(inner)Bits$" + let pointerName = "\(inner)Pointer$" + printer.print( + """ + let \(bitsName) = Int(Int64(fromJNI: \(inner), in: environment)) + guard let \(pointerName) = UnsafeRawPointer(bitPattern: \(bitsName)) 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)$" diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 4c6b8b0c..d7bd8a58 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,9 @@ 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) { diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index b0acd96a..8a78a459 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -51,14 +51,17 @@ struct JNIGenericTypeTests { private static native java.lang.String $getDescription(long self, long t0MetaPointer); """, """ + private static native java.lang.String $toString(long selfPointer, long t0MetaPointer); + """, + """ private static native long $typeMetadataAddressDowncall(long t0MetaPointer); @Override public long $typeMetadataAddress() { long self$ = this.$memoryAddress(); if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyID.$typeMetadataAddress", - "this", this, - "self", self$); + CallTraces.traceDowncall("MyID.$typeMetadataAddress", + "this", this, + "self", self$); } return MyID.$typeMetadataAddressDowncall(t0MetaPointer); } From f935933a7ce09d27ed411d49af2c035f65d9950d Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 16 Feb 2026 14:28:57 +0900 Subject: [PATCH 03/23] Keep type parameter names short in arguments --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 20 ++++++++++--------- ...ISwift2JavaGenerator+JavaTranslation.swift | 2 +- ...wift2JavaGenerator+NativeTranslation.swift | 2 +- .../JNI/JNIGenericTypeTests.swift | 11 ++++++---- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 8dbc4e5b..2a7cc853 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -322,7 +322,7 @@ extension JNISwift2JavaGenerator { var arguments = ["selfPointer"] var parameters = ["this.$memoryAddress()"] for (index, _) in decl.genericParameters.enumerated() { - arguments.append("t\(index)MetaPointer") + arguments.append("t\(index)Meta") parameters.append("this.t\(index)MetaPointer") } let argument = arguments.map { "long \($0)" }.joined(separator: ", ") @@ -722,14 +722,16 @@ extension JNISwift2JavaGenerator { } private func printTypeMetadataAddressFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - var downcallParams = [String]() - if !type.genericParameters.isEmpty { - for (index, _) in type.genericParameters.enumerated() { - downcallParams.append("t\(index)MetaPointer") - } + var arguments = [String]() + var parameters = [String]() + for (index, _) in type.genericParameters.enumerated() { + arguments.append("t\(index)Meta") + parameters.append("this.t\(index)MetaPointer") } - let downcallArg = downcallParams.map { "long \($0)" }.joined(separator: ", ") - printer.print("private static native long $typeMetadataAddressDowncall(\(downcallArg));") + let argument = arguments.map { "long \($0)" }.joined(separator: ", ") + let parameter = parameters.joined(separator: ", ") + + printer.print("private static native long $typeMetadataAddressDowncall(\(argument));") let funcName = "$typeMetadataAddress" printer.print("@Override") @@ -744,7 +746,7 @@ extension JNISwift2JavaGenerator { } """ ) - printer.print("return \(type.swiftNominal.name).$typeMetadataAddressDowncall(\(downcallParams.joined(separator: ", ")));") + printer.print("return \(type.swiftNominal.name).$typeMetadataAddressDowncall(\(parameter));") } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index c1561329..4568afdf 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -326,7 +326,7 @@ extension JNISwift2JavaGenerator { func translateTypeParameters(_ decl: SwiftNominalTypeDeclaration) -> [TranslatedParameter] { decl.genericParameters.enumerated().map { index, _ in TranslatedParameter( - parameter: JavaParameter(name: "t\(index)MetaPointer", type: .long), + parameter: JavaParameter(name: "t\(index)Meta", type: .long), conversion: .member("t\(index)MetaPointer") ) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index ca82cae5..f209d906 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -65,7 +65,7 @@ extension JNISwift2JavaGenerator { decl.genericParameters.enumerated().map { index, _ in NativeParameter( parameters: [ - JavaParameter(name: "t\(index)MetaPointer", type: .long) + JavaParameter(name: "t\(index)Meta", type: .long) ], conversion: .genericMetadataPointer(.placeholder), indirectConversion: nil, diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 8a78a459..1bf1a2f3 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -48,13 +48,16 @@ struct JNIGenericTypeTests { public java.lang.String getDescription() { return MyID.$getDescription(this.$memoryAddress(), this.t0MetaPointer); } - private static native java.lang.String $getDescription(long self, long t0MetaPointer); + private static native java.lang.String $getDescription(long self, long t0Meta); """, """ - private static native java.lang.String $toString(long selfPointer, long t0MetaPointer); + public String toString() { + return $toString(this.$memoryAddress(), this.t0MetaPointer); + } + private static native java.lang.String $toString(long selfPointer, long t0Meta); """, """ - private static native long $typeMetadataAddressDowncall(long t0MetaPointer); + private static native long $typeMetadataAddressDowncall(long t0Meta); @Override public long $typeMetadataAddress() { long self$ = this.$memoryAddress(); @@ -63,7 +66,7 @@ struct JNIGenericTypeTests { "this", this, "self", self$); } - return MyID.$typeMetadataAddressDowncall(t0MetaPointer); + return MyID.$typeMetadataAddressDowncall(this.t0MetaPointer); } """ ] From 4ff9e07ac329bf5a10a264e45f7d12909c25364d Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 16 Feb 2026 17:27:51 +0900 Subject: [PATCH 04/23] Changed to only handle metatypes for Self --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 58 +++++++++---------- ...ISwift2JavaGenerator+JavaTranslation.swift | 27 ++++----- ...wift2JavaGenerator+NativeTranslation.swift | 21 +++---- .../JNI/JNIGenericTypeTests.swift | 20 +++---- 4 files changed, 62 insertions(+), 64 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 2a7cc853..1a988e4a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -202,8 +202,8 @@ extension JNISwift2JavaGenerator { """ ) var swiftPointerParams = ["selfPointer"] - for (index, _) in decl.genericParameters.enumerated() { - swiftPointerParams.append("t\(index)MetaPointer") + if !decl.genericParameters.isEmpty { + swiftPointerParams.append("selfTypePointer") } let swiftPointerArg = swiftPointerParams.map { "long \($0)" }.joined(separator: ", ") printer.printBraceBlock("private \(decl.swiftNominal.name)(\(swiftPointerArg), SwiftArena swiftArena)") { printer in @@ -264,9 +264,9 @@ extension JNISwift2JavaGenerator { """ ) - for (index, param) in decl.genericParameters.enumerated() { - printer.print("/** Pointer to the metatype of type parameter '\(param.name)' */") - printer.print("private final long t\(index)MetaPointer;") + if !decl.genericParameters.isEmpty { + printer.print("/** Pointer to the metatype of Self */") + printer.print("private final long selfTypePointer;") } printer.println() @@ -321,9 +321,9 @@ extension JNISwift2JavaGenerator { private func printToStringMethods(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { var arguments = ["selfPointer"] var parameters = ["this.$memoryAddress()"] - for (index, _) in decl.genericParameters.enumerated() { - arguments.append("t\(index)Meta") - parameters.append("this.t\(index)MetaPointer") + if !decl.genericParameters.isEmpty { + arguments.append("selfType") + parameters.append("this.$typeMetadataAddress()") } let argument = arguments.map { "long \($0)" }.joined(separator: ", ") let parameter = parameters.joined(separator: ", ") @@ -663,7 +663,9 @@ extension JNISwift2JavaGenerator { if let selfParameter = nativeSignature.selfParameter?.parameters { parameters += selfParameter } - parameters += nativeSignature.selfTypeParameters.flatMap(\.parameters) + if let selfTypeParameter = nativeSignature.selfTypeParameter?.parameters { + parameters += selfTypeParameter + } parameters += nativeSignature.result.outParameters let renderedParameters = parameters.map { javaParameter in @@ -692,9 +694,9 @@ extension JNISwift2JavaGenerator { arguments.append(lowered) } - // Generic metadata pointers in Self - for typeParameter in translatedFunctionSignature.selfTypeParameters { - let lowered = typeParameter.conversion.render(&printer, "this") + // 'Self' metatype. + if let selfTypeParameter = translatedFunctionSignature.selfTypeParameter { + let lowered = selfTypeParameter.conversion.render(&printer, "this") arguments.append(lowered) } @@ -722,21 +724,18 @@ extension JNISwift2JavaGenerator { } private func printTypeMetadataAddressFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - var arguments = [String]() - var parameters = [String]() - for (index, _) in type.genericParameters.enumerated() { - arguments.append("t\(index)Meta") - parameters.append("this.t\(index)MetaPointer") - } - let argument = arguments.map { "long \($0)" }.joined(separator: ", ") - let parameter = parameters.joined(separator: ", ") - - printer.print("private static native long $typeMetadataAddressDowncall(\(argument));") - - let funcName = "$typeMetadataAddress" - printer.print("@Override") - printer.printBraceBlock("public long $typeMetadataAddress()") { printer in - printer.print( + if !type.genericParameters.isEmpty { + 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) { @@ -745,8 +744,9 @@ extension JNISwift2JavaGenerator { "self", self$); } """ - ) - printer.print("return \(type.swiftNominal.name).$typeMetadataAddressDowncall(\(parameter));") + ) + printer.print("return \(type.swiftNominal.name).$typeMetadataAddressDowncall();") + } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 4568afdf..a0b64167 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -155,7 +155,7 @@ extension JNISwift2JavaGenerator { ] ) ), - selfTypeParameters: [], // TODO: iceman + selfTypeParameter: nil, // TODO: iceman parameters: [], resultType: TranslatedResult( javaType: .class(package: nil, name: "Optional<\(caseName)>"), @@ -173,7 +173,7 @@ extension JNISwift2JavaGenerator { indirectConversion: nil, conversionCheck: nil ), - selfTypeParameters: [], // TODO: iceman + selfTypeParameter: nil, // TODO: iceman parameters: [], result: NativeResult( javaType: nativeParametersType, @@ -323,20 +323,21 @@ extension JNISwift2JavaGenerator { genericRequirements: functionSignature.genericRequirements ) - func translateTypeParameters(_ decl: SwiftNominalTypeDeclaration) -> [TranslatedParameter] { - decl.genericParameters.enumerated().map { index, _ in - TranslatedParameter( - parameter: JavaParameter(name: "t\(index)Meta", type: .long), - conversion: .member("t\(index)MetaPointer") + func translateSelfTypeParameters(_ decl: SwiftNominalTypeDeclaration) -> TranslatedParameter? { + if !decl.genericParameters.isEmpty { + return TranslatedParameter( + parameter: JavaParameter(name: "selfType", type: .long), + conversion: .typeMetadataAddress(.placeholder) ) } + return nil } - let typeParameters: [TranslatedParameter] = switch functionSignature.selfParameter { + let selfTypeParameter: TranslatedParameter? = switch functionSignature.selfParameter { case .instance(let selfParameter): - selfParameter.type.asNominalTypeDeclaration.map(translateTypeParameters) ?? [] + selfParameter.type.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) case .initializer(let swiftType), .staticMethod(let swiftType): - swiftType.asNominalTypeDeclaration.map(translateTypeParameters) ?? [] - case nil: [] + swiftType.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) + case nil: nil } var exceptions: [JavaExceptionType] = [] @@ -349,7 +350,7 @@ extension JNISwift2JavaGenerator { return TranslatedFunctionSignature( selfParameter: selfParameter, - selfTypeParameters: typeParameters, + selfTypeParameter: selfTypeParameter, parameters: parameters, resultType: resultType, exceptions: exceptions @@ -1068,7 +1069,7 @@ extension JNISwift2JavaGenerator { struct TranslatedFunctionSignature { var selfParameter: TranslatedParameter? - var selfTypeParameters: [TranslatedParameter] + var selfTypeParameter: TranslatedParameter? var parameters: [TranslatedParameter] var resultType: TranslatedResult var exceptions: [JavaExceptionType] diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index f209d906..f99b5cf6 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -61,31 +61,32 @@ extension JNISwift2JavaGenerator { nil } - func translateTypeParameters(_ decl: SwiftNominalTypeDeclaration) -> [NativeParameter] { - decl.genericParameters.enumerated().map { index, _ in - NativeParameter( + func translateSelfTypeParameters(_ decl: SwiftNominalTypeDeclaration) -> NativeParameter? { + if !decl.genericParameters.isEmpty { + return NativeParameter( parameters: [ - JavaParameter(name: "t\(index)Meta", type: .long) + JavaParameter(name: "selfType", type: .long) ], conversion: .genericMetadataPointer(.placeholder), indirectConversion: nil, conversionCheck: nil ) } + return nil } - let typeParameters: [NativeParameter] = switch functionSignature.selfParameter { + let selfTypeParameter: NativeParameter? = switch functionSignature.selfParameter { case .instance(let selfParameter): - selfParameter.type.asNominalTypeDeclaration.map(translateTypeParameters) ?? [] + selfParameter.type.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) case .initializer(let swiftType), .staticMethod(let swiftType): - swiftType.asNominalTypeDeclaration.map(translateTypeParameters) ?? [] - case nil: [] + swiftType.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) + case nil: nil } let result = try translate(swiftResult: functionSignature.result) return NativeFunctionSignature( selfParameter: nativeSelf, - selfTypeParameters: typeParameters, + selfTypeParameter: selfTypeParameter, parameters: parameters, result: result ) @@ -789,7 +790,7 @@ extension JNISwift2JavaGenerator { struct NativeFunctionSignature { let selfParameter: NativeParameter? - var selfTypeParameters: [NativeParameter] + var selfTypeParameter: NativeParameter? var parameters: [NativeParameter] var result: NativeResult } diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 1bf1a2f3..ee970bae 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -36,35 +36,31 @@ struct JNIGenericTypeTests { input: genericFile, .jni, .java, - detectChunkByInitialLines: 1, + detectChunkByInitialLines: 2, expectedChunks: [ """ public final class MyID implements JNISwiftInstance { """, """ - private final long t0MetaPointer; + private final long selfTypePointer; """, """ public java.lang.String getDescription() { - return MyID.$getDescription(this.$memoryAddress(), this.t0MetaPointer); + return MyID.$getDescription(this.$memoryAddress(), this.$typeMetadataAddress()); } - private static native java.lang.String $getDescription(long self, long t0Meta); + private static native java.lang.String $getDescription(long self, long selfType); """, """ public String toString() { - return $toString(this.$memoryAddress(), this.t0MetaPointer); + return $toString(this.$memoryAddress(), this.$typeMetadataAddress()); } - private static native java.lang.String $toString(long selfPointer, long t0Meta); + private static native java.lang.String $toString(long selfPointer, long selfType); """, """ - private static native long $typeMetadataAddressDowncall(long t0Meta); @Override public long $typeMetadataAddress() { - long self$ = this.$memoryAddress(); - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyID.$typeMetadataAddress", - "this", this, - "self", self$); + return this.selfTypePointer; + } } return MyID.$typeMetadataAddressDowncall(this.t0MetaPointer); } From 86ce9ac05f186682703914ac6b219b596b2cb0cc Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 16 Feb 2026 17:51:19 +0900 Subject: [PATCH 05/23] Add test case for swift side generated code --- .../JNI/JNIGenericTypeTests.swift | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index ee970bae..eb642eba 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -61,10 +61,77 @@ struct JNIGenericTypeTests { public long $typeMetadataAddress() { return this.selfTypePointer; } + """, + ] + ) + } + + @Test + func generateSwiftThunk() throws { + try assertOutput( + input: genericFile, + .jni, + .swift, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + protocol _SwiftModule_MyID_opener { + static func _get_description(selfBits$: Int) -> String + static func _toString(selfBits$: Int) -> String + static func _toDebugString(selfBits$: Int) -> String + } + """, + """ + extension MyID: _SwiftModule_MyID_opener { + static func _get_description(selfBits$: Int) -> String { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { + fatalError("self memory address was null in call to \\(#function)!") + } + return self$.pointee.description + } + static func _toString(selfBits$: Int) -> String { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { + fatalError("self memory address was null in call to \\(#function)!") + } + return String(describing: self$.pointee) + } + static func _toDebugString(selfBits$: Int) -> String { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { + fatalError("self memory address was null in call to \\(#function)!") + } + return String(reflecting: self$.pointee) + } + } + """, + """ + @_cdecl("Java_com_example_swift_MyID__00024getDescription__J") + public func Java_com_example_swift_MyID__00024getDescription__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong, selfType: jlong) -> jstring? { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) + guard let selfTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: selfType, in: environment))) else { + fatalError("selfType memory address was null") } - return MyID.$typeMetadataAddressDowncall(this.t0MetaPointer); + let erasedSelfType$: Any.Type = unsafeBitCast(selfTypeMetadataPointer$, to: Any.Type.self) + let openerType = erasedSelfType$ as! (any _SwiftModule_MyID_opener.Type) + return openerType._get_description(selfBits$: selfBits$).getJNIValue(in: environment) } + """, """ + @_cdecl("Java_com_example_swift_MyID__00024toString__J") + public func Java_com_example_swift_MyID__00024toString__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong, selfType: jlong) -> jstring? { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) + guard let selfTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: selfType, in: environment))) else { + fatalError("selfType memory address was null") + } + let erasedSelfType$: Any.Type = unsafeBitCast(selfTypeMetadataPointer$, to: Any.Type.self) + let openerType = erasedSelfType$ as! (any _SwiftModule_MyID_opener.Type) + return openerType._toString(selfBits$: selfBits$).getJNIValue(in: environment) + } + """, ] ) } From 49fbd08a3448aef8771e626e438a4c2b987e4d6c Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 16 Feb 2026 17:53:16 +0900 Subject: [PATCH 06/23] Remove unused step --- .../JNI/JNISwift2JavaGenerator+JavaTranslation.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index a0b64167..2b9137de 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -1169,8 +1169,6 @@ extension JNISwift2JavaGenerator { indirect case call(JavaNativeConversionStep, function: String) - case member(String) - indirect case method(JavaNativeConversionStep, function: String, arguments: [JavaNativeConversionStep] = []) case isOptionalPresent @@ -1281,9 +1279,6 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(function)(\(inner))" - case .member(let member): - return "\(placeholder).\(member)" - case .isOptionalPresent: return "(byte) (\(placeholder).isPresent() ? 1 : 0)" @@ -1391,7 +1386,7 @@ extension JNISwift2JavaGenerator { /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder, .constant, .isOptionalPresent, .combinedName, .member: + case .placeholder, .constant, .isOptionalPresent, .combinedName: return false case .constructSwiftValue, .wrapMemoryAddressUnsafe: From 32d99789611afea288a825cc75e88a4c44c592b2 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 18 Feb 2026 10:40:13 +0900 Subject: [PATCH 07/23] Print opener thunk --- .../Sources/MySwiftLibrary/GenericType.swift | 23 ++ Sources/JExtractSwiftLib/ImportedDecls.swift | 3 - ...t2JavaGenerator+JavaBindingsPrinting.swift | 8 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 2 +- ...wift2JavaGenerator+NativeTranslation.swift | 16 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 203 ++++++++++++++++-- .../SwiftNominalTypeDeclaration.swift | 4 + .../JNI/JNIGenericTypeTests.swift | 67 ++---- 8 files changed, 249 insertions(+), 77 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift index c3134ba4..a8444637 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift @@ -17,5 +17,28 @@ public struct MyID { public init(_ rawValue: T) { self.rawValue = rawValue } + public var description: String { + "\(rawValue)" + } +} + +public func makeIntID(_ value: Int) -> MyID { + return MyID(value) +} + +public func makeStringID(_ value: String) -> MyID { + return 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 func takeMyEntity() -> MyEntity { + return MyEntity(id: MyID(42), name: "Example") +} diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index b67b4e43..7bb2f041 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -43,9 +43,6 @@ package final class ImportedNominalType: ImportedDecl { package var variables: [ImportedFunc] = [] package var cases: [ImportedEnumCase] = [] var inheritedTypes: [SwiftType] - package var genericParameters: [SwiftGenericParameterDeclaration] { - self.swiftNominal.genericParameters - } package var parent: SwiftNominalTypeDeclaration? init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 1a988e4a..2f92d93c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -202,7 +202,7 @@ extension JNISwift2JavaGenerator { """ ) var swiftPointerParams = ["selfPointer"] - if !decl.genericParameters.isEmpty { + if decl.swiftNominal.isGeneric { swiftPointerParams.append("selfTypePointer") } let swiftPointerArg = swiftPointerParams.map { "long \($0)" }.joined(separator: ", ") @@ -264,7 +264,7 @@ extension JNISwift2JavaGenerator { """ ) - if !decl.genericParameters.isEmpty { + if decl.swiftNominal.isGeneric { printer.print("/** Pointer to the metatype of Self */") printer.print("private final long selfTypePointer;") } @@ -321,7 +321,7 @@ extension JNISwift2JavaGenerator { private func printToStringMethods(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { var arguments = ["selfPointer"] var parameters = ["this.$memoryAddress()"] - if !decl.genericParameters.isEmpty { + if decl.swiftNominal.isGeneric { arguments.append("selfType") parameters.append("this.$typeMetadataAddress()") } @@ -724,7 +724,7 @@ extension JNISwift2JavaGenerator { } private func printTypeMetadataAddressFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - if !type.genericParameters.isEmpty { + if type.swiftNominal.isGeneric { printer.print("@Override") printer.printBraceBlock("public long $typeMetadataAddress()") { printer in printer.print("return this.selfTypePointer;") diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 2b9137de..ce370718 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -324,7 +324,7 @@ extension JNISwift2JavaGenerator { ) func translateSelfTypeParameters(_ decl: SwiftNominalTypeDeclaration) -> TranslatedParameter? { - if !decl.genericParameters.isEmpty { + if decl.isGeneric { return TranslatedParameter( parameter: JavaParameter(name: "selfType", type: .long), conversion: .typeMetadataAddress(.placeholder) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index f99b5cf6..dad9b7de 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -62,7 +62,7 @@ extension JNISwift2JavaGenerator { } func translateSelfTypeParameters(_ decl: SwiftNominalTypeDeclaration) -> NativeParameter? { - if !decl.genericParameters.isEmpty { + if decl.isGeneric { return NativeParameter( parameters: [ JavaParameter(name: "selfType", type: .long) @@ -1047,7 +1047,12 @@ extension JNISwift2JavaGenerator { } else { printer.print("let \(inner)Bits$ = Int(\(inner))") } - printer.print("let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$)") + let typeName = if swiftType.asNominalTypeDeclaration?.isGeneric == true { + "Self" + } else { + swiftType.description + } + printer.print("let \(pointerName) = UnsafeMutablePointer<\(typeName)>(bitPattern: \(inner)Bits$)") if !allowNil { printer.print( """ @@ -1061,12 +1066,11 @@ extension JNISwift2JavaGenerator { case .genericMetadataPointer(let inner): let inner = inner.render(&printer, placeholder) - let bitsName = "\(inner)Bits$" - let pointerName = "\(inner)Pointer$" + let pointerName = "\(inner)$" printer.print( """ - let \(bitsName) = Int(Int64(fromJNI: \(inner), in: environment)) - guard let \(pointerName) = UnsafeRawPointer(bitPattern: \(bitsName)) else { + let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment)) + guard let \(pointerName) = UnsafeRawPointer(bitPattern: \(inner)Bits$) else { fatalError("\(inner) metadata address was null") } """ diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index c12080ee..b91264a5 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -246,6 +246,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() @@ -295,17 +300,25 @@ extension JNISwift2JavaGenerator { javaMethodName: "$toString", parentName: type.swiftNominal.qualifiedName, parameters: [ - selfPointerParam + selfPointerParam, + JavaParameter(name: "selfType", type: .long) ], resultType: .javaLangString ) { printer in - let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) - - printer.print( - """ - return String(describing: \(selfVar).pointee).getJNIValue(in: environment) - """ - ) + if type.swiftNominal.isGeneric { + printer.print("let selfBits$ = Int(Int64(fromJNI: selfPointer, in: environment))") + printer.printBraceBlock( + "guard let selfTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: selfType, in: environment))) else" + ) { printer in + printer.print("fatalError(\"selfType memory address was null\")") + } + printer.print("let erasedSelfType$: Any.Type = unsafeBitCast(selfTypeMetadataPointer$, to: Any.Type.self)") + printer.print("let openerType = erasedSelfType$ as! (any \(openerProtocolName(for: type.swiftNominal)).Type)") + printer.print("return openerType._toString(selfBits$: selfBits$).getJNIValue(in: environment)") + } else { + let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) + printer.print("return String(describing: \(selfVar).pointee).getJNIValue(in: environment)") + } } printer.println() @@ -315,17 +328,25 @@ extension JNISwift2JavaGenerator { javaMethodName: "$toDebugString", parentName: type.swiftNominal.qualifiedName, parameters: [ - selfPointerParam + selfPointerParam, + JavaParameter(name: "selfType", type: .long) ], resultType: .javaLangString ) { printer in - let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) - - printer.print( - """ - return String(reflecting: \(selfVar).pointee).getJNIValue(in: environment) - """ - ) + if type.swiftNominal.isGeneric { + printer.print("let selfBits$ = Int(Int64(fromJNI: selfPointer, in: environment))") + printer.printBraceBlock( + "guard let selfTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: selfType, in: environment))) else" + ) { printer in + printer.print("fatalError(\"selfType memory address was null\")") + } + printer.print("let erasedSelfType$: Any.Type = unsafeBitCast(selfTypeMetadataPointer$, to: Any.Type.self)") + printer.print("let openerType = erasedSelfType$ as! (any \(openerProtocolName(for: type.swiftNominal)).Type)") + printer.print("return openerType._toDebugString(selfBits$: selfBits$).getJNIValue(in: environment)") + } else { + let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) + printer.print("return String(reflecting: \(selfVar).pointee).getJNIValue(in: environment)") + } } } @@ -445,7 +466,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) + } } } @@ -712,7 +737,9 @@ extension JNISwift2JavaGenerator { if let selfParameter = nativeSignature.selfParameter { parameters += selfParameter.parameters } - + if let selfTypeParameter = nativeSignature.selfTypeParameter { + parameters += selfTypeParameter.parameters + } parameters += nativeSignature.result.outParameters printCDecl( @@ -886,6 +913,135 @@ 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 selfTypePointer = nativeSignature.selfTypeParameter!.conversion.render(&printer, "selfType") + let openerName = openerProtocolName(for: parentNominalType.nominalTypeDecl) + printer.print("let openerType = \(selfTypePointer) 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 { + if variable.isStatic { + logger.debug("Skipping \(variable.name), static variables in generic type is not yet supported.") + continue + } + printFunctionDecl(&printer, decl: variable, skipMethodBody: true) + } + + for method in type.methods { + if method.isStatic { + logger.debug("Skipping \(method.name), static methods in generic type is not yet supported.") + continue + } + printFunctionDecl(&printer, decl: method, skipMethodBody: true) + } + + printer.print("static func _toString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jstring?") + printer.print("static func _toDebugString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jstring?") + } + 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) + } + + printer.printBraceBlock("static func _toString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> String") { printer in + printer.print(#"assert(self != 0, "self memory address was null")"#) + printer.print("let selfBits$ = Int(Int64(fromJNI: self, 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("return String(describing: self$.pointee)") + } + + printer.printBraceBlock("static func _toDebugString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> String") { printer in + printer.print(#"assert(self != 0, "self memory address was null")"#) + printer.print("let selfBits$ = Int(Int64(fromJNI: self, 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("return String(reflecting: self$.pointee)") + } + } + } + /// Print the necessary conversion logic to go from a `jlong` to a `UnsafeMutablePointer` /// /// - Returns: name of the created "self" variable @@ -951,3 +1107,14 @@ 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/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index d7bd8a58..45d305d8 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -162,6 +162,10 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { return false } } + + var isGeneric: Bool { + return !genericParameters.isEmpty + } } package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index eb642eba..4a5d291c 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -28,6 +28,10 @@ struct JNIGenericTypeTests { "\(rawValue)" } } + + public func makeStringID(_ value: String) -> MyID { + return MyID(value) + } """# @Test @@ -76,62 +80,35 @@ struct JNIGenericTypeTests { expectedChunks: [ """ protocol _SwiftModule_MyID_opener { - static func _get_description(selfBits$: Int) -> String - static func _toString(selfBits$: Int) -> String - static func _toDebugString(selfBits$: Int) -> String + static func _get_description(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jstring? + ... } """, - """ + #""" extension MyID: _SwiftModule_MyID_opener { - static func _get_description(selfBits$: Int) -> String { - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } - return self$.pointee.description - } - static func _toString(selfBits$: Int) -> String { - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } - return String(describing: self$.pointee) - } - static func _toDebugString(selfBits$: Int) -> String { + 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)!") + fatalError("self memory address was null in call to \(#function)!") } - return String(reflecting: self$.pointee) - } - } - """, - """ - @_cdecl("Java_com_example_swift_MyID__00024getDescription__J") - public func Java_com_example_swift_MyID__00024getDescription__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong, selfType: jlong) -> jstring? { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment)) - guard let selfTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: selfType, in: environment))) else { - fatalError("selfType memory address was null") + return self$.pointee.description.getJNIValue(in: environment) } - let erasedSelfType$: Any.Type = unsafeBitCast(selfTypeMetadataPointer$, to: Any.Type.self) - let openerType = erasedSelfType$ as! (any _SwiftModule_MyID_opener.Type) - return openerType._get_description(selfBits$: selfBits$).getJNIValue(in: environment) + ... } - """, + """#, """ - @_cdecl("Java_com_example_swift_MyID__00024toString__J") - public func Java_com_example_swift_MyID__00024toString__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong, selfType: jlong) -> jstring? { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment)) - guard let selfTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: selfType, in: environment))) else { - fatalError("selfType memory address was null") + @_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 erasedSelfType$: Any.Type = unsafeBitCast(selfTypeMetadataPointer$, to: Any.Type.self) - let openerType = erasedSelfType$ as! (any _SwiftModule_MyID_opener.Type) - return openerType._toString(selfBits$: selfBits$).getJNIValue(in: environment) + let openerType = unsafeBitCast(selfType$, to: Any.Type.self) as! (any _SwiftModule_MyID_opener.Type) + return openerType._get_description(environment: environment, thisClass: thisClass, self: self) } - """, + """ ] ) } From 55465d174851afc2c844f4e388a018d3f3f5dd4b Mon Sep 17 00:00:00 2001 From: Iceman Date: Thu, 19 Feb 2026 15:16:40 +0900 Subject: [PATCH 08/23] Indirect generic return value --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 9 ++- ...ISwift2JavaGenerator+JavaTranslation.swift | 37 +++++++++-- ...wift2JavaGenerator+NativeTranslation.swift | 46 ++++++++++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 8 +-- Sources/JavaTypes/JavaType.swift | 6 ++ .../JNIMethodIDCaches.swift | 29 +++++++++ .../_JNIMethodIDCache.swift | 36 +++++++++- .../core/OutSwiftGenericInstance.java | 20 ++++++ .../JNI/JNIGenericTypeTests.swift | 65 +++++++++++++++++-- 9 files changed, 226 insertions(+), 30 deletions(-) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 2f92d93c..20e5e329 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -496,16 +496,16 @@ extension JNISwift2JavaGenerator { _ decl: ImportedFunc, skipMethodBody: Bool = false ) { - guard translatedDecl(for: decl) != nil else { + guard let translatedDecl = self.translatedDecl(for: decl) else { // Failed to translate. Skip. return } printer.printSeparator(decl.displayName) - printJavaBindingWrapperHelperClass(&printer, decl) + printJavaBindingWrapperHelperClass(&printer, translatedDecl) - printJavaBindingWrapperMethod(&printer, decl, skipMethodBody: skipMethodBody) + printJavaBindingWrapperMethod(&printer, translatedDecl, skipMethodBody: skipMethodBody) } /// Print the helper type container for a user-facing Java API. @@ -513,9 +513,8 @@ extension JNISwift2JavaGenerator { /// * User-facing functional interfaces. private func printJavaBindingWrapperHelperClass( _ printer: inout CodePrinter, - _ decl: ImportedFunc + _ translated: TranslatedFunctionDecl ) { - let translated = self.translatedDecl(for: decl)! if translated.functionTypes.isEmpty { return } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index ce370718..39e7abda 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -793,12 +793,28 @@ 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) @@ -1171,6 +1187,8 @@ extension JNISwift2JavaGenerator { indirect case method(JavaNativeConversionStep, function: String, arguments: [JavaNativeConversionStep] = []) + indirect case member(JavaNativeConversionStep, field: String) + case isOptionalPresent indirect case combinedValueToOptional( @@ -1288,6 +1306,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, @@ -1407,6 +1429,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 dad9b7de..b9b7ae0b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -67,7 +67,7 @@ extension JNISwift2JavaGenerator { parameters: [ JavaParameter(name: "selfType", type: .long) ], - conversion: .genericMetadataPointer(.placeholder), + conversion: .extractMetatypeValue(.placeholder), indirectConversion: nil, conversionCheck: nil ) @@ -653,11 +653,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( @@ -859,7 +871,7 @@ extension JNISwift2JavaGenerator { convertLongFromJNI: Bool = true ) - indirect case genericMetadataPointer(NativeSwiftConversionStep) + indirect case extractMetatypeValue(NativeSwiftConversionStep) /// Allocate memory for a Swift value and outputs the pointer indirect case allocateSwiftValue(NativeSwiftConversionStep, name: String, swiftType: SwiftType) @@ -898,6 +910,12 @@ extension JNISwift2JavaGenerator { placeholderValue: NativeSwiftConversionStep ) + indirect case genericValueIndirectReturn( + NativeSwiftConversionStep, + swiftFunctionResultType: SwiftType, + outArgumentName: String + ) + indirect case method( NativeSwiftConversionStep, function: String, @@ -1064,7 +1082,7 @@ extension JNISwift2JavaGenerator { } return pointerName - case .genericMetadataPointer(let inner): + case .extractMetatypeValue(let inner): let inner = inner.render(&printer, placeholder) let pointerName = "\(inner)$" printer.print( @@ -1260,6 +1278,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 b91264a5..5ce21236 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -314,7 +314,7 @@ extension JNISwift2JavaGenerator { } printer.print("let erasedSelfType$: Any.Type = unsafeBitCast(selfTypeMetadataPointer$, to: Any.Type.self)") printer.print("let openerType = erasedSelfType$ as! (any \(openerProtocolName(for: type.swiftNominal)).Type)") - printer.print("return openerType._toString(selfBits$: selfBits$).getJNIValue(in: environment)") + printer.print("return openerType._toString(environment: environment, thisClass: thisClass, self: self).getJNIValue(in: environment)") } else { let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) printer.print("return String(describing: \(selfVar).pointee).getJNIValue(in: environment)") @@ -342,7 +342,7 @@ extension JNISwift2JavaGenerator { } printer.print("let erasedSelfType$: Any.Type = unsafeBitCast(selfTypeMetadataPointer$, to: Any.Type.self)") printer.print("let openerType = erasedSelfType$ as! (any \(openerProtocolName(for: type.swiftNominal)).Type)") - printer.print("return openerType._toDebugString(selfBits$: selfBits$).getJNIValue(in: environment)") + printer.print("return openerType._toDebugString(environment: environment, thisClass: thisClass, self: self).getJNIValue(in: environment)") } else { let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) printer.print("return String(reflecting: \(selfVar).pointee).getJNIValue(in: environment)") @@ -922,9 +922,9 @@ extension JNISwift2JavaGenerator { } let nativeSignature = translatedDecl.nativeFunctionSignature - let selfTypePointer = nativeSignature.selfTypeParameter!.conversion.render(&printer, "selfType") + let selfType = nativeSignature.selfTypeParameter!.conversion.render(&printer, "selfType") let openerName = openerProtocolName(for: parentNominalType.nominalTypeDecl) - printer.print("let openerType = \(selfTypePointer) as! (any \(openerName).Type)") + printer.print("let openerType = \(selfType) as! (any \(openerName).Type)") var parameters = nativeSignature.parameters.flatMap(\.parameters) if let selfParameter = nativeSignature.selfParameter { 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/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..68989cad --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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; + +public final class OutSwiftGenericInstance { + public long selfPointer; + public long selfTypePointer; +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 4a5d291c..8ad7cb75 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -46,6 +46,18 @@ struct JNIGenericTypeTests { 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; """, """ @@ -55,12 +67,6 @@ struct JNIGenericTypeTests { private static native java.lang.String $getDescription(long self, long selfType); """, """ - public String toString() { - return $toString(this.$memoryAddress(), this.$typeMetadataAddress()); - } - private static native java.lang.String $toString(long selfPointer, long selfType); - """, - """ @Override public long $typeMetadataAddress() { return this.selfTypePointer; @@ -112,4 +118,51 @@ struct JNIGenericTypeTests { ] ) } + + @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 + } + """ + ] + ) + } } From a120d0b87b3d275b3c4a97fb70c8fc7442ecbc14 Mon Sep 17 00:00:00 2001 From: Iceman Date: Thu, 19 Feb 2026 15:24:44 +0900 Subject: [PATCH 09/23] Skip generating typeMetadataAddressDowncall if the type is generic --- .../JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 5ce21236..7d79bdd6 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -811,6 +811,10 @@ extension JNISwift2JavaGenerator { } private func printTypeMetadataAddressThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + if type.swiftNominal.isGeneric { + return + } + printCDecl( &printer, javaMethodName: "$typeMetadataAddressDowncall", From 0231d72f6c43957ef862a22e8b65031b9373ebd2 Mon Sep 17 00:00:00 2001 From: Iceman Date: Thu, 19 Feb 2026 16:48:42 +0900 Subject: [PATCH 10/23] Handling the destroy function --- .../com/example/swift/GenericTypeTest.java | 33 +++++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 22 +++-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 98 +++++++++++++------ .../JNI/JNIGenericTypeTests.swift | 22 +++++ 4 files changed, 139 insertions(+), 36 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java 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..d38ef934 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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()); + } + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 20e5e329..99578606 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -496,16 +496,16 @@ extension JNISwift2JavaGenerator { _ decl: ImportedFunc, skipMethodBody: Bool = false ) { - guard let translatedDecl = self.translatedDecl(for: decl) else { + guard translatedDecl(for: decl) != nil else { // Failed to translate. Skip. return } printer.printSeparator(decl.displayName) - printJavaBindingWrapperHelperClass(&printer, translatedDecl) + printJavaBindingWrapperHelperClass(&printer, decl) - printJavaBindingWrapperMethod(&printer, translatedDecl, skipMethodBody: skipMethodBody) + printJavaBindingWrapperMethod(&printer, decl, skipMethodBody: skipMethodBody) } /// Print the helper type container for a user-facing Java API. @@ -513,8 +513,9 @@ extension JNISwift2JavaGenerator { /// * User-facing functional interfaces. private func printJavaBindingWrapperHelperClass( _ printer: inout CodePrinter, - _ translated: TranslatedFunctionDecl + _ decl: ImportedFunc ) { + let translated = self.translatedDecl(for: decl)! if translated.functionTypes.isEmpty { return } @@ -751,11 +752,20 @@ extension JNISwift2JavaGenerator { /// 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 self, long selfType);") + } else { + printer.print("private static native void $destroy(long self);") + } let funcName = "$createDestroyFunction" printer.print("@Override") printer.printBraceBlock("public Runnable \(funcName)()") { printer in + if isGeneric { + printer.print("long selfType$ = this.$typeMetadataAddress();") + } + let destroyArg = isGeneric ? "self$, selfType$" : "self$" printer.print( """ long self$ = this.$memoryAddress(); @@ -770,7 +780,7 @@ extension JNISwift2JavaGenerator { if (CallTraces.TRACE_DOWNCALLS) { CallTraces.traceDowncall("\(type.swiftNominal.name).$destroy", "self", self$); } - \(type.swiftNominal.name).$destroy(self$); + \(type.swiftNominal.name).$destroy(\(destroyArg)); } }; """ diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 7d79bdd6..e0b7c35c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -20,6 +20,8 @@ import FoundationEssentials import Foundation #endif +import SwiftSyntax + extension JNISwift2JavaGenerator { func writeSwiftThunkSources() throws { var printer = CodePrinter() @@ -294,18 +296,21 @@ extension JNISwift2JavaGenerator { private func printToStringMethods(_ printer: inout CodePrinter, _ type: ImportedNominalType) { let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) let parentName = type.qualifiedName + let isGeneric = type.swiftNominal.isGeneric + + var parameters = [selfPointerParam] + if isGeneric { + parameters.append(JavaParameter(name: "selfType", type: .long)) + } printCDecl( &printer, javaMethodName: "$toString", parentName: type.swiftNominal.qualifiedName, - parameters: [ - selfPointerParam, - JavaParameter(name: "selfType", type: .long) - ], + parameters: parameters, resultType: .javaLangString ) { printer in - if type.swiftNominal.isGeneric { + if isGeneric { printer.print("let selfBits$ = Int(Int64(fromJNI: selfPointer, in: environment))") printer.printBraceBlock( "guard let selfTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: selfType, in: environment))) else" @@ -314,7 +319,7 @@ extension JNISwift2JavaGenerator { } printer.print("let erasedSelfType$: Any.Type = unsafeBitCast(selfTypeMetadataPointer$, to: Any.Type.self)") printer.print("let openerType = erasedSelfType$ as! (any \(openerProtocolName(for: type.swiftNominal)).Type)") - printer.print("return openerType._toString(environment: environment, thisClass: thisClass, self: self).getJNIValue(in: environment)") + printer.print("return openerType._toString(environment: environment, thisClass: thisClass, self: selfPointer)") } else { let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) printer.print("return String(describing: \(selfVar).pointee).getJNIValue(in: environment)") @@ -327,13 +332,10 @@ extension JNISwift2JavaGenerator { &printer, javaMethodName: "$toDebugString", parentName: type.swiftNominal.qualifiedName, - parameters: [ - selfPointerParam, - JavaParameter(name: "selfType", type: .long) - ], + parameters: parameters, resultType: .javaLangString ) { printer in - if type.swiftNominal.isGeneric { + if isGeneric { printer.print("let selfBits$ = Int(Int64(fromJNI: selfPointer, in: environment))") printer.printBraceBlock( "guard let selfTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: selfType, in: environment))) else" @@ -342,7 +344,7 @@ extension JNISwift2JavaGenerator { } printer.print("let erasedSelfType$: Any.Type = unsafeBitCast(selfTypeMetadataPointer$, to: Any.Type.self)") printer.print("let openerType = erasedSelfType$ as! (any \(openerProtocolName(for: type.swiftNominal)).Type)") - printer.print("return openerType._toDebugString(environment: environment, thisClass: thisClass, self: self).getJNIValue(in: environment)") + printer.print("return openerType._toDebugString(environment: environment, thisClass: thisClass, self: selfPointer)") } else { let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) printer.print("return String(reflecting: \(selfVar).pointee).getJNIValue(in: environment)") @@ -833,26 +835,50 @@ extension JNISwift2JavaGenerator { /// Prints the implementation of the destroy function. private func printDestroyFunctionThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) + let selfPointerParam = JavaParameter(name: "self", 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: "self", type: type.swiftType)), + parameters: [], + result: .void, + effectSpecifiers: [], + genericParameters: [], + genericRequirements: [] + ) + printFunctionOpenerCall( + &printer, + .init( + module: swiftModuleName, + swiftDecl: DeclSyntax("func destory()"), + 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() + """ + ) + } } } @@ -1011,6 +1037,7 @@ extension JNISwift2JavaGenerator { printer.print("static func _toString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jstring?") printer.print("static func _toDebugString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jstring?") + printer.print("static func _destroy(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong)") } printer.println() printer.printBraceBlock("extension \(type.swiftNominal.name): \(protocolName)") { printer in @@ -1024,24 +1051,35 @@ extension JNISwift2JavaGenerator { printFunctionDecl(&printer, decl: method, skipMethodBody: false) } - printer.printBraceBlock("static func _toString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> String") { printer in + printer.printBraceBlock("static func _toString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jstring?") { printer in + printer.print(#"assert(self != 0, "self memory address was null")"#) + printer.print("let selfBits$ = Int(Int64(fromJNI: self, 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("return String(describing: self$.pointee).getJNIValue(in: environment)") + } + + printer.printBraceBlock("static func _toDebugString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jstring?") { printer in printer.print(#"assert(self != 0, "self memory address was null")"#) printer.print("let selfBits$ = Int(Int64(fromJNI: self, 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("return String(describing: self$.pointee)") + printer.print("return String(reflecting: self$.pointee).getJNIValue(in: environment)") } - printer.printBraceBlock("static func _toDebugString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> String") { printer in + printer.printBraceBlock("static func _destroy(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong)") { printer in printer.print(#"assert(self != 0, "self memory address was null")"#) printer.print("let selfBits$ = Int(Int64(fromJNI: self, 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("return String(reflecting: self$.pointee)") + printer.print("self$.deinitialize(count: 1)") + printer.print("self$.deallocate()") } } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 8ad7cb75..154c0859 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -72,6 +72,28 @@ struct JNIGenericTypeTests { return this.selfTypePointer; } """, + """ + private static native void $destroy(long self, long selfType); + @Override + public Runnable $createDestroyFunction() { + long selfType$ = this.$typeMetadataAddress(); + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyID.$createDestroyFunction", + "this", this, + "self", self$); + } + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyID.$destroy", "self", self$); + } + MyID.$destroy(self$, selfType$); + } + }; + } + """ ] ) } From d7253d28e60044e9a7c2f3a729361af609eef990 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 20 Feb 2026 09:15:43 +0900 Subject: [PATCH 11/23] A bit refactoring --- ...wift2JavaGenerator+NativeTranslation.swift | 54 ++++++++++--------- .../SwiftTypes/SwiftFunctionSignature.swift | 9 ++++ 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index b9b7ae0b..2c990505 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -61,26 +61,21 @@ extension JNISwift2JavaGenerator { nil } - func translateSelfTypeParameters(_ decl: SwiftNominalTypeDeclaration) -> NativeParameter? { - if decl.isGeneric { - return NativeParameter( - parameters: [ - JavaParameter(name: "selfType", type: .long) - ], - conversion: .extractMetatypeValue(.placeholder), - indirectConversion: nil, - conversionCheck: 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 } - return nil - } - let selfTypeParameter: NativeParameter? = switch functionSignature.selfParameter { - case .instance(let selfParameter): - selfParameter.type.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) - case .initializer(let swiftType), .staticMethod(let swiftType): - swiftType.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) - case nil: nil - } let result = try translate(swiftResult: functionSignature.result) @@ -311,7 +306,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) } } @@ -1065,11 +1070,12 @@ extension JNISwift2JavaGenerator { } else { printer.print("let \(inner)Bits$ = Int(\(inner))") } - let typeName = if swiftType.asNominalTypeDeclaration?.isGeneric == true { - "Self" - } else { - swiftType.description - } + let typeName = + if swiftType.asNominalTypeDeclaration?.isGeneric == true { + "Self" + } else { + swiftType.description + } printer.print("let \(pointerName) = UnsafeMutablePointer<\(typeName)>(bitPattern: \(inner)Bits$)") if !allowNil { printer.print( 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 { From 7a5a7c908cc5462c6c7ca9e0b343063796f2eec6 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 20 Feb 2026 09:16:56 +0900 Subject: [PATCH 12/23] apply formatter --- .../Sources/MySwiftLibrary/GenericType.swift | 6 +-- ...t2JavaGenerator+JavaBindingsPrinting.swift | 26 ++++++------- ...ISwift2JavaGenerator+JavaTranslation.swift | 37 ++++++++++-------- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 38 ++++++++++--------- .../SwiftNominalTypeDeclaration.swift | 9 +++-- .../JNI/JNIGenericTypeTests.swift | 10 ++--- 6 files changed, 68 insertions(+), 58 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift index a8444637..c8edcb1c 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift @@ -15,7 +15,7 @@ public struct MyID { public var rawValue: T public init(_ rawValue: T) { - self.rawValue = rawValue + self.rawValue = rawValue } public var description: String { "\(rawValue)" @@ -23,11 +23,11 @@ public struct MyID { } public func makeIntID(_ value: Int) -> MyID { - return MyID(value) + MyID(value) } public func makeStringID(_ value: String) -> MyID { - return MyID(value) + MyID(value) } public struct MyEntity { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 99578606..257ab387 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -209,10 +209,10 @@ extension JNISwift2JavaGenerator { printer.printBraceBlock("private \(decl.swiftNominal.name)(\(swiftPointerArg), SwiftArena swiftArena)") { printer in for param in swiftPointerParams { printer.print( - """ - SwiftObjects.requireNonZero(\(param), "\(param)"); - this.\(param) = \(param); - """ + """ + SwiftObjects.requireNonZero(\(param), "\(param)"); + this.\(param) = \(param); + """ ) } printer.print( @@ -731,19 +731,19 @@ extension JNISwift2JavaGenerator { } } 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$); - } - """ + """ + 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();") } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 39e7abda..fa132386 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -332,13 +332,14 @@ extension JNISwift2JavaGenerator { } return nil } - let selfTypeParameter: TranslatedParameter? = switch functionSignature.selfParameter { - case .instance(let selfParameter): - selfParameter.type.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) - case .initializer(let swiftType), .staticMethod(let swiftType): - swiftType.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) - case nil: nil - } + let selfTypeParameter: TranslatedParameter? = + switch functionSignature.selfParameter { + case .instance(let selfParameter): + selfParameter.type.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) + case .initializer(let swiftType), .staticMethod(let swiftType): + swiftType.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) + case nil: nil + } var exceptions: [JavaExceptionType] = [] @@ -798,14 +799,20 @@ extension JNISwift2JavaGenerator { 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) - ]) + 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( diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index e0b7c35c..fafe11a7 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,7 +21,6 @@ import FoundationEssentials import Foundation #endif -import SwiftSyntax extension JNISwift2JavaGenerator { func writeSwiftThunkSources() throws { @@ -816,7 +816,7 @@ extension JNISwift2JavaGenerator { if type.swiftNominal.isGeneric { return } - + printCDecl( &printer, javaMethodName: "$typeMetadataAddressDowncall", @@ -963,12 +963,13 @@ extension JNISwift2JavaGenerator { parameters += nativeSignature.result.outParameters let openerArguments = - [ - "environment: environment", - "thisClass: thisClass", - ] + parameters.map { javaParameter in - "\(javaParameter.name): \(javaParameter.name)" - } + [ + "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 { @@ -1000,12 +1001,12 @@ extension JNISwift2JavaGenerator { let translatedParameters = parameters.map { "\($0.name): \($0.type.jniTypeName)" } - + let thunkParameters = - [ - "environment: UnsafeMutablePointer!", - "thisClass: jclass", - ] + translatedParameters + [ + "environment: UnsafeMutablePointer!", + "thisClass: jclass", + ] + translatedParameters let thunkReturnType = resultType != .void ? " -> \(resultType.jniTypeName)" : "" let signature = #"static func \#(decl.openerMethodName)(\#(thunkParameters.joined(separator: ", ")))\#(thunkReturnType)"# @@ -1152,11 +1153,12 @@ extension SwiftNominalTypeDeclaration { extension ImportedFunc { fileprivate var openerMethodName: String { - let prefix = switch apiKind { - case .getter: "_get_" - case .setter: "_set_" - default: "_" - } + let prefix = + switch apiKind { + case .getter: "_get_" + case .setter: "_set_" + default: "_" + } return "\(prefix)\(name)" } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 45d305d8..78ae5289 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -92,9 +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) - } ?? [] + 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) { @@ -164,7 +165,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { } var isGeneric: Bool { - return !genericParameters.isEmpty + !genericParameters.isEmpty } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 154c0859..8c34c4c7 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -28,7 +28,7 @@ struct JNIGenericTypeTests { "\(rawValue)" } } - + public func makeStringID(_ value: String) -> MyID { return MyID(value) } @@ -52,7 +52,7 @@ struct JNIGenericTypeTests { 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); } @@ -93,7 +93,7 @@ struct JNIGenericTypeTests { } }; } - """ + """, ] ) } @@ -136,7 +136,7 @@ struct JNIGenericTypeTests { let openerType = unsafeBitCast(selfType$, to: Any.Type.self) as! (any _SwiftModule_MyID_opener.Type) return openerType._get_description(environment: environment, thisClass: thisClass, self: self) } - """ + """, ] ) } @@ -158,7 +158,7 @@ struct JNIGenericTypeTests { """, """ private static native void $makeStringID(java.lang.String value, org.swift.swiftkit.core.OutSwiftGenericInstance out); - """ + """, ] ) } From e5f65f3253c87952e6aed81c7362121b3308c9ac Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 20 Feb 2026 10:45:33 +0900 Subject: [PATCH 13/23] Refactor translateSelfTypeParameter --- ...ISwift2JavaGenerator+JavaTranslation.swift | 65 ++++++++++++++----- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index fa132386..c492ec9f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -323,23 +323,13 @@ extension JNISwift2JavaGenerator { genericRequirements: functionSignature.genericRequirements ) - func translateSelfTypeParameters(_ decl: SwiftNominalTypeDeclaration) -> TranslatedParameter? { - if decl.isGeneric { - return TranslatedParameter( - parameter: JavaParameter(name: "selfType", type: .long), - conversion: .typeMetadataAddress(.placeholder) - ) - } - return nil - } - let selfTypeParameter: TranslatedParameter? = - switch functionSignature.selfParameter { - case .instance(let selfParameter): - selfParameter.type.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) - case .initializer(let swiftType), .staticMethod(let swiftType): - swiftType.asNominalTypeDeclaration.flatMap(translateSelfTypeParameters) - case nil: nil - } + let selfTypeParameter = try self.translateSelfTypeParameter( + functionSignature.selfParameter, + methodName: methodName, + parentName: parentName, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements + ) var exceptions: [JavaExceptionType] = [] @@ -402,6 +392,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, @@ -537,7 +560,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) } } From 686b34358cd2d17ca26bf4578e616b957611c14f Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 20 Feb 2026 11:20:24 +0900 Subject: [PATCH 14/23] Add example for generic type property --- .../Sources/MySwiftLibrary/GenericType.swift | 4 ---- .../src/test/java/com/example/swift/GenericTypeTest.java | 9 +++++++++ .../JNI/JNISwift2JavaGenerator+NativeTranslation.swift | 8 +------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift index c8edcb1c..60d88c1b 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift @@ -38,7 +38,3 @@ public struct MyEntity { self.name = name } } - -public func takeMyEntity() -> MyEntity { - return MyEntity(id: MyID(42), name: "Example") -} 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 d38ef934..368ed5fa 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java @@ -30,4 +30,13 @@ void returnsGenericType() { 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()); + } + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 2c990505..632d3b98 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -1070,13 +1070,7 @@ extension JNISwift2JavaGenerator { } else { printer.print("let \(inner)Bits$ = Int(\(inner))") } - let typeName = - if swiftType.asNominalTypeDeclaration?.isGeneric == true { - "Self" - } else { - swiftType.description - } - printer.print("let \(pointerName) = UnsafeMutablePointer<\(typeName)>(bitPattern: \(inner)Bits$)") + printer.print("let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$)") if !allowNil { printer.print( """ From 00373a7d8738e572be8aad5968e65cd10e87bdc6 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 20 Feb 2026 11:51:29 +0900 Subject: [PATCH 15/23] Fix some parameter name for test --- ...ISwift2JavaGenerator+JavaBindingsPrinting.swift | 4 ++-- ...JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 14 ++++++-------- .../JNI/JNIGenericTypeTests.swift | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 257ab387..69908333 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -754,9 +754,9 @@ extension JNISwift2JavaGenerator { private func printDestroyFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { let isGeneric = type.swiftNominal.isGeneric if isGeneric { - printer.print("private static native void $destroy(long self, long selfType);") + printer.print("private static native void $destroy(long selfPointer, long selfType);") } else { - printer.print("private static native void $destroy(long self);") + printer.print("private static native void $destroy(long selfPointer);") } let funcName = "$createDestroyFunction" diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index fafe11a7..c4c73788 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -311,7 +311,6 @@ extension JNISwift2JavaGenerator { resultType: .javaLangString ) { printer in if isGeneric { - printer.print("let selfBits$ = Int(Int64(fromJNI: selfPointer, in: environment))") printer.printBraceBlock( "guard let selfTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: selfType, in: environment))) else" ) { printer in @@ -336,7 +335,6 @@ extension JNISwift2JavaGenerator { resultType: .javaLangString ) { printer in if isGeneric { - printer.print("let selfBits$ = Int(Int64(fromJNI: selfPointer, in: environment))") printer.printBraceBlock( "guard let selfTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: selfType, in: environment))) else" ) { printer in @@ -835,7 +833,7 @@ extension JNISwift2JavaGenerator { /// Prints the implementation of the destroy function. private func printDestroyFunctionThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - let selfPointerParam = JavaParameter(name: "self", type: .long) + let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) var parameters = [selfPointerParam] if type.swiftNominal.isGeneric { parameters.append(JavaParameter(name: "selfType", type: .long)) @@ -850,7 +848,7 @@ extension JNISwift2JavaGenerator { ) { printer in if type.swiftNominal.isGeneric { let destroyFunctionSignature = SwiftFunctionSignature( - selfParameter: .instance(SwiftParameter(convention: .byValue, parameterName: "self", type: type.swiftType)), + selfParameter: .instance(SwiftParameter(convention: .byValue, parameterName: "selfPointer", type: type.swiftType)), parameters: [], result: .void, effectSpecifiers: [], @@ -1038,7 +1036,7 @@ extension JNISwift2JavaGenerator { printer.print("static func _toString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jstring?") printer.print("static func _toDebugString(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jstring?") - printer.print("static func _destroy(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong)") + printer.print("static func _destroy(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong)") } printer.println() printer.printBraceBlock("extension \(type.swiftNominal.name): \(protocolName)") { printer in @@ -1072,9 +1070,9 @@ extension JNISwift2JavaGenerator { printer.print("return String(reflecting: self$.pointee).getJNIValue(in: environment)") } - printer.printBraceBlock("static func _destroy(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong)") { printer in - printer.print(#"assert(self != 0, "self memory address was null")"#) - printer.print("let selfBits$ = Int(Int64(fromJNI: self, in: environment))") + 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)!\")") diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 8c34c4c7..e46843c3 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -73,7 +73,7 @@ struct JNIGenericTypeTests { } """, """ - private static native void $destroy(long self, long selfType); + private static native void $destroy(long selfPointer, long selfType); @Override public Runnable $createDestroyFunction() { long selfType$ = this.$typeMetadataAddress(); @@ -117,7 +117,7 @@ struct JNIGenericTypeTests { 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$) + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \(#function)!") } From f4f27ccca27367ed027df877d76528e9d3d59b78 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 20 Feb 2026 12:52:11 +0900 Subject: [PATCH 16/23] Fix static method skipping way --- ...NISwift2JavaGenerator+SwiftThunkPrinting.swift | 8 -------- Sources/JExtractSwiftLib/Swift2JavaVisitor.swift | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index c4c73788..c3624475 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -1019,18 +1019,10 @@ extension JNISwift2JavaGenerator { printer.printBraceBlock("protocol \(protocolName)") { printer in for variable in type.variables { - if variable.isStatic { - logger.debug("Skipping \(variable.name), static variables in generic type is not yet supported.") - continue - } printFunctionDecl(&printer, decl: variable, skipMethodBody: true) } for method in type.methods { - if method.isStatic { - logger.debug("Skipping \(method.name), static methods in generic type is not yet supported.") - continue - } printFunctionDecl(&printer, decl: method, skipMethodBody: true) } diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index ff6295bb..44e9c66f 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -160,6 +160,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) @@ -266,6 +271,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 @@ -363,6 +373,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)" ) From b5856c3c8a65965e091bbf600490ecd3f8e7fbc8 Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 24 Feb 2026 17:12:41 +0900 Subject: [PATCH 17/23] Fix typo --- .../JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 27df86d2..489acb97 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -809,7 +809,7 @@ extension JNISwift2JavaGenerator { &printer, .init( module: swiftModuleName, - swiftDecl: DeclSyntax("func destory()"), + swiftDecl: DeclSyntax("func destroy()"), name: "destroy", apiKind: .function, functionSignature: destroyFunctionSignature From 66018b311138f90058b280ad1ffbfb00941f34ea Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 25 Feb 2026 10:38:01 +0900 Subject: [PATCH 18/23] Add enum support --- .../Sources/MySwiftLibrary/GenericType.swift | 9 +++ .../com/example/swift/GenericTypeTest.java | 11 +++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 18 ++++- ...ISwift2JavaGenerator+JavaTranslation.swift | 18 ++++- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 72 +++++++++++++++---- 5 files changed, 111 insertions(+), 17 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift index 60d88c1b..02626671 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift @@ -38,3 +38,12 @@ public struct MyEntity { 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 index 368ed5fa..2a89d871 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java @@ -39,4 +39,15 @@ void genericTypeProperty() { 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 62404e8c..c6d2d8fb 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -391,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) { @@ -423,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) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 9461d007..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,7 +161,12 @@ extension JNISwift2JavaGenerator { ] ) ), - selfTypeParameter: nil, // TODO: iceman + selfTypeParameter: !isGenericParent + ? nil + : .init( + parameter: JavaParameter(name: "selfType", type: .long), + conversion: .typeMetadataAddress(.placeholder) + ), parameters: [], resultType: TranslatedResult( javaType: .class(package: nil, name: "Optional<\(caseName)>"), @@ -177,7 +184,14 @@ extension JNISwift2JavaGenerator { indirectConversion: nil, conversionCheck: nil ), - selfTypeParameter: nil, // TODO: iceman + selfTypeParameter: !isGenericParent + ? nil + : .init( + parameters: [JavaParameter(name: "selfType", type: .long)], + conversion: .extractMetatypeValue(.placeholder), + indirectConversion: nil, + conversionCheck: nil + ), parameters: [], result: NativeResult( javaType: nativeParametersType, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 489acb97..6e750e28 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -262,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() + } } } @@ -294,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)") + } } } } @@ -976,6 +1005,10 @@ extension JNISwift2JavaGenerator { 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() @@ -990,6 +1023,21 @@ extension JNISwift2JavaGenerator { 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))") From d4cd58ed598ac9068c48bd354a6ca000c4085b3d Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 25 Feb 2026 10:57:51 +0900 Subject: [PATCH 19/23] Update supported feature list --- .../Documentation.docc/SupportedFeatures.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 0789700e..61b8b3bf 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)` | ❌ | ❌ | From 27a7a9a481855f98ae9ef287435ada48af577432 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 25 Feb 2026 11:23:03 +0900 Subject: [PATCH 20/23] Fix copyright year --- .../Sources/MySwiftLibrary/GenericType.swift | 2 +- .../src/test/java/com/example/swift/GenericTypeTest.java | 2 +- .../java/org/swift/swiftkit/core/OutSwiftGenericInstance.java | 2 +- Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift index 02626671..4e0041fb 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information 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 2a89d871..fa0d76da 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/GenericTypeTest.java @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java index 68989cad..3e99a3a7 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index e46843c3..1933cf5a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information From 10f9f2a4074bef040723b381178c2e2e34f1885d Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 25 Feb 2026 14:38:38 +0900 Subject: [PATCH 21/23] includes selfType$ in CallTraces.traceDowncall --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 59 ++++++++++++------- .../JNI/JNIGenericTypeTests.swift | 7 ++- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index c6d2d8fb..ca2b1e6a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -740,29 +740,48 @@ extension JNISwift2JavaGenerator { let funcName = "$createDestroyFunction" printer.print("@Override") printer.printBraceBlock("public Runnable \(funcName)()") { printer in + printer.print("long self$ = this.$memoryAddress();") if isGeneric { - printer.print("long selfType$ = this.$typeMetadataAddress();") - } - let destroyArg = isGeneric ? "self$, selfType$" : "self$" - 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 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(\(destroyArg)); + }; + """ + ) + } 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/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 1933cf5a..8bc4a0e0 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -76,18 +76,19 @@ struct JNIGenericTypeTests { private static native void $destroy(long selfPointer, long selfType); @Override public Runnable $createDestroyFunction() { - long selfType$ = this.$typeMetadataAddress(); long self$ = this.$memoryAddress(); + long selfType$ = this.$typeMetadataAddress(); if (CallTraces.TRACE_DOWNCALLS) { CallTraces.traceDowncall("MyID.$createDestroyFunction", "this", this, - "self", self$); + "self", self$, + "selfType", selfType$); } return new Runnable() { @Override public void run() { if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyID.$destroy", "self", self$); + CallTraces.traceDowncall("MyID.$destroy", "self", self$, "selfType", selfType$); } MyID.$destroy(self$, selfType$); } From 91bb797189a99f78135993b7e2d5fc6c82f459b2 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 25 Feb 2026 14:52:45 +0900 Subject: [PATCH 22/23] Add document to OutSwiftGenericInstance --- .../org/swift/swiftkit/core/OutSwiftGenericInstance.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java index 3e99a3a7..018b5108 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/OutSwiftGenericInstance.java @@ -14,6 +14,14 @@ 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; From 016766b1e74b4dc33c2f4c4918e78aebce06099f Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 25 Feb 2026 15:08:52 +0900 Subject: [PATCH 23/23] Add detailed document about generic types --- .../Documentation.docc/SupportedFeatures.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 61b8b3bf..ab8f2217 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -357,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(); +} +```