Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6e02a68
initial implementation
sidepelican May 7, 2026
090e4fa
Add support generic types
sidepelican May 7, 2026
98ce301
Skip to print method cache for Core classes
sidepelican May 7, 2026
72b4808
Remove JavaBoxable extension generations for collection types
sidepelican May 7, 2026
d31aa69
Update examples
sidepelican May 7, 2026
c744d2a
Fix optional boxing
sidepelican May 8, 2026
381586e
Skip optional supporting
sidepelican May 8, 2026
9d2ff60
Nested array support
sidepelican May 8, 2026
ae54814
Remove array support
sidepelican May 8, 2026
09cedb6
slim test code
sidepelican May 8, 2026
583b25b
Merge remote-tracking branch 'origin/main' into collection_boxable
sidepelican May 13, 2026
8f8b1b4
Add javaBoxClass support
sidepelican May 13, 2026
e784100
Merge remote-tracking branch 'origin/main' into collection_boxable
sidepelican May 13, 2026
37e19f9
Use default arena explicitly
sidepelican May 14, 2026
5cdfc4f
print JavaBoxable extension for all types
sidepelican May 14, 2026
e2790d2
Use bridge type strategy
sidepelican May 14, 2026
153fd1d
Simplified Implementation
sidepelican May 14, 2026
dccc874
Restore removal of JavaValue from JavaBoxable
sidepelican May 14, 2026
81b1a21
rename bridge types
sidepelican May 15, 2026
163193d
Fix tests
sidepelican May 15, 2026
f33c2bf
Add Array and Optional support
sidepelican May 15, 2026
407886d
Add intArrayDictionary test
sidepelican May 15, 2026
85d8658
formatting
sidepelican May 15, 2026
6c421e2
Add documentation comment and error details
sidepelican May 15, 2026
9b61ec0
Fix build error in LinkageTest
sidepelican May 15, 2026
c5cdc0a
Fix generic clause join operation
sidepelican May 15, 2026
02ea80e
Pickup generic where clause
sidepelican May 15, 2026
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
Expand Up @@ -12,15 +12,15 @@
//
//===----------------------------------------------------------------------===//

