From d20f063f4cd4880fd7934e8e96755cb07369c483 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 18 May 2026 17:27:39 +0900 Subject: [PATCH 1/4] Add example and simple fix for variadic types --- .../MySwiftLibrary/VariadicGenericType.swift | 35 +++++++++++++++++++ .../swift/VariadicGenericTypeTest.java | 26 ++++++++++++++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 6 ++-- .../SwiftNominalTypeDeclaration.swift | 20 +++++++++++ 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/VariadicGenericType.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VariadicGenericTypeTest.java diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/VariadicGenericType.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/VariadicGenericType.swift new file mode 100644 index 00000000..d261f188 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/VariadicGenericType.swift @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public struct VariadicBox { + public var values: (repeat each T) + public init(values: repeat each T) { + self.values = (repeat each values) + } + + public static var count: Int { + ComputeParameterPackLength.count(ofPack: (repeat each T).self) + } +} + +public typealias IntStringBoolBox = VariadicBox + +private enum ComputeParameterPackLength { + enum BoolConverter { + typealias Bool = Swift.Bool + } + static func count(ofPack t: (repeat each T).Type) -> Int { + MemoryLayout<(repeat BoolConverter.Bool)>.size / MemoryLayout.stride + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VariadicGenericTypeTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VariadicGenericTypeTest.java new file mode 100644 index 00000000..dde87b97 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VariadicGenericTypeTest.java @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class VariadicGenericTypeTest { + @Test + void callSpecializedStaticMethod() { + assertEquals(3, IntStringBoolBox.getCount()); + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 70162f8d..3db07a5f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -821,15 +821,15 @@ extension JNISwift2JavaGenerator { if type.genericParameterNames.isEmpty { type.effectiveSwiftTypeName } else { - "\(type.baseTypeName)<\(type.genericParameterNames.joined(separator: ", "))>" + "\(type.baseTypeName)<\(type.swiftNominal.genericParameters.map(\.packExpansionName).joined(separator: ", "))>" } let parentProtocol = isEffectivelyGeneric ? "JextractedGenericTypeBridge" : "JextractedTypeBridge" let bridgeDeclaration = if let bridgeWhereClause { - "enum \(bridgeName)\(bridgeGenericClause): \(parentProtocol) \(bridgeWhereClause)" + "struct \(bridgeName)\(bridgeGenericClause): \(parentProtocol) \(bridgeWhereClause)" } else { - "enum \(bridgeName)\(bridgeGenericClause): \(parentProtocol)" + "struct \(bridgeName)\(bridgeGenericClause): \(parentProtocol)" } printer.printBraceBlock(bridgeDeclaration) { printer in diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index bbc5a97f..07543bf2 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -205,6 +205,26 @@ package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { self.syntax = node super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text) } + + var hasEach: Bool { + syntax.specifier?.tokenKind == .keyword(.each) + } + + var packReferenceName: String { + if hasEach { + "each \(name)" + } else { + name + } + } + + var packExpansionName: String { + if hasEach { + "repeat each \(name)" + } else { + name + } + } } /// A plain typealias will resolve as the right hand type in generated code. From 0aca41b0f7fd12ebbb9273fbab96d3d9088ff23e Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 18 May 2026 17:53:43 +0900 Subject: [PATCH 2/4] Fix packed parameter name in swift type --- Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 6c758acd..49bea52d 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -156,7 +156,7 @@ extension SwiftType: CustomStringConvertible { var description: String { switch self { case .nominal(let nominal): return nominal.description - case .genericParameter(let genericParam): return genericParam.name + case .genericParameter(let genericParam): return genericParam.packExpansionName case .function(let functionType): return functionType.description case .metatype(let instanceType): var instanceTypeStr = instanceType.description From 2520ade9b5c5e2fc4bbbe5fa9e615c91384ad993 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 18 May 2026 18:06:30 +0900 Subject: [PATCH 3/4] add unit test --- .../JNI/JNIGenericTypeTests.swift | 33 +++++++++++++++++++ .../JNI/JNIJobjectBridgeTests.swift | 30 ++++++++++++++--- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 9c9b89e9..3396540a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -247,6 +247,39 @@ struct JNIGenericTypeTests { ) } + @Test + func generatesOpenerForVariadicGenericStaticMethod() throws { + let input = + #""" + public struct VariadicBox { + public static func describe() -> String { + "\(VariadicBox.self)" + } + } + """# + + try assertOutput( + input: input, + .jni, + .swift, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + protocol _SwiftModule_VariadicBox_opener { + static func _describe(environment: UnsafeMutablePointer!, thisClass: jclass) -> jstring? + } + """, + #""" + extension VariadicBox: _SwiftModule_VariadicBox_opener { + static func _describe(environment: UnsafeMutablePointer!, thisClass: jclass) -> jstring? { + return VariadicBox.describe().getJNILocalRefValue(in: environment) + } + } + """#, + ] + ) + } + @Test func genericValueInEnumCase() throws { let input = diff --git a/Tests/JExtractSwiftTests/JNI/JNIJobjectBridgeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJobjectBridgeTests.swift index 9653503f..60c936cb 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIJobjectBridgeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIJobjectBridgeTests.swift @@ -26,7 +26,7 @@ struct JNIJobjectBridgeTests { """, .jni, .swift, - detectChunkByInitialLines: 2, + detectChunkByInitialLines: 1, expectedChunks: [ """ private enum _JNI_ReefFish { @@ -48,7 +48,7 @@ struct JNIJobjectBridgeTests { } """, """ - enum _JNIBridge_ReefFish: JextractedTypeBridge { + struct _JNIBridge_ReefFish: JextractedTypeBridge { typealias SwiftType = ReefFish static var javaClass: jclass { @@ -82,11 +82,11 @@ struct JNIJobjectBridgeTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - enum _JNIBridge_MyID: JextractedGenericTypeBridge { + struct _JNIBridge_MyID: JextractedGenericTypeBridge { typealias SwiftType = MyID """, """ - enum _JNIBridge_MyValue: JextractedGenericTypeBridge where O : Swift.Comparable, E : Swift.Error { + struct _JNIBridge_MyValue: JextractedGenericTypeBridge where O : Swift.Comparable, E : Swift.Error { typealias SwiftType = MyValue """, """ @@ -95,4 +95,26 @@ struct JNIJobjectBridgeTests { ] ) } + + @Test("JNI generates explicit bridges for variadic generic types") + func generatesBridgeDeclarationForVariadicGenericType() throws { + try assertOutput( + input: """ + public struct VariadicBox {} + public func f() -> [Int: VariadicBox] {} + """, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + struct _JNIBridge_VariadicBox: JextractedGenericTypeBridge { + typealias SwiftType = VariadicBox + """, + """ + return SwiftModule.f().dictionaryGetJNIValue(in: environment, keyBridge: JavaBoxableBridge.self, valueBridge: _JNIBridge_VariadicBox.self) + """, + ] + ) + } } From d00d3e9d406493f439c44092d9da13c00c0677da Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 19 May 2026 09:21:48 +0900 Subject: [PATCH 4/4] Update document --- .../Documentation.docc/SupportedFeatures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 5e9b4f7d..75dbb06c 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -88,7 +88,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ✅ * | ✅ * | | String (with copying data) | ✅ | ✅ | | Variadic parameters: `T...` | ❌ | ❌ | -| Parametrer packs / Variadic generics | ❌ | ❌ | +| Parametrer packs / Variadic generics | ❌ | 🟡 | | Ownership modifiers: `inout`, `borrowing`, `consuming` | ❌ | ❌ | | Default parameter values: `func p(name: String = "")` | ❌ | ❌ | | Operators: `+`, `-`, user defined | ❌ | ❌ |