Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<each T> {
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<Int, String, Bool>

private enum ComputeParameterPackLength {
enum BoolConverter<T> {
typealias Bool = Swift.Bool
}
static func count<each T>(ofPack t: (repeat each T).Type) -> Int {
MemoryLayout<(repeat BoolConverter<each T>.Bool)>.size / MemoryLayout<Bool>.stride
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 | ❌ | ❌ |
Expand Down
33 changes: 33 additions & 0 deletions Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,39 @@ struct JNIGenericTypeTests {
)
}

@Test
func generatesOpenerForVariadicGenericStaticMethod() throws {
let input =
#"""
public struct VariadicBox<each T> {
public static func describe() -> String {
"\(VariadicBox<repeat each T>.self)"
}
}
"""#

try assertOutput(
input: input,
.jni,
.swift,
detectChunkByInitialLines: 2,
expectedChunks: [
"""
protocol _SwiftModule_VariadicBox_opener {
static func _describe(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass) -> jstring?
}
""",
#"""
extension VariadicBox: _SwiftModule_VariadicBox_opener {
static func _describe(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass) -> jstring? {
return VariadicBox<repeat each T>.describe().getJNILocalRefValue(in: environment)
}
}
"""#,
]
)
}

@Test
func genericValueInEnumCase() throws {
let input =
Expand Down
30 changes: 26 additions & 4 deletions Tests/JExtractSwiftTests/JNI/JNIJobjectBridgeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct JNIJobjectBridgeTests {
""",
.jni,
.swift,
detectChunkByInitialLines: 2,
detectChunkByInitialLines: 1,
expectedChunks: [
"""
private enum _JNI_ReefFish {
Expand All @@ -48,7 +48,7 @@ struct JNIJobjectBridgeTests {
}
""",
"""
enum _JNIBridge_ReefFish: JextractedTypeBridge {
struct _JNIBridge_ReefFish: JextractedTypeBridge {
typealias SwiftType = ReefFish

static var javaClass: jclass {
Expand Down Expand Up @@ -82,11 +82,11 @@ struct JNIJobjectBridgeTests {
detectChunkByInitialLines: 1,
expectedChunks: [
"""
enum _JNIBridge_MyID<T: Hashable, U>: JextractedGenericTypeBridge {
struct _JNIBridge_MyID<T: Hashable, U>: JextractedGenericTypeBridge {
typealias SwiftType = MyID<T, U>
""",
"""
enum _JNIBridge_MyValue<T, O, E>: JextractedGenericTypeBridge where O : Swift.Comparable, E : Swift.Error {
struct _JNIBridge_MyValue<T, O, E>: JextractedGenericTypeBridge where O : Swift.Comparable, E : Swift.Error {
typealias SwiftType = MyValue<T, O, E>
""",
"""
Expand All @@ -95,4 +95,26 @@ struct JNIJobjectBridgeTests {
]
)
}

@Test("JNI generates explicit bridges for variadic generic types")
func generatesBridgeDeclarationForVariadicGenericType() throws {
try assertOutput(
input: """
public struct VariadicBox<each T> {}
public func f() -> [Int: VariadicBox<Int, String>] {}
""",
.jni,
.swift,
detectChunkByInitialLines: 1,
expectedChunks: [
"""
struct _JNIBridge_VariadicBox<each T>: JextractedGenericTypeBridge {
typealias SwiftType = VariadicBox<repeat each T>
""",
"""
return SwiftModule.f().dictionaryGetJNIValue(in: environment, keyBridge: JavaBoxableBridge<Int>.self, valueBridge: _JNIBridge_VariadicBox<Int, String>.self)
""",
]
)
}
}
Loading