public struct Box<Element> {
public struct Box<Element>: Hashable {
public var count: Int64

public init(count: Int64) {
self.count = count
}
}

public struct Fish {
public struct Fish: Hashable {
public var name: String

public init(name: String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif

public func makeIntToFishDictionary() -> [Int: Fish] {
[
1: Fish(name: "salmon"),
2: Fish(name: "clownfish"),
]
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic, thank you :)


public func intToFishDictionary(dict: [Int: Fish]) -> [Int: Fish] {
dict
}

public func makeFishSet() -> Set<Fish> {
[
Fish(name: "salmon"),
Fish(name: "clownfish"),
]
}

public func fishSet(set: Set<Fish>) -> Set<Fish> {
set
}

public func makeMyIDToFish() -> [MyID<Int>: Fish] {
[
.init(0): Fish(name: "salmon"),
.init(1): Fish(name: "clownfish"),
]
}

public func makeSpecializedGenericTypeSet() -> Set<FishBox> {
[.init(count: 2), .init(count: 3)]
}

public func makeSetInDictionary() -> [String: Set<Int32>] {
[
"even": [0, 2, 4],
"odd": [1, 3, 5],
]
}

public func makeIntArrayDictionary() -> [String: [Int32]] {
[
"even": [0, 2, 4],
"odd": [1, 3, 5],
]
}

public func intArrayDictionary(dict: [String: [Int32]]) -> [String: [Int32]] {
dict
}

public func makeFishArrayDictionary() -> [String: [Fish]] {
[
"reef": [
Fish(name: "clownfish"),
Fish(name: "blue tang"),
],
"river": [
Fish(name: "salmon")
],
]
}

public func fishArrayDictionary(dict: [String: [Fish]]) -> [String: [Fish]] {
dict
}

public func makeOptionalFishDictionary() -> [String: Fish?] {
[
"reef": Fish(name: "clownfish"),
"empty": nil,
]
}

public func optionalFishDictionary(dict: [String: Fish?]) -> [String: Fish?] {
dict
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//

public struct MyID<T> {
public struct MyID<T: Hashable>: Hashable {
public var rawValue: T
public init(_ rawValue: T) {
self.rawValue = rawValue
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

package com.example.swift;

import org.junit.jupiter.api.Test;
import org.swift.swiftkit.core.SwiftArena;
import org.swift.swiftkit.core.collections.SwiftDictionaryMap;
import org.swift.swiftkit.core.collections.SwiftSet;

import static org.junit.jupiter.api.Assertions.*;

import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public class CollectionBoxableTest {
@Test
void intToFishDictionaryRoundtrip() {
try (var arena = SwiftArena.ofConfined()) {
SwiftDictionaryMap<Long, Fish> original = MySwiftLibrary.makeIntToFishDictionary(arena);
assertEquals(2, original.size());
assertEquals("salmon", original.get(1L).getName());
assertEquals("clownfish", original.get(2L).getName());

SwiftDictionaryMap<Long, Fish> roundtripped = MySwiftLibrary.intToFishDictionary(original, arena);
assertEquals(2, roundtripped.size());
assertEquals("salmon", roundtripped.get(1L).getName());
assertEquals("clownfish", roundtripped.get(2L).getName());
}
}

@Test
void fishSetRoundtrip() {
try (var arena = SwiftArena.ofConfined()) {
SwiftSet<Fish> original = MySwiftLibrary.makeFishSet(arena);
assertEquals(2, original.size());
assertTrue(original.contains(Fish.init("salmon", arena)));
assertTrue(original.contains(Fish.init("clownfish", arena)));

SwiftSet<Fish> roundtripped = MySwiftLibrary.fishSet(original, arena);
assertEquals(2, roundtripped.size());
assertTrue(roundtripped.contains(Fish.init("salmon", arena)));
assertTrue(roundtripped.contains(Fish.init("clownfish", arena)));
}
}

@Test
void makeMyIDToFish() {
try (var arena = SwiftArena.ofConfined()) {
SwiftDictionaryMap<MyID<Long>, Fish> dict = MySwiftLibrary.makeMyIDToFish(arena);
assertEquals(2, dict.size());

MyID<Long> salmonId = MyIDs.makeIntID(0, arena);
MyID<Long> clownfishId = MyIDs.makeIntID(1, arena);
MyID<Long> unknownId = MyIDs.makeIntID(-100, arena);

assertTrue(dict.containsKey(salmonId));
assertTrue(dict.containsKey(clownfishId));
assertFalse(dict.containsKey(unknownId));
assertEquals("salmon", dict.get(salmonId).getName());
assertEquals("clownfish", dict.get(clownfishId).getName());
}
}

@Test
void makeSpecializedGenericTypeSet() {
try (var arena = SwiftArena.ofConfined()) {
SwiftSet<Box<Fish>> set = MySwiftLibrary.makeSpecializedGenericTypeSet(arena);

assertEquals(
set.stream()
.map(Box::getCount)
.collect(Collectors.toSet()),
Set.of(2L, 3L)
);
}
}

@Test
void makeSetInDictionary() {
try (var arena = SwiftArena.ofConfined()) {
SwiftDictionaryMap<String, SwiftSet<Integer>> dict = MySwiftLibrary.makeSetInDictionary(arena);
assertEquals(Set.of(0, 2, 4), dict.get("even").toJavaSet());
assertNull(dict.get("unknown"));
}
}

@Test
void intArrayDictionaryRoundtrip() {
try (var arena = SwiftArena.ofConfined()) {
SwiftDictionaryMap<String, Integer[]> original = MySwiftLibrary.makeIntArrayDictionary(arena);
assertArrayEquals(new Integer[] {0, 2, 4}, original.get("even"));

SwiftDictionaryMap<String, Integer[]> roundtripped = MySwiftLibrary.intArrayDictionary(original, arena);
assertArrayEquals(new Integer[] {0, 2, 4}, roundtripped.get("even"));
}
}

@Test
void fishArrayDictionaryRoundtrip() {
try (var arena = SwiftArena.ofConfined()) {
SwiftDictionaryMap<String, Fish[]> original = MySwiftLibrary.makeFishArrayDictionary(arena);
assertArrayEquals(new String[] {"clownfish", "blue tang"}, fishNames(original.get("reef")));
assertArrayEquals(new String[] {"salmon"}, fishNames(original.get("river")));

SwiftDictionaryMap<String, Fish[]> roundtripped = MySwiftLibrary.fishArrayDictionary(original, arena);
assertArrayEquals(new String[] {"clownfish", "blue tang"}, fishNames(roundtripped.get("reef")));
assertArrayEquals(new String[] {"salmon"}, fishNames(roundtripped.get("river")));
}
}

@Test
void optionalFishDictionaryRoundtrip() {
try (var arena = SwiftArena.ofConfined()) {
SwiftDictionaryMap<String, Optional<Fish>> original = MySwiftLibrary.makeOptionalFishDictionary(arena);
assertDoesNotThrow(() -> {
var value = original.get("reef").orElseThrow();
assertEquals("clownfish", value.getName());
});
assertDoesNotThrow(() -> {
assertTrue(original.get("empty").isEmpty());
});

SwiftDictionaryMap<String, Optional<Fish>> roundtripped = MySwiftLibrary.optionalFishDictionary(original, arena);
assertDoesNotThrow(() -> {
var value = roundtripped.get("reef").orElseThrow();
assertEquals("clownfish", value.getName());
});
assertDoesNotThrow(() -> {
assertTrue(roundtripped.get("empty").isEmpty());
});
}
}

private static String[] fishNames(Fish[] fish) {
return Arrays.stream(fish)
.map(Fish::getName)
.toArray(String[]::new);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void data_echo() {
var data = Data.fromByteArray(bytes, arena);

var echoed = MySwiftLibrary.echoData(data, arena);
assertEquals(4, echoed.getCount());
assertArrayEquals(bytes, echoed.toByteArray());
}
}

Expand Down
12 changes: 12 additions & 0 deletions Sources/JExtractSwiftLib/JNI/JNICaching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,22 @@ enum JNICaching {
cacheName(for: type.nominalTypeDecl.qualifiedTypeName)
}

static func bridgeName(for type: ImportedNominalType) -> String {
bridgeName(for: type.swiftNominal.qualifiedTypeName)
}

static func bridgeName(for type: SwiftNominalType) -> String {
bridgeName(for: type.nominalTypeDecl.qualifiedTypeName)
}

private static func cacheName(for typeName: SwiftQualifiedTypeName) -> String {
"_JNI_\(typeName.fullFlatName)"
}

private static func bridgeName(for typeName: SwiftQualifiedTypeName) -> String {
"_JNIBridge_\(typeName.fullFlatName)"
}

static func cacheMemberName(for enumCase: ImportedEnumCase) -> String {
"\(enumCase.enumType.nominalTypeDecl.name.firstCharacterLowercased)\(enumCase.name.firstCharacterUppercased)Cache"
}
Expand Down
Loading